VoiceBroadcast - Introduce listening view
This commit is contained in:
parent
4c71209573
commit
f1b4ebbc37
|
@ -15,12 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.app.features.home.room.detail.timeline.factory
|
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.ColorProvider
|
||||||
import im.vector.app.core.resources.DrawableProvider
|
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.TimelineEventController
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
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.helper.VoiceBroadcastEventsGroup
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
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.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.VoiceBroadcastRecorder
|
||||||
|
@ -46,7 +50,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes,
|
attributes: AbsMessageItem.Attributes,
|
||||||
): MessageVoiceBroadcastRecordingItem? {
|
): VectorEpoxyModel<out VectorEpoxyHolder>? {
|
||||||
// Only display item of the initial event with updated data
|
// Only display item of the initial event with updated data
|
||||||
if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null
|
if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null
|
||||||
val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null
|
val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null
|
||||||
|
@ -57,7 +61,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
|
||||||
return if (isRecording) {
|
return if (isRecording) {
|
||||||
createRecordingItem(params.event.roomId, highlight, callback, attributes)
|
createRecordingItem(params.event.roomId, highlight, callback, attributes)
|
||||||
} else {
|
} 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,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes,
|
attributes: AbsMessageItem.Attributes,
|
||||||
): MessageVoiceBroadcastRecordingItem? {
|
): MessageVoiceBroadcastRecordingItem {
|
||||||
val roomSummary = session.getRoom(roomId)?.roomSummary()
|
val roomSummary = session.getRoom(roomId)?.roomSummary()
|
||||||
return MessageVoiceBroadcastRecordingItem_()
|
return MessageVoiceBroadcastRecordingItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
|
@ -78,4 +82,22 @@ class VoiceBroadcastItemFactory @Inject constructor(
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.callback(callback)
|
.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<MessageVoiceBroadcastListeningItem.Holder>() {
|
||||||
|
|
||||||
|
@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<TextView>(R.id.liveIndicator)
|
||||||
|
val roomAvatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||||
|
val titleText by bind<TextView>(R.id.titleText)
|
||||||
|
val recordButton by bind<ImageButton>(R.id.recordButton)
|
||||||
|
val stopRecordButton by bind<ImageButton>(R.id.stopRecordButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val STUB_ID = R.id.messageVoiceBroadcastListeningStub
|
||||||
|
}
|
||||||
|
}
|
|
@ -132,6 +132,6 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem<MessageVoiceB
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val STUB_ID = R.id.messageVoiceBroadcastStub
|
private val STUB_ID = R.id.messageVoiceBroadcastRecordingStub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,10 +48,17 @@
|
||||||
tools:visibility="gone" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageVoiceBroadcastStub"
|
android:id="@+id/messageVoiceBroadcastRecordingStub"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout="@layout/item_timeline_event_voice_broadcast_stub"
|
android:layout="@layout/item_timeline_event_voice_broadcast_recording_stub"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/messageVoiceBroadcastListeningStub"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout="@layout/item_timeline_event_voice_broadcast_listening_stub"
|
||||||
tools:visibility="gone" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/liveIndicator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:background="@drawable/rounded_rect_shape_2"
|
||||||
|
android:backgroundTint="?colorError"
|
||||||
|
android:drawablePadding="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:maxWidth="100dp"
|
||||||
|
android:paddingHorizontal="4dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="@string/voice_broadcast_live"
|
||||||
|
android:textColor="?colorOnError"
|
||||||
|
app:drawableStartCompat="@drawable/ic_live_broadcast_16"
|
||||||
|
app:drawableTint="?colorOnError"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/roomAvatarImageView"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@sample/user_round_avatars" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/avatarRightBarrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="right"
|
||||||
|
app:barrierMargin="6dp"
|
||||||
|
app:constraint_referenced_ids="roomAvatarImageView" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleText"
|
||||||
|
style="@style/Widget.Vector.TextView.Body.Medium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@sample/users.json/data/displayName" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/headerBottomBarrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:barrierMargin="12dp"
|
||||||
|
app:constraint_referenced_ids="roomAvatarImageView,titleText" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/recordButton"
|
||||||
|
android:layout_width="@dimen/voice_broadcast_controller_button_size"
|
||||||
|
android:layout_height="@dimen/voice_broadcast_controller_button_size"
|
||||||
|
android:background="@drawable/bg_rounded_button"
|
||||||
|
android:backgroundTint="?vctr_system"
|
||||||
|
android:contentDescription="@string/a11y_resume_voice_broadcast_record"
|
||||||
|
android:src="@drawable/ic_recording_dot"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/stopRecordButton"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/stopRecordButton"
|
||||||
|
android:layout_width="@dimen/voice_broadcast_controller_button_size"
|
||||||
|
android:layout_height="@dimen/voice_broadcast_controller_button_size"
|
||||||
|
android:background="@drawable/bg_rounded_button"
|
||||||
|
android:backgroundTint="?vctr_system"
|
||||||
|
android:contentDescription="@string/a11y_stop_voice_broadcast_record"
|
||||||
|
android:src="@drawable/ic_stop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/recordButton"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/recordButton"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/recordButton" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue