From 6a88c61d1253e6b7f3d4de9c54da789213c6c896 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 21 Oct 2022 14:49:29 +0200 Subject: [PATCH 01/12] Group voice broadcast controller buttons in a Flow --- ...e_event_voice_broadcast_listening_stub.xml | 21 +++++++++---------- ...e_event_voice_broadcast_recording_stub.xml | 21 ++++++++++--------- 2 files changed, 21 insertions(+), 21 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 248c04a2f62..97f15967e11 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 @@ -118,6 +118,15 @@ app:barrierMargin="12dp" app:constraint_referenced_ids="roomAvatarImageView,titleText,broadcasterViewGroup,voiceBroadcastViewGroup" /> + + - - + android:indeterminateTint="?vctr_content_secondary" /> diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml index e3bb85138d0..7b45a194e87 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml @@ -64,6 +64,15 @@ app:barrierMargin="12dp" app:constraint_referenced_ids="roomAvatarImageView,titleText" /> + + + android:src="@drawable/ic_recording_dot" /> + android:src="@drawable/ic_stop" /> From 1566adb66992894f2713a06a73f5340aca19ead2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 24 Oct 2022 14:10:51 +0200 Subject: [PATCH 02/12] Timeline - Add abstraction on voice broadcast items --- .../src/main/res/values/donottranslate.xml | 1 + ...stylable_voice_broadcast_metadata_view.xml | 9 ++ .../factory/VoiceBroadcastItemFactory.kt | 4 + .../item/AbsMessageVoiceBroadcastItem.kt | 96 ++++++++++++++++ .../MessageVoiceBroadcastListeningItem.kt | 104 +++--------------- .../MessageVoiceBroadcastRecordingItem.kt | 54 ++------- .../voicebroadcast/VoiceBroadcastPlayer.kt | 29 +++-- .../views/VoiceBroadcastMetadataView.kt | 66 +++++++++++ vector/src/main/res/drawable/ic_timer.xml | 9 ++ .../res/drawable/ic_voice_broadcast_16.xml | 21 ---- .../res/drawable/ic_voice_broadcast_mic.xml | 12 ++ ...e_event_voice_broadcast_listening_stub.xml | 77 +++++-------- ...e_event_voice_broadcast_recording_stub.xml | 36 +++++- .../layout/view_voice_broadcast_metadata.xml | 27 +++++ 14 files changed, 329 insertions(+), 216 deletions(-) create mode 100644 library/ui-styles/src/main/res/values/stylable_voice_broadcast_metadata_view.xml create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/views/VoiceBroadcastMetadataView.kt create mode 100644 vector/src/main/res/drawable/ic_timer.xml delete mode 100644 vector/src/main/res/drawable/ic_voice_broadcast_16.xml create mode 100644 vector/src/main/res/drawable/ic_voice_broadcast_mic.xml create mode 100644 vector/src/main/res/layout/view_voice_broadcast_metadata.xml diff --git a/library/ui-strings/src/main/res/values/donottranslate.xml b/library/ui-strings/src/main/res/values/donottranslate.xml index 741d23dbc6e..bfe751ef5a5 100755 --- a/library/ui-strings/src/main/res/values/donottranslate.xml +++ b/library/ui-strings/src/main/res/values/donottranslate.xml @@ -2,6 +2,7 @@ + Not implemented yet in ${app_name} diff --git a/library/ui-styles/src/main/res/values/stylable_voice_broadcast_metadata_view.xml b/library/ui-styles/src/main/res/values/stylable_voice_broadcast_metadata_view.xml new file mode 100644 index 00000000000..1f72eeb3969 --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_voice_broadcast_metadata_view.xml @@ -0,0 +1,9 @@ + + + + + + + + + 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 5dc601a91a2..7b8c9271866 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 @@ -67,6 +67,7 @@ class VoiceBroadcastItemFactory @Inject constructor( createRecordingItem( params.event.roomId, eventsGroup.groupId, + mostRecentMessageContent.voiceBroadcastState, highlight, callback, attributes @@ -87,6 +88,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private fun createRecordingItem( roomId: String, voiceBroadcastId: String, + voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, @@ -100,6 +102,8 @@ class VoiceBroadcastItemFactory @Inject constructor( .colorProvider(colorProvider) .drawableProvider(drawableProvider) .voiceBroadcastRecorder(voiceBroadcastRecorder) + .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/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt new file mode 100644 index 00000000000..cbf35e89d26 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -0,0 +1,96 @@ +/* + * 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.ImageView +import android.widget.TextView +import androidx.annotation.IdRes +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +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.timeline.TimelineEventController +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import org.matrix.android.sdk.api.util.MatrixItem + +abstract class AbsMessageVoiceBroadcastItem : AbsMessageItem() { + + @EpoxyAttribute + var callback: TimelineEventController.Callback? = null + + @EpoxyAttribute + lateinit var colorProvider: ColorProvider + + @EpoxyAttribute + lateinit var drawableProvider: DrawableProvider + + @EpoxyAttribute + lateinit var voiceBroadcastId: String + + @EpoxyAttribute + var voiceBroadcastState: VoiceBroadcastState? = null + + @EpoxyAttribute + var roomItem: MatrixItem? = null + + override fun isCacheable(): Boolean = false + + override fun bind(holder: H) { + super.bind(holder) + renderHeader(holder) + } + + private fun renderHeader(holder: H) { + with(holder) { + roomItem?.let { + attributes.avatarRenderer.render(it, roomAvatarImageView) + titleText.text = it.displayName + } + } + renderLiveIcon(holder) + renderMetadata(holder) + } + + private fun renderLiveIcon(holder: H) { + with(holder) { + when (voiceBroadcastState) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.RESUMED -> { + 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 + } + } + } + } + + abstract fun renderMetadata(holder: H) + + abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) { + val liveIndicator by bind(R.id.liveIndicator) + val roomAvatarImageView by bind(R.id.roomAvatarImageView) + val titleText by bind(R.id.titleText) + } +} 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 5b58dda4e6c..135053d9a96 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 @@ -18,56 +18,26 @@ 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 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 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 +import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass -abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem() { - - @EpoxyAttribute - var callback: TimelineEventController.Callback? = null +abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem() { @EpoxyAttribute var voiceBroadcastPlayer: VoiceBroadcastPlayer? = null - @EpoxyAttribute - lateinit var voiceBroadcastId: String - - @EpoxyAttribute - var voiceBroadcastState: VoiceBroadcastState? = null - @EpoxyAttribute var broadcasterName: String? = null - @EpoxyAttribute - lateinit var colorProvider: ColorProvider - - @EpoxyAttribute - lateinit var drawableProvider: DrawableProvider - - @EpoxyAttribute - var roomItem: MatrixItem? = null - - @EpoxyAttribute - var title: String? = null - private lateinit var playerListener: VoiceBroadcastPlayer.Listener - override fun isCacheable(): Boolean = false - override fun bind(holder: Holder) { super.bind(holder) bindVoiceBroadcastItem(holder) @@ -75,51 +45,20 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem - renderState(holder, state) + renderPlayingState(holder, state) } - voiceBroadcastPlayer?.addListener(playerListener) - renderHeader(holder) - renderLiveIcon(holder) + voiceBroadcastPlayer?.addListener(voiceBroadcastId, playerListener) } - private fun renderHeader(holder: Holder) { + override fun renderMetadata(holder: Holder) { with(holder) { - roomItem?.let { - attributes.avatarRenderer.render(it, roomAvatarImageView) - titleText.text = it.displayName - } - broadcasterNameText.text = broadcasterName + broadcasterNameMetadata.value = broadcasterName.orEmpty() + voiceBroadcastMetadata.isVisible = true + listenersCountMetadata.isVisible = false } } - private fun renderLiveIcon(holder: Holder) { - with(holder) { - when (voiceBroadcastState) { - VoiceBroadcastState.STARTED, - VoiceBroadcastState.RESUMED -> { - 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) - } else { - renderInactiveMedia(holder) - } - } - - private fun renderActiveMedia(holder: Holder, state: VoiceBroadcastPlayer.State) { + private fun renderPlayingState(holder: Holder, state: VoiceBroadcastPlayer.State) { with(holder) { bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING playPauseButton.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING @@ -143,34 +82,19 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem(R.id.liveIndicator) - val roomAvatarImageView by bind(R.id.roomAvatarImageView) - val titleText by bind(R.id.titleText) + class Holder : AbsMessageVoiceBroadcastItem.Holder(STUB_ID) { val playPauseButton by bind(R.id.playPauseButton) val bufferingView by bind(R.id.bufferingView) - val broadcasterNameText by bind(R.id.broadcasterNameText) + val broadcasterNameMetadata by bind(R.id.broadcasterNameMetadata) + val voiceBroadcastMetadata by bind(R.id.voiceBroadcastMetadata) + val listenersCountMetadata by bind(R.id.listenersCountMetadata) } companion object { 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 c417053b2a5..b766698851e 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 @@ -17,46 +17,23 @@ 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.epoxy.onClick -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 +import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass -abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem() { - - @EpoxyAttribute - var callback: TimelineEventController.Callback? = null +abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem() { @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) @@ -65,32 +42,26 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem { stopRecordButton.isEnabled = true recordButton.isEnabled = true - liveIndicator.isVisible = true - liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError)) - val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) recordButton.setImageDrawable(drawable) @@ -102,9 +73,6 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem { recordButton.isEnabled = false stopRecordButton.isEnabled = false - liveIndicator.isVisible = false } } } @@ -126,10 +93,9 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem(R.id.liveIndicator) - val roomAvatarImageView by bind(R.id.roomAvatarImageView) - val titleText by bind(R.id.titleText) + class Holder : AbsMessageVoiceBroadcastItem.Holder(STUB_ID) { + val listenersCountMetadata by bind(R.id.listenersCountMetadata) + val remainingTimeMetadata by bind(R.id.remainingTimeMetadata) val recordButton by bind(R.id.recordButton) val stopRecordButton by bind(R.id.stopRecordButton) } 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 2c892c8306f..6545948021b 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 @@ -82,10 +82,17 @@ class VoiceBroadcastPlayer @Inject constructor( set(value) { Timber.w("## VoiceBroadcastPlayer state: $field -> $value") field = value - listeners.forEach { it.onStateChanged(value) } + // Notify state change to all the listeners attached to the current voice broadcast id + currentVoiceBroadcastId?.let { voiceBroadcastId -> + listeners[voiceBroadcastId]?.forEach { listener -> listener.onStateChanged(value) } + } } private var currentRoomId: String? = null - private var listeners = CopyOnWriteArrayList() + + /** + * Map voiceBroadcastId to listeners + */ + private var listeners: MutableMap> = mutableMapOf() fun playOrResume(roomId: String, eventId: String) { val hasChanged = currentVoiceBroadcastId != eventId @@ -133,13 +140,21 @@ class VoiceBroadcastPlayer @Inject constructor( currentVoiceBroadcastId = null } - fun addListener(listener: Listener) { - listeners.add(listener) - listener.onStateChanged(state) + /** + * Add a [Listener] to the given voice broadcast id. + */ + fun addListener(voiceBroadcastId: String, listener: Listener) { + listeners[voiceBroadcastId]?.add(listener) ?: run { + listeners[voiceBroadcastId] = CopyOnWriteArrayList().apply { add(listener) } + } + if (voiceBroadcastId == currentVoiceBroadcastId) listener.onStateChanged(state) else listener.onStateChanged(State.IDLE) } - fun removeListener(listener: Listener) { - listeners.remove(listener) + /** + * Remove a [Listener] from the given voice broadcast id. + */ + fun removeListener(voiceBroadcastId: String, listener: Listener) { + listeners[voiceBroadcastId]?.remove(listener) } private fun startPlayback(roomId: String, eventId: String) { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/views/VoiceBroadcastMetadataView.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/views/VoiceBroadcastMetadataView.kt new file mode 100644 index 00000000000..e142cb15cea --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/views/VoiceBroadcastMetadataView.kt @@ -0,0 +1,66 @@ +/* + * 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.voicebroadcast.views + +import android.content.Context +import android.content.res.TypedArray +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.LinearLayout +import androidx.core.content.res.use +import im.vector.app.R +import im.vector.app.databinding.ViewVoiceBroadcastMetadataBinding + +class VoiceBroadcastMetadataView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + + private val views = ViewVoiceBroadcastMetadataBinding.inflate( + LayoutInflater.from(context), + this + ) + + var value: String + get() = views.metadataValue.text.toString() + set(newValue) { + views.metadataValue.text = newValue + } + + init { + context.obtainStyledAttributes( + attrs, + R.styleable.VoiceBroadcastMetadataView, + 0, + 0 + ).use { + setIcon(it) + setValue(it) + } + } + + private fun setIcon(typedArray: TypedArray) { + val icon = typedArray.getDrawable(R.styleable.VoiceBroadcastMetadataView_metadataIcon) + views.metadataIcon.setImageDrawable(icon) + } + + private fun setValue(typedArray: TypedArray) { + val value = typedArray.getString(R.styleable.VoiceBroadcastMetadataView_metadataValue) + views.metadataValue.text = value + } +} diff --git a/vector/src/main/res/drawable/ic_timer.xml b/vector/src/main/res/drawable/ic_timer.xml new file mode 100644 index 00000000000..11a42b0696d --- /dev/null +++ b/vector/src/main/res/drawable/ic_timer.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/ic_voice_broadcast_16.xml b/vector/src/main/res/drawable/ic_voice_broadcast_16.xml deleted file mode 100644 index 7d427a56d05..00000000000 --- a/vector/src/main/res/drawable/ic_voice_broadcast_16.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/vector/src/main/res/drawable/ic_voice_broadcast_mic.xml b/vector/src/main/res/drawable/ic_voice_broadcast_mic.xml new file mode 100644 index 00000000000..edadb55b814 --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_broadcast_mic.xml @@ -0,0 +1,12 @@ + + + + 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 97f15967e11..16a5b17d681 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 @@ -7,8 +7,7 @@ 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"> + android:padding="@dimen/layout_vertical_margin"> @@ -54,61 +53,41 @@ android:contentDescription="@string/avatar" app:layout_constraintStart_toEndOf="@id/avatarRightBarrier" app:layout_constraintTop_toTopOf="parent" - tools:src="@sample/rooms.json/data/name" /> + tools:text="@sample/rooms.json/data/name" /> - - - + app:layout_constraintTop_toBottomOf="@id/titleText" /> - - - - + app:metadataIcon="@drawable/ic_voice_broadcast_mic" + tools:metadataValue="@sample/users.json/data/displayName" /> - + - - + + app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataFlow" /> + android:padding="@dimen/layout_vertical_margin"> @@ -54,7 +53,34 @@ android:contentDescription="@string/avatar" app:layout_constraintStart_toEndOf="@id/avatarRightBarrier" app:layout_constraintTop_toTopOf="parent" - tools:src="@sample/users.json/data/displayName" /> + tools:text="@sample/users.json/data/displayName" /> + + + + + + + app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataFlow" /> + + + + + + From 4defc3dded84ac411544e5d8f8f8173becbc4509 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 24 Oct 2022 14:50:56 +0200 Subject: [PATCH 03/12] Voice Broadcast - Add style for the "live" indicator --- .../res/values/styles_voice_broadcast.xml | 19 +++++++++++++++++ .../main/res/drawable/ic_voice_broadcast.xml | 21 +++++++++++++++++++ ...e_event_voice_broadcast_listening_stub.xml | 16 +++----------- ...e_event_voice_broadcast_recording_stub.xml | 14 ++----------- .../layout/view_voice_broadcast_metadata.xml | 2 +- 5 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 library/ui-styles/src/main/res/values/styles_voice_broadcast.xml create mode 100644 vector/src/main/res/drawable/ic_voice_broadcast.xml diff --git a/library/ui-styles/src/main/res/values/styles_voice_broadcast.xml b/library/ui-styles/src/main/res/values/styles_voice_broadcast.xml new file mode 100644 index 00000000000..eb85378141c --- /dev/null +++ b/library/ui-styles/src/main/res/values/styles_voice_broadcast.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_voice_broadcast.xml b/vector/src/main/res/drawable/ic_voice_broadcast.xml new file mode 100644 index 00000000000..7d427a56d05 --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_broadcast.xml @@ -0,0 +1,21 @@ + + + + + + + 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 16a5b17d681..d508569cb07 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 @@ -11,20 +11,10 @@ @@ -78,7 +68,7 @@ android:id="@+id/voiceBroadcastMetadata" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:metadataIcon="@drawable/ic_attachment_voice_broadcast" + app:metadataIcon="@drawable/ic_voice_broadcast" app:metadataValue="@string/attachment_type_voice_broadcast" /> diff --git a/vector/src/main/res/layout/view_voice_broadcast_metadata.xml b/vector/src/main/res/layout/view_voice_broadcast_metadata.xml index 4f0c584d5c3..3bc31cd9a02 100644 --- a/vector/src/main/res/layout/view_voice_broadcast_metadata.xml +++ b/vector/src/main/res/layout/view_voice_broadcast_metadata.xml @@ -15,7 +15,7 @@ android:layout_marginEnd="4dp" android:contentDescription="@null" app:tint="?vctr_content_secondary" - tools:src="@drawable/ic_attachment_voice_broadcast" /> + tools:src="@drawable/ic_voice_broadcast" /> Date: Mon, 24 Oct 2022 16:35:16 +0200 Subject: [PATCH 04/12] Improve VoiceBroadcastItemFactory --- .../factory/VoiceBroadcastItemFactory.kt | 48 +++++++++---------- .../timeline/helper/TimelineEventsGroups.kt | 3 ++ 2 files changed, 27 insertions(+), 24 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 7b8c9271866..b639a2dbaea 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,14 +15,14 @@ */ 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.displayname.getBestName 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.AbsMessageVoiceBroadcastItem 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 @@ -34,7 +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.session.getUserOrDefault import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -53,31 +53,31 @@ class VoiceBroadcastItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): VectorEpoxyModel? { + ): AbsMessageVoiceBroadcastItem<*>? { // Only display item of the initial event with updated data if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) 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 - 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 voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null + val voiceBroadcastEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent().root.asVoiceBroadcastEvent() ?: return null + val voiceBroadcastContent = voiceBroadcastEvent.content ?: return null + val voiceBroadcastId = voiceBroadcastEventsGroup.voiceBroadcastId + + val isRecording = voiceBroadcastContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && voiceBroadcastEvent.root.stateKey == session.myUserId + return if (isRecording) { createRecordingItem( - params.event.roomId, - eventsGroup.groupId, - mostRecentMessageContent.voiceBroadcastState, + params, + voiceBroadcastId, + voiceBroadcastContent.voiceBroadcastState, highlight, callback, attributes ) } else { createListeningItem( - params.event.roomId, - eventsGroup.groupId, - mostRecentMessageContent.voiceBroadcastState, - recorderName, + params, + voiceBroadcastId, + voiceBroadcastContent.voiceBroadcastState, highlight, callback, attributes @@ -86,14 +86,14 @@ class VoiceBroadcastItemFactory @Inject constructor( } private fun createRecordingItem( - roomId: String, + params: TimelineItemFactoryParams, voiceBroadcastId: String, voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): MessageVoiceBroadcastRecordingItem { - val roomSummary = session.getRoom(roomId)?.roomSummary() + val roomSummary = session.getRoom(params.event.roomId)?.roomSummary() return MessageVoiceBroadcastRecordingItem_() .id("voice_broadcast_$voiceBroadcastId") .attributes(attributes) @@ -109,15 +109,15 @@ class VoiceBroadcastItemFactory @Inject constructor( } private fun createListeningItem( - roomId: String, + params: TimelineItemFactoryParams, voiceBroadcastId: String, voiceBroadcastState: VoiceBroadcastState?, - broadcasterName: String?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): MessageVoiceBroadcastListeningItem { - val roomSummary = session.getRoom(roomId)?.roomSummary() + val roomSummary = session.getRoom(params.event.roomId)?.roomSummary() + val recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName() return MessageVoiceBroadcastListeningItem_() .id("voice_broadcast_$voiceBroadcastId") .attributes(attributes) @@ -128,7 +128,7 @@ class VoiceBroadcastItemFactory @Inject constructor( .voiceBroadcastPlayer(voiceBroadcastPlayer) .voiceBroadcastId(voiceBroadcastId) .voiceBroadcastState(voiceBroadcastState) - .broadcasterName(broadcasterName) + .broadcasterName(recorderName) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt index d8817c1f449..8a3be7d5f25 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt @@ -141,6 +141,9 @@ class CallSignalingEventsGroup(private val group: TimelineEventsGroup) { } class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) { + + val voiceBroadcastId = group.groupId + fun getLastDisplayableEvent(): TimelineEvent { return group.events.find { it.root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED } ?: group.events.filter { it.root.type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO }.maxBy { it.root.originServerTs ?: 0L } From 2c144614cabc6427319ebfcb3143ab176b6d565a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 24 Oct 2022 16:49:59 +0200 Subject: [PATCH 05/12] Improve recording state rendering if app has been relaunched --- .../MessageVoiceBroadcastRecordingItem.kt | 87 +++++++++++-------- 1 file changed, 53 insertions(+), 34 deletions(-) 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 b766698851e..183d2a55779 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 @@ -24,6 +24,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.onClick import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass @@ -32,7 +33,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem @EpoxyAttribute var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null - private lateinit var recorderListener: VoiceBroadcastRecorder.Listener + private var recorderListener: VoiceBroadcastRecorder.Listener? = null override fun bind(holder: Holder) { super.bind(holder) @@ -40,12 +41,15 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem } private fun bindVoiceBroadcastItem(holder: Holder) { - recorderListener = object : VoiceBroadcastRecorder.Listener { - override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { - renderRecordingState(holder, state) - } + if (voiceBroadcastRecorder != null && voiceBroadcastRecorder?.state != VoiceBroadcastRecorder.State.Idle) { + recorderListener = object : VoiceBroadcastRecorder.Listener { + override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { + renderRecordingState(holder, state) + } + }.also { voiceBroadcastRecorder?.addListener(it) } + } else { + renderVoiceBroadcastState(holder) } - voiceBroadcastRecorder?.addListener(recorderListener) } override fun renderMetadata(holder: Holder) { @@ -56,39 +60,54 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem } private fun renderRecordingState(holder: Holder, state: VoiceBroadcastRecorder.State) { - with(holder) { - when (state) { - VoiceBroadcastRecorder.State.Recording -> { - stopRecordButton.isEnabled = true - recordButton.isEnabled = true - - 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.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } - stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } - } - VoiceBroadcastRecorder.State.Paused -> { - stopRecordButton.isEnabled = true - recordButton.isEnabled = true - - recordButton.setImageResource(R.drawable.ic_recording_dot) - recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) - recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } - stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } - } - VoiceBroadcastRecorder.State.Idle -> { - recordButton.isEnabled = false - stopRecordButton.isEnabled = false - } - } + when (state) { + VoiceBroadcastRecorder.State.Recording -> renderPlayingState(holder) + VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder) + VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder) + } + } + + private fun renderVoiceBroadcastState(holder: Holder) { + when (voiceBroadcastState) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.RESUMED -> renderPlayingState(holder) + VoiceBroadcastState.PAUSED -> renderPausedState(holder) + VoiceBroadcastState.STOPPED, + null -> renderStoppedState(holder) } } + private fun renderPlayingState(holder: Holder) = with(holder) { + stopRecordButton.isEnabled = true + recordButton.isEnabled = true + + 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.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } + stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + } + + private fun renderPausedState(holder: Holder) = with(holder) { + stopRecordButton.isEnabled = true + recordButton.isEnabled = true + + recordButton.setImageResource(R.drawable.ic_recording_dot) + recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) + recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } + stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + } + + private fun renderStoppedState(holder: Holder) = with(holder) { + recordButton.isEnabled = false + stopRecordButton.isEnabled = false + } + override fun unbind(holder: Holder) { super.unbind(holder) - voiceBroadcastRecorder?.removeListener(recorderListener) + recorderListener?.let { voiceBroadcastRecorder?.removeListener(it) } + recorderListener = null } override fun getViewStubId() = STUB_ID From f31429cf25ca232344715d7a39906472e2e290be Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 15:22:16 +0200 Subject: [PATCH 06/12] Rename renderLiveIcon method --- .../room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt index cbf35e89d26..afe705ffb6c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -63,11 +63,11 @@ abstract class AbsMessageVoiceBroadcastItem Date: Tue, 25 Oct 2022 16:29:42 +0200 Subject: [PATCH 07/12] Move voice broadcast item attributes to dedicated class --- .../timeline/factory/MessageItemFactory.kt | 2 +- .../factory/VoiceBroadcastItemFactory.kt | 65 ++++++------------- .../item/AbsMessageVoiceBroadcastItem.kt | 42 +++++++----- .../MessageVoiceBroadcastListeningItem.kt | 17 ++--- .../MessageVoiceBroadcastRecordingItem.kt | 18 ++--- 5 files changed, 57 insertions(+), 87 deletions(-) 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 245d92f95b3..f4d506fa4b6 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(params, messageContent, highlight, callback, attributes) + is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, 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 b639a2dbaea..d43ccd98345 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 @@ -18,7 +18,6 @@ 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.displayname.getBestName -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 @@ -51,7 +50,6 @@ class VoiceBroadcastItemFactory @Inject constructor( params: TimelineItemFactoryParams, messageContent: MessageVoiceBroadcastInfoContent, highlight: Boolean, - callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): AbsMessageVoiceBroadcastItem<*>? { // Only display item of the initial event with updated data @@ -64,72 +62,47 @@ class VoiceBroadcastItemFactory @Inject constructor( val isRecording = voiceBroadcastContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && voiceBroadcastEvent.root.stateKey == session.myUserId + val voiceBroadcastAttributes = AbsMessageVoiceBroadcastItem.Attributes( + voiceBroadcastId = voiceBroadcastId, + voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState, + recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(), + recorder = voiceBroadcastRecorder, + player = voiceBroadcastPlayer, + roomItem = session.getRoom(params.event.roomId)?.roomSummary()?.toMatrixItem(), + colorProvider = colorProvider, + drawableProvider = drawableProvider, + ) + return if (isRecording) { - createRecordingItem( - params, - voiceBroadcastId, - voiceBroadcastContent.voiceBroadcastState, - highlight, - callback, - attributes - ) + createRecordingItem(highlight, attributes, voiceBroadcastAttributes) } else { - createListeningItem( - params, - voiceBroadcastId, - voiceBroadcastContent.voiceBroadcastState, - highlight, - callback, - attributes - ) + createListeningItem(highlight, attributes, voiceBroadcastAttributes) } } private fun createRecordingItem( - params: TimelineItemFactoryParams, - voiceBroadcastId: String, - voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, - callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, + voiceBroadcastAttributes: AbsMessageVoiceBroadcastItem.Attributes, ): MessageVoiceBroadcastRecordingItem { - val roomSummary = session.getRoom(params.event.roomId)?.roomSummary() return MessageVoiceBroadcastRecordingItem_() - .id("voice_broadcast_$voiceBroadcastId") + .id("voice_broadcast_${voiceBroadcastAttributes.voiceBroadcastId}") .attributes(attributes) + .voiceBroadcastAttributes(voiceBroadcastAttributes) .highlighted(highlight) - .roomItem(roomSummary?.toMatrixItem()) - .colorProvider(colorProvider) - .drawableProvider(drawableProvider) - .voiceBroadcastRecorder(voiceBroadcastRecorder) - .voiceBroadcastId(voiceBroadcastId) - .voiceBroadcastState(voiceBroadcastState) .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) } private fun createListeningItem( - params: TimelineItemFactoryParams, - voiceBroadcastId: String, - voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, - callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, + voiceBroadcastAttributes: AbsMessageVoiceBroadcastItem.Attributes, ): MessageVoiceBroadcastListeningItem { - val roomSummary = session.getRoom(params.event.roomId)?.roomSummary() - val recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName() return MessageVoiceBroadcastListeningItem_() - .id("voice_broadcast_$voiceBroadcastId") + .id("voice_broadcast_${voiceBroadcastAttributes.voiceBroadcastId}") .attributes(attributes) + .voiceBroadcastAttributes(voiceBroadcastAttributes) .highlighted(highlight) - .roomItem(roomSummary?.toMatrixItem()) - .colorProvider(colorProvider) - .drawableProvider(drawableProvider) - .voiceBroadcastPlayer(voiceBroadcastPlayer) - .voiceBroadcastId(voiceBroadcastId) - .voiceBroadcastState(voiceBroadcastState) - .broadcasterName(recorderName) .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt index afe705ffb6c..45f10b68d01 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -25,29 +25,26 @@ 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.timeline.TimelineEventController +import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import org.matrix.android.sdk.api.util.MatrixItem abstract class AbsMessageVoiceBroadcastItem : AbsMessageItem() { @EpoxyAttribute - var callback: TimelineEventController.Callback? = null + lateinit var voiceBroadcastAttributes: Attributes - @EpoxyAttribute - lateinit var colorProvider: ColorProvider - - @EpoxyAttribute - lateinit var drawableProvider: DrawableProvider - - @EpoxyAttribute - lateinit var voiceBroadcastId: String - - @EpoxyAttribute - var voiceBroadcastState: VoiceBroadcastState? = null - - @EpoxyAttribute - var roomItem: MatrixItem? = null + protected val voiceBroadcastId get() = voiceBroadcastAttributes.voiceBroadcastId + protected val voiceBroadcastState get() = voiceBroadcastAttributes.voiceBroadcastState + protected val recorderName get() = voiceBroadcastAttributes.recorderName + protected val recorder get() = voiceBroadcastAttributes.recorder + protected val player get() = voiceBroadcastAttributes.player + protected val roomItem get() = voiceBroadcastAttributes.roomItem + protected val colorProvider get() = voiceBroadcastAttributes.colorProvider + protected val drawableProvider get() = voiceBroadcastAttributes.drawableProvider + protected val avatarRenderer get() = attributes.avatarRenderer + protected val callback get() = attributes.callback override fun isCacheable(): Boolean = false @@ -59,7 +56,7 @@ abstract class AbsMessageVoiceBroadcastItem(R.id.roomAvatarImageView) val titleText by bind(R.id.titleText) } + + data class Attributes( + val voiceBroadcastId: String, + val voiceBroadcastState: VoiceBroadcastState?, + val recorderName: String, + val recorder: VoiceBroadcastRecorder?, + val player: VoiceBroadcastPlayer, + val roomItem: MatrixItem?, + val colorProvider: ColorProvider, + val drawableProvider: DrawableProvider, + ) } 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 135053d9a96..d94bee36722 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 @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.item import android.view.View import android.widget.ImageButton 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 @@ -30,12 +29,6 @@ import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem() { - @EpoxyAttribute - var voiceBroadcastPlayer: VoiceBroadcastPlayer? = null - - @EpoxyAttribute - var broadcasterName: String? = null - private lateinit var playerListener: VoiceBroadcastPlayer.Listener override fun bind(holder: Holder) { @@ -47,12 +40,12 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem playerListener = VoiceBroadcastPlayer.Listener { state -> renderPlayingState(holder, state) } - voiceBroadcastPlayer?.addListener(voiceBroadcastId, playerListener) + player.addListener(voiceBroadcastId, playerListener) } override fun renderMetadata(holder: Holder) { with(holder) { - broadcasterNameMetadata.value = broadcasterName.orEmpty() + broadcasterNameMetadata.value = recorderName voiceBroadcastMetadata.isVisible = true listenersCountMetadata.isVisible = false } @@ -67,14 +60,14 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem VoiceBroadcastPlayer.State.PLAYING -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) - playPauseButton.onClick { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } + playPauseButton.onClick { 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.onClick { - attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) + callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) } } VoiceBroadcastPlayer.State.BUFFERING -> Unit @@ -84,7 +77,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem override fun unbind(holder: Holder) { super.unbind(holder) - voiceBroadcastPlayer?.removeListener(voiceBroadcastId, playerListener) + player.removeListener(voiceBroadcastId, playerListener) } override fun getViewStubId() = STUB_ID 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 183d2a55779..47e89658ca1 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 @@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail.timeline.item import android.widget.ImageButton 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 @@ -30,9 +29,6 @@ import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem() { - @EpoxyAttribute - var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null - private var recorderListener: VoiceBroadcastRecorder.Listener? = null override fun bind(holder: Holder) { @@ -41,12 +37,12 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem } private fun bindVoiceBroadcastItem(holder: Holder) { - if (voiceBroadcastRecorder != null && voiceBroadcastRecorder?.state != VoiceBroadcastRecorder.State.Idle) { + if (recorder != null && recorder?.state != VoiceBroadcastRecorder.State.Idle) { recorderListener = object : VoiceBroadcastRecorder.Listener { override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { renderRecordingState(holder, state) } - }.also { voiceBroadcastRecorder?.addListener(it) } + }.also { recorder?.addListener(it) } } else { renderVoiceBroadcastState(holder) } @@ -85,8 +81,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem 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.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } - stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + recordButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } + stopRecordButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } } private fun renderPausedState(holder: Holder) = with(holder) { @@ -95,8 +91,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem recordButton.setImageResource(R.drawable.ic_recording_dot) recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) - recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } - stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + recordButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } + stopRecordButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } } private fun renderStoppedState(holder: Holder) = with(holder) { @@ -106,7 +102,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem override fun unbind(holder: Holder) { super.unbind(holder) - recorderListener?.let { voiceBroadcastRecorder?.removeListener(it) } + recorderListener?.let { recorder?.removeListener(it) } recorderListener = null } From 513097585a7ab9592eb50d5172e30eaf3c428406 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 17:38:05 +0200 Subject: [PATCH 08/12] Fix kdoc issue --- .../vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 2 +- 1 file changed, 1 insertion(+), 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 6545948021b..d8a062c8f84 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 @@ -90,7 +90,7 @@ class VoiceBroadcastPlayer @Inject constructor( private var currentRoomId: String? = null /** - * Map voiceBroadcastId to listeners + * Map voiceBroadcastId to listeners. */ private var listeners: MutableMap> = mutableMapOf() From 0f21f404e694a465cfa82a0da8e178dc9a0af821 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 17:41:36 +0200 Subject: [PATCH 09/12] Add changelog --- changelog.d/7448.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7448.wip diff --git a/changelog.d/7448.wip b/changelog.d/7448.wip new file mode 100644 index 00000000000..a99e5bbcfa6 --- /dev/null +++ b/changelog.d/7448.wip @@ -0,0 +1 @@ +[Voice Broadcast] Improve timeline items factory and handle bad recording state display From 6091ec4ce3731b6498c42860d1d82dce677c2a8d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:45:25 +0200 Subject: [PATCH 10/12] Fix wrong content description --- .../timeline/item/MessageVoiceBroadcastListeningItem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 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 d94bee36722..a3e7cc55d51 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 @@ -59,13 +59,13 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem when (state) { VoiceBroadcastPlayer.State.PLAYING -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) - playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) playPauseButton.onClick { 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.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) playPauseButton.onClick { callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) } From 8fe3b5e75077d21b32cf6c2ba82fc7c9e47200d7 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:46:33 +0200 Subject: [PATCH 11/12] Rename method renderPlayingState to renderRecordingState --- .../timeline/item/MessageVoiceBroadcastRecordingItem.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 47e89658ca1..e3e86f38e38 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 @@ -57,7 +57,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem private fun renderRecordingState(holder: Holder, state: VoiceBroadcastRecorder.State) { when (state) { - VoiceBroadcastRecorder.State.Recording -> renderPlayingState(holder) + VoiceBroadcastRecorder.State.Recording -> renderRecordingState(holder) VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder) VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder) } @@ -66,14 +66,14 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem private fun renderVoiceBroadcastState(holder: Holder) { when (voiceBroadcastState) { VoiceBroadcastState.STARTED, - VoiceBroadcastState.RESUMED -> renderPlayingState(holder) + VoiceBroadcastState.RESUMED -> renderRecordingState(holder) VoiceBroadcastState.PAUSED -> renderPausedState(holder) VoiceBroadcastState.STOPPED, null -> renderStoppedState(holder) } } - private fun renderPlayingState(holder: Holder) = with(holder) { + private fun renderRecordingState(holder: Holder) = with(holder) { stopRecordButton.isEnabled = true recordButton.isEnabled = true From 1554d79f1a57095f3f98b20ffee8e944e7d60374 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:48:11 +0200 Subject: [PATCH 12/12] Change listeners Map variable to immutable --- .../vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 2 +- 1 file changed, 1 insertion(+), 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 d8a062c8f84..5a04904f69a 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 @@ -92,7 +92,7 @@ class VoiceBroadcastPlayer @Inject constructor( /** * Map voiceBroadcastId to listeners. */ - private var listeners: MutableMap> = mutableMapOf() + private val listeners: MutableMap> = mutableMapOf() fun playOrResume(roomId: String, eventId: String) { val hasChanged = currentVoiceBroadcastId != eventId