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