separating the drag state from the main UI state in order to clarify which actions should be handled in each layer
This commit is contained in:
parent
7d262ebc32
commit
331bcbfc8a
@ -709,17 +709,18 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onVoiceRecordingCancelled() {
|
||||
onDeleteVoiceMessage()
|
||||
roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled = true))
|
||||
updateRecordingUiState(RecordingUiState.Cancelled)
|
||||
}
|
||||
|
||||
override fun onVoiceRecordingLocked() {
|
||||
updateRecordingUiState(RecordingUiState.Locked)
|
||||
}
|
||||
|
||||
override fun onVoiceRecordingEnded() {
|
||||
onSendVoiceMessage()
|
||||
}
|
||||
|
||||
override fun onUiStateChanged(state: RecordingUiState) {
|
||||
updateRecordingUiState(state)
|
||||
}
|
||||
|
||||
override fun onSendVoiceMessage() {
|
||||
roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled = false))
|
||||
updateRecordingUiState(RecordingUiState.None)
|
||||
|
@ -54,8 +54,6 @@ data class TextComposerViewState(
|
||||
VoiceMessageRecorderView.RecordingUiState.None,
|
||||
VoiceMessageRecorderView.RecordingUiState.Cancelled,
|
||||
VoiceMessageRecorderView.RecordingUiState.Playback -> false
|
||||
is VoiceMessageRecorderView.DraggingState.Cancelling,
|
||||
is VoiceMessageRecorderView.DraggingState.Locking,
|
||||
VoiceMessageRecorderView.RecordingUiState.Locked,
|
||||
VoiceMessageRecorderView.RecordingUiState.Started -> true
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import android.view.MotionEvent
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.DraggingState
|
||||
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState
|
||||
|
||||
class DraggableStateProcessor(
|
||||
resources: Resources,
|
||||
@ -44,37 +43,37 @@ class DraggableStateProcessor(
|
||||
lastDistanceY = 0F
|
||||
}
|
||||
|
||||
fun process(event: MotionEvent, recordingState: RecordingUiState): RecordingUiState {
|
||||
fun process(event: MotionEvent, draggingState: DraggingState): DraggingState {
|
||||
val currentX = event.rawX
|
||||
val currentY = event.rawY
|
||||
val distanceX = firstX - currentX
|
||||
val distanceY = firstY - currentY
|
||||
return recordingState.nextRecordingState(currentX, currentY, distanceX, distanceY).also {
|
||||
return draggingState.nextDragState(currentX, currentY, distanceX, distanceY).also {
|
||||
lastDistanceX = distanceX
|
||||
lastDistanceY = distanceY
|
||||
}
|
||||
}
|
||||
|
||||
private fun RecordingUiState.nextRecordingState(currentX: Float, currentY: Float, distanceX: Float, distanceY: Float): RecordingUiState {
|
||||
private fun DraggingState.nextDragState(currentX: Float, currentY: Float, distanceX: Float, distanceY: Float): DraggingState {
|
||||
return when (this) {
|
||||
RecordingUiState.Started -> {
|
||||
DraggingState.Ready -> {
|
||||
when {
|
||||
isDraggingToCancel(currentX, distanceX, distanceY) -> DraggingState.Cancelling(distanceX)
|
||||
isDraggingToLock(currentY, distanceX, distanceY) -> DraggingState.Locking(distanceY)
|
||||
else -> this
|
||||
else -> DraggingState.Ready
|
||||
}
|
||||
}
|
||||
is DraggingState.Cancelling -> {
|
||||
when {
|
||||
isDraggingToLock(currentY, distanceX, distanceY) -> DraggingState.Locking(distanceY)
|
||||
shouldCancelRecording(distanceX) -> RecordingUiState.Cancelled
|
||||
shouldCancelRecording(distanceX) -> DraggingState.Cancel
|
||||
else -> DraggingState.Cancelling(distanceX)
|
||||
}
|
||||
}
|
||||
is DraggingState.Locking -> {
|
||||
when {
|
||||
isDraggingToCancel(currentX, distanceX, distanceY) -> DraggingState.Cancelling(distanceX)
|
||||
shouldLockRecording(distanceY) -> RecordingUiState.Locked
|
||||
shouldLockRecording(distanceY) -> DraggingState.Lock
|
||||
else -> DraggingState.Locking(distanceY)
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
||||
fun onVoiceRecordingEnded()
|
||||
fun onVoicePlaybackButtonClicked()
|
||||
fun onVoiceRecordingCancelled()
|
||||
fun onUiStateChanged(state: RecordingUiState)
|
||||
fun onVoiceRecordingLocked()
|
||||
fun onSendVoiceMessage()
|
||||
fun onDeleteVoiceMessage()
|
||||
fun onRecordingLimitReached()
|
||||
@ -58,6 +58,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
||||
|
||||
private var recordingTicker: CountUpTimer? = null
|
||||
private var lastKnownState: RecordingUiState? = null
|
||||
private var dragState: DraggingState = DraggingState.Ignored
|
||||
|
||||
init {
|
||||
inflate(this.context, R.layout.view_voice_message_recorder, this)
|
||||
@ -74,13 +75,13 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
||||
voiceMessageViews.start(object : VoiceMessageViews.Actions {
|
||||
override fun onRequestRecording() = callback.onVoiceRecordingStarted()
|
||||
override fun onMicButtonReleased() {
|
||||
when (lastKnownState) {
|
||||
RecordingUiState.Locked -> {
|
||||
when (dragState) {
|
||||
DraggingState.Lock -> {
|
||||
// do nothing,
|
||||
// onSendVoiceMessage, onDeleteVoiceMessage or onRecordingLimitReached will be triggered instead
|
||||
}
|
||||
RecordingUiState.Cancelled -> callback.onVoiceRecordingCancelled()
|
||||
else -> callback.onVoiceRecordingEnded()
|
||||
DraggingState.Cancel -> callback.onVoiceRecordingCancelled()
|
||||
else -> callback.onVoiceRecordingEnded()
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,21 +89,8 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
||||
override fun onDeleteVoiceMessage() = callback.onDeleteVoiceMessage()
|
||||
override fun onWaveformClicked() = callback.onRecordingWaveformClicked()
|
||||
override fun onVoicePlaybackButtonClicked() = callback.onVoicePlaybackButtonClicked()
|
||||
override fun onMicButtonDrag(updater: (RecordingUiState) -> RecordingUiState) {
|
||||
when (val currentState = lastKnownState) {
|
||||
null, RecordingUiState.None -> {
|
||||
// ignore drag events when the view is idle
|
||||
}
|
||||
else -> {
|
||||
updater(currentState).also { newState ->
|
||||
when (newState) {
|
||||
// display drag events directly without leaving the view for faster UI feedback
|
||||
is DraggingState -> display(newState)
|
||||
else -> callback.onUiStateChanged(newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onMicButtonDrag(nextDragStateCreator: (DraggingState) -> DraggingState) {
|
||||
onDrag(dragState, newDragState = nextDragStateCreator(dragState))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -117,21 +105,19 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
||||
|
||||
fun display(recordingState: RecordingUiState) {
|
||||
if (lastKnownState == recordingState) return
|
||||
val previousState = lastKnownState
|
||||
lastKnownState = recordingState
|
||||
when (recordingState) {
|
||||
RecordingUiState.None -> {
|
||||
stopRecordingTicker()
|
||||
voiceMessageViews.initViews()
|
||||
reset()
|
||||
}
|
||||
RecordingUiState.Started -> {
|
||||
startRecordingTicker()
|
||||
voiceMessageViews.renderToast(context.getString(R.string.voice_message_release_to_send_toast))
|
||||
voiceMessageViews.showRecordingViews()
|
||||
dragState = DraggingState.Ready
|
||||
}
|
||||
RecordingUiState.Cancelled -> {
|
||||
stopRecordingTicker()
|
||||
voiceMessageViews.hideRecordingViews(recordingState)
|
||||
reset()
|
||||
vibrate(context)
|
||||
}
|
||||
RecordingUiState.Locked -> {
|
||||
@ -144,18 +130,34 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
||||
stopRecordingTicker()
|
||||
voiceMessageViews.showPlaybackViews()
|
||||
}
|
||||
is DraggingState -> when (recordingState) {
|
||||
is DraggingState.Cancelling -> voiceMessageViews.renderCancelling(recordingState.distanceX)
|
||||
is DraggingState.Locking -> {
|
||||
if (previousState is DraggingState.Cancelling) {
|
||||
voiceMessageViews.showRecordingViews()
|
||||
}
|
||||
voiceMessageViews.renderLocking(recordingState.distanceY)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun reset() {
|
||||
stopRecordingTicker()
|
||||
voiceMessageViews.initViews()
|
||||
dragState = DraggingState.Ignored
|
||||
}
|
||||
|
||||
private fun onDrag(currentDragState: DraggingState, newDragState: DraggingState) {
|
||||
when (newDragState) {
|
||||
is DraggingState.Cancelling -> voiceMessageViews.renderCancelling(newDragState.distanceX)
|
||||
is DraggingState.Locking -> {
|
||||
if (currentDragState is DraggingState.Cancelling) {
|
||||
voiceMessageViews.showRecordingViews()
|
||||
}
|
||||
voiceMessageViews.renderLocking(newDragState.distanceY)
|
||||
}
|
||||
DraggingState.Cancel -> callback.onVoiceRecordingCancelled()
|
||||
DraggingState.Lock -> callback.onVoiceRecordingLocked()
|
||||
DraggingState.Ignored,
|
||||
DraggingState.Ready -> {
|
||||
// do nothing
|
||||
}
|
||||
}.exhaustive
|
||||
dragState = newDragState
|
||||
}
|
||||
|
||||
private fun startRecordingTicker() {
|
||||
recordingTicker?.stop()
|
||||
recordingTicker = CountUpTimer().apply {
|
||||
@ -214,8 +216,12 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
||||
object Playback : RecordingUiState
|
||||
}
|
||||
|
||||
sealed interface DraggingState : RecordingUiState {
|
||||
sealed interface DraggingState {
|
||||
object Ready : DraggingState
|
||||
object Ignored : DraggingState
|
||||
data class Cancelling(val distanceX: Float) : DraggingState
|
||||
data class Locking(val distanceY: Float) : DraggingState
|
||||
object Cancel : DraggingState
|
||||
object Lock : DraggingState
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import im.vector.app.core.extensions.setAttributeTintedBackground
|
||||
import im.vector.app.core.extensions.setAttributeTintedImageResource
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.databinding.ViewVoiceMessageRecorderBinding
|
||||
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.DraggingState
|
||||
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
||||
|
||||
@ -68,11 +69,11 @@ class VoiceMessageViews(
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun observeMicButton(actions: Actions) {
|
||||
val positions = DraggableStateProcessor(resources, dimensionConverter)
|
||||
val draggableStateProcessor = DraggableStateProcessor(resources, dimensionConverter)
|
||||
views.voiceMessageMicButton.setOnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
positions.initialize(event)
|
||||
draggableStateProcessor.initialize(event)
|
||||
actions.onRequestRecording()
|
||||
true
|
||||
}
|
||||
@ -81,7 +82,7 @@ class VoiceMessageViews(
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
actions.onMicButtonDrag { currentState -> positions.process(event, currentState) }
|
||||
actions.onMicButtonDrag { currentState -> draggableStateProcessor.process(event, currentState) }
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
@ -339,7 +340,7 @@ class VoiceMessageViews(
|
||||
interface Actions {
|
||||
fun onRequestRecording()
|
||||
fun onMicButtonReleased()
|
||||
fun onMicButtonDrag(updater: (RecordingUiState) -> RecordingUiState)
|
||||
fun onMicButtonDrag(nextDragStateCreator: (DraggingState) -> DraggingState)
|
||||
fun onSendVoiceMessage()
|
||||
fun onDeleteVoiceMessage()
|
||||
fun onWaveformClicked()
|
||||
|
Loading…
Reference in New Issue
Block a user