Add basic voice message recording UI

This commit is contained in:
Niels Andriesse
2021-06-16 14:50:41 +10:00
parent bf25a44f7b
commit 5ae201b81b
7 changed files with 198 additions and 41 deletions

View File

@@ -6,6 +6,7 @@ import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.widget.RelativeLayout
import androidx.core.view.isVisible
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
@@ -112,6 +113,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
recyclerViewLayoutParams.bottomMargin = newValue
conversationRecyclerView.layoutParams = recyclerViewLayoutParams
}
override fun showVoiceMessageUI() {
inputBarRecordingView.isVisible = true
}
// endregion
// region Interaction

View File

@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.conversation.v2.input_bar
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.view_input_bar.view.*
@@ -12,7 +11,7 @@ import org.thoughtcrime.securesms.loki.utilities.toDp
import org.thoughtcrime.securesms.loki.utilities.toPx
import kotlin.math.max
class InputBar : LinearLayout, InputBarEditTextDelegate {
class InputBar : RelativeLayout, InputBarEditTextDelegate {
var delegate: InputBarDelegate? = null
private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24) }
@@ -20,30 +19,26 @@ class InputBar : LinearLayout, InputBarEditTextDelegate {
private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true) }
// region Lifecycle
constructor(context: Context) : super(context) {
setUpViewHierarchy()
}
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() }
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
setUpViewHierarchy()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
setUpViewHierarchy()
}
private fun setUpViewHierarchy() {
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.setOnClickListener { }
// Microphone button
microphoneOrSendButtonContainer.addView(microphoneButton)
microphoneButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
microphoneButton.setOnClickListener { }
microphoneButton.onLongPress = {
showVoiceMessageUI()
}
// Send button
microphoneOrSendButtonContainer.addView(sendButton)
sendButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
sendButton.setOnClickListener { }
sendButton.isVisible = false
// Edit text
inputBarEditText.imeOptions = inputBarEditText.imeOptions or 16777216 // Always use incognito keyboard
inputBarEditText.delegate = this
}
@@ -63,10 +58,15 @@ class InputBar : LinearLayout, InputBarEditTextDelegate {
inputBarLinearLayout.layoutParams = layoutParams
delegate?.inputBarHeightChanged(newHeight)
}
private fun showVoiceMessageUI() {
delegate?.showVoiceMessageUI()
}
// endregion
}
interface InputBarDelegate {
fun inputBarHeightChanged(newValue: Int)
fun showVoiceMessageUI()
}

View File

@@ -6,7 +6,10 @@ import android.content.Context
import android.content.res.ColorStateList
import android.graphics.PointF
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -14,13 +17,22 @@ import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.annotation.DrawableRes
import network.loki.messenger.R
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
import org.thoughtcrime.securesms.loki.views.InputBarButtonImageViewContainer
import java.util.*
import kotlin.math.abs
class InputBarButton : RelativeLayout {
private val gestureHandler = Handler(Looper.getMainLooper())
private var isSendButton = false
@DrawableRes private var iconID = 0
private var longPressCallback: Runnable? = null
private var onDownTimestamp = 0L
var onPress: (() -> Unit)? = null
var onLongPress: (() -> Unit)? = null
companion object {
val animationDuration = 250.toLong()
@@ -99,16 +111,37 @@ class InputBarButton : RelativeLayout {
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
expand()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
} else {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { collapse() }
MotionEvent.ACTION_DOWN -> onDown(event)
MotionEvent.ACTION_UP -> onUp(event)
MotionEvent.ACTION_CANCEL -> onCancel(event)
}
return true
}
private fun onDown(event: MotionEvent) {
expand()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
} else {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
val newLongPressCallback = Runnable { onLongPress?.invoke() }
this.longPressCallback = newLongPressCallback
gestureHandler.postDelayed(newLongPressCallback, VisibleMessageView.longPressDurationThreshold)
onDownTimestamp = Date().time
}
private fun onCancel(event: MotionEvent) {
collapse()
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
}
private fun onUp(event: MotionEvent) {
collapse()
if ((Date().time - onDownTimestamp) < VisibleMessageView.longPressDurationThreshold) {
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
onPress?.invoke()
}
}
}

View File

@@ -0,0 +1,18 @@
package org.thoughtcrime.securesms.conversation.v2.input_bar
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.RelativeLayout
import network.loki.messenger.R
class InputBarRecordingView : RelativeLayout {
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_recording, this)
}
}

View File

@@ -230,18 +230,7 @@ class VisibleMessageView : LinearLayout {
private fun onCancel(event: MotionEvent) {
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
animate()
.translationX(0.0f)
.setDuration(150)
.setUpdateListener {
postInvalidate() // Ensure onDraw(canvas:) is called
}
.start()
// Bit of a hack to keep the date break text view from moving
dateBreakTextView.animate()
.translationX(0.0f)
.setDuration(150)
.start()
resetPosition()
}
private fun onUp(event: MotionEvent) {
@@ -251,6 +240,10 @@ class VisibleMessageView : LinearLayout {
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
onPress?.invoke()
}
resetPosition()
}
private fun resetPosition() {
animate()
.translationX(0.0f)
.setDuration(150)
@@ -260,9 +253,9 @@ class VisibleMessageView : LinearLayout {
.start()
// Bit of a hack to keep the date break text view from moving
dateBreakTextView.animate()
.translationX(0.0f)
.setDuration(150)
.start()
.translationX(0.0f)
.setDuration(150)
.start()
}
private fun onLongPress() {