Support scrolling playback on recorded audio before sending.

This commit is contained in:
Onuray Sahin 2022-03-04 16:21:28 +03:00
parent 3bd4a4ccd3
commit 5168d715ce
8 changed files with 65 additions and 23 deletions

View File

@ -786,6 +786,18 @@ class TimelineFragment @Inject constructor(
updateRecordingUiState(RecordingUiState.Draft) updateRecordingUiState(RecordingUiState.Draft)
} }
override fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int) {
messageComposerViewModel.handle(
MessageComposerAction.VoiceWaveformTouchedUp(VoiceMessagePlaybackTracker.RECORDING_ID, duration, percentage)
)
}
override fun onVoiceWaveformMoved(percentage: Float, duration: Int) {
messageComposerViewModel.handle(
MessageComposerAction.VoiceWaveformTouchedUp(VoiceMessagePlaybackTracker.RECORDING_ID, duration, percentage)
)
}
private fun updateRecordingUiState(state: RecordingUiState) { private fun updateRecordingUiState(state: RecordingUiState) {
messageComposerViewModel.handle( messageComposerViewModel.handle(
MessageComposerAction.OnVoiceRecordingUiStateChanged(state)) MessageComposerAction.OnVoiceRecordingUiStateChanged(state))
@ -2051,12 +2063,12 @@ class TimelineFragment @Inject constructor(
messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent)) messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
} }
override fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) { override fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float) {
messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, messageAudioContent, percentage)) messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, duration, percentage))
} }
override fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) { override fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float) {
messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, messageAudioContent, percentage)) messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, duration, percentage))
} }
private fun onShareActionClicked(action: EventSharedAction.Share) { private fun onShareActionClicked(action: EventSharedAction.Share) {

View File

@ -40,6 +40,6 @@ sealed class MessageComposerAction : VectorViewModelAction {
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction() data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
object PlayOrPauseRecordingPlayback : MessageComposerAction() object PlayOrPauseRecordingPlayback : MessageComposerAction()
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction() data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
data class VoiceWaveformTouchedUp(val eventId: String, val messageAudioContent: MessageAudioContent, val percentage: Float) : MessageComposerAction() data class VoiceWaveformTouchedUp(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
data class VoiceWaveformMovedTo(val eventId: String, val messageAudioContent: MessageAudioContent, val percentage: Float) : MessageComposerAction() data class VoiceWaveformMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
} }

View File

@ -864,15 +864,11 @@ class MessageComposerViewModel @AssistedInject constructor(
} }
private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) { private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) {
val duration = (action.messageAudioContent.audioInfo?.duration ?: 0) voiceMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
val toMillisecond = (action.percentage * duration).toInt()
voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
} }
private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) { private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) {
val duration = (action.messageAudioContent.audioInfo?.duration ?: 0) voiceMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
val toMillisecond = (action.percentage * duration).toInt()
voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
} }
private fun handleEntersBackground(composerText: String) { private fun handleEntersBackground(composerText: String) {

View File

@ -171,13 +171,13 @@ class VoiceMessageHelper @Inject constructor(
} }
fun stopPlayback() { fun stopPlayback() {
playbackTracker.stopPlayback(VoiceMessagePlaybackTracker.RECORDING_ID) playbackTracker.pausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID)
mediaPlayer?.stop() mediaPlayer?.stop()
stopPlaybackTicker() stopPlaybackTicker()
} }
fun movePlaybackTo(id: String, toMillisecond: Int, totalDuration: Int) { fun movePlaybackTo(id: String, percentage: Float, totalDuration: Int) {
val percentage = toMillisecond.toFloat() / totalDuration val toMillisecond = (totalDuration * percentage).toInt()
playbackTracker.updateCurrentPlaybackTime(id, toMillisecond, percentage) playbackTracker.updateCurrentPlaybackTime(id, toMillisecond, percentage)
stopPlayback() stopPlayback()

View File

@ -53,6 +53,8 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
fun onDeleteVoiceMessage() fun onDeleteVoiceMessage()
fun onRecordingLimitReached() fun onRecordingLimitReached()
fun onRecordingWaveformClicked() fun onRecordingWaveformClicked()
fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int)
fun onVoiceWaveformMoved(percentage: Float, duration: Int)
} }
@Inject lateinit var clock: Clock @Inject lateinit var clock: Clock
@ -65,6 +67,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 private var dragState: DraggingState = DraggingState.Ignored
private var recordingDuration: Long = 0
init { init {
inflate(this.context, R.layout.view_voice_message_recorder, this) inflate(this.context, R.layout.view_voice_message_recorder, this)
@ -95,7 +98,6 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
override fun onDeleteVoiceMessage() = callback.onDeleteVoiceMessage() override fun onDeleteVoiceMessage() = callback.onDeleteVoiceMessage()
override fun onWaveformClicked() { override fun onWaveformClicked() {
when (lastKnownState) { when (lastKnownState) {
RecordingUiState.Draft -> callback.onVoicePlaybackButtonClicked()
is RecordingUiState.Recording, is RecordingUiState.Recording,
is RecordingUiState.Locked -> callback.onRecordingWaveformClicked() is RecordingUiState.Locked -> callback.onRecordingWaveformClicked()
} }
@ -105,6 +107,18 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
override fun onMicButtonDrag(nextDragStateCreator: (DraggingState) -> DraggingState) { override fun onMicButtonDrag(nextDragStateCreator: (DraggingState) -> DraggingState) {
onDrag(dragState, newDragState = nextDragStateCreator(dragState)) onDrag(dragState, newDragState = nextDragStateCreator(dragState))
} }
override fun onVoiceWaveformTouchedUp(percentage: Float) {
if (lastKnownState == RecordingUiState.Draft) {
callback.onVoiceWaveformTouchedUp(percentage, recordingDuration.toInt())
}
}
override fun onVoiceWaveformMoved(percentage: Float) {
if (lastKnownState == RecordingUiState.Draft) {
callback.onVoiceWaveformMoved(percentage, recordingDuration.toInt())
}
}
}) })
} }
@ -203,6 +217,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
} }
private fun stopRecordingTicker() { private fun stopRecordingTicker() {
recordingDuration = recordingTicker?.elapsedTime() ?: 0
recordingTicker?.stop() recordingTicker?.stop()
recordingTicker = null recordingTicker = null
} }

