From 4c712095735fc5844ad93b5916fbe110bbf13ae2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 17:58:03 +0200 Subject: [PATCH 01/14] VoiceBroadcast - Add recording view --- .../src/main/res/values/strings.xml | 5 + .../ui-styles/src/main/res/values/dimens.xml | 3 + .../timeline/factory/MessageItemFactory.kt | 2 +- .../factory/VoiceBroadcastItemFactory.kt | 44 ++++-- .../item/MessageVoiceBroadcastItem.kt | 104 ------------- .../MessageVoiceBroadcastRecordingItem.kt | 137 ++++++++++++++++++ .../voicebroadcast/VoiceBroadcastRecorder.kt | 17 ++- .../voicebroadcast/VoiceBroadcastRecorderQ.kt | 26 +++- .../usecase/StartVoiceBroadcastUseCase.kt | 8 +- .../res/drawable/ic_live_broadcast_16.xml | 21 +++ .../main/res/drawable/ic_recording_dot.xml | 9 ++ vector/src/main/res/drawable/ic_stop.xml | 9 ++ .../res/drawable/rounded_rect_shape_2.xml | 11 ++ ...em_timeline_event_voice_broadcast_stub.xml | 107 +++++++++----- 14 files changed, 337 insertions(+), 166 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt create mode 100644 vector/src/main/res/drawable/ic_live_broadcast_16.xml create mode 100644 vector/src/main/res/drawable/ic_recording_dot.xml create mode 100644 vector/src/main/res/drawable/ic_stop.xml create mode 100644 vector/src/main/res/drawable/rounded_rect_shape_2.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e6714005a1..69b4d57e28 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3078,6 +3078,11 @@ %1$s (%2$s) (%1$s) + Live + Resume voice broadcast record + Pause voice broadcast record + Stop voice broadcast record + 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/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 52d16eae7d..50d5aaf014 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -73,6 +73,9 @@ 12dp 22dp + + 48dp + 112dp diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index cb947a67ce..245d92f95b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -201,7 +201,7 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) - is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(messageContent, params.eventsGroup, highlight, callback, attributes) + is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { 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 f2dfb020a1..1064d2bbc5 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 @@ -15,46 +15,66 @@ */ package im.vector.app.features.home.room.detail.timeline.factory +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.DrawableProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider -import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroup import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_ +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.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class VoiceBroadcastItemFactory @Inject constructor( private val session: Session, private val avatarSizeProvider: AvatarSizeProvider, - private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, + private val colorProvider: ColorProvider, + private val drawableProvider: DrawableProvider, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, ) { fun create( + params: TimelineItemFactoryParams, messageContent: MessageVoiceBroadcastInfoContent, - eventsGroup: TimelineEventsGroup?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): MessageVoiceBroadcastItem? { + ): MessageVoiceBroadcastRecordingItem? { // Only display item of the initial event with updated data if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null - val voiceBroadcastEventsGroup = eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null + val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() val mostRecentMessageContent = mostRecentEvent?.content ?: return null val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId - return MessageVoiceBroadcastItem_() + return if (isRecording) { + createRecordingItem(params.event.roomId, highlight, callback, attributes) + } else { + createRecordingItem(params.event.roomId, highlight, callback, attributes) + } + } + + private fun createRecordingItem( + roomId: String, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): MessageVoiceBroadcastRecordingItem? { + val roomSummary = session.getRoom(roomId)?.roomSummary() + return MessageVoiceBroadcastRecordingItem_() .attributes(attributes) .highlighted(highlight) - .voiceBroadcastState(mostRecentMessageContent.voiceBroadcastState) - .recording(isRecording) - .audioMessagePlaybackTracker(audioMessagePlaybackTracker) + .roomItem(roomSummary?.toMatrixItem()) + .colorProvider(colorProvider) + .drawableProvider(drawableProvider) + .voiceBroadcastRecorder(voiceBroadcastRecorder) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt deleted file mode 100644 index 1927024a36..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.home.room.detail.timeline.item - -import android.annotation.SuppressLint -import android.widget.ImageButton -import android.widget.TextView -import com.airbnb.epoxy.EpoxyAttribute -import com.airbnb.epoxy.EpoxyModelClass -import im.vector.app.R -import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction -import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State -import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState - -@EpoxyModelClass -abstract class MessageVoiceBroadcastItem : AbsMessageItem() { - - @EpoxyAttribute - var callback: TimelineEventController.Callback? = null - - @EpoxyAttribute - var voiceBroadcastState: VoiceBroadcastState? = null - - @EpoxyAttribute - var recording: Boolean = false - - @EpoxyAttribute - lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker - - private val voiceBroadcastEventId - get() = attributes.informationData.eventId - - override fun isCacheable(): Boolean = false - - override fun bind(holder: Holder) { - super.bind(holder) - bindVoiceBroadcastItem(holder) - } - - @SuppressLint("SetTextI18n") // Temporary text - private fun bindVoiceBroadcastItem(holder: Holder) { - holder.currentStateText.text = "Voice Broadcast state: ${voiceBroadcastState?.value ?: "None"}" - if (recording) { - renderRecording(holder) - } else { - renderListening(holder) - } - } - - private fun renderListening(holder: Holder) { - audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { - override fun onUpdate(state: State) { - holder.playButton.isEnabled = state !is State.Playing - holder.pauseButton.isEnabled = state is State.Playing - holder.stopButton.isEnabled = state !is State.Idle - } - }) - holder.playButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastEventId)) } - holder.pauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) } - holder.stopButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Stop) } - } - - private fun renderRecording(holder: Holder) { - with(holder) { - playButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.PAUSED - pauseButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || voiceBroadcastState == VoiceBroadcastState.RESUMED - stopButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || - voiceBroadcastState == VoiceBroadcastState.RESUMED || - voiceBroadcastState == VoiceBroadcastState.PAUSED - playButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } - pauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } - stopButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } - } - } - - override fun getViewStubId() = STUB_ID - - class Holder : AbsMessageLocationItem.Holder(STUB_ID) { - val currentStateText by bind(R.id.currentStateText) - val playButton by bind(R.id.playButton) - val pauseButton by bind(R.id.pauseButton) - val stopButton by bind(R.id.stopButton) - } - - companion object { - private val STUB_ID = R.id.messageVoiceBroadcastStub - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt new file mode 100644 index 0000000000..d271c55ebb --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.item + +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +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.timeline.TimelineEventController +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem() { + + @EpoxyAttribute + var callback: TimelineEventController.Callback? = null + + @EpoxyAttribute + var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null + + @EpoxyAttribute + lateinit var colorProvider: ColorProvider + + @EpoxyAttribute + lateinit var drawableProvider: DrawableProvider + + @EpoxyAttribute + var roomItem: MatrixItem? = null + + @EpoxyAttribute + var title: String? = null + + private lateinit var recorderListener: VoiceBroadcastRecorder.Listener + + override fun isCacheable(): Boolean = false + + override fun bind(holder: Holder) { + super.bind(holder) + bindVoiceBroadcastItem(holder) + } + + private fun bindVoiceBroadcastItem(holder: Holder) { + recorderListener = object : VoiceBroadcastRecorder.Listener { + override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { + renderState(holder, state) + } + } + voiceBroadcastRecorder?.addListener(recorderListener) + renderHeader(holder) + } + + private fun renderHeader(holder: Holder) { + with(holder) { + roomItem?.let { + attributes.avatarRenderer.render(it, roomAvatarImageView) + titleText.text = it.displayName + } + } + } + + private fun renderState(holder: Holder, state: VoiceBroadcastRecorder.State) { + with(holder) { + when (state) { + VoiceBroadcastRecorder.State.Recording -> { + 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) } + } + 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 + } + } + } + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + voiceBroadcastRecorder?.removeListener(recorderListener) + } + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val liveIndicator by bind(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) + } + + companion object { + private val STUB_ID = R.id.messageVoiceBroadcastStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index c9bb0c5f54..8b69051823 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -22,12 +22,21 @@ import java.io.File interface VoiceBroadcastRecorder : VoiceRecorder { - var listener: Listener? - var currentSequence: Int + val currentSequence: Int + val state: State fun startRecord(roomId: String, chunkLength: Int) + fun addListener(listener: Listener) + fun removeListener(listener: Listener) - fun interface Listener { - fun onVoiceMessageCreated(file: File, @IntRange(from = 1) sequence: Int) + interface Listener { + fun onVoiceMessageCreated(file: File, @IntRange(from = 1) sequence: Int) = Unit + fun onStateUpdated(state: State) = Unit + } + + enum class State { + Recording, + Paused, + Idle, } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index a65aae6f8a..cd1a61b986 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -32,8 +32,13 @@ class VoiceBroadcastRecorderQ( private var maxFileSize = 0L // zero or negative for no limit private var currentRoomId: String? = null override var currentSequence = 0 + override var state = VoiceBroadcastRecorder.State.Idle + set(value) { + field = value + listeners.forEach { it.onStateUpdated(value) } + } - override var listener: VoiceBroadcastRecorder.Listener? = null + private val listeners = mutableListOf() override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC @@ -57,24 +62,28 @@ class VoiceBroadcastRecorderQ( maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong() currentSequence = 1 startRecord(roomId) + state = VoiceBroadcastRecorder.State.Recording } override fun pauseRecord() { tryOrNull { mediaRecorder?.stop() } mediaRecorder?.reset() notifyOutputFileCreated() + state = VoiceBroadcastRecorder.State.Paused } override fun resumeRecord() { currentSequence++ currentRoomId?.let { startRecord(it) } + state = VoiceBroadcastRecorder.State.Recording } override fun stopRecord() { super.stopRecord() notifyOutputFileCreated() - listener = null + listeners.clear() currentSequence = 0 + state = VoiceBroadcastRecorder.State.Idle } override fun release() { @@ -82,6 +91,15 @@ class VoiceBroadcastRecorderQ( super.release() } + override fun addListener(listener: VoiceBroadcastRecorder.Listener) { + listeners.add(listener) + listener.onStateUpdated(state) + } + + override fun removeListener(listener: VoiceBroadcastRecorder.Listener) { + listeners.remove(listener) + } + private fun onMaxFileSizeApproaching(roomId: String) { setNextOutputFile(roomId) } @@ -92,8 +110,8 @@ class VoiceBroadcastRecorderQ( } private fun notifyOutputFileCreated() { - outputFile?.let { - listener?.onVoiceMessageCreated(it, currentSequence) + outputFile?.let { file -> + listeners.forEach { it.onVoiceMessageCreated(file, currentSequence) } outputFile = nextOutputFile nextOutputFile = null } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index d5d58f822e..7934d18e36 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -81,9 +81,11 @@ class StartVoiceBroadcastUseCase @Inject constructor( } private fun startRecording(room: Room, eventId: String, chunkLength: Int) { - voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file, sequence -> - sendVoiceFile(room, file, eventId, sequence) - } + voiceBroadcastRecorder?.addListener(object : VoiceBroadcastRecorder.Listener { + override fun onVoiceMessageCreated(file: File, sequence: Int) { + sendVoiceFile(room, file, eventId, sequence) + } + }) voiceBroadcastRecorder?.startRecord(room.roomId, chunkLength) } diff --git a/vector/src/main/res/drawable/ic_live_broadcast_16.xml b/vector/src/main/res/drawable/ic_live_broadcast_16.xml new file mode 100644 index 0000000000..7d427a56d0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_live_broadcast_16.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/vector/src/main/res/drawable/ic_recording_dot.xml b/vector/src/main/res/drawable/ic_recording_dot.xml new file mode 100644 index 0000000000..f5d92f9718 --- /dev/null +++ b/vector/src/main/res/drawable/ic_recording_dot.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/ic_stop.xml b/vector/src/main/res/drawable/ic_stop.xml new file mode 100644 index 0000000000..459a7cfce2 --- /dev/null +++ b/vector/src/main/res/drawable/ic_stop.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/rounded_rect_shape_2.xml b/vector/src/main/res/drawable/rounded_rect_shape_2.xml new file mode 100644 index 0000000000..977de2fd09 --- /dev/null +++ b/vector/src/main/res/drawable/rounded_rect_shape_2.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml index e35060f72a..6773280ba5 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml @@ -5,58 +5,89 @@ android:id="@+id/messageRootLayout" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@drawable/rounded_rect_shape_8" + android:backgroundTint="?vctr_content_quinary" android:padding="@dimen/layout_vertical_margin" tools:viewBindingIgnore="true"> + + + tools:src="@sample/user_round_avatars" /> + + + + + + + app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" /> - - + app:layout_constraintStart_toEndOf="@id/recordButton" + app:layout_constraintTop_toTopOf="@id/recordButton" /> From f1b4ebbc37f266702327fc9db517c8d142178387 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 18:10:32 +0200 Subject: [PATCH 02/14] VoiceBroadcast - Introduce listening view --- .../factory/VoiceBroadcastItemFactory.kt | 28 +++- .../MessageVoiceBroadcastListeningItem.kt | 137 ++++++++++++++++++ .../MessageVoiceBroadcastRecordingItem.kt | 2 +- ...em_timeline_event_view_stubs_container.xml | 11 +- ..._event_voice_broadcast_listening_stub.xml} | 0 ...e_event_voice_broadcast_recording_stub.xml | 93 ++++++++++++ 6 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt rename vector/src/main/res/layout/{item_timeline_event_voice_broadcast_stub.xml => item_timeline_event_voice_broadcast_listening_stub.xml} (100%) create mode 100644 vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml 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 1064d2bbc5..13a38ac4be 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 @@ -15,12 +15,16 @@ */ package im.vector.app.features.home.room.detail.timeline.factory +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem +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.VoiceBroadcastRecorder @@ -46,7 +50,7 @@ class VoiceBroadcastItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): MessageVoiceBroadcastRecordingItem? { + ): 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 @@ -57,7 +61,7 @@ class VoiceBroadcastItemFactory @Inject constructor( return if (isRecording) { createRecordingItem(params.event.roomId, highlight, callback, attributes) } else { - createRecordingItem(params.event.roomId, highlight, callback, attributes) + createListeningItem(params.event.roomId, highlight, callback, attributes) } } @@ -66,7 +70,7 @@ class VoiceBroadcastItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): MessageVoiceBroadcastRecordingItem? { + ): MessageVoiceBroadcastRecordingItem { val roomSummary = session.getRoom(roomId)?.roomSummary() return MessageVoiceBroadcastRecordingItem_() .attributes(attributes) @@ -78,4 +82,22 @@ class VoiceBroadcastItemFactory @Inject constructor( .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } + + private fun createListeningItem( + roomId: String, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): MessageVoiceBroadcastListeningItem { + val roomSummary = session.getRoom(roomId)?.roomSummary() + return MessageVoiceBroadcastListeningItem_() + .attributes(attributes) + .highlighted(highlight) + .roomItem(roomSummary?.toMatrixItem()) + .colorProvider(colorProvider) + .drawableProvider(drawableProvider) + .voiceBroadcastRecorder(voiceBroadcastRecorder) + .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 new file mode 100644 index 0000000000..e5d0fd6c30 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.item + +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +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.timeline.TimelineEventController +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem() { + + @EpoxyAttribute + var callback: TimelineEventController.Callback? = null + + @EpoxyAttribute + var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null + + @EpoxyAttribute + lateinit var colorProvider: ColorProvider + + @EpoxyAttribute + lateinit var drawableProvider: DrawableProvider + + @EpoxyAttribute + var roomItem: MatrixItem? = null + + @EpoxyAttribute + var title: String? = null + + private lateinit var recorderListener: VoiceBroadcastRecorder.Listener + + override fun isCacheable(): Boolean = false + + override fun bind(holder: Holder) { + super.bind(holder) + bindVoiceBroadcastItem(holder) + } + + private fun bindVoiceBroadcastItem(holder: Holder) { + recorderListener = object : VoiceBroadcastRecorder.Listener { + override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { + renderState(holder, state) + } + } + voiceBroadcastRecorder?.addListener(recorderListener) + renderHeader(holder) + } + + private fun renderHeader(holder: Holder) { + with(holder) { + roomItem?.let { + attributes.avatarRenderer.render(it, roomAvatarImageView) + titleText.text = it.displayName + } + } + } + + private fun renderState(holder: Holder, state: VoiceBroadcastRecorder.State) { + with(holder) { + when (state) { + VoiceBroadcastRecorder.State.Recording -> { + 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) } + } + 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 + } + } + } + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + voiceBroadcastRecorder?.removeListener(recorderListener) + } + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val liveIndicator by bind(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) + } + + companion object { + private val STUB_ID = R.id.messageVoiceBroadcastListeningStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index d271c55ebb..30475c1659 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -132,6 +132,6 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem + + + + + + + + + + + + + + + + + + + From f711a0ea740f14f4d63eff35d67355e5224bdf57 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 19:07:34 +0200 Subject: [PATCH 03/14] VoiceBroadcast - Listening view --- .../src/main/res/values/strings.xml | 3 + .../factory/VoiceBroadcastItemFactory.kt | 11 ++- .../MessageVoiceBroadcastListeningItem.kt | 97 +++++++++++-------- .../voicebroadcast/VoiceBroadcastPlayer.kt | 17 +++- ...e_event_voice_broadcast_listening_stub.xml | 32 +++--- 5 files changed, 98 insertions(+), 62 deletions(-) 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" /> + + - - From cebc096ac70dad17ebef02f5cc3882ae2b5e93d2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 21:06:27 +0200 Subject: [PATCH 04/14] VoiceBroadcast - Update live indicator icon --- .../factory/VoiceBroadcastItemFactory.kt | 4 ++- .../MessageVoiceBroadcastListeningItem.kt | 28 +++++++++++++++++-- .../MessageVoiceBroadcastRecordingItem.kt | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) 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 af14d532c8..5a70f66d75 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 @@ -64,7 +64,7 @@ class VoiceBroadcastItemFactory @Inject constructor( return if (isRecording) { createRecordingItem(params.event.roomId, highlight, callback, attributes) } else { - createListeningItem(params.event.roomId, eventsGroup.groupId, highlight, callback, attributes) + createListeningItem(params.event.roomId, eventsGroup.groupId, mostRecentMessageContent.voiceBroadcastState, highlight, callback, attributes) } } @@ -89,6 +89,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private fun createListeningItem( roomId: String, voiceBroadcastId: String, + voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, @@ -102,6 +103,7 @@ class VoiceBroadcastItemFactory @Inject constructor( .drawableProvider(drawableProvider) .voiceBroadcastPlayer(voiceBroadcastPlayer) .voiceBroadcastId(voiceBroadcastId) + .voiceBroadcastState(voiceBroadcastState) .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 3a090b0eb6..bf0f202f0c 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 @@ -24,11 +24,13 @@ 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 import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass @@ -43,6 +45,9 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError)) + liveIndicator.isVisible = true + } + VoiceBroadcastState.PAUSED -> { + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary)) + liveIndicator.isVisible = true + } + VoiceBroadcastState.STOPPED, null -> { + liveIndicator.isVisible = false + } + } + } + } + private fun renderState(holder: Holder, state: VoiceBroadcastPlayer.State) { if (isCurrentMediaActive()) { renderActiveMedia(holder, state) @@ -94,8 +119,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { @@ -118,7 +141,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem Date: Thu, 20 Oct 2022 21:11:00 +0200 Subject: [PATCH 05/14] Fix null voiceBroadcastId when the playlist is empty --- .../features/voicebroadcast/VoiceBroadcastPlayer.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 9c118f957d..28ed7a58b8 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 @@ -30,7 +30,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -73,8 +72,7 @@ class VoiceBroadcastPlayer @Inject constructor( private var currentSequence: Int? = null private var playlist = emptyList() - val currentVoiceBroadcastId - get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId + var currentVoiceBroadcastId: String? = null private var state: State = State.IDLE set(value) { @@ -128,6 +126,7 @@ class VoiceBroadcastPlayer @Inject constructor( playlist = emptyList() currentSequence = null currentRoomId = null + currentVoiceBroadcastId = null } fun addListener(listener: Listener) { @@ -141,11 +140,12 @@ class VoiceBroadcastPlayer @Inject constructor( private fun startPlayback(roomId: String, eventId: String) { val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") - currentRoomId = roomId - // Stop listening previous voice broadcast if any if (state != State.IDLE) stop() + currentRoomId = roomId + currentVoiceBroadcastId = eventId + state = State.BUFFERING val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState From 6ff7a7f3aeb43bd8819d3d53d3a20125eada8125 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 21:20:17 +0200 Subject: [PATCH 06/14] Update buffering view --- ...item_timeline_event_voice_broadcast_listening_stub.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 506ca4ff47..6a4fcef77a 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 @@ -78,14 +78,18 @@ app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" app:tint="?vctr_content_secondary" /> - + From 72a1acec8959a0db909e529f0e7ac585d537cf2f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 21:26:39 +0200 Subject: [PATCH 07/14] Fix voice broadcast state update on wrong thread --- .../app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 28ed7a58b8..bb5fe5c37d 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 @@ -18,6 +18,7 @@ package im.vector.app.features.voicebroadcast import android.media.AudioAttributes import android.media.MediaPlayer +import androidx.annotation.MainThread import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.voice.VoiceFailure @@ -29,6 +30,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room @@ -75,6 +77,7 @@ class VoiceBroadcastPlayer @Inject constructor( var currentVoiceBroadcastId: String? = null private var state: State = State.IDLE + @MainThread set(value) { Timber.w("## VoiceBroadcastPlayer state: $field -> $value") field = value @@ -168,7 +171,7 @@ class VoiceBroadcastPlayer @Inject constructor( currentMediaPlayer?.start() currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } currentSequence = sequence - state = State.PLAYING + withContext(Dispatchers.Main) { state = State.PLAYING } nextMediaPlayer = prepareNextMediaPlayer() } catch (failure: Throwable) { Timber.e(failure, "Unable to start playback") From 930c85672866c5d83c47795656d40c8b740639e6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:38:02 +0200 Subject: [PATCH 08/14] Add additional information in listening tile --- .../factory/VoiceBroadcastItemFactory.kt | 14 ++++- .../MessageVoiceBroadcastListeningItem.kt | 6 +- ...dcast_16.xml => ic_voice_broadcast_16.xml} | 0 ...e_event_voice_broadcast_listening_stub.xml | 60 +++++++++++++++++-- ...e_event_voice_broadcast_recording_stub.xml | 2 +- 5 files changed, 75 insertions(+), 7 deletions(-) rename vector/src/main/res/drawable/{ic_live_broadcast_16.xml => ic_voice_broadcast_16.xml} (100%) 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 5a70f66d75..98f45cd5db 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 @@ -34,6 +34,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -61,10 +62,19 @@ class VoiceBroadcastItemFactory @Inject constructor( val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() val mostRecentMessageContent = mostRecentEvent?.content ?: return null val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId + val recorderName = mostRecentTimelineEvent.root.stateKey?.let { session.getUser(it) }?.displayName ?: mostRecentTimelineEvent.root.stateKey return if (isRecording) { createRecordingItem(params.event.roomId, highlight, callback, attributes) } else { - createListeningItem(params.event.roomId, eventsGroup.groupId, mostRecentMessageContent.voiceBroadcastState, highlight, callback, attributes) + createListeningItem( + params.event.roomId, + eventsGroup.groupId, + mostRecentMessageContent.voiceBroadcastState, + recorderName, + highlight, + callback, + attributes + ) } } @@ -90,6 +100,7 @@ class VoiceBroadcastItemFactory @Inject constructor( roomId: String, voiceBroadcastId: String, voiceBroadcastState: VoiceBroadcastState?, + broadcasterName: String?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, @@ -104,6 +115,7 @@ class VoiceBroadcastItemFactory @Inject constructor( .voiceBroadcastPlayer(voiceBroadcastPlayer) .voiceBroadcastId(voiceBroadcastId) .voiceBroadcastState(voiceBroadcastState) + .broadcasterName(broadcasterName) .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 bf0f202f0c..130d44202f 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 @@ -48,6 +48,9 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem(R.id.titleText) val playPauseButton by bind(R.id.playPauseButton) val bufferingView by bind(R.id.bufferingView) + val broadcasterNameText by bind(R.id.broadcasterNameText) } companion object { diff --git a/vector/src/main/res/drawable/ic_live_broadcast_16.xml b/vector/src/main/res/drawable/ic_voice_broadcast_16.xml similarity index 100% rename from vector/src/main/res/drawable/ic_live_broadcast_16.xml rename to vector/src/main/res/drawable/ic_voice_broadcast_16.xml 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 6a4fcef77a..73acde2942 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 @@ -24,7 +24,7 @@ android:singleLine="true" android:text="@string/voice_broadcast_live" android:textColor="?colorOnError" - app:drawableStartCompat="@drawable/ic_live_broadcast_16" + app:drawableStartCompat="@drawable/ic_voice_broadcast_16" app:drawableTint="?colorOnError" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -36,7 +36,7 @@ android:contentDescription="@string/avatar" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:src="@sample/user_round_avatars" /> + tools:src="@sample/room_round_avatars" /> + tools:src="@sample/rooms.json/data/name" /> + + + + + + + + + + + + + + + app:constraint_referenced_ids="roomAvatarImageView,titleText,broadcasterViewGroup,voiceBroadcastViewGroup" /> From 8869d82dd0f7a3e2aa232456f7cf324daaf04d16 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:39:27 +0200 Subject: [PATCH 09/14] Add changelog --- changelog.d/7421.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7421.wip diff --git a/changelog.d/7421.wip b/changelog.d/7421.wip new file mode 100644 index 0000000000..4a399eee04 --- /dev/null +++ b/changelog.d/7421.wip @@ -0,0 +1 @@ +[Voice Broadcast] Improve rendering in the timeline From 9a96de4f06f60ed47261f9cb34b743db4e169918 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:45:22 +0200 Subject: [PATCH 10/14] Set id to VoiceBroadcast items --- .../timeline/factory/VoiceBroadcastItemFactory.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 98f45cd5db..5dc601a91a 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 @@ -62,9 +62,15 @@ class VoiceBroadcastItemFactory @Inject constructor( val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() val mostRecentMessageContent = mostRecentEvent?.content ?: return null val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId - val recorderName = mostRecentTimelineEvent.root.stateKey?.let { session.getUser(it) }?.displayName ?: mostRecentTimelineEvent.root.stateKey + val recorderName = mostRecentTimelineEvent.root.stateKey?.let { session.getUser(it) }?.displayName ?: mostRecentTimelineEvent.root.stateKey return if (isRecording) { - createRecordingItem(params.event.roomId, highlight, callback, attributes) + createRecordingItem( + params.event.roomId, + eventsGroup.groupId, + highlight, + callback, + attributes + ) } else { createListeningItem( params.event.roomId, @@ -80,12 +86,14 @@ class VoiceBroadcastItemFactory @Inject constructor( private fun createRecordingItem( roomId: String, + voiceBroadcastId: String, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): MessageVoiceBroadcastRecordingItem { val roomSummary = session.getRoom(roomId)?.roomSummary() return MessageVoiceBroadcastRecordingItem_() + .id("voice_broadcast_$voiceBroadcastId") .attributes(attributes) .highlighted(highlight) .roomItem(roomSummary?.toMatrixItem()) @@ -107,6 +115,7 @@ class VoiceBroadcastItemFactory @Inject constructor( ): MessageVoiceBroadcastListeningItem { val roomSummary = session.getRoom(roomId)?.roomSummary() return MessageVoiceBroadcastListeningItem_() + .id("voice_broadcast_$voiceBroadcastId") .attributes(attributes) .highlighted(highlight) .roomItem(roomSummary?.toMatrixItem()) From f2cc08263fa59f3ca58b3ac41a7508b04aa65117 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:48:09 +0200 Subject: [PATCH 11/14] Call onClick instead of setOnClickListener --- .../timeline/item/MessageVoiceBroadcastListeningItem.kt | 7 ++++--- .../timeline/item/MessageVoiceBroadcastRecordingItem.kt | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) 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 130d44202f..5b58dda4e6 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 @@ -24,6 +24,7 @@ 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.epoxy.onClick import im.vector.app.core.extensions.tintBackground import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider @@ -127,13 +128,13 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { 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) } + playPauseButton.onClick { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } } 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 { + playPauseButton.onClick { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) } } @@ -148,7 +149,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { stopRecordButton.isEnabled = true @@ -104,8 +105,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem { recordButton.isEnabled = false From 4a76998c98bec82af2cbe8e436f7edbd07f7bfaa Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:50:09 +0200 Subject: [PATCH 12/14] Use CopyOnWriteArrayList --- .../vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 3 ++- .../app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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 bb5fe5c37d..2c892c8306 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 @@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import timber.log.Timber +import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject import javax.inject.Singleton @@ -84,7 +85,7 @@ class VoiceBroadcastPlayer @Inject constructor( listeners.forEach { it.onStateChanged(value) } } private var currentRoomId: String? = null - private var listeners = mutableListOf() + private var listeners = CopyOnWriteArrayList() fun playOrResume(roomId: String, eventId: String) { val hasChanged = currentVoiceBroadcastId != eventId diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index cd1a61b986..5285dc5e3b 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -23,6 +23,7 @@ import androidx.annotation.RequiresApi import im.vector.app.features.voice.AbstractVoiceRecorderQ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import java.util.concurrent.CopyOnWriteArrayList @RequiresApi(Build.VERSION_CODES.Q) class VoiceBroadcastRecorderQ( @@ -38,7 +39,7 @@ class VoiceBroadcastRecorderQ( listeners.forEach { it.onStateUpdated(value) } } - private val listeners = mutableListOf() + private val listeners = CopyOnWriteArrayList() override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC From 34cafa373f5f29a65d3d4e3318bfe0273396c149 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 23:43:33 +0200 Subject: [PATCH 13/14] Add missing content description --- .../item_timeline_event_voice_broadcast_listening_stub.xml | 2 ++ 1 file changed, 2 insertions(+) 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 73acde2942..248c04a2f6 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 @@ -71,6 +71,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_marginEnd="5dp" + android:contentDescription="@null" android:src="@drawable/ic_microphone" app:tint="?vctr_content_secondary" /> @@ -97,6 +98,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_marginEnd="5dp" + android:contentDescription="@null" android:src="@drawable/ic_voice_broadcast_16" app:tint="?vctr_content_secondary" /> From 926f4d920180e40905f253d00df93aef714793ae Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 23:58:17 +0200 Subject: [PATCH 14/14] Fix play/pause button disabled --- .../detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index 41ef1fac9c..c417053b2a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -86,6 +86,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem { stopRecordButton.isEnabled = true + recordButton.isEnabled = true liveIndicator.isVisible = true liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError)) @@ -99,6 +100,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem { stopRecordButton.isEnabled = true + recordButton.isEnabled = true liveIndicator.isVisible = true liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary))