203 lines
9.5 KiB
Kotlin
203 lines
9.5 KiB
Kotlin
package org.thoughtcrime.securesms.conversation.v2.input_bar
|
|
|
|
import android.content.Context
|
|
import android.content.res.Resources
|
|
import android.net.Uri
|
|
import android.text.InputType
|
|
import android.util.AttributeSet
|
|
import android.view.LayoutInflater
|
|
import android.view.MotionEvent
|
|
import android.widget.RelativeLayout
|
|
import androidx.core.view.isVisible
|
|
import kotlinx.android.synthetic.main.view_input_bar.view.*
|
|
import network.loki.messenger.R
|
|
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
|
import org.session.libsession.utilities.TextSecurePreferences
|
|
import org.session.libsession.utilities.recipients.Recipient
|
|
import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView
|
|
import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftViewDelegate
|
|
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteView
|
|
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteViewDelegate
|
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
|
import org.thoughtcrime.securesms.util.toDp
|
|
import org.thoughtcrime.securesms.util.toPx
|
|
import kotlin.math.max
|
|
import kotlin.math.roundToInt
|
|
|
|
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, LinkPreviewDraftViewDelegate {
|
|
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
|
private val vMargin by lazy { toDp(4, resources) }
|
|
private val minHeight by lazy { toPx(56, resources) }
|
|
private var linkPreviewDraftView: LinkPreviewDraftView? = null
|
|
var delegate: InputBarDelegate? = null
|
|
var additionalContentHeight = 0
|
|
var quote: MessageRecord? = null
|
|
var linkPreview: LinkPreview? = null
|
|
var showInput: Boolean = true
|
|
set(value) { field = value; showOrHideInputIfNeeded() }
|
|
|
|
var text: String
|
|
get() { return inputBarEditText.text?.toString() ?: "" }
|
|
set(value) { inputBarEditText.setText(value) }
|
|
|
|
private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24) }
|
|
private val microphoneButton by lazy { InputBarButton(context, R.drawable.ic_microphone) }
|
|
private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true) }
|
|
|
|
// region Lifecycle
|
|
constructor(context: Context) : super(context) { initialize() }
|
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
|
|
|
private fun initialize() {
|
|
LayoutInflater.from(context).inflate(R.layout.view_input_bar, this)
|
|
// Attachments button
|
|
attachmentsButtonContainer.addView(attachmentsButton)
|
|
attachmentsButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
|
attachmentsButton.onPress = { toggleAttachmentOptions() }
|
|
// Microphone button
|
|
microphoneOrSendButtonContainer.addView(microphoneButton)
|
|
microphoneButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
|
microphoneButton.onLongPress = { startRecordingVoiceMessage() }
|
|
microphoneButton.onMove = { delegate?.onMicrophoneButtonMove(it) }
|
|
microphoneButton.onCancel = { delegate?.onMicrophoneButtonCancel(it) }
|
|
microphoneButton.onUp = { delegate?.onMicrophoneButtonUp(it) }
|
|
// Send button
|
|
microphoneOrSendButtonContainer.addView(sendButton)
|
|
sendButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
|
sendButton.isVisible = false
|
|
sendButton.onUp = { delegate?.sendMessage() }
|
|
// Edit text
|
|
val incognitoFlag = if (TextSecurePreferences.isIncognitoKeyboardEnabled(context)) 16777216 else 0
|
|
inputBarEditText.imeOptions = inputBarEditText.imeOptions or incognitoFlag // Always use incognito keyboard if setting enabled
|
|
inputBarEditText.inputType = inputBarEditText.inputType or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
inputBarEditText.delegate = this
|
|
}
|
|
// endregion
|
|
|
|
// region General
|
|
private fun setHeight(newHeight: Int) {
|
|
val layoutParams = inputBarLinearLayout.layoutParams as LayoutParams
|
|
layoutParams.height = newHeight
|
|
inputBarLinearLayout.layoutParams = layoutParams
|
|
delegate?.inputBarHeightChanged(newHeight)
|
|
}
|
|
// endregion
|
|
|
|
// region Updating
|
|
override fun inputBarEditTextContentChanged(text: CharSequence) {
|
|
sendButton.isVisible = text.isNotEmpty()
|
|
microphoneButton.isVisible = text.isEmpty()
|
|
delegate?.inputBarEditTextContentChanged(text)
|
|
}
|
|
|
|
override fun inputBarEditTextHeightChanged(newValue: Int) {
|
|
val newHeight = max(newValue + 2 * vMargin, minHeight) + inputBarAdditionalContentContainer.height
|
|
setHeight(newHeight)
|
|
}
|
|
|
|
override fun commitInputContent(contentUri: Uri) {
|
|
delegate?.commitInputContent(contentUri)
|
|
}
|
|
|
|
private fun toggleAttachmentOptions() {
|
|
delegate?.toggleAttachmentOptions()
|
|
}
|
|
|
|
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.
|
|
|
|
fun draftQuote(thread: Recipient, message: MessageRecord, glide: GlideRequests) {
|
|
quote = message
|
|
linkPreview = null
|
|
linkPreviewDraftView = null
|
|
inputBarAdditionalContentContainer.removeAllViews()
|
|
val quoteView = QuoteView(context, QuoteView.Mode.Draft)
|
|
quoteView.delegate = this
|
|
inputBarAdditionalContentContainer.addView(quoteView)
|
|
val attachments = (message as? MmsMessageRecord)?.slideDeck
|
|
// The max content width is the screen width - 2 times the horizontal input bar padding - the
|
|
// quote view content area's start and end margins. This unfortunately has to be calculated manually
|
|
// here to get the layout right.
|
|
val maxContentWidth = (screenWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources) - toPx(30, resources)).roundToInt()
|
|
val sender = if (message.isOutgoing) TextSecurePreferences.getLocalNumber(context)!! else message.individualRecipient.address.serialize()
|
|
quoteView.bind(sender, message.body, attachments,
|
|
thread, true, maxContentWidth, message.isOpenGroupInvitation, message.threadId, false, glide)
|
|
// 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)
|
|
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight) + quoteViewIntrinsicHeight
|
|
additionalContentHeight = quoteViewIntrinsicHeight
|
|
setHeight(newHeight)
|
|
}
|
|
|
|
override fun cancelQuoteDraft() {
|
|
quote = null
|
|
inputBarAdditionalContentContainer.removeAllViews()
|
|
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight)
|
|
additionalContentHeight = 0
|
|
setHeight(newHeight)
|
|
}
|
|
|
|
fun draftLinkPreview() {
|
|
quote = null
|
|
val linkPreviewDraftHeight = toPx(88, resources)
|
|
inputBarAdditionalContentContainer.removeAllViews()
|
|
val linkPreviewDraftView = LinkPreviewDraftView(context)
|
|
linkPreviewDraftView.delegate = this
|
|
this.linkPreviewDraftView = linkPreviewDraftView
|
|
inputBarAdditionalContentContainer.addView(linkPreviewDraftView)
|
|
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight) + linkPreviewDraftHeight
|
|
additionalContentHeight = linkPreviewDraftHeight
|
|
setHeight(newHeight)
|
|
}
|
|
|
|
fun updateLinkPreviewDraft(glide: GlideRequests, linkPreview: LinkPreview) {
|
|
this.linkPreview = linkPreview
|
|
val linkPreviewDraftView = this.linkPreviewDraftView ?: return
|
|
linkPreviewDraftView.update(glide, linkPreview)
|
|
}
|
|
|
|
override fun cancelLinkPreviewDraft() {
|
|
if (quote != null) { return }
|
|
linkPreview = null
|
|
inputBarAdditionalContentContainer.removeAllViews()
|
|
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight)
|
|
additionalContentHeight = 0
|
|
setHeight(newHeight)
|
|
}
|
|
|
|
private fun showOrHideInputIfNeeded() {
|
|
if (showInput) {
|
|
setOf( inputBarEditText, attachmentsButton ).forEach { it.isVisible = true }
|
|
microphoneButton.isVisible = text.isEmpty()
|
|
sendButton.isVisible = text.isNotEmpty()
|
|
} else {
|
|
cancelQuoteDraft()
|
|
cancelLinkPreviewDraft()
|
|
val views = setOf( inputBarEditText, attachmentsButton, microphoneButton, sendButton )
|
|
views.forEach { it.isVisible = false }
|
|
}
|
|
}
|
|
// endregion
|
|
}
|
|
|
|
interface InputBarDelegate {
|
|
|
|
fun inputBarHeightChanged(newValue: Int)
|
|
fun inputBarEditTextContentChanged(newContent: CharSequence)
|
|
fun toggleAttachmentOptions()
|
|
fun showVoiceMessageUI()
|
|
fun startRecordingVoiceMessage()
|
|
fun onMicrophoneButtonMove(event: MotionEvent)
|
|
fun onMicrophoneButtonCancel(event: MotionEvent)
|
|
fun onMicrophoneButtonUp(event: MotionEvent)
|
|
fun sendMessage()
|
|
fun commitInputContent(contentUri: Uri)
|
|
} |