mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 03:48:12 +03:00
Timeline - Add abstraction on voice broadcast items
This commit is contained in:
parent
6a88c61d12
commit
1566adb669
14 changed files with 329 additions and 216 deletions
|
@ -2,6 +2,7 @@
|
|||
<resources>
|
||||
|
||||
<string name="ellipsis" translatable="false">…</string>
|
||||
<string name="no_value_placeholder" translatable="false">–</string>
|
||||
|
||||
<!-- Temporary string -->
|
||||
<string name="not_implemented" translatable="false">Not implemented yet in ${app_name}</string>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="VoiceBroadcastMetadataView">
|
||||
<attr name="metadataIcon" format="reference" />
|
||||
<attr name="metadataValue" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<H : AbsMessageVoiceBroadcastItem.Holder> : AbsMessageItem<H>() {
|
||||
|
||||
@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<TextView>(R.id.liveIndicator)
|
||||
val roomAvatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||
val titleText by bind<TextView>(R.id.titleText)
|
||||
}
|
||||
}
|
|
@ -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<MessageVoiceBroadcastListeningItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var callback: TimelineEventController.Callback? = null
|
||||
abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem<MessageVoiceBroadcastListeningItem.Holder>() {
|
||||
|
||||
@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<MessageVoiceB
|
|||
|
||||
private fun bindVoiceBroadcastItem(holder: Holder) {
|
||||
playerListener = VoiceBroadcastPlayer.Listener { state ->
|
||||
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<MessageVoiceB
|
|||
}
|
||||
}
|
||||
|
||||
private fun renderInactiveMedia(holder: Holder) {
|
||||
with(holder) {
|
||||
bufferingView.isVisible = false
|
||||
playPauseButton.isVisible = true
|
||||
playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
|
||||
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
|
||||
playPauseButton.onClick {
|
||||
attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCurrentMediaActive() = voiceBroadcastPlayer?.currentVoiceBroadcastId == voiceBroadcastId
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
super.unbind(holder)
|
||||
voiceBroadcastPlayer?.removeListener(playerListener)
|
||||
voiceBroadcastPlayer?.removeListener(voiceBroadcastId, playerListener)
|
||||
}
|
||||
|
||||
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)
|
||||
class Holder : AbsMessageVoiceBroadcastItem.Holder(STUB_ID) {
|
||||
val playPauseButton by bind<ImageButton>(R.id.playPauseButton)
|
||||
val bufferingView by bind<View>(R.id.bufferingView)
|
||||
val broadcasterNameText by bind<TextView>(R.id.broadcasterNameText)
|
||||
val broadcasterNameMetadata by bind<VoiceBroadcastMetadataView>(R.id.broadcasterNameMetadata)
|
||||
val voiceBroadcastMetadata by bind<VoiceBroadcastMetadataView>(R.id.voiceBroadcastMetadata)
|
||||
val listenersCountMetadata by bind<VoiceBroadcastMetadataView>(R.id.listenersCountMetadata)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -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<MessageVoiceBroadcastRecordingItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var callback: TimelineEventController.Callback? = null
|
||||
abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem<MessageVoiceBroadcastRecordingItem.Holder>() {
|
||||
|
||||
@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<MessageVoiceB
|
|||
private fun bindVoiceBroadcastItem(holder: Holder) {
|
||||
recorderListener = object : VoiceBroadcastRecorder.Listener {
|
||||
override fun onStateUpdated(state: VoiceBroadcastRecorder.State) {
|
||||
renderState(holder, state)
|
||||
renderRecordingState(holder, state)
|
||||
}
|
||||
}
|
||||
voiceBroadcastRecorder?.addListener(recorderListener)
|
||||
renderHeader(holder)
|
||||
}
|
||||
|
||||
private fun renderHeader(holder: Holder) {
|
||||
override fun renderMetadata(holder: Holder) {
|
||||
with(holder) {
|
||||
roomItem?.let {
|
||||
attributes.avatarRenderer.render(it, roomAvatarImageView)
|
||||
titleText.text = it.displayName
|
||||
}
|
||||
listenersCountMetadata.isVisible = false
|
||||
remainingTimeMetadata.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderState(holder: Holder, state: VoiceBroadcastRecorder.State) {
|
||||
private fun renderRecordingState(holder: Holder, state: VoiceBroadcastRecorder.State) {
|
||||
with(holder) {
|
||||
when (state) {
|
||||
VoiceBroadcastRecorder.State.Recording -> {
|
||||
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<MessageVoiceB
|
|||
stopRecordButton.isEnabled = true
|
||||
recordButton.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.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) }
|
||||
|
@ -113,7 +81,6 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem<MessageVoiceB
|
|||
VoiceBroadcastRecorder.State.Idle -> {
|
||||
recordButton.isEnabled = false
|
||||
stopRecordButton.isEnabled = false
|
||||
liveIndicator.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,10 +93,9 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem<MessageVoiceB
|
|||
|
||||
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)
|
||||
class Holder : AbsMessageVoiceBroadcastItem.Holder(STUB_ID) {
|
||||
val listenersCountMetadata by bind<VoiceBroadcastMetadataView>(R.id.listenersCountMetadata)
|
||||
val remainingTimeMetadata by bind<VoiceBroadcastMetadataView>(R.id.remainingTimeMetadata)
|
||||
val recordButton by bind<ImageButton>(R.id.recordButton)
|
||||
val stopRecordButton by bind<ImageButton>(R.id.stopRecordButton)
|
||||
}
|
||||
|
|
|
@ -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<Listener>()
|
||||
|
||||
/**
|
||||
* Map voiceBroadcastId to listeners
|
||||
*/
|
||||
private var listeners: MutableMap<String, CopyOnWriteArrayList<Listener>> = 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<Listener>().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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
9
vector/src/main/res/drawable/ic_timer.xml
Normal file
9
vector/src/main/res/drawable/ic_timer.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M10,1H6V2.333H10V1ZM7.333,9.667H8.667V5.667H7.333V9.667ZM12.687,5.26L13.633,4.313C13.347,3.973 13.033,3.653 12.693,3.373L11.747,4.32C10.713,3.493 9.413,3 8,3C4.687,3 2,5.687 2,9C2,12.313 4.68,15 8,15C11.32,15 14,12.313 14,9C14,7.587 13.507,6.287 12.687,5.26ZM8,13.667C5.42,13.667 3.333,11.58 3.333,9C3.333,6.42 5.42,4.333 8,4.333C10.58,4.333 12.667,6.42 12.667,9C12.667,11.58 10.58,13.667 8,13.667Z"
|
||||
android:fillColor="#737D8C"/>
|
||||
</vector>
|
|
@ -1,21 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M13.459,2.791C13.233,2.5 12.814,2.448 12.523,2.674C12.233,2.9 12.181,3.318 12.406,3.609L12.406,3.609L12.407,3.61L12.416,3.622C12.425,3.634 12.439,3.654 12.458,3.68C12.496,3.733 12.552,3.815 12.62,3.923C12.757,4.138 12.943,4.456 13.128,4.854C13.502,5.654 13.866,6.756 13.866,8C13.866,9.245 13.502,10.347 13.128,11.147C12.943,11.545 12.757,11.863 12.62,12.078C12.552,12.186 12.496,12.267 12.458,12.321C12.439,12.347 12.425,12.367 12.416,12.378L12.407,12.391L12.406,12.391L12.406,12.392C12.181,12.683 12.233,13.101 12.523,13.327C12.814,13.553 13.233,13.5 13.459,13.21L12.962,12.823C13.459,13.21 13.459,13.21 13.459,13.21L13.46,13.208L13.462,13.205L13.468,13.198L13.485,13.175C13.5,13.155 13.52,13.128 13.545,13.093C13.595,13.023 13.664,12.922 13.745,12.794C13.908,12.538 14.123,12.17 14.337,11.711C14.763,10.797 15.199,9.499 15.199,8C15.199,6.502 14.763,5.204 14.337,4.29C14.123,3.831 13.908,3.463 13.745,3.207C13.664,3.079 13.595,2.978 13.545,2.908C13.52,2.873 13.5,2.846 13.485,2.826L13.468,2.803L13.462,2.795L13.46,2.793L13.46,2.792C13.46,2.792 13.459,2.791 12.933,3.2L13.459,2.791Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M11.726,5.191C11.5,4.901 11.081,4.848 10.791,5.074C10.501,5.3 10.448,5.717 10.672,6.008L10.674,6.011C10.677,6.015 10.683,6.022 10.691,6.033C10.707,6.056 10.731,6.092 10.762,6.141C10.825,6.238 10.91,6.384 10.996,6.568C11.169,6.94 11.333,7.442 11.333,8.001C11.333,8.559 11.169,9.061 10.996,9.433C10.91,9.617 10.825,9.763 10.762,9.86C10.731,9.909 10.707,9.945 10.691,9.968C10.683,9.979 10.677,9.986 10.674,9.99L10.672,9.994C10.448,10.284 10.501,10.701 10.791,10.927C11.081,11.153 11.5,11.101 11.726,10.81L11.2,10.401C11.726,10.81 11.726,10.81 11.726,10.81L11.727,10.808L11.729,10.806L11.733,10.801L11.744,10.787C11.752,10.775 11.764,10.759 11.778,10.74C11.806,10.7 11.843,10.646 11.887,10.576C11.975,10.438 12.09,10.241 12.204,9.997C12.431,9.511 12.667,8.813 12.667,8.001C12.667,7.188 12.431,6.49 12.204,6.004C12.09,5.76 11.975,5.563 11.887,5.425C11.843,5.356 11.806,5.301 11.778,5.261C11.764,5.242 11.752,5.226 11.744,5.214L11.733,5.2L11.729,5.195L11.727,5.193L11.727,5.192C11.727,5.192 11.726,5.191 11.2,5.601L11.726,5.191Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M2.407,13.21C2.633,13.5 3.052,13.553 3.343,13.327C3.633,13.101 3.686,12.683 3.461,12.392L3.46,12.391L3.459,12.391L3.45,12.378C3.441,12.366 3.427,12.347 3.408,12.321C3.371,12.267 3.314,12.186 3.246,12.078C3.109,11.863 2.924,11.545 2.738,11.147C2.364,10.347 2,9.245 2,8C2,6.756 2.364,5.654 2.738,4.854C2.924,4.456 3.109,4.138 3.246,3.923C3.314,3.815 3.371,3.733 3.408,3.68C3.427,3.654 3.441,3.634 3.45,3.622L3.459,3.61L3.46,3.609L3.461,3.609C3.686,3.318 3.633,2.9 3.343,2.674C3.052,2.448 2.633,2.5 2.407,2.791L2.904,3.177C2.407,2.791 2.407,2.791 2.407,2.791L2.406,2.793L2.404,2.795L2.399,2.802L2.381,2.826C2.366,2.846 2.346,2.873 2.321,2.908C2.272,2.978 2.203,3.079 2.121,3.207C1.958,3.463 1.744,3.831 1.529,4.29C1.103,5.204 0.667,6.502 0.667,8C0.667,9.499 1.103,10.797 1.529,11.711C1.744,12.17 1.958,12.538 2.121,12.794C2.203,12.922 2.272,13.023 2.321,13.093C2.346,13.128 2.366,13.155 2.381,13.175L2.399,13.198L2.404,13.205L2.406,13.208L2.407,13.209C2.407,13.209 2.407,13.21 2.934,12.8L2.407,13.21Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M4.14,10.809C4.366,11.1 4.785,11.153 5.076,10.926C5.365,10.701 5.418,10.284 5.194,9.993L5.192,9.99C5.189,9.986 5.183,9.978 5.175,9.967C5.16,9.945 5.135,9.909 5.104,9.86C5.042,9.762 4.956,9.616 4.87,9.433C4.697,9.061 4.533,8.559 4.533,8C4.533,7.442 4.697,6.94 4.87,6.568C4.956,6.384 5.042,6.238 5.104,6.14C5.135,6.092 5.16,6.055 5.175,6.033C5.183,6.022 5.189,6.014 5.192,6.01L5.194,6.007C5.418,5.717 5.365,5.299 5.076,5.074C4.785,4.848 4.366,4.9 4.14,5.191L4.666,5.6C4.14,5.191 4.14,5.191 4.14,5.191L4.139,5.192L4.137,5.194L4.134,5.199L4.123,5.214C4.114,5.226 4.102,5.241 4.088,5.261C4.061,5.3 4.023,5.355 3.979,5.424C3.891,5.562 3.776,5.759 3.662,6.004C3.436,6.489 3.2,7.187 3.2,8C3.2,8.813 3.436,9.511 3.662,9.996C3.776,10.241 3.891,10.438 3.979,10.576C4.023,10.645 4.061,10.7 4.088,10.739C4.102,10.759 4.114,10.775 4.123,10.786L4.134,10.801L4.137,10.806L4.139,10.808L4.14,10.809C4.14,10.809 4.14,10.809 4.666,10.4L4.14,10.809Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M8,8m-1.333,0a1.333,1.333 0,1 1,2.667 0a1.333,1.333 0,1 1,-2.667 0"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
12
vector/src/main/res/drawable/ic_voice_broadcast_mic.xml
Normal file
12
vector/src/main/res/drawable/ic_voice_broadcast_mic.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M5.4,4.1C5.4,2.664 6.564,1.5 8,1.5C9.436,1.5 10.6,2.664 10.6,4.1V7.988C10.6,9.424 9.436,10.588 8,10.588C6.564,10.588 5.4,9.424 5.4,7.988V4.1Z"
|
||||
android:fillColor="#737D8C"/>
|
||||
<path
|
||||
android:pathData="M3.45,7.158C3.91,7.158 4.283,7.531 4.283,7.992C4.283,10.037 5.941,11.697 7.99,11.703C7.993,11.703 7.996,11.703 8,11.703C8.003,11.703 8.006,11.703 8.01,11.703C10.059,11.697 11.716,10.037 11.716,7.992C11.716,7.531 12.089,7.158 12.55,7.158C13.01,7.158 13.383,7.531 13.383,7.992C13.383,10.679 11.41,12.905 8.833,13.305V13.834C8.833,14.294 8.46,14.667 8,14.667C7.539,14.667 7.166,14.294 7.166,13.834V13.305C4.59,12.905 2.616,10.679 2.616,7.992C2.616,7.531 2.989,7.158 3.45,7.158Z"
|
||||
android:fillColor="#737D8C"/>
|
||||
</vector>
|
|
@ -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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/liveIndicator"
|
||||
|
@ -24,7 +23,7 @@
|
|||
android:singleLine="true"
|
||||
android:text="@string/voice_broadcast_live"
|
||||
android:textColor="?colorOnError"
|
||||
app:drawableStartCompat="@drawable/ic_voice_broadcast_16"
|
||||
app:drawableStartCompat="@drawable/ic_attachment_voice_broadcast"
|
||||
app:drawableTint="?colorOnError"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
@ -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" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/broadcasterViewGroup"
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:id="@+id/metadataFlow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:orientation="vertical"
|
||||
app:constraint_referenced_ids="broadcasterNameMetadata,voiceBroadcastMetadata,listenersCountMetadata"
|
||||
app:flow_horizontalAlign="start"
|
||||
app:flow_verticalGap="4dp"
|
||||
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleText">
|
||||
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/broadcasterIcon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_microphone"
|
||||
app:tint="?vctr_content_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/broadcasterNameText"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@sample/users.json/data/displayName" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/voiceBroadcastViewGroup"
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/broadcasterNameMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/broadcasterViewGroup">
|
||||
app:metadataIcon="@drawable/ic_voice_broadcast_mic"
|
||||
tools:metadataValue="@sample/users.json/data/displayName" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/voiceBroadcastIcon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_voice_broadcast_16"
|
||||
app:tint="?vctr_content_secondary" />
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/voiceBroadcastMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_attachment_voice_broadcast"
|
||||
app:metadataValue="@string/attachment_type_voice_broadcast" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/voiceBroadcastText"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/attachment_type_voice_broadcast" />
|
||||
</LinearLayout>
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/listenersCountMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_member_small"
|
||||
app:metadataValue="@string/no_value_placeholder"
|
||||
tools:metadataValue="5 listeners" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/headerBottomBarrier"
|
||||
|
@ -116,7 +95,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:barrierMargin="12dp"
|
||||
app:constraint_referenced_ids="roomAvatarImageView,titleText,broadcasterViewGroup,voiceBroadcastViewGroup" />
|
||||
app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataFlow" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:id="@+id/controllerButtonsFlow"
|
||||
|
|
|
@ -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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/liveIndicator"
|
||||
|
@ -24,7 +23,7 @@
|
|||
android:singleLine="true"
|
||||
android:text="@string/voice_broadcast_live"
|
||||
android:textColor="?colorOnError"
|
||||
app:drawableStartCompat="@drawable/ic_voice_broadcast_16"
|
||||
app:drawableStartCompat="@drawable/ic_attachment_voice_broadcast"
|
||||
app:drawableTint="?colorOnError"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
@ -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" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:id="@+id/metadataFlow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:orientation="vertical"
|
||||
app:constraint_referenced_ids="listenersCountMetadata,remainingTimeMetadata"
|
||||
app:flow_horizontalAlign="start"
|
||||
app:flow_verticalGap="4dp"
|
||||
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
||||
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/listenersCountMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_member_small"
|
||||
app:metadataValue="@string/no_value_placeholder"
|
||||
tools:metadataValue="5 listening" />
|
||||
|
||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||
android:id="@+id/remainingTimeMetadata"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:metadataIcon="@drawable/ic_timer"
|
||||
tools:metadataValue="3h 2m 50s left" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/headerBottomBarrier"
|
||||
|
@ -62,7 +88,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:barrierMargin="12dp"
|
||||
app:constraint_referenced_ids="roomAvatarImageView,titleText" />
|
||||
app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataFlow" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:id="@+id/controllerButtonsFlow"
|
||||
|
|
27
vector/src/main/res/layout/view_voice_broadcast_metadata.xml
Normal file
27
vector/src/main/res/layout/view_voice_broadcast_metadata.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge 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:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/metadataIcon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:contentDescription="@null"
|
||||
app:tint="?vctr_content_secondary"
|
||||
tools:src="@drawable/ic_attachment_voice_broadcast" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/metadata_value"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/no_value_placeholder"
|
||||
tools:text="@string/attachment_type_voice_broadcast" />
|
||||
</merge>
|
Loading…
Reference in a new issue