From 4c712095735fc5844ad93b5916fbe110bbf13ae2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 17:58:03 +0200 Subject: [PATCH] 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" />