session-android/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt

202 lines
8.6 KiB
Kotlin
Raw Normal View History

2021-06-15 01:42:18 +02:00
package org.thoughtcrime.securesms.conversation.v2.input_bar
import android.content.Context
2021-06-23 03:32:05 +02:00
import android.content.res.Resources
import android.graphics.PointF
import android.net.Uri
2021-06-30 06:58:27 +02:00
import android.text.InputType
import android.text.TextWatcher
2021-06-15 01:42:18 +02:00
import android.util.AttributeSet
import android.view.LayoutInflater
2021-06-17 02:53:56 +02:00
import android.view.MotionEvent
2021-06-15 06:05:32 +02:00
import android.widget.RelativeLayout
2021-06-16 02:39:24 +02:00
import androidx.core.view.isVisible
2021-06-15 01:42:18 +02:00
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewInputBarBinding
2021-06-24 08:23:37 +02:00
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
2021-07-01 03:27:52 +02:00
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
2021-06-24 08:23:37 +02:00
import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView
import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftViewDelegate
2021-06-18 07:11:41 +02:00
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteView
2021-06-18 08:04:22 +02:00
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteViewDelegate
2021-06-18 07:11:41 +02:00
import org.thoughtcrime.securesms.database.model.MessageRecord
2021-06-21 06:24:00 +02:00
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.contains
2021-07-09 05:18:48 +02:00
import org.thoughtcrime.securesms.util.toDp
import org.thoughtcrime.securesms.util.toPx
2021-06-15 01:42:18 +02:00
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, LinkPreviewDraftViewDelegate {
private lateinit var binding: ViewInputBarBinding
2021-06-23 03:32:05 +02:00
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
2021-06-18 08:04:22 +02:00
private val vMargin by lazy { toDp(4, resources) }
2021-06-23 05:57:13 +02:00
private val minHeight by lazy { toPx(56, resources) }
2021-06-24 08:23:37 +02:00
private var linkPreviewDraftView: LinkPreviewDraftView? = null
2021-06-16 01:51:50 +02:00
var delegate: InputBarDelegate? = null
2021-06-18 08:24:56 +02:00
var additionalContentHeight = 0
2021-06-28 05:29:17 +02:00
var quote: MessageRecord? = null
var linkPreview: LinkPreview? = null
var showInput: Boolean = true
set(value) { field = value; showOrHideInputIfNeeded() }
var showMediaControls: Boolean = true
set(value) {
field = value
showOrHideMediaControlsIfNeeded()
binding.inputBarEditText.showMediaControls = value
}
2021-06-15 01:42:18 +02:00
2021-06-22 08:23:47 +02:00
var text: String
get() { return binding.inputBarEditText.text?.toString() ?: "" }
set(value) { binding.inputBarEditText.setText(value) }
val attachmentButtonsContainerHeight: Int
get() = binding.attachmentsButtonContainer.height
2021-06-22 08:23:47 +02:00
2021-06-15 06:05:32 +02:00
private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24) }
private val microphoneButton by lazy { InputBarButton(context, R.drawable.ic_microphone) }
2021-06-16 02:39:24 +02:00
private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true) }
2021-06-15 06:05:32 +02:00
2021-06-15 01:42:18 +02:00
// region Lifecycle
2021-06-16 06:50:41 +02:00
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() }
2021-06-15 01:42:18 +02:00
2021-06-16 06:50:41 +02:00
private fun initialize() {
binding = ViewInputBarBinding.inflate(LayoutInflater.from(context), this, true)
2021-06-16 06:50:41 +02:00
// Attachments button
binding.attachmentsButtonContainer.addView(attachmentsButton)
attachmentsButton.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
2021-06-17 08:29:57 +02:00
attachmentsButton.onPress = { toggleAttachmentOptions() }
2021-06-16 06:50:41 +02:00
// Microphone button
binding.microphoneOrSendButtonContainer.addView(microphoneButton)
microphoneButton.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
microphoneButton.onLongPress = { startRecordingVoiceMessage() }
2021-06-17 02:53:56 +02:00
microphoneButton.onMove = { delegate?.onMicrophoneButtonMove(it) }
microphoneButton.onCancel = { delegate?.onMicrophoneButtonCancel(it) }
microphoneButton.onUp = { delegate?.onMicrophoneButtonUp(it) }
2021-06-16 06:50:41 +02:00
// Send button
binding.microphoneOrSendButtonContainer.addView(sendButton)
sendButton.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
2021-06-16 02:39:24 +02:00
sendButton.isVisible = false
sendButton.onUp = { e ->
if (sendButton.contains(PointF(e.x, e.y))) {
delegate?.sendMessage()
}
}
2021-06-16 06:50:41 +02:00
// Edit text
val incognitoFlag = if (TextSecurePreferences.isIncognitoKeyboardEnabled(context)) 16777216 else 0
binding.inputBarEditText.imeOptions = binding.inputBarEditText.imeOptions or incognitoFlag // Always use incognito keyboard if setting enabled
binding.inputBarEditText.inputType = binding.inputBarEditText.inputType or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
binding.inputBarEditText.delegate = this
2021-06-15 08:14:35 +02:00
}
// endregion
2021-06-16 02:39:24 +02:00
// region Updating
override fun inputBarEditTextContentChanged(text: CharSequence) {
sendButton.isVisible = text.isNotEmpty()
microphoneButton.isVisible = text.isEmpty()
2021-06-18 03:00:52 +02:00
delegate?.inputBarEditTextContentChanged(text)
2021-06-16 02:39:24 +02:00
}
2021-06-15 08:14:35 +02:00
override fun inputBarEditTextHeightChanged(newValue: Int) {
2021-06-15 01:42:18 +02:00
}
2021-06-16 06:50:41 +02:00
override fun commitInputContent(contentUri: Uri) {
delegate?.commitInputContent(contentUri)
}
2021-06-17 08:29:57 +02:00
private fun toggleAttachmentOptions() {
delegate?.toggleAttachmentOptions()
}
2021-06-28 03:11:29 +02:00
private fun startRecordingVoiceMessage() {
delegate?.startRecordingVoiceMessage()
}
2021-06-25 01:19:21 +02:00
// 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.
2021-07-01 03:27:52 +02:00
fun draftQuote(thread: Recipient, message: MessageRecord, glide: GlideRequests) {
2021-06-28 05:29:17 +02:00
quote = message
linkPreview = null
2021-06-24 08:23:37 +02:00
linkPreviewDraftView = null
binding.inputBarAdditionalContentContainer.removeAllViews()
2021-06-21 02:53:52 +02:00
val quoteView = QuoteView(context, QuoteView.Mode.Draft)
2021-06-18 08:04:22 +02:00
quoteView.delegate = this
binding.inputBarAdditionalContentContainer.addView(quoteView)
2021-06-21 06:24:00 +02:00
val attachments = (message as? MmsMessageRecord)?.slideDeck
2021-07-01 03:27:52 +02:00
val sender = if (message.isOutgoing) TextSecurePreferences.getLocalNumber(context)!! else message.individualRecipient.address.serialize()
quoteView.bind(sender, message.body, attachments,
thread, true, message.isOpenGroupInvitation, message.threadId, false, glide)
requestLayout()
2021-06-18 08:04:22 +02:00
}
override fun cancelQuoteDraft() {
2021-06-28 05:29:17 +02:00
quote = null
binding.inputBarAdditionalContentContainer.removeAllViews()
requestLayout()
2021-06-18 07:11:41 +02:00
}
2021-06-24 08:23:37 +02:00
fun draftLinkPreview() {
2021-06-28 05:29:17 +02:00
quote = null
binding.inputBarAdditionalContentContainer.removeAllViews()
2021-06-24 08:23:37 +02:00
val linkPreviewDraftView = LinkPreviewDraftView(context)
linkPreviewDraftView.delegate = this
2021-06-24 08:23:37 +02:00
this.linkPreviewDraftView = linkPreviewDraftView
binding.inputBarAdditionalContentContainer.addView(linkPreviewDraftView)
requestLayout()
2021-06-24 08:23:37 +02:00
}
fun updateLinkPreviewDraft(glide: GlideRequests, linkPreview: LinkPreview) {
2021-06-28 05:29:17 +02:00
this.linkPreview = linkPreview
2021-06-24 08:23:37 +02:00
val linkPreviewDraftView = this.linkPreviewDraftView ?: return
linkPreviewDraftView.update(glide, linkPreview)
}
override fun cancelLinkPreviewDraft() {
2021-06-28 08:03:41 +02:00
if (quote != null) { return }
2021-06-28 05:29:17 +02:00
linkPreview = null
binding.inputBarAdditionalContentContainer.removeAllViews()
requestLayout()
}
private fun showOrHideInputIfNeeded() {
if (showInput) {
setOf( binding.inputBarEditText, attachmentsButton ).forEach { it.isVisible = true }
microphoneButton.isVisible = text.isEmpty()
sendButton.isVisible = text.isNotEmpty()
} else {
cancelQuoteDraft()
cancelLinkPreviewDraft()
val views = setOf( binding.inputBarEditText, attachmentsButton, microphoneButton, sendButton )
views.forEach { it.isVisible = false }
}
}
private fun showOrHideMediaControlsIfNeeded() {
setOf(attachmentsButton, microphoneButton).forEach { it.snIsEnabled = showMediaControls }
}
fun addTextChangedListener(textWatcher: TextWatcher) {
binding.inputBarEditText.addTextChangedListener(textWatcher)
}
fun setSelection(index: Int) {
binding.inputBarEditText.setSelection(index)
}
2021-06-15 01:42:18 +02:00
// endregion
2021-06-16 01:51:50 +02:00
}
interface InputBarDelegate {
2021-06-18 03:00:52 +02:00
fun inputBarEditTextContentChanged(newContent: CharSequence)
2021-06-17 08:29:57 +02:00
fun toggleAttachmentOptions()
2021-06-16 06:50:41 +02:00
fun showVoiceMessageUI()
2021-06-28 03:11:29 +02:00
fun startRecordingVoiceMessage()
2021-06-17 02:53:56 +02:00
fun onMicrophoneButtonMove(event: MotionEvent)
fun onMicrophoneButtonCancel(event: MotionEvent)
fun onMicrophoneButtonUp(event: MotionEvent)
2021-06-28 02:00:18 +02:00
fun sendMessage()
fun commitInputContent(contentUri: Uri)
2021-06-15 01:42:18 +02:00
}