mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-26 17:57:42 +00:00
Add basic voice message recording UI
This commit is contained in:
parent
bf25a44f7b
commit
5ae201b81b
@ -6,6 +6,7 @@ import android.view.ActionMode
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
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
|
||||||
@ -112,6 +113,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
recyclerViewLayoutParams.bottomMargin = newValue
|
recyclerViewLayoutParams.bottomMargin = newValue
|
||||||
conversationRecyclerView.layoutParams = recyclerViewLayoutParams
|
conversationRecyclerView.layoutParams = recyclerViewLayoutParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showVoiceMessageUI() {
|
||||||
|
inputBarRecordingView.isVisible = true
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Interaction
|
// region Interaction
|
||||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.conversation.v2.input_bar
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import kotlinx.android.synthetic.main.view_input_bar.view.*
|
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 org.thoughtcrime.securesms.loki.utilities.toPx
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class InputBar : LinearLayout, InputBarEditTextDelegate {
|
class InputBar : RelativeLayout, InputBarEditTextDelegate {
|
||||||
var delegate: InputBarDelegate? = null
|
var delegate: InputBarDelegate? = null
|
||||||
|
|
||||||
private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24) }
|
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) }
|
private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true) }
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
constructor(context: Context) : super(context) {
|
constructor(context: Context) : super(context) { initialize() }
|
||||||
setUpViewHierarchy()
|
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) {
|
private fun initialize() {
|
||||||
setUpViewHierarchy()
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
|
||||||
setUpViewHierarchy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUpViewHierarchy() {
|
|
||||||
LayoutInflater.from(context).inflate(R.layout.view_input_bar, this)
|
LayoutInflater.from(context).inflate(R.layout.view_input_bar, this)
|
||||||
|
// Attachments button
|
||||||
attachmentsButtonContainer.addView(attachmentsButton)
|
attachmentsButtonContainer.addView(attachmentsButton)
|
||||||
attachmentsButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
attachmentsButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
||||||
attachmentsButton.setOnClickListener { }
|
// Microphone button
|
||||||
microphoneOrSendButtonContainer.addView(microphoneButton)
|
microphoneOrSendButtonContainer.addView(microphoneButton)
|
||||||
microphoneButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
microphoneButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
||||||
microphoneButton.setOnClickListener { }
|
microphoneButton.onLongPress = {
|
||||||
|
showVoiceMessageUI()
|
||||||
|
}
|
||||||
|
// Send button
|
||||||
microphoneOrSendButtonContainer.addView(sendButton)
|
microphoneOrSendButtonContainer.addView(sendButton)
|
||||||
sendButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
sendButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
||||||
sendButton.setOnClickListener { }
|
|
||||||
sendButton.isVisible = false
|
sendButton.isVisible = false
|
||||||
|
// Edit text
|
||||||
inputBarEditText.imeOptions = inputBarEditText.imeOptions or 16777216 // Always use incognito keyboard
|
inputBarEditText.imeOptions = inputBarEditText.imeOptions or 16777216 // Always use incognito keyboard
|
||||||
inputBarEditText.delegate = this
|
inputBarEditText.delegate = this
|
||||||
}
|
}
|
||||||
@ -63,10 +58,15 @@ class InputBar : LinearLayout, InputBarEditTextDelegate {
|
|||||||
inputBarLinearLayout.layoutParams = layoutParams
|
inputBarLinearLayout.layoutParams = layoutParams
|
||||||
delegate?.inputBarHeightChanged(newHeight)
|
delegate?.inputBarHeightChanged(newHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showVoiceMessageUI() {
|
||||||
|
delegate?.showVoiceMessageUI()
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InputBarDelegate {
|
interface InputBarDelegate {
|
||||||
|
|
||||||
fun inputBarHeightChanged(newValue: Int)
|
fun inputBarHeightChanged(newValue: Int)
|
||||||
|
fun showVoiceMessageUI()
|
||||||
}
|
}
|
@ -6,7 +6,10 @@ import android.content.Context
|
|||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
@ -14,13 +17,22 @@ import android.widget.ImageView
|
|||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.*
|
||||||
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
||||||
import org.thoughtcrime.securesms.loki.views.InputBarButtonImageViewContainer
|
import org.thoughtcrime.securesms.loki.views.InputBarButtonImageViewContainer
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
class InputBarButton : RelativeLayout {
|
class InputBarButton : RelativeLayout {
|
||||||
|
private val gestureHandler = Handler(Looper.getMainLooper())
|
||||||
private var isSendButton = false
|
private var isSendButton = false
|
||||||
@DrawableRes private var iconID = 0
|
@DrawableRes private var iconID = 0
|
||||||
|
private var longPressCallback: Runnable? = null
|
||||||
|
private var onDownTimestamp = 0L
|
||||||
|
|
||||||
|
var onPress: (() -> Unit)? = null
|
||||||
|
var onLongPress: (() -> Unit)? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val animationDuration = 250.toLong()
|
val animationDuration = 250.toLong()
|
||||||
@ -99,16 +111,37 @@ class InputBarButton : RelativeLayout {
|
|||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> onDown(event)
|
||||||
|
MotionEvent.ACTION_UP -> onUp(event)
|
||||||
|
MotionEvent.ACTION_CANCEL -> onCancel(event)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDown(event: MotionEvent) {
|
||||||
expand()
|
expand()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
|
performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
|
||||||
} else {
|
} else {
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
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
|
||||||
}
|
}
|
||||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { collapse() }
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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) {
|
private fun onCancel(event: MotionEvent) {
|
||||||
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
|
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
|
||||||
animate()
|
resetPosition()
|
||||||
.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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onUp(event: MotionEvent) {
|
private fun onUp(event: MotionEvent) {
|
||||||
@ -251,6 +240,10 @@ class VisibleMessageView : LinearLayout {
|
|||||||
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
|
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
|
||||||
onPress?.invoke()
|
onPress?.invoke()
|
||||||
}
|
}
|
||||||
|
resetPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetPosition() {
|
||||||
animate()
|
animate()
|
||||||
.translationX(0.0f)
|
.translationX(0.0f)
|
||||||
.setDuration(150)
|
.setDuration(150)
|
||||||
|
@ -18,4 +18,12 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true" />
|
android:layout_alignParentBottom="true" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarRecordingView
|
||||||
|
android:id="@+id/inputBarRecordingView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="-12dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_alignParentBottom="true" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
100
app/src/main/res/layout/view_input_bar_recording.xml
Normal file
100
app/src/main/res/layout/view_input_bar_recording.xml
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp">
|
||||||
|
|
||||||
|
<!-- The height of the fake input bar below is 68 dp
|
||||||
|
because the input bar is 56 dp but we have to
|
||||||
|
account for the fact that this whole view has a
|
||||||
|
negative bottom margin of 12 dp -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="68dp"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="@color/separator" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/input_bar_background" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:background="@drawable/circle_tintable"
|
||||||
|
android:backgroundTint="@color/destructive" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/small_spacing"
|
||||||
|
android:text="00:00"
|
||||||
|
android:textSize="@dimen/small_font_size"
|
||||||
|
android:textColor="@color/text" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@drawable/ic_keyboard_arrow_left_grey600_24dp"
|
||||||
|
android:layout_marginTop="1dp"
|
||||||
|
app:tint="@color/text"
|
||||||
|
android:alpha="0.6" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:text="@string/conversation_input_panel__slide_to_cancel"
|
||||||
|
android:textSize="@dimen/very_small_font_size"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:alpha="0.6" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginEnd="-8dp"
|
||||||
|
android:background="@drawable/circle_tintable"
|
||||||
|
android:backgroundTint="@color/destructive" >
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:src="@drawable/ic_microphone" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
Loading…
x
Reference in New Issue
Block a user