Show inactive message item when a live is inactive

This commit is contained in:
Maxime NATUREL 2022-05-03 09:54:20 +02:00
parent a37edb591b
commit c10b2a405c
8 changed files with 203 additions and 61 deletions

View file

@ -25,6 +25,8 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSummaryData
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationInactiveItem
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationInactiveItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem
@ -54,8 +56,8 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
attributes: AbsMessageItem.Attributes,
): VectorEpoxyModel<*>? {
val item = when (val currentState = getViewState(liveLocationShareSummaryData)) {
LiveLocationShareViewState.Inactive -> buildInactiveItem(highlight, attributes)
LiveLocationShareViewState.Loading -> buildLoadingItem(highlight, attributes)
LiveLocationShareViewState.Inactive -> buildInactiveItem()
is LiveLocationShareViewState.Running -> buildRunningItem(highlight, attributes, currentState)
LiveLocationShareViewState.Unkwown -> null
}
@ -64,6 +66,21 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
return item
}
private fun buildInactiveItem(
highlight: Boolean,
attributes: AbsMessageItem.Attributes,
): MessageLiveLocationInactiveItem {
val width = timelineMediaSizeProvider.getMaxSize().first
val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP)
return MessageLiveLocationInactiveItem_()
.attributes(attributes)
.mapWidth(width)
.mapHeight(height)
.highlighted(highlight)
.leftGuideline(avatarSizeProvider.leftGuideline)
}
private fun buildLoadingItem(
highlight: Boolean,
attributes: AbsMessageItem.Attributes,
@ -106,11 +123,9 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
.vectorDateFormatter(vectorDateFormatter)
}
private fun buildInactiveItem() = null
private fun getViewState(liveLocationShareSummaryData: LiveLocationShareSummaryData?): LiveLocationShareViewState {
return when {
liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown
liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown
liveLocationShareSummaryData.isActive.not() || isLiveTimedOut(liveLocationShareSummaryData) -> LiveLocationShareViewState.Inactive
liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading
else ->

View file

@ -0,0 +1,80 @@
/*
* 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.content.res.Resources
import android.graphics.drawable.ColorDrawable
import android.widget.ImageView
import androidx.core.view.updateLayoutParams
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import im.vector.app.R
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
import im.vector.app.features.themes.ThemeUtils
/**
* Default implementation of common methods for item representing the status of a live location share.
*/
class DefaultLiveLocationShareStatusItem : LiveLocationShareStatusItem {
override fun bindMap(
mapImageView: ImageView,
mapWidth: Int,
mapHeight: Int,
messageLayout: TimelineMessageLayout
) {
val mapCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
messageLayout.cornersRadius.granularRoundedCorners()
} else {
RoundedCorners(getDefaultLayoutCornerRadiusInDp(mapImageView.resources))
}
mapImageView.updateLayoutParams {
width = mapWidth
height = mapHeight
}
GlideApp.with(mapImageView)
.load(R.drawable.bg_no_location_map)
.transform(mapCornerTransformation)
.into(mapImageView)
}
override fun bindBottomBanner(bannerImageView: ImageView, messageLayout: TimelineMessageLayout) {
val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
GranularRoundedCorners(
0f,
0f,
messageLayout.cornersRadius.bottomEndRadius,
messageLayout.cornersRadius.bottomStartRadius
)
} else {
val bottomCornerRadius = getDefaultLayoutCornerRadiusInDp(bannerImageView.resources).toFloat()
GranularRoundedCorners(0f, 0f, bottomCornerRadius, bottomCornerRadius)
}
GlideApp.with(bannerImageView)
.load(ColorDrawable(ThemeUtils.getColor(bannerImageView.context, R.attr.colorSurface)))
.transform(imageCornerTransformation)
.into(bannerImageView)
}
private fun getDefaultLayoutCornerRadiusInDp(resources: Resources): Int {
val dimensionConverter = DimensionConverter(resources)
return dimensionConverter.dpToPx(8)
}
}

View file

