Add mention candidates view

This commit is contained in:
Niels Andriesse 2021-06-18 11:00:52 +10:00
parent 7c3b1b22d7
commit efc752e3a1
12 changed files with 224 additions and 9 deletions

View File

@ -6,13 +6,11 @@ import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.graphics.Rect import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.ActionMode import android.view.ActionMode
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent import android.view.MotionEvent
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.view.marginBottom
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -23,15 +21,17 @@ import kotlinx.android.synthetic.main.view_input_bar.view.*
import kotlinx.android.synthetic.main.view_input_bar_recording.* import kotlinx.android.synthetic.main.view_input_bar_recording.*
import kotlinx.android.synthetic.main.view_input_bar_recording.view.* import kotlinx.android.synthetic.main.view_input_bar_recording.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.mentions.MentionsManager
import org.session.libsession.messaging.mentions.MentionsManager.getMentionCandidates
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarRecordingViewDelegate import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarRecordingViewDelegate
import org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCandidatesView
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.loki.utilities.toDp
import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.loki.utilities.toPx
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import kotlin.math.abs import kotlin.math.abs
@ -152,9 +152,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// region Updating // region Updating
override fun inputBarHeightChanged(newValue: Int) { override fun inputBarHeightChanged(newValue: Int) {
// Recycler view
val recyclerViewLayoutParams = conversationRecyclerView.layoutParams as RelativeLayout.LayoutParams val recyclerViewLayoutParams = conversationRecyclerView.layoutParams as RelativeLayout.LayoutParams
recyclerViewLayoutParams.bottomMargin = newValue recyclerViewLayoutParams.bottomMargin = newValue + inputBarAdditionalContentContainer.height
conversationRecyclerView.layoutParams = recyclerViewLayoutParams conversationRecyclerView.layoutParams = recyclerViewLayoutParams
// Input bar additional content container
val inputBarAdditionalContentContainerLayoutParams = inputBarAdditionalContentContainer.layoutParams as RelativeLayout.LayoutParams
inputBarAdditionalContentContainerLayoutParams.bottomMargin = newValue
inputBarAdditionalContentContainer.layoutParams = inputBarAdditionalContentContainerLayoutParams
// Attachment options
val attachmentButtonHeight = inputBar.attachmentsButtonContainer.height val attachmentButtonHeight = inputBar.attachmentsButtonContainer.height
val bottomMargin = (newValue - attachmentButtonHeight) / 2 val bottomMargin = (newValue - attachmentButtonHeight) / 2
val margin = toPx(8, resources) val margin = toPx(8, resources)
@ -163,6 +169,22 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
attachmentOptionsContainer.layoutParams = attachmentOptionsContainerLayoutParams attachmentOptionsContainer.layoutParams = attachmentOptionsContainerLayoutParams
} }
override fun inputBarEditTextContentChanged(newContent: CharSequence) {
// TODO: Work this out further
if (newContent.contains("@")) {
showMentionCandidates()
}
}
private fun showMentionCandidates() {
inputBarAdditionalContentContainer.removeAllViews()
val mentionCandidatesView = MentionCandidatesView(this)
mentionCandidatesView.glide = glide
inputBarAdditionalContentContainer.addView(mentionCandidatesView)
val mentionCandidates = MentionsManager.getMentionCandidates("", threadID, thread.isOpenGroupRecipient)
mentionCandidatesView.show(mentionCandidates, threadID)
}
override fun toggleAttachmentOptions() { override fun toggleAttachmentOptions() {
val targetAlpha = if (isShowingAttachmentOptions) 0.0f else 1.0f val targetAlpha = if (isShowingAttachmentOptions) 0.0f else 1.0f
val allButtons = listOf( cameraButtonContainer, libraryButtonContainer, documentButtonContainer, gifButtonContainer) val allButtons = listOf( cameraButtonContainer, libraryButtonContainer, documentButtonContainer, gifButtonContainer)

View File

@ -52,6 +52,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate {
override fun inputBarEditTextContentChanged(text: CharSequence) { override fun inputBarEditTextContentChanged(text: CharSequence) {
sendButton.isVisible = text.isNotEmpty() sendButton.isVisible = text.isNotEmpty()
microphoneButton.isVisible = text.isEmpty() microphoneButton.isVisible = text.isEmpty()
delegate?.inputBarEditTextContentChanged(text)
} }
override fun inputBarEditTextHeightChanged(newValue: Int) { override fun inputBarEditTextHeightChanged(newValue: Int) {
@ -76,6 +77,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate {
interface InputBarDelegate { interface InputBarDelegate {
fun inputBarHeightChanged(newValue: Int) fun inputBarHeightChanged(newValue: Int)
fun inputBarEditTextContentChanged(newContent: CharSequence)
fun toggleAttachmentOptions() fun toggleAttachmentOptions()
fun showVoiceMessageUI() fun showVoiceMessageUI()
fun onMicrophoneButtonMove(event: MotionEvent) fun onMicrophoneButtonMove(event: MotionEvent)

View File

@ -0,0 +1,47 @@
package org.thoughtcrime.securesms.conversation.v2.input_bar.mentions
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import kotlinx.android.synthetic.main.view_mention_candidate.view.*
import network.loki.messenger.R
import org.session.libsession.messaging.mentions.Mention
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.thoughtcrime.securesms.mms.GlideRequests
class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : RelativeLayout(context, attrs, defStyleAttr) {
var candidate = Mention("", "")
set(newValue) { field = newValue; update() }
var glide: GlideRequests? = null
var openGroupServer: String? = null
var openGroupRoom: String? = null
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context) : this(context, null)
companion object {
fun inflate(layoutInflater: LayoutInflater, parent: ViewGroup): MentionCandidateView {
return layoutInflater.inflate(R.layout.view_mention_candidate_v2, parent, false) as MentionCandidateView
}
}
private fun update() {
mentionCandidateNameTextView.text = candidate.displayName
profilePictureView.publicKey = candidate.publicKey
profilePictureView.displayName = candidate.displayName
profilePictureView.additionalPublicKey = null
profilePictureView.glide = glide!!
profilePictureView.update()
if (openGroupServer != null && openGroupRoom != null) {
val isUserModerator = OpenGroupAPIV2.isUserModerator(candidate.publicKey, openGroupRoom!!, openGroupServer!!)
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
} else {
moderatorIconImageView.visibility = View.GONE
}
}
}

View File

@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.conversation.v2.input_bar.mentions
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.toPx
import org.thoughtcrime.securesms.mms.GlideRequests
import org.session.libsession.messaging.mentions.Mention
class MentionCandidatesView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
private var candidates = listOf<Mention>()
set(newValue) { field = newValue; snAdapter.mentionCandidates = newValue }
var glide: GlideRequests? = null
set(newValue) { field = newValue; snAdapter.glide = newValue }
var openGroupServer: String? = null
set(newValue) { field = newValue; snAdapter.openGroupServer = openGroupServer }
var openGroupRoom: String? = null
set(newValue) { field = newValue; snAdapter.openGroupRoom = openGroupRoom }
var onCandidateSelected: ((Mention) -> Unit)? = null
private val snAdapter by lazy { Adapter(context) }
private class Adapter(private val context: Context) : BaseAdapter() {
var mentionCandidates = listOf<Mention>()
set(newValue) { field = newValue; notifyDataSetChanged() }
var glide: GlideRequests? = null
var openGroupServer: String? = null
var openGroupRoom: String? = null
override fun getCount(): Int { return mentionCandidates.count() }
override fun getItemId(position: Int): Long { return position.toLong() }
override fun getItem(position: Int): Mention { return mentionCandidates[position] }
override fun getView(position: Int, cellToBeReused: View?, parent: ViewGroup): View {
val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView.inflate(LayoutInflater.from(context), parent)
val mentionCandidate = getItem(position)
cell.glide = glide
cell.candidate = mentionCandidate
cell.openGroupServer = openGroupServer
cell.openGroupRoom = openGroupRoom
return cell
}
}
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context) : this(context, null)
init {
clipToOutline = true
adapter = snAdapter
snAdapter.mentionCandidates = candidates
setOnItemClickListener { _, _, position, _ ->
onCandidateSelected?.invoke(candidates[position])
}
}
fun show(mentionCandidates: List<Mention>, threadID: Long) {
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)
if (openGroup != null) {
openGroupServer = openGroup.server
openGroupRoom = openGroup.room
}
this.candidates = mentionCandidates
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
layoutParams.height = toPx(Math.min(mentionCandidates.count(), 4) * 44, resources)
this.layoutParams = layoutParams
}
fun hide() {
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
layoutParams.height = 0
this.layoutParams = layoutParams
}
}

View File

@ -30,7 +30,7 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr:
} }
private fun update() { private fun update() {
btnGroupNameDisplay.text = mentionCandidate.displayName mentionCandidateNameTextView.text = mentionCandidate.displayName
profilePictureView.publicKey = mentionCandidate.publicKey profilePictureView.publicKey = mentionCandidate.publicKey
profilePictureView.displayName = mentionCandidate.displayName profilePictureView.displayName = mentionCandidate.displayName
profilePictureView.additionalPublicKey = null profilePictureView.additionalPublicKey = null

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ripple <ripple
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/cell_selected"> android:color="@color/mention_candidates_view_background_ripple">
<item> <item>
<color android:color="@color/compose_view_background" /> <color android:color="@color/mention_candidates_view_background" />
</item> </item>
</ripple> </ripple>

View File

@ -18,6 +18,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />
<FrameLayout
android:id="@+id/inputBarAdditionalContentContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/input_bar_height" />
<LinearLayout <LinearLayout
android:id="@+id/attachmentOptionsContainer" android:id="@+id/attachmentOptionsContainer"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -30,7 +30,7 @@
</RelativeLayout> </RelativeLayout>
<TextView <TextView
android:id="@+id/btnGroupNameDisplay" android:id="@+id/mentionCandidateNameTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_spacing" android:layout_marginStart="@dimen/medium_spacing"

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCandidateView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@drawable/mention_candidate_view_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/medium_spacing"
android:paddingEnd="@dimen/medium_spacing"
android:gravity="center_vertical"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="26dp"
android:layout_height="32dp">
<org.thoughtcrime.securesms.loki.views.ProfilePictureView
android:id="@+id/profilePictureView"
android:layout_width="@dimen/very_small_profile_picture_size"
android:layout_height="@dimen/very_small_profile_picture_size"
android:layout_marginTop="3dp" />
<ImageView
android:id="@+id/moderatorIconImageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_crown"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>
<TextView
android:id="@+id/mentionCandidateNameTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_spacing"
android:textSize="@dimen/small_font_size"
android:textColor="@color/text"
android:maxLines="1"
android:ellipsize="end" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_alignParentTop="true"
android:background="@color/separator" />
</org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCandidateView>

View File

@ -10,7 +10,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="40dp"
android:textColor="@color/text" android:textColor="@color/text"
android:textSize="@dimen/small_font_size" android:textSize="@dimen/very_small_font_size"
android:textStyle="bold" android:textStyle="bold"
android:gravity="center" /> android:gravity="center" />

View File

@ -26,6 +26,8 @@
<color name="input_bar_button_background_opaque_border">#33000000</color> <color name="input_bar_button_background_opaque_border">#33000000</color>
<color name="input_bar_lock_view_background">#FCFCFC</color> <color name="input_bar_lock_view_background">#FCFCFC</color>
<color name="input_bar_lock_view_border">#66000000</color> <color name="input_bar_lock_view_border">#66000000</color>
<color name="mention_candidates_view_background">#FCFCFC</color>
<color name="mention_candidates_view_background_ripple">#DFDFDF</color>
<color name="default_background_start">#ffffff</color> <color name="default_background_start">#ffffff</color>
<color name="default_background_end">#fcfcfc</color> <color name="default_background_end">#fcfcfc</color>

View File

@ -33,6 +33,8 @@
<color name="input_bar_button_background_opaque_border">#33FFFFFF</color> <color name="input_bar_button_background_opaque_border">#33FFFFFF</color>
<color name="input_bar_lock_view_background">#171717</color> <color name="input_bar_lock_view_background">#171717</color>
<color name="input_bar_lock_view_border">#66FFFFFF</color> <color name="input_bar_lock_view_border">#66FFFFFF</color>
<color name="mention_candidates_view_background">#171717</color>
<color name="mention_candidates_view_background_ripple">#0C0C0C</color>
<array name="profile_picture_placeholder_colors"> <array name="profile_picture_placeholder_colors">
<item>#5ff8b0</item> <item>#5ff8b0</item>