mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-14 06:52:19 +00:00
Add basic voice message recording UI
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user