Support scrolling playback on timeline.

This commit is contained in:
Onuray Sahin 2022-03-03 17:59:51 +03:00
parent 243a714586
commit 4254f46065
7 changed files with 71 additions and 1 deletions

View file

@ -2051,6 +2051,14 @@ class TimelineFragment @Inject constructor(
messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
}
override fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, messageAudioContent, percentage))
}
override fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, messageAudioContent, percentage))
}
private fun onShareActionClicked(action: EventSharedAction.Share) {
when (action.messageContent) {
is MessageTextContent -> shareText(requireContext(), action.messageContent.body)

View file

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

View file

@ -108,7 +108,9 @@ class MessageComposerViewModel @AssistedInject constructor(
is MessageComposerAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord)
is MessageComposerAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(action.attachmentData)
is MessageComposerAction.OnEntersBackground -> handleEntersBackground(action.composerText)
}
is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action)
is MessageComposerAction.VoiceWaveformMovedTo -> handleVoiceWaveformMovedTo(action)
}.exhaustive
}
private fun handleOnVoiceRecordingUiStateChanged(action: MessageComposerAction.OnVoiceRecordingUiStateChanged) = setState {
@ -861,6 +863,18 @@ class MessageComposerViewModel @AssistedInject constructor(
voiceMessageHelper.pauseRecording()
}
private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) {
val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
val toMillisecond = (action.percentage * duration).toInt()
voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
}
private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) {
val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
val toMillisecond = (action.percentage * duration).toInt()
voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
}
private fun handleEntersBackground(composerText: String) {
val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
if (isVoiceRecording) {

View file

@ -174,6 +174,14 @@ class VoiceMessageHelper @Inject constructor(
stopPlaybackTicker()
}
fun movePlaybackTo(id: String, toMillisecond: Int, totalDuration: Int) {
val percentage = toMillisecond.toFloat() / totalDuration
playbackTracker.updateCurrentPlaybackTime(id, toMillisecond, percentage)
stopPlayback()
playbackTracker.pausePlayback(id)
}
private fun startRecordingAmplitudes() {
amplitudeTicker?.stop()
amplitudeTicker = CountUpTimer(50).apply {

View file

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

View file

@ -357,11 +357,22 @@ class MessageItemFactory @Inject constructor(
}
}
val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener {
override fun onWaveformTouchedUp(percentage: Float) {
params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, messageContent, percentage)
}
override fun onWaveformMovedTo(percentage: Float) {
params.callback?.onVoiceWaveformMovedTo(informationData.eventId, messageContent, percentage)
}
}
return MessageVoiceItem_()
.attributes(attributes)
.duration(messageContent.audioWaveformInfo?.duration ?: 0)
.waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
.playbackControlButtonClickListener(playbackControlButtonClickListener)
.waveformTouchListener(waveformTouchListener)
.voiceMessagePlaybackTracker(voiceMessagePlaybackTracker)
.izLocalFile(localFilesHelper.isLocalFile(fileUrl))
.izDownloaded(session.fileService().isFileInCache(

View file

@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item
import android.content.res.ColorStateList
import android.graphics.Color
import android.text.format.DateUtils
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
@ -38,6 +39,11 @@ import im.vector.app.features.voice.AudioWaveformView
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
interface WaveformTouchListener {
fun onWaveformTouchedUp(percentage: Float)
fun onWaveformMovedTo(percentage: Float)
}
@EpoxyAttribute
var mxcUrl: String = ""
@ -62,6 +68,9 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var playbackControlButtonClickListener: ClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var waveformTouchListener: WaveformTouchListener? = null
@EpoxyAttribute
lateinit var voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker
@ -87,6 +96,20 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
holder.voicePlaybackWaveform.add(AudioWaveformView.FFT(amplitude.toFloat(), waveformColorIdle))
}
holder.voicePlaybackWaveform.summarize()
holder.voicePlaybackWaveform.setOnTouchListener { view, motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_UP -> {
val percentage = getTouchedPositionPercentage(motionEvent, view)
waveformTouchListener?.onWaveformTouchedUp(percentage)
}
MotionEvent.ACTION_MOVE -> {
val percentage = getTouchedPositionPercentage(motionEvent, view)
waveformTouchListener?.onWaveformMovedTo(percentage)
}
}
true
}
}
val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
@ -111,6 +134,8 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
}
}
private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = motionEvent.x / view.width
private fun renderIdleState(holder: Holder, idleColor: Int, playedColor: Int) {
holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message)