@ -0,0 +1,31 @@
/*
* 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 im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
interface LiveLocationShareStatusItem {
fun bindMap(
mapImageView: ImageView,
mapWidth: Int,
mapHeight: Int,
messageLayout: TimelineMessageLayout
)
fun bindBottomBanner(bannerImageView: ImageView, messageLayout: TimelineMessageLayout)
}

View file

@ -0,0 +1,52 @@
/*
* 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 com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageLiveLocationInactiveItem :
AbsMessageItem<MessageLiveLocationInactiveItem.Holder>(),
LiveLocationShareStatusItem by DefaultLiveLocationShareStatusItem() {
@EpoxyAttribute
var mapWidth: Int = 0
@EpoxyAttribute
var mapHeight: Int = 0
override fun bind(holder: Holder) {
super.bind(holder)
renderSendState(holder.view, null)
bindMap(holder.noLocationMapImageView, mapWidth, mapHeight, attributes.informationData.messageLayout)
bindBottomBanner(holder.bannerImageView, attributes.informationData.messageLayout)
}
override fun getViewStubId() = STUB_ID
class Holder : AbsMessageItem.Holder(STUB_ID) {
val bannerImageView by bind<ImageView>(R.id.locationLiveInactiveBanner)
val noLocationMapImageView by bind<ImageView>(R.id.locationLiveInactiveMap)
}
companion object {
private const val STUB_ID = R.id.messageContentLiveLocationInactiveStub
}
}

View file

@ -16,22 +16,15 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.graphics.drawable.ColorDrawable
import android.widget.ImageView
import androidx.core.view.updateLayoutParams
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import im.vector.app.R
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
import im.vector.app.features.themes.ThemeUtils
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageLiveLocationStartItem : AbsMessageItem<MessageLiveLocationStartItem.Holder>() {
abstract class MessageLiveLocationStartItem :
AbsMessageItem<MessageLiveLocationStartItem.Holder>(),
LiveLocationShareStatusItem by DefaultLiveLocationShareStatusItem() {
@EpoxyAttribute
var mapWidth: Int = 0
@ -42,44 +35,8 @@ abstract class MessageLiveLocationStartItem : AbsMessageItem<MessageLiveLocation
override fun bind(holder: Holder) {
super.bind(holder)
renderSendState(holder.view, null)
bindMap(holder)
bindBottomBanner(holder)
}
private fun bindMap(holder: Holder) {
val messageLayout = attributes.informationData.messageLayout
val mapCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
messageLayout.cornersRadius.granularRoundedCorners()
} else {
RoundedCorners(getDefaultLayoutCornerRadiusInDp(holder))
}
holder.noLocationMapImageView.updateLayoutParams {
width = mapWidth
height = mapHeight
}
GlideApp.with(holder.noLocationMapImageView)
.load(R.drawable.bg_no_location_map)
.transform(mapCornerTransformation)
.into(holder.noLocationMapImageView)
}
private fun bindBottomBanner(holder: Holder) {
val messageLayout = attributes.informationData.messageLayout
val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
GranularRoundedCorners(0f, 0f, messageLayout.cornersRadius.bottomEndRadius, messageLayout.cornersRadius.bottomStartRadius)
} else {
val bottomCornerRadius = getDefaultLayoutCornerRadiusInDp(holder).toFloat()
GranularRoundedCorners(0f, 0f, bottomCornerRadius, bottomCornerRadius)
}
GlideApp.with(holder.bannerImageView)
.load(ColorDrawable(ThemeUtils.getColor(holder.bannerImageView.context, R.attr.colorSurface)))
.transform(imageCornerTransformation)
.into(holder.bannerImageView)
}
private fun getDefaultLayoutCornerRadiusInDp(holder: Holder): Int {
val dimensionConverter = DimensionConverter(holder.view.resources)
return dimensionConverter.dpToPx(8)
bindMap(holder.noLocationMapImageView, mapWidth, mapHeight, attributes.informationData.messageLayout)
bindBottomBanner(holder.bannerImageView, attributes.informationData.messageLayout)
}
override fun getViewStubId() = STUB_ID

View file

@ -20,7 +20,7 @@
android:layout_width="0dp"
android:layout_height="48dp"
android:alpha="0.85"
android:src="@color/element_background_light"
android:src="?colorSurface"
app:layout_constraintBottom_toBottomOf="@id/locationLiveInactiveMap"
app:layout_constraintEnd_toEndOf="@id/locationLiveInactiveMap"
app:layout_constraintStart_toStartOf="@id/locationLiveInactiveMap"
@ -30,7 +30,6 @@
android:id="@+id/locationLiveInactiveIcon"
android:layout_width="0dp"
android:layout_height="65dp"
android:alpha="0.85"
android:src="@drawable/ic_attachment_location_white"
app:layout_constraintBottom_toTopOf="@id/locationLiveInactiveVerticalCenter"
app:layout_constraintEnd_toEndOf="@id/locationLiveInactiveMap"
@ -40,9 +39,10 @@
<ImageView
android:id="@+id/locationLiveInactiveBannerIcon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginHorizontal="8dp"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginVertical="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/circle"
android:backgroundTint="?vctr_content_quaternary"
android:padding="3dp"

View file

@ -20,7 +20,7 @@
android:layout_width="0dp"
android:layout_height="48dp"
android:alpha="0.85"
android:src="@color/element_background_light"
android:src="?colorSurface"
app:layout_constraintBottom_toBottomOf="@id/locationLiveStartMap"
app:layout_constraintEnd_toEndOf="@id/locationLiveStartMap"
app:layout_constraintStart_toStartOf="@id/locationLiveStartMap"
@ -28,9 +28,10 @@
<ImageView
android:id="@+id/locationLiveStartIcon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginHorizontal="8dp"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginVertical="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/circle"
android:backgroundTint="?vctr_content_quaternary"
android:padding="3dp"

View file

@ -65,6 +65,12 @@
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_live_location_start_stub" />
<ViewStub
android:id="@+id/messageContentLiveLocationInactiveStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_live_location_inactive_stub" />
</FrameLayout>