From 7f1af51013a99dd166170569e361e65c1032585f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 10:02:59 +1000 Subject: [PATCH 01/18] Update unread message count on scroll --- .../conversation/v2/ConversationActivityV2.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 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 bbd1899b7a..7847ef8fac 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 @@ -68,6 +68,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private var isLockViewExpanded = false private var isShowingAttachmentOptions = false private var mentionCandidatesView: MentionCandidatesView? = null + private var unreadCount = 0 + + private val layoutManager: LinearLayoutManager + get() { return conversationRecyclerView.layoutManager as LinearLayoutManager } // TODO: Selected message background color // TODO: Overflow menu background + text color @@ -119,7 +123,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe restoreDraftIfNeeded() addOpenGroupGuidelinesIfNeeded() scrollToBottomButton.setOnClickListener { conversationRecyclerView.smoothScrollToPosition(0) } - updateUnreadCount() + unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID) + updateUnreadCountIndicator() setUpTypingObserver() updateSubtitle() getLatestOpenGroupInfoIfNeeded() @@ -404,11 +409,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe (scrollButtonFullVisibilityThreshold - scrollButtonNoVisibilityThreshold) val alpha = max(min(rawAlpha, 1.0f), 0.0f) scrollToBottomButton.alpha = alpha - updateUnreadCount() + unreadCount = layoutManager.findFirstVisibleItemPosition() + updateUnreadCountIndicator() } - private fun updateUnreadCount() { - val unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID) + private fun updateUnreadCountIndicator() { val formattedUnreadCount = if (unreadCount < 100) unreadCount.toString() else "99+" unreadCountTextView.text = formattedUnreadCount val textSize = if (unreadCount < 100) 12.0f else 9.0f From 851f4c5cf7771f351bbdecf52ab8bfdd42c0be28 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 10:18:04 +1000 Subject: [PATCH 02/18] Send read receipts --- .../conversation/v2/ConversationActivityV2.kt | 21 +++++++++++++++---- .../notifications/MarkReadReceiver.java | 1 - .../sskenvironment/ReadReceiptManager.kt | 1 + 3 files changed, 18 insertions(+), 5 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 7847ef8fac..e12db6dd81 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 @@ -2,12 +2,13 @@ package org.thoughtcrime.securesms.conversation.v2 import android.animation.FloatEvaluator import android.animation.ValueAnimator +import android.content.Context import android.content.res.Resources import android.database.Cursor import android.graphics.Rect import android.graphics.Typeface +import android.os.AsyncTask import android.os.Bundle -import android.util.Log import android.util.TypedValue import android.view.* import android.widget.RelativeLayout @@ -30,6 +31,7 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.v2.dialogs.* @@ -47,8 +49,10 @@ import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState +import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.shouldSendReadReceipt import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.mms.GlideApp +import org.thoughtcrime.securesms.notifications.MarkReadReceiver import org.thoughtcrime.securesms.util.DateUtils import java.util.* import kotlin.math.* @@ -130,7 +134,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe getLatestOpenGroupInfoIfNeeded() setUpBlockedBanner() setUpLinkPreviewObserver() - scrollToFirstUnreadMessage() + scrollToFirstUnreadMessageIfNeeded() markAllAsRead() } @@ -244,9 +248,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe }) } - private fun scrollToFirstUnreadMessage() { + private fun scrollToFirstUnreadMessageIfNeeded() { val lastSeenTimestamp = DatabaseFactory.getThreadDatabase(this).getLastSeenAndHasSent(threadID).first() val lastSeenItemPosition = adapter.findLastSeenItemPosition(lastSeenTimestamp) ?: return + if (lastSeenItemPosition <= 3) { return } conversationRecyclerView.scrollToPosition(lastSeenItemPosition) } @@ -264,7 +269,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // region Updating & Animation private fun markAllAsRead() { - DatabaseFactory.getThreadDatabase(this).setRead(threadID, true) + val messages = DatabaseFactory.getThreadDatabase(this).setRead(threadID, true) + if (thread.isGroupRecipient) { + for (message in messages) { + MarkReadReceiver.scheduleDeletion(this, message.expirationInfo) + } + } else { + MarkReadReceiver.process(this, messages) + } + ApplicationContext.getInstance(this).messageNotifier.updateNotification(this) } override fun inputBarHeightChanged(newValue: Int) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index 3df423a6de..c12a3f196b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -81,7 +81,6 @@ public class MarkReadReceiver extends BroadcastReceiver { for (Address address : addressMap.keySet()) { List timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); - // Loki - Check whether we want to send a read receipt to this user if (!SessionMetaProtocol.shouldSendReadReceipt(address)) { continue; } ReadReceipt readReceipt = new ReadReceipt(timestamps); readReceipt.setSentTimestamp(System.currentTimeMillis()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ReadReceiptManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ReadReceiptManager.kt index 2fc8c1cbaf..62002c88b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ReadReceiptManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ReadReceiptManager.kt @@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId class ReadReceiptManager: SSKEnvironment.ReadReceiptManagerProtocol { + override fun processReadReceipts(context: Context, fromRecipientId: String, sentTimestamps: List, readTimestamp: Long) { if (TextSecurePreferences.isReadReceiptsEnabled(context)) { From 40f859c56761a599b44783988f0a9f6de0d8d23e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 10:55:50 +1000 Subject: [PATCH 03/18] Fix scroll to bottom button visibility logic --- .../conversation/v2/ConversationActivityV2.kt | 22 +++++++++++-------- .../v2/ConversationRecyclerView.kt | 18 --------------- 2 files changed, 13 insertions(+), 27 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 e12db6dd81..cd0f105b77 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 @@ -9,6 +9,7 @@ import android.graphics.Rect import android.graphics.Typeface import android.os.AsyncTask import android.os.Bundle +import android.util.Log import android.util.TypedValue import android.view.* import android.widget.RelativeLayout @@ -17,6 +18,7 @@ import androidx.lifecycle.ViewModelProviders import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView 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.* @@ -62,9 +64,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, ConversationRecyclerViewDelegate { - private val scrollButtonFullVisibilityThreshold by lazy { toPx(120.0f, resources) } - private val scrollButtonNoVisibilityThreshold by lazy { toPx(20.0f, resources) } + InputBarRecordingViewDelegate { private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private var linkPreviewViewModel: LinkPreviewViewModel? = null private var threadID: Long = -1 @@ -142,7 +142,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe conversationRecyclerView.adapter = adapter val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true) conversationRecyclerView.layoutManager = layoutManager - conversationRecyclerView.delegate = this // Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will) LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks { @@ -158,6 +157,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe adapter.changeCursor(null) } }) + conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + handleRecyclerViewScrolled() + } + }) } private fun setUpToolBar() { @@ -417,12 +422,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe animation.start() } - override fun handleConversationRecyclerViewBottomOffsetChanged(bottomOffset: Int) { - val rawAlpha = (bottomOffset.toFloat() - scrollButtonNoVisibilityThreshold) / - (scrollButtonFullVisibilityThreshold - scrollButtonNoVisibilityThreshold) - val alpha = max(min(rawAlpha, 1.0f), 0.0f) + private fun handleRecyclerViewScrolled() { + val position = layoutManager.findFirstCompletelyVisibleItemPosition() + val alpha = if (position > 0) 1.0f else 0.0f scrollToBottomButton.alpha = alpha - unreadCount = layoutManager.findFirstVisibleItemPosition() + unreadCount = min(unreadCount, layoutManager.findFirstVisibleItemPosition()) updateUnreadCountIndicator() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRecyclerView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRecyclerView.kt index 7d8957a065..1926024015 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRecyclerView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRecyclerView.kt @@ -17,7 +17,6 @@ class ConversationRecyclerView : RecyclerView { private val maxLongPressVelocityY = toPx(10, resources) private val minSwipeVelocityX = toPx(10, resources) private var velocityTracker: VelocityTracker? = null - var delegate: ConversationRecyclerViewDelegate? = null constructor(context: Context) : super(context) { initialize() } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } @@ -25,18 +24,6 @@ class ConversationRecyclerView : RecyclerView { private fun initialize() { disableClipping() - addOnScrollListener(object : RecyclerView.OnScrollListener() { - private var bottomOffset = 0 - - override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { - // Do nothing - } - - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - bottomOffset += dy // FIXME: Not sure this is fully accurate, but it seems close enough - delegate?.handleConversationRecyclerViewBottomOffsetChanged(abs(bottomOffset)) - } - }) } override fun onInterceptTouchEvent(e: MotionEvent): Boolean { @@ -66,8 +53,3 @@ class ConversationRecyclerView : RecyclerView { return super.dispatchTouchEvent(e) } } - -interface ConversationRecyclerViewDelegate { - - fun handleConversationRecyclerViewBottomOffsetChanged(bottomOffset: Int) -} \ No newline at end of file From cc98ab5c9f0b2f8e0c56a16779096ebb1b4d5629 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 11:11:03 +1000 Subject: [PATCH 04/18] Send typing indicators & fix missing mention handling --- .../securesms/components/TypingStatusSender.java | 3 +-- .../conversation/v2/ConversationActivityV2.kt | 14 +++++++++----- .../conversation/v2/input_bar/InputBar.kt | 2 +- .../conversation/v2/messages/QuoteView.kt | 10 ++++------ .../v2/messages/VisibleMessageContentView.kt | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java index e3317ff1cd..027a319651 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -79,8 +79,7 @@ public class TypingStatusSender { ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); Recipient recipient = threadDatabase.getRecipientForThreadId(threadId); if (recipient == null) { return; } - // Loki - Check whether we want to send a typing indicator to this user - if (recipient != null && !SessionMetaProtocol.shouldSendTypingIndicator(recipient.getAddress())) { return; } + if (!SessionMetaProtocol.shouldSendTypingIndicator(recipient.getAddress())) { return; } TypingIndicator typingIndicator; if (typingStarted) { typingIndicator = new TypingIndicator(TypingIndicator.Kind.STARTED); 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 cd0f105b77..407e529143 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 @@ -2,14 +2,11 @@ package org.thoughtcrime.securesms.conversation.v2 import android.animation.FloatEvaluator import android.animation.ValueAnimator -import android.content.Context import android.content.res.Resources import android.database.Cursor import android.graphics.Rect import android.graphics.Typeface -import android.os.AsyncTask import android.os.Bundle -import android.util.Log import android.util.TypedValue import android.view.* import android.widget.RelativeLayout @@ -33,9 +30,9 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity +import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher import org.thoughtcrime.securesms.conversation.v2.dialogs.* import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate @@ -51,7 +48,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState -import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.shouldSendReadReceipt import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.notifications.MarkReadReceiver @@ -217,6 +213,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe typingIndicatorViewContainer.setTypists(recipients) inputBarHeightChanged(inputBar.height) } + if (TextSecurePreferences.isTypingIndicatorsEnabled(this)) { + inputBar.inputBarEditText.addTextChangedListener(object : SimpleTextWatcher() { + + override fun onTextChanged(text: String?) { + ApplicationContext.getInstance(this@ConversationActivityV2).typingStatusSender.onTypingStarted(threadID) + } + }) + } } private fun getLatestOpenGroupInfoIfNeeded() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 98ce5d630d..f07ac18af6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -111,7 +111,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li // here to get the layout right. val maxContentWidth = (screenWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources) - toPx(30, resources)).roundToInt() quoteView.bind(message.individualRecipient.address.toString(), message.body, attachments, - message.recipient, true, maxContentWidth, message.isOpenGroupInvitation) + message.recipient, true, maxContentWidth, message.isOpenGroupInvitation, message.threadId) // The 6 DP below is the padding the quote view applies to itself, which isn't included in the // intrinsic height calculation. val quoteViewIntrinsicHeight = quoteView.getIntrinsicHeight(maxContentWidth) + toPx(6, resources) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 43c4f17412..e4ffa588f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -10,6 +10,7 @@ import android.widget.LinearLayout import android.widget.RelativeLayout import androidx.annotation.ColorInt import androidx.core.content.res.ResourcesCompat +import androidx.core.text.toSpannable import androidx.core.view.isVisible import androidx.core.view.marginStart import kotlinx.android.synthetic.main.view_quote.view.* @@ -20,10 +21,7 @@ import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.MessageRecord -import org.thoughtcrime.securesms.loki.utilities.UiMode -import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities -import org.thoughtcrime.securesms.loki.utilities.toDp -import org.thoughtcrime.securesms.loki.utilities.toPx +import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.mms.SlideDeck import kotlin.math.max import kotlin.math.min @@ -106,7 +104,7 @@ class QuoteView : LinearLayout { // region Updating fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient, - isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean) { + isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean, threadID: Long) { val contactDB = DatabaseFactory.getSessionContactDatabase(context) // Reduce the max body text view line count to 2 if this is a group thread because // we'll be showing the author text view and we don't want the overall quote view height @@ -121,7 +119,7 @@ class QuoteView : LinearLayout { } quoteViewAuthorTextView.isVisible = thread.isGroupRecipient // Body - quoteViewBodyTextView.text = if (isOpenGroupInvitation) resources.getString(R.string.open_group_invitation_view__open_group_invitation) else body + quoteViewBodyTextView.text = if (isOpenGroupInvitation) resources.getString(R.string.open_group_invitation_view__open_group_invitation) else MentionUtilities.highlightMentions((body ?: "").toSpannable(), threadID, context); quoteViewBodyTextView.setTextColor(getTextColor(isOutgoingMessage)) // Accent line / attachment preview val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty()) 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 589b493af6..a0ea58f6e1 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 @@ -70,7 +70,7 @@ class VisibleMessageContentView : LinearLayout { // here to get the layout right. val maxContentWidth = (maxWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources)).roundToInt() quoteView.bind(quote.author.toString(), quote.text, quote.attachment, thread, - message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation) + message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId) mainContainer.addView(quoteView) val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message) ViewUtil.setPaddingTop(bodyTextView, 0) From 40317d9834b6de216d387496a69486857393f5f5 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 14:06:53 +1000 Subject: [PATCH 05/18] Fix corner rounding issue --- .../v2/messages/ControlMessageView.kt | 4 +++ .../v2/messages/LinkPreviewView.kt | 17 +++++++++- .../v2/messages/VisibleMessageContentView.kt | 4 +-- .../v2/messages/VoiceMessageView.kt | 19 +++++++++--- .../v2/utilities/MessageBubbleUtilities.kt | 31 +++++++++++++++++++ .../main/res/layout/view_control_message.xml | 2 +- 6 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MessageBubbleUtilities.kt 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 490272d8e4..ab6c11dcb5 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 @@ -1,11 +1,14 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context +import android.content.res.Resources import android.util.AttributeSet +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout import androidx.core.content.res.ResourcesCompat +import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.view_control_message.view.* import network.loki.messenger.R import org.thoughtcrime.securesms.database.model.MessageRecord @@ -19,6 +22,7 @@ class ControlMessageView : LinearLayout { private fun initialize() { LayoutInflater.from(context).inflate(R.layout.view_control_message, this) + layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index 19943c70be..00a98d1d33 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context +import android.graphics.Canvas import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.LayoutInflater @@ -9,12 +10,15 @@ import android.widget.LinearLayout import androidx.core.content.res.ResourcesCompat import kotlinx.android.synthetic.main.view_link_preview.view.* import network.loki.messenger.R +import org.thoughtcrime.securesms.components.CornerMask +import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.ImageSlide class LinkPreviewView : LinearLayout { + private val cornerMask by lazy { CornerMask(this) } // region Lifecycle constructor(context: Context) : super(context) { initialize() } @@ -27,7 +31,7 @@ class LinkPreviewView : LinearLayout { // endregion // region Updating - fun bind(message: MmsMessageRecord, glide: GlideRequests, background: Drawable) { + fun bind(message: MmsMessageRecord, glide: GlideRequests, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean) { mainLinkPreviewContainer.background = background mainLinkPreviewContainer.outlineProvider = ViewOutlineProvider.BACKGROUND mainLinkPreviewContainer.clipToOutline = true @@ -48,6 +52,17 @@ class LinkPreviewView : LinearLayout { // Body val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message) mainLinkPreviewContainer.addView(bodyTextView) + // Corner radii + val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing) + cornerMask.setTopLeftRadius(cornerRadii[0]) + cornerMask.setTopRightRadius(cornerRadii[1]) + cornerMask.setBottomRightRadius(cornerRadii[2]) + cornerMask.setBottomLeftRadius(cornerRadii[3]) + } + + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) + cornerMask.mask(canvas) } fun recycle() { 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 a0ea58f6e1..da12568ed4 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 @@ -59,7 +59,7 @@ class VisibleMessageContentView : LinearLayout { onContentClick = null if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) { val linkPreviewView = LinkPreviewView(context) - linkPreviewView.bind(message, glide, background) + linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster) mainContainer.addView(linkPreviewView) // Body text view is inside the link preview for layout convenience } else if (message is MmsMessageRecord && message.quote != null) { @@ -77,7 +77,7 @@ class VisibleMessageContentView : LinearLayout { mainContainer.addView(bodyTextView) } else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) { val voiceMessageView = VoiceMessageView(context) - voiceMessageView.bind(message, background) + voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster) mainContainer.addView(voiceMessageView) // We have to use onContentClick (rather than a click listener directly on the voice // message view) so as to not interfere with all the other gestures. diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt index f85ebec0f6..111f251805 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context +import android.graphics.Canvas import android.graphics.drawable.Drawable import android.os.Handler import android.os.Looper @@ -12,12 +13,15 @@ import android.widget.RelativeLayout import androidx.core.view.isVisible import kotlinx.android.synthetic.main.view_voice_message.view.* import network.loki.messenger.R +import org.thoughtcrime.securesms.components.CornerMask +import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities import org.thoughtcrime.securesms.database.model.MmsMessageRecord import java.util.concurrent.TimeUnit import kotlin.math.roundToInt class VoiceMessageView : LinearLayout { private val snHandler = Handler(Looper.getMainLooper()) + private val cornerMask by lazy { CornerMask(this) } private var runnable: Runnable? = null private var mockIsPlaying = false private var mockProgress = 0L @@ -38,12 +42,14 @@ class VoiceMessageView : LinearLayout { // endregion // region Updating - fun bind(message: MmsMessageRecord, background: Drawable) { + fun bind(message: MmsMessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean) { val audio = message.slideDeck.audioSlide!! voiceMessageViewLoader.isVisible = audio.isPendingDownload - mainVoiceMessageViewContainer.background = background - mainVoiceMessageViewContainer.outlineProvider = ViewOutlineProvider.BACKGROUND - mainVoiceMessageViewContainer.clipToOutline = true + val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing) + cornerMask.setTopLeftRadius(cornerRadii[0]) + cornerMask.setTopRightRadius(cornerRadii[1]) + cornerMask.setBottomRightRadius(cornerRadii[2]) + cornerMask.setBottomLeftRadius(cornerRadii[3]) } private fun handleProgressChanged() { @@ -56,6 +62,11 @@ class VoiceMessageView : LinearLayout { progressView.layoutParams = layoutParams } + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) + cornerMask.mask(canvas) + } + fun recycle() { // TODO: Implement } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MessageBubbleUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MessageBubbleUtilities.kt new file mode 100644 index 0000000000..c4c5d5a5d8 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MessageBubbleUtilities.kt @@ -0,0 +1,31 @@ +package org.thoughtcrime.securesms.conversation.v2.utilities + +import android.content.Context +import network.loki.messenger.R +import kotlin.math.roundToInt + +object MessageBubbleUtilities { + + fun calculateRadii(context: Context, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean, isOutgoing: Boolean): IntArray { + val roundedDimen = context.resources.getDimension(R.dimen.message_corner_radius).roundToInt() + val collapsedDimen = context.resources.getDimension(R.dimen.message_corner_collapse_radius).roundToInt() + val (tl, tr, bl, br) = when { + // Single message + isStartOfMessageCluster && isEndOfMessageCluster -> intArrayOf(roundedDimen, roundedDimen, roundedDimen, roundedDimen) + // Start of message cluster; collapsed BL + isStartOfMessageCluster -> intArrayOf(roundedDimen, roundedDimen, collapsedDimen, roundedDimen) + // End of message cluster; collapsed TL + isEndOfMessageCluster -> intArrayOf(collapsedDimen, roundedDimen, roundedDimen, roundedDimen) + // In the middle; no rounding on the left + else -> intArrayOf(collapsedDimen, roundedDimen, collapsedDimen, roundedDimen) + } + // TL, TR, BR, BL (CW direction) + // Flip if the message is outgoing + return intArrayOf( + if (!isOutgoing) tl else tr, // TL + if (!isOutgoing) tr else tl, // TR + if (!isOutgoing) br else bl, // BR + if (!isOutgoing) bl else br // BL + ) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/view_control_message.xml b/app/src/main/res/layout/view_control_message.xml index 714c7150ee..5dbbe14ab6 100644 --- a/app/src/main/res/layout/view_control_message.xml +++ b/app/src/main/res/layout/view_control_message.xml @@ -16,7 +16,7 @@ Date: Fri, 25 Jun 2021 14:42:04 +1000 Subject: [PATCH 06/18] Add back mentions business logic --- .../conversation/v2/ConversationActivityV2.kt | 123 +++++++++++++----- .../conversation/v2/input_bar/InputBar.kt | 2 + .../mentions/MentionCandidatesView.kt | 4 + 3 files changed, 100 insertions(+), 29 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 407e529143..c3f3ee1e83 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 @@ -7,6 +7,7 @@ import android.database.Cursor import android.graphics.Rect import android.graphics.Typeface import android.os.Bundle +import android.util.Log import android.util.TypedValue import android.view.* import android.widget.RelativeLayout @@ -27,6 +28,7 @@ import kotlinx.android.synthetic.main.view_input_bar_recording.view.* import network.loki.messenger.R 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.open_groups.OpenGroupAPIV2 import org.session.libsession.utilities.TextSecurePreferences @@ -65,10 +67,16 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private var linkPreviewViewModel: LinkPreviewViewModel? = null private var threadID: Long = -1 private var actionMode: ActionMode? = null + private var unreadCount = 0 + // Attachments private var isLockViewExpanded = false private var isShowingAttachmentOptions = false + // Mentions + private val mentions = mutableListOf() private var mentionCandidatesView: MentionCandidatesView? = null - private var unreadCount = 0 + private var previousText: CharSequence = "" + private var currentMentionStartIndex = -1 + private var isShowingMentionCandidatesView = false private val layoutManager: LinearLayoutManager get() { return conversationRecyclerView.layoutManager as LinearLayoutManager } @@ -294,9 +302,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // 36 DP is the exact height of the typing indicator view. It's also exactly 18 * 2, and 18 is the large message // corner radius. This makes 36 DP look "correct" in the context of other messages on the screen. val typingIndicatorHeight = if (typingIndicatorViewContainer.isVisible) toPx(36, resources) else 0 + // We * don't * want to move the recycler view up when showing the mention candidates view + val additionalContentContainerHeight = if (isShowingMentionCandidatesView) 0 else additionalContentContainer.height // Recycler view val recyclerViewLayoutParams = conversationRecyclerView.layoutParams as RelativeLayout.LayoutParams - recyclerViewLayoutParams.bottomMargin = newValue + additionalContentContainer.height + typingIndicatorHeight + recyclerViewLayoutParams.bottomMargin = newValue + additionalContentContainerHeight + typingIndicatorHeight conversationRecyclerView.layoutParams = recyclerViewLayoutParams // Additional content container val additionalContentContainerLayoutParams = additionalContentContainer.layoutParams as RelativeLayout.LayoutParams @@ -317,40 +327,77 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun inputBarEditTextContentChanged(newContent: CharSequence) { linkPreviewViewModel?.onTextChanged(this, inputBar.text, 0, 0) - // TODO: Implement the full mention show/hide logic - if (newContent.contains("@")) { - showMentionCandidates() - } else { - hideMentionCandidates() - } + showOrHideMentionCandidatesIfNeeded(newContent) } - private fun showMentionCandidates() { - additionalContentContainer.removeAllViews() - val mentionCandidatesView = MentionCandidatesView(this) - mentionCandidatesView.glide = glide - additionalContentContainer.addView(mentionCandidatesView) - val mentionCandidates = MentionsManager.getMentionCandidates("", threadID, thread.isOpenGroupRecipient) - this.mentionCandidatesView = mentionCandidatesView - mentionCandidatesView.show(mentionCandidates, threadID) - mentionCandidatesView.alpha = 0.0f - val animation = ValueAnimator.ofObject(FloatEvaluator(), mentionCandidatesView.alpha, 1.0f) - animation.duration = 250L - animation.addUpdateListener { animator -> - mentionCandidatesView.alpha = animator.animatedValue as Float + private fun showOrHideMentionCandidatesIfNeeded(text: CharSequence) { + val isBackspace = (text.length < previousText.length) + if (isBackspace) { + currentMentionStartIndex = -1 + hideMentionCandidates() + val mentionsToRemove = mentions.filter { !text.contains(it.displayName) } + mentions.removeAll(mentionsToRemove) } - animation.start() + if (text.isNotEmpty()) { + if (currentMentionStartIndex > text.length) { resetMentions() } // Should never occur + val lastCharIndex = text.lastIndex + val lastChar = text[lastCharIndex] + val secondToLastChar = if (lastCharIndex > 0) text[lastCharIndex - 1] else ' ' + if (lastChar == '@' && Character.isWhitespace(secondToLastChar)) { + currentMentionStartIndex = lastCharIndex + showOrUpdateMentionCandidatesIfNeeded() + } else if (Character.isWhitespace(lastChar)) { + currentMentionStartIndex = -1 + hideMentionCandidates() + } else if (currentMentionStartIndex != -1) { + val query = text.substring(currentMentionStartIndex + 1) // + 1 to get rid of the "@" + showOrUpdateMentionCandidatesIfNeeded(query) + } + } + previousText = text + } + + private fun showOrUpdateMentionCandidatesIfNeeded(query: String = "") { + if (!isShowingMentionCandidatesView) { + additionalContentContainer.removeAllViews() + val view = MentionCandidatesView(this) + view.glide = glide + additionalContentContainer.addView(view) + val candidates = MentionsManager.getMentionCandidates(query, threadID, thread.isOpenGroupRecipient) + this.mentionCandidatesView = view + view.show(candidates, threadID) + view.alpha = 0.0f + val animation = ValueAnimator.ofObject(FloatEvaluator(), view.alpha, 1.0f) + animation.duration = 250L + animation.addUpdateListener { animator -> + view.alpha = animator.animatedValue as Float + } + animation.start() + } else { + val candidates = MentionsManager.getMentionCandidates(query, threadID, thread.isOpenGroupRecipient) + this.mentionCandidatesView!!.setMentionCandidates(candidates) + } + isShowingMentionCandidatesView = true } private fun hideMentionCandidates() { - val mentionCandidatesView = mentionCandidatesView ?: return - val animation = ValueAnimator.ofObject(FloatEvaluator(), mentionCandidatesView.alpha, 0.0f) - animation.duration = 250L - animation.addUpdateListener { animator -> - mentionCandidatesView.alpha = animator.animatedValue as Float - if (animator.animatedFraction == 1.0f) { additionalContentContainer.removeAllViews() } + if (isShowingMentionCandidatesView) { + val mentionCandidatesView = mentionCandidatesView ?: return + val animation = ValueAnimator.ofObject(FloatEvaluator(), mentionCandidatesView.alpha, 0.0f) + animation.duration = 250L + animation.addUpdateListener { animator -> + mentionCandidatesView.alpha = animator.animatedValue as Float + if (animator.animatedFraction == 1.0f) { additionalContentContainer.removeAllViews() } + } + animation.start() } - animation.start() + isShowingMentionCandidatesView = false + } + + private fun resetMentions() { + previousText = "" + currentMentionStartIndex = -1 + mentions.clear() } override fun toggleAttachmentOptions() { @@ -569,9 +616,27 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun unblock() { // TODO: Implement } + + override fun send() { + + } // endregion // region General + private fun getMessageBody(): CharSequence { + var result = inputBar.inputBarEditText.text?.trim() ?: "" + for (mention in mentions) { + try { + val startIndex = result.indexOf("@" + mention.displayName) + val endIndex = startIndex + mention.displayName.count() + 1 // + 1 to include the "@" + result = result.substring(0, startIndex) + "@" + mention.publicKey + result.substring(endIndex) + } catch (exception: Exception) { + Log.d("Loki", "Failed to process mention due to error: $exception") + } + } + return result + } + private fun saveDraft() { val text = inputBar.text.trim() if (text.isEmpty()) { return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index f07ac18af6..54fdfa4455 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -61,6 +61,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li microphoneOrSendButtonContainer.addView(sendButton) sendButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) sendButton.isVisible = false + sendButton.onUp = { delegate?.send() } // Edit text inputBarEditText.imeOptions = inputBarEditText.imeOptions or 16777216 // Always use incognito keyboard inputBarEditText.delegate = this @@ -162,4 +163,5 @@ interface InputBarDelegate { fun onMicrophoneButtonMove(event: MotionEvent) fun onMicrophoneButtonCancel(event: MotionEvent) fun onMicrophoneButtonUp(event: MotionEvent) + fun send() } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt index a685dbc828..bbf97f0afd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt @@ -65,6 +65,10 @@ class MentionCandidatesView(context: Context, attrs: AttributeSet?, defStyleAttr openGroupServer = openGroup.server openGroupRoom = openGroup.room } + setMentionCandidates(candidates) + } + + fun setMentionCandidates(candidates: List) { this.candidates = candidates val layoutParams = this.layoutParams as ViewGroup.LayoutParams layoutParams.height = toPx(Math.min(candidates.count(), 4) * 44, resources) From 0ac0cba4489a25159ec1fc9bd5c110491d7fa60f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 15:11:38 +1000 Subject: [PATCH 07/18] Insert mentions --- .../conversation/v2/ConversationActivityV2.kt | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 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 c3f3ee1e83..f8ad83d7f2 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 @@ -302,11 +302,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // 36 DP is the exact height of the typing indicator view. It's also exactly 18 * 2, and 18 is the large message // corner radius. This makes 36 DP look "correct" in the context of other messages on the screen. val typingIndicatorHeight = if (typingIndicatorViewContainer.isVisible) toPx(36, resources) else 0 - // We * don't * want to move the recycler view up when showing the mention candidates view - val additionalContentContainerHeight = if (isShowingMentionCandidatesView) 0 else additionalContentContainer.height // Recycler view val recyclerViewLayoutParams = conversationRecyclerView.layoutParams as RelativeLayout.LayoutParams - recyclerViewLayoutParams.bottomMargin = newValue + additionalContentContainerHeight + typingIndicatorHeight + recyclerViewLayoutParams.bottomMargin = newValue + typingIndicatorHeight conversationRecyclerView.layoutParams = recyclerViewLayoutParams // Additional content container val additionalContentContainerLayoutParams = additionalContentContainer.layoutParams as RelativeLayout.LayoutParams @@ -331,22 +329,28 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun showOrHideMentionCandidatesIfNeeded(text: CharSequence) { - val isBackspace = (text.length < previousText.length) - if (isBackspace) { + if (text.length < previousText.length) { currentMentionStartIndex = -1 hideMentionCandidates() val mentionsToRemove = mentions.filter { !text.contains(it.displayName) } mentions.removeAll(mentionsToRemove) } if (text.isNotEmpty()) { - if (currentMentionStartIndex > text.length) { resetMentions() } // Should never occur val lastCharIndex = text.lastIndex val lastChar = text[lastCharIndex] - val secondToLastChar = if (lastCharIndex > 0) text[lastCharIndex - 1] else ' ' - if (lastChar == '@' && Character.isWhitespace(secondToLastChar)) { + // Check if there is whitespace before the '@' or the '@' is the first character + + val isCharacterBeforeLastWhiteSpaceOrStartOfLine: Boolean + if (text.length == 1) { + isCharacterBeforeLastWhiteSpaceOrStartOfLine = true // Start of line + } else { + val charBeforeLast = text[lastCharIndex - 1] + isCharacterBeforeLastWhiteSpaceOrStartOfLine = Character.isWhitespace(charBeforeLast) + } + if (lastChar == '@' && isCharacterBeforeLastWhiteSpaceOrStartOfLine) { currentMentionStartIndex = lastCharIndex showOrUpdateMentionCandidatesIfNeeded() - } else if (Character.isWhitespace(lastChar)) { + } else if (Character.isWhitespace(lastChar) || lastChar == '@') { // the lastCharacter == "@" is to check for @@ currentMentionStartIndex = -1 hideMentionCandidates() } else if (currentMentionStartIndex != -1) { @@ -362,6 +366,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe additionalContentContainer.removeAllViews() val view = MentionCandidatesView(this) view.glide = glide + view.onCandidateSelected = { handleMentionSelected(it) } additionalContentContainer.addView(view) val candidates = MentionsManager.getMentionCandidates(query, threadID, thread.isOpenGroupRecipient) this.mentionCandidatesView = view @@ -617,6 +622,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // TODO: Implement } + private fun handleMentionSelected(mention: Mention) { + if (currentMentionStartIndex == -1) { return } + mentions.add(mention) + val previousText = inputBar.text + val newText = previousText.substring(0, currentMentionStartIndex) + "@" + mention.displayName + " " + inputBar.text = newText + inputBar.inputBarEditText.setSelection(newText.length) + currentMentionStartIndex = -1 + hideMentionCandidates() + this.previousText = newText + } + override fun send() { } From 82e4d3125c3cbed2aa6dcedaa5bb56bc02804c42 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 15:20:54 +1000 Subject: [PATCH 08/18] Basic message sending (incl. mentions) --- .../conversation/v2/ConversationActivityV2.kt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 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 f8ad83d7f2..065bab2716 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 @@ -30,7 +30,11 @@ 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.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.utilities.TextSecurePreferences import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity @@ -635,12 +639,28 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun send() { + val message = VisibleMessage() + message.sentTimestamp = System.currentTimeMillis() + message.text = getMessageBody() + val outgoingTextMessage = OutgoingTextMessage.from(message, thread) + ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(threadID) +// silentlySetComposeText("") +// val id: Long = fragment.stageOutgoingMessage(outgoingTextMessage) +// +// if (initiating) { +// DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true) +// } +// +// val allocatedThreadId: Long = getAllocatedThreadId(context) + message.id = DatabaseFactory.getSmsDatabase(this).insertMessageOutbox(threadID, outgoingTextMessage, false, message.sentTimestamp!!) { /**fragment.releaseOutgoingMessage(id)*/ } + + MessageSender.send(message, thread.address) } // endregion // region General - private fun getMessageBody(): CharSequence { + private fun getMessageBody(): String { var result = inputBar.inputBarEditText.text?.trim() ?: "" for (mention in mentions) { try { @@ -651,7 +671,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe Log.d("Loki", "Failed to process mention due to error: $exception") } } - return result + return result.toString() } private fun saveDraft() { From 20abe8fdb88e6d53b814eadc6401cd3472cbe4f8 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 15:24:34 +1000 Subject: [PATCH 09/18] Clean --- .../conversation/v2/ConversationActivityV2.kt | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 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 065bab2716..c95a514b8e 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 @@ -343,7 +343,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val lastCharIndex = text.lastIndex val lastChar = text[lastCharIndex] // Check if there is whitespace before the '@' or the '@' is the first character - val isCharacterBeforeLastWhiteSpaceOrStartOfLine: Boolean if (text.length == 1) { isCharacterBeforeLastWhiteSpaceOrStartOfLine = true // Start of line @@ -403,12 +402,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe isShowingMentionCandidatesView = false } - private fun resetMentions() { - previousText = "" - currentMentionStartIndex = -1 - mentions.clear() - } - override fun toggleAttachmentOptions() { val targetAlpha = if (isShowingAttachmentOptions) 0.0f else 1.0f val allButtons = listOf( cameraButtonContainer, libraryButtonContainer, documentButtonContainer, gifButtonContainer) @@ -639,23 +632,23 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun send() { + // Create the message val message = VisibleMessage() message.sentTimestamp = System.currentTimeMillis() message.text = getMessageBody() val outgoingTextMessage = OutgoingTextMessage.from(message, thread) - ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(threadID) - -// silentlySetComposeText("") -// val id: Long = fragment.stageOutgoingMessage(outgoingTextMessage) -// -// if (initiating) { -// DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true) -// } -// -// val allocatedThreadId: Long = getAllocatedThreadId(context) - message.id = DatabaseFactory.getSmsDatabase(this).insertMessageOutbox(threadID, outgoingTextMessage, false, message.sentTimestamp!!) { /**fragment.releaseOutgoingMessage(id)*/ } - + // Clear the input bar + inputBar.text = "" + // Clear mentions + previousText = "" + currentMentionStartIndex = -1 + mentions.clear() + // Put the message in the database + message.id = DatabaseFactory.getSmsDatabase(this).insertMessageOutbox(threadID, outgoingTextMessage, false, message.sentTimestamp!!) { } + // Send it MessageSender.send(message, thread.address) + // Send a typing stopped message + ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(threadID) } // endregion From 20f8931338f720d658ee19304833989c3986fd88 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 15:53:47 +1000 Subject: [PATCH 10/18] Set up attachment pickers --- .../conversation/v2/ConversationActivityV2.kt | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 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 c95a514b8e..ea7555e65e 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 @@ -34,11 +34,11 @@ 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.utilities.TextSecurePreferences import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher +import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.v2.dialogs.* import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate @@ -55,6 +55,7 @@ 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.toPx +import org.thoughtcrime.securesms.mms.AttachmentManager import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.notifications.MarkReadReceiver import org.thoughtcrime.securesms.util.DateUtils @@ -66,13 +67,14 @@ 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 { + InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener { private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private var linkPreviewViewModel: LinkPreviewViewModel? = null private var threadID: Long = -1 private var actionMode: ActionMode? = null private var unreadCount = 0 // Attachments + private val attachmentManager by lazy { AttachmentManager(this, this) } private var isLockViewExpanded = false private var isShowingAttachmentOptions = false // Mentions @@ -121,6 +123,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // region Settings companion object { const val THREAD_ID = "thread_id" + const val PICK_DOCUMENT = 2 + const val TAKE_PHOTO = 7 + const val PICK_GIF = 10 + const val PICK_FROM_LIBRARY = 12 } // endregion @@ -188,15 +194,19 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // GIF button gifButtonContainer.addView(gifButton) gifButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) + gifButton.onUp = { showGIFPicker() } // Document button documentButtonContainer.addView(documentButton) documentButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) + documentButton.onUp = { showDocumentPicker() } // Library button libraryButtonContainer.addView(libraryButton) libraryButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) + libraryButton.onUp = { pickFromLibrary() } // Camera button cameraButtonContainer.addView(cameraButton) cameraButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) + cameraButton.onUp = { showCamera() } } private fun restoreDraftIfNeeded() { @@ -650,6 +660,26 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // Send a typing stopped message ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(threadID) } + + private fun showGIFPicker() { + AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF) + } + + private fun showDocumentPicker() { + AttachmentManager.selectDocument(this, ConversationActivityV2.PICK_DOCUMENT) + } + + private fun pickFromLibrary() { + AttachmentManager.selectGallery(this, ConversationActivityV2.PICK_FROM_LIBRARY, thread, inputBar.text.trim()) + } + + private fun showCamera() { + attachmentManager.capturePhoto(this, ConversationActivityV2.TAKE_PHOTO) + } + + override fun onAttachmentChanged() { + + } // endregion // region General From e459ea0288de4ff04b980b23fb18a595112b017d Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 25 Jun 2021 15:57:43 +1000 Subject: [PATCH 11/18] fix overflow menu background color --- app/src/main/res/values/themes.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 5928d5687f..110ac1dfe2 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -18,7 +18,7 @@ @color/default_background_start @color/compose_view_background - @style/ThemeOverlay.AppCompat.Light + @style/ThemeOverlay.AppCompat.DayNight @null @style/ThemeOverlay.AppCompat.DayNight.ActionBar @style/Widget.Session.ActionBar From 7f2ffcc114073e0b8213a2ba3cc6d585ff175350 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 25 Jun 2021 16:09:37 +1000 Subject: [PATCH 12/18] Implement onActivityResult --- .../conversation/v2/ConversationActivityV2.kt | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 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 ea7555e65e..30fefabc4c 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 @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.animation.FloatEvaluator import android.animation.ValueAnimator +import android.content.Intent import android.content.res.Resources import android.database.Cursor import android.graphics.Rect @@ -51,14 +52,17 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DraftDatabase import org.thoughtcrime.securesms.database.DraftDatabase.Drafts import org.thoughtcrime.securesms.database.model.MessageRecord +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.toPx -import org.thoughtcrime.securesms.mms.AttachmentManager -import org.thoughtcrime.securesms.mms.GlideApp +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.util.DateUtils +import org.thoughtcrime.securesms.util.MediaUtil import java.util.* import kotlin.math.* @@ -680,6 +684,46 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun onAttachmentChanged() { } + + override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { + super.onActivityResult(requestCode, resultCode, intent) + intent ?: return + when (requestCode) { + PICK_DOCUMENT -> { + val data = intent.data ?: return + } + TAKE_PHOTO -> { + val uri = attachmentManager.captureUri ?: return + } + PICK_GIF -> { + val data = intent.data ?: return + val type = AttachmentManager.MediaType.GIF + val width = intent.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0) + val height = intent.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0) + } + PICK_FROM_LIBRARY -> { + val message = intent.getStringExtra(MediaSendActivity.EXTRA_MESSAGE) + val media = intent.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA) ?: return + val slideDeck = SlideDeck() + for (item in media) { + when { + MediaUtil.isVideoType(item.mimeType) -> { + slideDeck.addSlide(VideoSlide(this, item.uri, 0, item.caption.orNull())) + } + MediaUtil.isGif(item.mimeType) -> { + slideDeck.addSlide(GifSlide(this, item.uri, 0, item.width, item.height, item.caption.orNull())) + } + MediaUtil.isImageType(item.mimeType) -> { + slideDeck.addSlide(ImageSlide(this, item.uri, 0, item.width, item.height, item.caption.orNull())) + } + else -> { + Log.d("Loki", "Asked to send an unexpected media type: '" + item.mimeType + "'. Skipping.") + } + } + } + } + } + } // endregion // region General From 2b26876c4c7437b974110876cec3f4c33c68501a Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Mon, 28 Jun 2021 09:56:49 +1000 Subject: [PATCH 13/18] fix text color for system default dark mode --- .../conversation/v2/messages/VisibleMessageContentView.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 da12568ed4..e242d6e6b5 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 @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context import android.content.res.ColorStateList +import android.graphics.Color import android.graphics.drawable.Drawable import android.text.util.Linkify import android.util.AttributeSet @@ -141,11 +142,11 @@ class VisibleMessageContentView : LinearLayout { @ColorInt fun getTextColor(context: Context, message: MessageRecord): Int { - val uiMode = UiModeUtilities.getUserSelectedUiMode(context) + val isDayUiMode = UiModeUtilities.isDayUiMode(context) val colorID = if (message.isOutgoing) { - if (uiMode == UiMode.NIGHT) R.color.black else R.color.white + if (isDayUiMode) R.color.white else R.color.black } else { - if (uiMode == UiMode.NIGHT) R.color.white else R.color.black + if (isDayUiMode) R.color.black else R.color.white } return context.resources.getColorWithID(colorID, context.theme) } From bf80f5c449ea81c1a63ce1944a659428ddcc0093 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 28 Jun 2021 10:00:18 +1000 Subject: [PATCH 14/18] Re-implement attachment sending --- .../conversation/v2/ConversationActivityV2.kt | 49 +++++++++++++++++-- .../conversation/v2/input_bar/InputBar.kt | 5 +- 2 files changed, 46 insertions(+), 8 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 30fefabc4c..a6974e29b5 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 @@ -7,6 +7,7 @@ import android.content.res.Resources import android.database.Cursor import android.graphics.Rect import android.graphics.Typeface +import android.net.Uri import android.os.Bundle import android.util.Log import android.util.TypedValue @@ -31,15 +32,16 @@ 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.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.attachments.Attachment import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher -import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.v2.dialogs.* import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate @@ -645,7 +647,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe this.previousText = newText } - override fun send() { + override fun sendMessage() { // Create the message val message = VisibleMessage() message.sentTimestamp = System.currentTimeMillis() @@ -665,6 +667,31 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(threadID) } + private fun sendAttachments(attachments: List, body: String?) { + // TODO: Quotes & link previews + // Create the message + val message = VisibleMessage() + message.sentTimestamp = System.currentTimeMillis() + message.text = body + val outgoingTextMessage = OutgoingMediaMessage.from(message, thread, attachments, null, null) + // Clear the input bar + inputBar.text = "" + // Clear mentions + previousText = "" + currentMentionStartIndex = -1 + mentions.clear() + // Reset the attachment manager + attachmentManager.clear(glide, false) + // + + // Put the message in the database + message.id = DatabaseFactory.getMmsDatabase(this).insertMessageOutbox(outgoingTextMessage, threadID, false) { } + // Send it + MessageSender.send(message, thread.address, attachments, null, null) + // Send a typing stopped message + ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(threadID) + } + private fun showGIFPicker() { AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF) } @@ -690,19 +717,22 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe intent ?: return when (requestCode) { PICK_DOCUMENT -> { - val data = intent.data ?: return + val uri = intent.data ?: return + prepMediaForSending(uri, AttachmentManager.MediaType.DOCUMENT) } TAKE_PHOTO -> { val uri = attachmentManager.captureUri ?: return + prepMediaForSending(uri, AttachmentManager.MediaType.IMAGE) } PICK_GIF -> { - val data = intent.data ?: return + val uri = intent.data ?: return val type = AttachmentManager.MediaType.GIF val width = intent.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0) val height = intent.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0) + prepMediaForSending(uri, type, width, height) } PICK_FROM_LIBRARY -> { - val message = intent.getStringExtra(MediaSendActivity.EXTRA_MESSAGE) + val body = intent.getStringExtra(MediaSendActivity.EXTRA_MESSAGE) val media = intent.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA) ?: return val slideDeck = SlideDeck() for (item in media) { @@ -721,9 +751,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } } + sendAttachments(slideDeck.asAttachments(), body) } } } + + private fun prepMediaForSending(uri: Uri, type: AttachmentManager.MediaType) { + prepMediaForSending(uri, type, null, null) + } + + private fun prepMediaForSending(uri: Uri, type: AttachmentManager.MediaType, width: Int?, height: Int?) { + attachmentManager.setMedia(glide, uri, type, MediaConstraints.getPushMediaConstraints(), width ?: 0, height ?: 0) + } // endregion // region General diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 54fdfa4455..2270aef2ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -8,7 +8,6 @@ import android.view.MotionEvent import android.widget.RelativeLayout import androidx.core.view.isVisible import kotlinx.android.synthetic.main.view_input_bar.view.* -import kotlinx.android.synthetic.main.view_quote.view.* import network.loki.messenger.R import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView @@ -61,7 +60,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li microphoneOrSendButtonContainer.addView(sendButton) sendButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) sendButton.isVisible = false - sendButton.onUp = { delegate?.send() } + sendButton.onUp = { delegate?.sendMessage() } // Edit text inputBarEditText.imeOptions = inputBarEditText.imeOptions or 16777216 // Always use incognito keyboard inputBarEditText.delegate = this @@ -163,5 +162,5 @@ interface InputBarDelegate { fun onMicrophoneButtonMove(event: MotionEvent) fun onMicrophoneButtonCancel(event: MotionEvent) fun onMicrophoneButtonUp(event: MotionEvent) - fun send() + fun sendMessage() } \ No newline at end of file From ca3034cb057b22e761ad94eb020115ba8a1a74e4 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 28 Jun 2021 10:44:00 +1000 Subject: [PATCH 15/18] Re-implement GIF & document sending --- .../conversation/ConversationActivity.java | 18 +-- .../conversation/v2/ConversationActivityV2.kt | 35 +++-- .../securesms/mms/AttachmentManager.java | 133 +----------------- app/src/main/res/values/strings.xml | 2 + 4 files changed, 41 insertions(+), 147 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index c1f46a6bcf..288a55eeea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -422,9 +422,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return; } - if (!org.thoughtcrime.securesms.util.Util.isEmpty(composeText) || attachmentManager.isAttachmentPresent()) { + if (!org.thoughtcrime.securesms.util.Util.isEmpty(composeText)) { saveDraft(); - attachmentManager.clear(glideRequests, false); + attachmentManager.clear(); silentlySetComposeText(""); } @@ -1426,7 +1426,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity case AttachmentTypeSelector.ADD_CONTACT_INFO: AttachmentManager.selectContactInfo(this, PICK_CONTACT); break; case AttachmentTypeSelector.ADD_LOCATION: - AttachmentManager.selectLocation(this, PICK_LOCATION); break; + break; case AttachmentTypeSelector.TAKE_PHOTO: attachmentManager.capturePhoto(this, TAKE_PHOTO); break; case AttachmentTypeSelector.ADD_GIF: @@ -1620,7 +1620,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private String getMessage() throws InvalidMessageException { String result = composeText.getTextTrimmed(); - if (result.length() < 1 && !attachmentManager.isAttachmentPresent()) throw new InvalidMessageException(); + if (result.length() < 1) throw new InvalidMessageException(); for (Mention mention : mentions) { try { int startIndex = result.indexOf("@" + mention.getDisplayName()); @@ -1723,7 +1723,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity String message = getMessage(); boolean initiating = threadId == -1; boolean needsSplit = message.length() > characterCalculator.calculateCharacters(message).maxPrimaryMessageSize; - boolean isMediaMessage = attachmentManager.isAttachmentPresent() || + boolean isMediaMessage = false || // recipient.isGroupRecipient() || inputPanel.getQuote().isPresent() || linkPreviewViewModel.hasLinkPreview() || @@ -1785,7 +1785,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); inputPanel.clearQuote(); - attachmentManager.clear(glideRequests, false); + attachmentManager.clear(); silentlySetComposeText(""); final long id = fragment.stageOutgoingMessage(outgoingMessage); @@ -1859,7 +1859,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return; } - if (composeText.getText().length() == 0 && !attachmentManager.isAttachmentPresent()) { + if (composeText.getText().length() == 0) { buttonToggle.display(attachButton); quickAttachmentToggle.show(); inlineAttachmentToggle.hide(); @@ -1867,7 +1867,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity buttonToggle.display(sendButton); quickAttachmentToggle.hide(); - if (!attachmentManager.isAttachmentPresent() && !linkPreviewViewModel.hasLinkPreview()) { + if (!linkPreviewViewModel.hasLinkPreview()) { inlineAttachmentToggle.show(); } else { inlineAttachmentToggle.hide(); @@ -1876,7 +1876,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void updateLinkPreviewState() { - if (TextSecurePreferences.isLinkPreviewsEnabled(this) && !attachmentManager.isAttachmentPresent()) { + if (TextSecurePreferences.isLinkPreviewsEnabled(this)) { linkPreviewViewModel.onEnabled(); linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), composeText.getSelectionStart(), composeText.getSelectionEnd()); } else { 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 a6974e29b5..e184607588 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 @@ -13,6 +13,7 @@ import android.util.Log import android.util.TypedValue import android.view.* import android.widget.RelativeLayout +import android.widget.Toast import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProviders import androidx.loader.app.LoaderManager @@ -39,6 +40,7 @@ import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.ListenableFuture import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher @@ -66,6 +68,7 @@ import org.thoughtcrime.securesms.notifications.MarkReadReceiver import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.MediaUtil import java.util.* +import java.util.concurrent.ExecutionException import kotlin.math.* // Some things that seemingly belong to the input bar (e.g. the voice message recording UI) are actually @@ -681,9 +684,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe currentMentionStartIndex = -1 mentions.clear() // Reset the attachment manager - attachmentManager.clear(glide, false) - // - + attachmentManager.clear() + // Reset attachments button if needed + if (isShowingAttachmentOptions) { toggleAttachmentOptions() } // Put the message in the database message.id = DatabaseFactory.getMmsDatabase(this).insertMessageOutbox(outgoingTextMessage, threadID, false) { } // Send it @@ -709,27 +712,37 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun onAttachmentChanged() { - + // TODO: Do we need to do something here? } override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) intent ?: return + val mediaPreppedListener = object : ListenableFuture.Listener { + + override fun onSuccess(result: Boolean?) { + sendAttachments(attachmentManager.buildSlideDeck().asAttachments(), null) + } + + override fun onFailure(e: ExecutionException?) { + Toast.makeText(this@ConversationActivityV2, R.string.activity_conversation_attachment_prep_failed, Toast.LENGTH_LONG).show() + } + } when (requestCode) { PICK_DOCUMENT -> { val uri = intent.data ?: return - prepMediaForSending(uri, AttachmentManager.MediaType.DOCUMENT) + prepMediaForSending(uri, AttachmentManager.MediaType.DOCUMENT).addListener(mediaPreppedListener) } TAKE_PHOTO -> { val uri = attachmentManager.captureUri ?: return - prepMediaForSending(uri, AttachmentManager.MediaType.IMAGE) + prepMediaForSending(uri, AttachmentManager.MediaType.IMAGE).addListener(mediaPreppedListener) } PICK_GIF -> { val uri = intent.data ?: return val type = AttachmentManager.MediaType.GIF val width = intent.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0) val height = intent.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0) - prepMediaForSending(uri, type, width, height) + prepMediaForSending(uri, type, width, height).addListener(mediaPreppedListener) } PICK_FROM_LIBRARY -> { val body = intent.getStringExtra(MediaSendActivity.EXTRA_MESSAGE) @@ -756,12 +769,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } - private fun prepMediaForSending(uri: Uri, type: AttachmentManager.MediaType) { - prepMediaForSending(uri, type, null, null) + private fun prepMediaForSending(uri: Uri, type: AttachmentManager.MediaType): ListenableFuture { + return prepMediaForSending(uri, type, null, null) } - private fun prepMediaForSending(uri: Uri, type: AttachmentManager.MediaType, width: Int?, height: Int?) { - attachmentManager.setMedia(glide, uri, type, MediaConstraints.getPushMediaConstraints(), width ?: 0, height ?: 0) + private fun prepMediaForSending(uri: Uri, type: AttachmentManager.MediaType, width: Int?, height: Int?): ListenableFuture { + return attachmentManager.setMedia(glide, uri, type, MediaConstraints.getPushMediaConstraints(), width ?: 0, height ?: 0) } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index e2b7980ab6..47c4e0f0eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -73,20 +73,13 @@ import network.loki.messenger.R; import static android.provider.MediaStore.EXTRA_OUTPUT; - public class AttachmentManager { private final static String TAG = AttachmentManager.class.getSimpleName(); private final @NonNull Context context; - private final @NonNull Stub attachmentViewStub; private final @NonNull AttachmentListener attachmentListener; - private RemovableEditableMediaView removableMediaView; - private ThumbnailView thumbnail; - private MessageAudioView audioView; - private DocumentView documentView; - private @NonNull List garbage = new LinkedList<>(); private @NonNull Optional slide = Optional.absent(); private @Nullable Uri captureUri; @@ -94,51 +87,12 @@ public class AttachmentManager { public AttachmentManager(@NonNull Activity activity, @NonNull AttachmentListener listener) { this.context = activity; this.attachmentListener = listener; - this.attachmentViewStub = ViewUtil.findStubById(activity, R.id.attachment_editor_stub); } - private void inflateStub() { - if (!attachmentViewStub.resolved()) { - View root = attachmentViewStub.get(); - - this.thumbnail = ViewUtil.findById(root, R.id.attachment_thumbnail); - this.audioView = ViewUtil.findById(root, R.id.attachment_audio); - this.documentView = ViewUtil.findById(root, R.id.attachment_document); - this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view); - - removableMediaView.setRemoveClickListener(new RemoveButtonListener()); - thumbnail.setOnClickListener(new ThumbnailClickListener()); - documentView.getBackground().setColorFilter(ThemeUtil.getThemedColor(context, R.attr.conversation_item_bubble_background), PorterDuff.Mode.MULTIPLY); - } - } - - public void clear(@NonNull GlideRequests glideRequests, boolean animate) { - if (attachmentViewStub.resolved()) { - - if (animate) { - ViewUtil.fadeOut(attachmentViewStub.get(), 200).addListener(new Listener() { - @Override - public void onSuccess(Boolean result) { - thumbnail.clear(glideRequests); - attachmentViewStub.get().setVisibility(View.GONE); - attachmentListener.onAttachmentChanged(); - } - - @Override - public void onFailure(ExecutionException e) { - } - }); - } else { - thumbnail.clear(glideRequests); - attachmentViewStub.get().setVisibility(View.GONE); - attachmentListener.onAttachmentChanged(); - } - - markGarbage(getSlideUri()); - slide = Optional.absent(); - - audioView.cleanup(); - } + public void clear() { + markGarbage(getSlideUri()); + slide = Optional.absent(); + attachmentListener.onAttachmentChanged(); } public void cleanup() { @@ -190,16 +144,12 @@ public class AttachmentManager { final int width, final int height) { - inflateStub(); - final SettableFuture result = new SettableFuture<>(); new AsyncTask() { @Override protected void onPreExecute() { - thumbnail.clear(glideRequests); - thumbnail.showProgressSpinner(); - attachmentViewStub.get().setVisibility(View.VISIBLE); + } @Override @@ -222,35 +172,12 @@ public class AttachmentManager { @Override protected void onPostExecute(@Nullable final Slide slide) { if (slide == null) { - attachmentViewStub.get().setVisibility(View.GONE); - Toast.makeText(context, - R.string.ConversationActivity_sorry_there_was_an_error_setting_your_attachment, - Toast.LENGTH_SHORT).show(); result.set(false); } else if (!areConstraintsSatisfied(context, slide, constraints)) { - attachmentViewStub.get().setVisibility(View.GONE); - Toast.makeText(context, - R.string.ConversationActivity_attachment_exceeds_size_limits, - Toast.LENGTH_SHORT).show(); result.set(false); } else { setSlide(slide); - attachmentViewStub.get().setVisibility(View.VISIBLE); - - if (slide.hasAudio()) { - audioView.setAudio((AudioSlide) slide, false); - removableMediaView.display(audioView, false); - result.set(true); - } else if (slide.hasDocument()) { - documentView.setDocument((DocumentSlide) slide, false); - removableMediaView.display(documentView, false); - result.set(true); - } else { - Attachment attachment = slide.asAttachment(); - result.deferTo(thumbnail.setImageResource(glideRequests, slide, false, true, attachment.getWidth(), attachment.getHeight())); - removableMediaView.display(thumbnail, mediaType == MediaType.IMAGE); - } - + result.set(true); attachmentListener.onAttachmentChanged(); } } @@ -317,10 +244,6 @@ public class AttachmentManager { return result; } - public boolean isAttachmentPresent() { - return attachmentViewStub.resolved() && attachmentViewStub.get().getVisibility() == View.VISIBLE; - } - public @NonNull SlideDeck buildSlideDeck() { SlideDeck deck = new SlideDeck(); if (slide.isPresent()) deck.addSlide(slide.get()); @@ -354,22 +277,6 @@ public class AttachmentManager { .execute(); } - public static void selectLocation(Activity activity, int requestCode) { - /* Loki - Enable again once we have location sharing - Permissions.with(activity) - .request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) - .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location)) - .onAllGranted(() -> { - try { - activity.startActivityForResult(new PlacePicker.IntentBuilder().build(activity), requestCode); - } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException e) { - Log.w(TAG, e); - } - }) - .execute(); - */ - } - public static void selectGif(Activity activity, int requestCode) { Intent intent = new Intent(activity, GiphyActivity.class); intent.putExtra(GiphyActivity.EXTRA_IS_MMS, false); @@ -445,34 +352,6 @@ public class AttachmentManager { constraints.canResize(slide.asAttachment()); } - private void previewImageDraft(final @NonNull Slide slide) { - if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { - Intent intent = new Intent(context, MediaPreviewActivity.class); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, slide.asAttachment().getSize()); - intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, slide.getCaption().orNull()); - intent.putExtra(MediaPreviewActivity.OUTGOING_EXTRA, true); - intent.setDataAndType(slide.getUri(), slide.getContentType()); - - context.startActivity(intent); - } - } - - private class ThumbnailClickListener implements View.OnClickListener { - @Override - public void onClick(View v) { - if (slide.isPresent()) previewImageDraft(slide.get()); - } - } - - private class RemoveButtonListener implements View.OnClickListener { - @Override - public void onClick(View v) { - cleanup(); - clear(GlideApp.with(context.getApplicationContext()), true); - } - } - public interface AttachmentListener { void onAttachmentChanged(); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27203d0863..3c4c17330e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -872,4 +872,6 @@ Download %s is blocked. Unblock them? + + Failed to prepare attachment for sending. From 0e23e45e89e0cd0d820f57dd4e5de10b2ae39cf5 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 28 Jun 2021 10:50:35 +1000 Subject: [PATCH 16/18] Debug --- .../conversation/ConversationActivity.java | 2 +- .../conversation/v2/ConversationActivityV2.kt | 5 +- .../securesms/mms/AttachmentManager.java | 61 +++++++------------ 3 files changed, 27 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 288a55eeea..be261012cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -1424,7 +1424,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity case AttachmentTypeSelector.ADD_SOUND: AttachmentManager.selectAudio(this, PICK_AUDIO); break; case AttachmentTypeSelector.ADD_CONTACT_INFO: - AttachmentManager.selectContactInfo(this, PICK_CONTACT); break; + break; case AttachmentTypeSelector.ADD_LOCATION: break; case AttachmentTypeSelector.TAKE_PHOTO: 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 e184607588..f7ffe3060e 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 @@ -717,7 +717,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) - intent ?: return val mediaPreppedListener = object : ListenableFuture.Listener { override fun onSuccess(result: Boolean?) { @@ -730,7 +729,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } when (requestCode) { PICK_DOCUMENT -> { - val uri = intent.data ?: return + val uri = intent?.data ?: return prepMediaForSending(uri, AttachmentManager.MediaType.DOCUMENT).addListener(mediaPreppedListener) } TAKE_PHOTO -> { @@ -738,6 +737,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe prepMediaForSending(uri, AttachmentManager.MediaType.IMAGE).addListener(mediaPreppedListener) } PICK_GIF -> { + intent ?: return val uri = intent.data ?: return val type = AttachmentManager.MediaType.GIF val width = intent.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0) @@ -745,6 +745,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe prepMediaForSending(uri, type, width, height).addListener(mediaPreppedListener) } PICK_FROM_LIBRARY -> { + intent ?: return val body = intent.getStringExtra(MediaSendActivity.EXTRA_MESSAGE) val media = intent.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA) ?: return val slideDeck = SlideDeck() diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index 47c4e0f0eb..907b98d2b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -256,27 +256,16 @@ public class AttachmentManager { public static void selectGallery(Activity activity, int requestCode, @NonNull Recipient recipient, @NonNull String body) { Permissions.with(activity) - .request(Manifest.permission.READ_EXTERNAL_STORAGE) - .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio)) - .onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body), requestCode)) - .execute(); + .request(Manifest.permission.READ_EXTERNAL_STORAGE) + .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio)) + .onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body), requestCode)) + .execute(); } public static void selectAudio(Activity activity, int requestCode) { selectMediaType(activity, "audio/*", null, requestCode); } - public static void selectContactInfo(Activity activity, int requestCode) { - Permissions.with(activity) - .request(Manifest.permission.WRITE_CONTACTS) - .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_contacts_permission_in_order_to_attach_contact_information)) - .onAllGranted(() -> { - Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); - activity.startActivityForResult(intent, requestCode); - }) - .execute(); - } - public static void selectGif(Activity activity, int requestCode) { Intent intent = new Intent(activity, GiphyActivity.class); intent.putExtra(GiphyActivity.EXTRA_IS_MMS, false); @@ -293,28 +282,25 @@ public class AttachmentManager { public void capturePhoto(Activity activity, int requestCode) { Permissions.with(activity) - .request(Manifest.permission.CAMERA) - .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_camera_permission_in_order_to_take_photos_but_it_has_been_permanently_denied)) - .onAllGranted(() -> { - try { - File captureFile = File.createTempFile( - "conversation-capture", - ".jpg", - ExternalStorageUtil.getImageDir(activity)); - Uri captureUri = FileProviderUtil.getUriFor(context, captureFile); - Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - captureIntent.putExtra(EXTRA_OUTPUT, captureUri); - captureIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { - Log.d(TAG, "captureUri path is " + captureUri.getPath()); - this.captureUri = captureUri; - activity.startActivityForResult(captureIntent, requestCode); - } - } catch (IOException | NoExternalStorageException e) { - throw new RuntimeException("Error creating image capture intent.", e); - } - }) - .execute(); + .request(Manifest.permission.CAMERA) + .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_camera_permission_in_order_to_take_photos_but_it_has_been_permanently_denied)) + .onAllGranted(() -> { + try { + File captureFile = File.createTempFile("conversation-capture", ".jpg", ExternalStorageUtil.getImageDir(activity)); + Uri captureUri = FileProviderUtil.getUriFor(context, captureFile); + Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + captureIntent.putExtra(EXTRA_OUTPUT, captureUri); + captureIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { + Log.d(TAG, "captureUri path is " + captureUri.getPath()); + this.captureUri = captureUri; + activity.startActivityForResult(captureIntent, requestCode); + } + } catch (IOException | NoExternalStorageException e) { + throw new RuntimeException("Error creating image capture intent.", e); + } + }) + .execute(); } private static void selectMediaType(Activity activity, @NonNull String type, @Nullable String[] extraMimeType, int requestCode) { @@ -392,6 +378,5 @@ public class AttachmentManager { return DOCUMENT; } - } } From 0da248740176721ef43a090766e9028e01de31c3 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 28 Jun 2021 11:11:29 +1000 Subject: [PATCH 17/18] Send voice messages --- .../conversation/ConversationActivity.java | 5 +-- .../conversation/v2/ConversationActivityV2.kt | 41 ++++++++++++++++++- .../conversation/v2/input_bar/InputBar.kt | 10 ++++- .../v2/utilities}/AttachmentManager.java | 29 ++++++------- 4 files changed, 63 insertions(+), 22 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/{mms => conversation/v2/utilities}/AttachmentManager.java (94%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index be261012cf..d6db6ec16d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -102,7 +102,6 @@ import org.session.libsession.utilities.recipients.RecipientModifiedListener; import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.MediaTypes; -import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; @@ -165,8 +164,8 @@ import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView; import org.thoughtcrime.securesms.loki.views.ProfilePictureView; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.MediaSendActivity; -import org.thoughtcrime.securesms.mms.AttachmentManager; -import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType; +import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager; +import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager.MediaType; import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.GifSlide; import org.thoughtcrime.securesms.mms.GlideApp; 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 f7ffe3060e..a6e3c5f9d6 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 @@ -8,8 +8,9 @@ import android.database.Cursor import android.graphics.Rect import android.graphics.Typeface import android.net.Uri -import android.os.Bundle +import android.os.* import android.util.Log +import android.util.Pair import android.util.TypedValue import android.view.* import android.widget.RelativeLayout @@ -39,10 +40,15 @@ 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.attachments.Attachment +import org.session.libsession.utilities.MediaTypes +import org.session.libsession.utilities.ServiceUtil import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.concurrent.AssertedSuccessListener import org.session.libsignal.utilities.ListenableFuture +import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity +import org.thoughtcrime.securesms.audio.AudioRecorder import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher import org.thoughtcrime.securesms.conversation.v2.dialogs.* import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton @@ -52,6 +58,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.ConversationMenuHelper import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView +import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DraftDatabase import org.thoughtcrime.securesms.database.DraftDatabase.Drafts @@ -65,6 +72,7 @@ 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.providers.BlobProvider import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.MediaUtil import java.util.* @@ -83,6 +91,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private var actionMode: ActionMode? = null private var unreadCount = 0 // Attachments + private val audioRecorder = AudioRecorder(this) + private val stopAudioHandler = Handler(Looper.getMainLooper()) + private val stopVoiceMessageRecordingTask = Runnable { stopVoiceMessageRecording() } private val attachmentManager by lazy { AttachmentManager(this, this) } private var isLockViewExpanded = false private var isShowingAttachmentOptions = false @@ -621,6 +632,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe inputBarRecordingView.lock() } else { hideVoiceMessageUI() + stopVoiceMessageRecording() } } @@ -712,7 +724,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun onAttachmentChanged() { - // TODO: Do we need to do something here? + // Do nothing } override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { @@ -777,6 +789,31 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun prepMediaForSending(uri: Uri, type: AttachmentManager.MediaType, width: Int?, height: Int?): ListenableFuture { return attachmentManager.setMedia(glide, uri, type, MediaConstraints.getPushMediaConstraints(), width ?: 0, height ?: 0) } + + override fun startRecordingVoiceMessage() { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + audioRecorder.startRecording() + stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each + } + + fun stopVoiceMessageRecording() { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + val future = audioRecorder.stopRecording() + stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask) + future.addListener(object : ListenableFuture.Listener> { + + override fun onSuccess(result: Pair) { + val audioSlide = AudioSlide(this@ConversationActivityV2, result.first, result.second!!, MediaTypes.AUDIO_AAC, true) + val slideDeck = SlideDeck() + slideDeck.addSlide(audioSlide) + sendAttachments(slideDeck.asAttachments(), null) + } + + override fun onFailure(e: ExecutionException) { + Toast.makeText(this@ConversationActivityV2, R.string.ConversationActivity_unable_to_record_audio, Toast.LENGTH_LONG).show() + } + }) + } // endregion // region General diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 2270aef2ee..86d39ffd83 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -52,7 +52,10 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li // Microphone button microphoneOrSendButtonContainer.addView(microphoneButton) microphoneButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) - microphoneButton.onLongPress = { showVoiceMessageUI() } + microphoneButton.onLongPress = { + showVoiceMessageUI() + startRecordingVoiceMessage() + } microphoneButton.onMove = { delegate?.onMicrophoneButtonMove(it) } microphoneButton.onCancel = { delegate?.onMicrophoneButtonCancel(it) } microphoneButton.onUp = { delegate?.onMicrophoneButtonUp(it) } @@ -96,6 +99,10 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li delegate?.showVoiceMessageUI() } + private fun startRecordingVoiceMessage() { + delegate?.startRecordingVoiceMessage() + } + // Drafting quotes and drafting link previews is mutually exclusive, i.e. you can't draft // a quote and a link preview at the same time. @@ -159,6 +166,7 @@ interface InputBarDelegate { fun inputBarEditTextContentChanged(newContent: CharSequence) fun toggleAttachmentOptions() fun showVoiceMessageUI() + fun startRecordingVoiceMessage() fun onMicrophoneButtonMove(event: MotionEvent) fun onMicrophoneButtonCancel(event: MotionEvent) fun onMicrophoneButtonUp(event: MotionEvent) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java similarity index 94% rename from app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java rename to app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index 907b98d2b4..a5298305a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.thoughtcrime.securesms.mms; +package org.thoughtcrime.securesms.conversation.v2.utilities; import android.Manifest; import android.annotation.SuppressLint; @@ -23,29 +23,31 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.graphics.PorterDuff; import android.net.Uri; import android.os.AsyncTask; -import android.provider.ContactsContract; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.text.TextUtils; import android.util.Pair; -import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.MediaPreviewActivity; -import org.thoughtcrime.securesms.loki.views.MessageAudioView; -import org.thoughtcrime.securesms.components.DocumentView; -import org.thoughtcrime.securesms.components.RemovableEditableMediaView; -import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView; import org.session.libsignal.utilities.NoExternalStorageException; import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.mediasend.MediaSendActivity; +import org.thoughtcrime.securesms.mms.AudioSlide; +import org.thoughtcrime.securesms.mms.DocumentSlide; +import org.thoughtcrime.securesms.mms.GifSlide; +import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.mms.ImageSlide; +import org.thoughtcrime.securesms.mms.MediaConstraints; +import org.thoughtcrime.securesms.mms.PartAuthority; +import org.thoughtcrime.securesms.mms.Slide; +import org.thoughtcrime.securesms.mms.SlideDeck; +import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.BlobProvider; import org.session.libsignal.utilities.ExternalStorageUtil; @@ -53,13 +55,8 @@ import org.thoughtcrime.securesms.util.FileProviderUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.session.libsignal.utilities.guava.Optional; -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.ThemeUtil; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.Stub; import org.session.libsignal.utilities.ListenableFuture; -import org.session.libsignal.utilities.ListenableFuture.Listener; import org.session.libsignal.utilities.SettableFuture; import java.io.File; @@ -67,7 +64,6 @@ import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.ExecutionException; import network.loki.messenger.R; @@ -244,7 +240,8 @@ public class AttachmentManager { return result; } - public @NonNull SlideDeck buildSlideDeck() { + public @NonNull + SlideDeck buildSlideDeck() { SlideDeck deck = new SlideDeck(); if (slide.isPresent()) deck.addSlide(slide.get()); return deck; From 489516b03b1cac5578bc20730641d10655e0fdcd Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 28 Jun 2021 11:26:13 +1000 Subject: [PATCH 18/18] Hook up all voice message recording controls --- .../conversation/v2/ConversationActivityV2.kt | 30 ++++++++++++++----- .../conversation/v2/input_bar/InputBar.kt | 9 +----- .../v2/input_bar/InputBarRecordingView.kt | 4 +++ .../res/layout/view_input_bar_recording.xml | 1 + 4 files changed, 29 insertions(+), 15 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 a6e3c5f9d6..5153d2309a 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 @@ -43,9 +43,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.ServiceUtil import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.concurrent.AssertedSuccessListener import org.session.libsignal.utilities.ListenableFuture -import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.audio.AudioRecorder @@ -93,7 +91,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // Attachments private val audioRecorder = AudioRecorder(this) private val stopAudioHandler = Handler(Looper.getMainLooper()) - private val stopVoiceMessageRecordingTask = Runnable { stopVoiceMessageRecording() } + private val stopVoiceMessageRecordingTask = Runnable { sendVoiceMessage() } private val attachmentManager by lazy { AttachmentManager(this, this) } private var isLockViewExpanded = false private var isShowingAttachmentOptions = false @@ -628,11 +626,20 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun onMicrophoneButtonUp(event: MotionEvent) { - if (isValidLockViewLocation(event.rawX.roundToInt(), event.rawY.roundToInt())) { + val x = event.rawX.roundToInt() + val y = event.rawY.roundToInt() + if (isValidLockViewLocation(x, y)) { inputBarRecordingView.lock() } else { - hideVoiceMessageUI() - stopVoiceMessageRecording() + val recordButtonOverlay = inputBarRecordingView.recordButtonOverlay + val location = IntArray(2) { 0 } + recordButtonOverlay.getLocationOnScreen(location) + val hitRect = Rect(location[0], location[1], location[0] + recordButtonOverlay.width, location[1] + recordButtonOverlay.height) + if (hitRect.contains(x, y)) { + sendVoiceMessage() + } else { + cancelVoiceMessage() + } } } @@ -791,12 +798,14 @@ 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 } - fun stopVoiceMessageRecording() { + override fun sendVoiceMessage() { + hideVoiceMessageUI() window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) val future = audioRecorder.stopRecording() stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask) @@ -814,6 +823,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } }) } + + override fun cancelVoiceMessage() { + hideVoiceMessageUI() + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + audioRecorder.stopRecording() + stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask) + } // endregion // region General diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 86d39ffd83..d00dc10f1e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -52,10 +52,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li // Microphone button microphoneOrSendButtonContainer.addView(microphoneButton) microphoneButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) - microphoneButton.onLongPress = { - showVoiceMessageUI() - startRecordingVoiceMessage() - } + microphoneButton.onLongPress = { startRecordingVoiceMessage() } microphoneButton.onMove = { delegate?.onMicrophoneButtonMove(it) } microphoneButton.onCancel = { delegate?.onMicrophoneButtonCancel(it) } microphoneButton.onUp = { delegate?.onMicrophoneButtonUp(it) } @@ -95,10 +92,6 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li delegate?.toggleAttachmentOptions() } - private fun showVoiceMessageUI() { - delegate?.showVoiceMessageUI() - } - private fun startRecordingVoiceMessage() { delegate?.startRecordingVoiceMessage() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt index a4da17434f..0a210d6ae5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt @@ -134,10 +134,14 @@ class InputBarRecordingView : RelativeLayout { } fadeInAnimation.start() recordButtonOverlayImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_arrow_up, context.theme)) + recordButtonOverlay.setOnClickListener { delegate?.sendVoiceMessage() } + inputBarCancelButton.setOnClickListener { delegate?.cancelVoiceMessage() } } } interface InputBarRecordingViewDelegate { fun handleVoiceMessageUIHidden() + fun sendVoiceMessage() + fun cancelVoiceMessage() } diff --git a/app/src/main/res/layout/view_input_bar_recording.xml b/app/src/main/res/layout/view_input_bar_recording.xml index 5a0e32a085..35628d2997 100644 --- a/app/src/main/res/layout/view_input_bar_recording.xml +++ b/app/src/main/res/layout/view_input_bar_recording.xml @@ -128,6 +128,7 @@