diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 69b4d57e28..ea9b4b5999 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3082,6 +3082,9 @@ Resume voice broadcast record Pause voice broadcast record Stop voice broadcast record + Play or resume voice broadcast + Pause voice broadcast + Buffering Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 13a38ac4be..af14d532c8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -27,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadca import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_ +import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -42,6 +43,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private val colorProvider: ColorProvider, private val drawableProvider: DrawableProvider, private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, + private val voiceBroadcastPlayer: VoiceBroadcastPlayer, ) { fun create( @@ -53,7 +55,8 @@ class VoiceBroadcastItemFactory @Inject constructor( ): VectorEpoxyModel? { // Only display item of the initial event with updated data if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null - val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null + val eventsGroup = params.eventsGroup ?: return null + val voiceBroadcastEventsGroup = VoiceBroadcastEventsGroup(eventsGroup) val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() val mostRecentMessageContent = mostRecentEvent?.content ?: return null @@ -61,7 +64,7 @@ class VoiceBroadcastItemFactory @Inject constructor( return if (isRecording) { createRecordingItem(params.event.roomId, highlight, callback, attributes) } else { - createListeningItem(params.event.roomId, highlight, callback, attributes) + createListeningItem(params.event.roomId, eventsGroup.groupId, highlight, callback, attributes) } } @@ -85,6 +88,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private fun createListeningItem( roomId: String, + voiceBroadcastId: String, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, @@ -96,7 +100,8 @@ class VoiceBroadcastItemFactory @Inject constructor( .roomItem(roomSummary?.toMatrixItem()) .colorProvider(colorProvider) .drawableProvider(drawableProvider) - .voiceBroadcastRecorder(voiceBroadcastRecorder) + .voiceBroadcastPlayer(voiceBroadcastPlayer) + .voiceBroadcastId(voiceBroadcastId) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index e5d0fd6c30..3a090b0eb6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.view.View import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView @@ -23,12 +24,11 @@ import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R -import im.vector.app.core.extensions.tintBackground import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider -import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction +import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass @@ -38,7 +38,10 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem + renderState(holder, state) } - voiceBroadcastRecorder?.addListener(recorderListener) + voiceBroadcastPlayer?.addListener(playerListener) renderHeader(holder) } @@ -80,45 +81,59 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { - stopRecordButton.isEnabled = true - - liveIndicator.isVisible = true - liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorOnError)) - - val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) - val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) - recordButton.setImageDrawable(drawable) - recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record) - recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } - stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + VoiceBroadcastPlayer.State.PLAYING -> { + playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) + playPauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } } - VoiceBroadcastRecorder.State.Paused -> { - stopRecordButton.isEnabled = true - - liveIndicator.isVisible = true - liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary)) - - recordButton.setImageResource(R.drawable.ic_recording_dot) - recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) - recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } - stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } - } - VoiceBroadcastRecorder.State.Idle -> { - recordButton.isEnabled = false - stopRecordButton.isEnabled = false - liveIndicator.isVisible = false + VoiceBroadcastPlayer.State.IDLE, + VoiceBroadcastPlayer.State.PAUSED -> { + playPauseButton.setImageResource(R.drawable.ic_play_pause_play) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) + playPauseButton.setOnClickListener { + attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) + } } + VoiceBroadcastPlayer.State.BUFFERING -> Unit } } } + private fun renderInactiveMedia(holder: Holder) { + with(holder) { + liveIndicator.isVisible = false + bufferingView.isVisible = false + playPauseButton.isVisible = true + playPauseButton.setImageResource(R.drawable.ic_play_pause_play) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) + playPauseButton.setOnClickListener { + attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) + } + } + } + + private fun isCurrentMediaActive() = voiceBroadcastPlayer?.currentVoiceBroadcastId == voiceBroadcastId + override fun unbind(holder: Holder) { super.unbind(holder) - voiceBroadcastRecorder?.removeListener(recorderListener) + voiceBroadcastPlayer?.removeListener(playerListener) } override fun getViewStubId() = STUB_ID @@ -127,8 +142,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem(R.id.liveIndicator) val roomAvatarImageView by bind(R.id.roomAvatarImageView) val titleText by bind(R.id.titleText) - val recordButton by bind(R.id.recordButton) - val stopRecordButton by bind(R.id.stopRecordButton) + val playPauseButton by bind(R.id.playPauseButton) + val bufferingView by bind(R.id.bufferingView) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 62252570c6..9c118f957d 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -73,15 +73,17 @@ class VoiceBroadcastPlayer @Inject constructor( private var currentSequence: Int? = null private var playlist = emptyList() - private val currentVoiceBroadcastId + val currentVoiceBroadcastId get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId private var state: State = State.IDLE set(value) { Timber.w("## VoiceBroadcastPlayer state: $field -> $value") field = value + listeners.forEach { it.onStateChanged(value) } } private var currentRoomId: String? = null + private var listeners = mutableListOf() fun playOrResume(roomId: String, eventId: String) { val hasChanged = currentVoiceBroadcastId != eventId @@ -128,6 +130,15 @@ class VoiceBroadcastPlayer @Inject constructor( currentRoomId = null } + fun addListener(listener: Listener) { + listeners.add(listener) + listener.onStateChanged(state) + } + + fun removeListener(listener: Listener) { + listeners.remove(listener) + } + private fun startPlayback(roomId: String, eventId: String) { val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") currentRoomId = roomId @@ -316,4 +327,8 @@ class VoiceBroadcastPlayer @Inject constructor( BUFFERING, IDLE } + + fun interface Listener { + fun onStateChanged(state: State) + } } diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index 6773280ba5..506ca4ff47 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -65,29 +65,27 @@ app:constraint_referenced_ids="roomAvatarImageView,titleText" /> + + - -