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)