View File

@ -60,8 +60,21 @@ class VoiceMessageViews(
actions.onDeleteVoiceMessage() actions.onDeleteVoiceMessage()
} }
views.voicePlaybackWaveform.setOnClickListener { views.voicePlaybackWaveform.setOnTouchListener { view, motionEvent ->
actions.onWaveformClicked() when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
actions.onWaveformClicked()
}
MotionEvent.ACTION_UP -> {
val percentage = getTouchedPositionPercentage(motionEvent, view)
actions.onVoiceWaveformTouchedUp(percentage)
}
MotionEvent.ACTION_MOVE -> {
val percentage = getTouchedPositionPercentage(motionEvent, view)
actions.onVoiceWaveformMoved(percentage)
}
}
true
} }
views.voicePlaybackControlButton.setOnClickListener { views.voicePlaybackControlButton.setOnClickListener {
@ -70,6 +83,8 @@ class VoiceMessageViews(
observeMicButton(actions) observeMicButton(actions)
} }
private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = motionEvent.x / view.width
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun observeMicButton(actions: Actions) { private fun observeMicButton(actions: Actions) {
val draggableStateProcessor = DraggableStateProcessor(resources, dimensionConverter) val draggableStateProcessor = DraggableStateProcessor(resources, dimensionConverter)
@ -332,7 +347,7 @@ class VoiceMessageViews(
fun renderRecordingWaveform(amplitudeList: Array<Int>) { fun renderRecordingWaveform(amplitudeList: Array<Int>) {
views.voicePlaybackWaveform.doOnLayout { waveFormView -> views.voicePlaybackWaveform.doOnLayout { waveFormView ->
val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_secondary) val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_quaternary)
amplitudeList.iterator().forEach { amplitudeList.iterator().forEach {
(waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor)) (waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor))
} }
@ -355,5 +370,7 @@ class VoiceMessageViews(
fun onDeleteVoiceMessage() fun onDeleteVoiceMessage()
fun onWaveformClicked() fun onWaveformClicked()
fun onVoicePlaybackButtonClicked() fun onVoicePlaybackButtonClicked()
fun onVoiceWaveformTouchedUp(percentage: Float)
fun onVoiceWaveformMoved(percentage: Float)
} }
} }

View File

@ -138,8 +138,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
fun getPreviewUrlRetriever(): PreviewUrlRetriever fun getPreviewUrlRetriever(): PreviewUrlRetriever
fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent) fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float)
fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float)
} }
interface ReactionPillCallback { interface ReactionPillCallback {

View File

@ -359,11 +359,13 @@ class MessageItemFactory @Inject constructor(
val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener { val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener {
override fun onWaveformTouchedUp(percentage: Float) { override fun onWaveformTouchedUp(percentage: Float) {
params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, messageContent, percentage) val duration = messageContent.audioInfo?.duration ?: 0
params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, duration, percentage)
} }
override fun onWaveformMovedTo(percentage: Float) { override fun onWaveformMovedTo(percentage: Float) {
params.callback?.onVoiceWaveformMovedTo(informationData.eventId, messageContent, percentage) val duration = messageContent.audioInfo?.duration ?: 0
params.callback?.onVoiceWaveformMovedTo(informationData.eventId, duration, percentage)
} }
} }