mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-29 14:38:45 +03:00
Integration into location message item
This commit is contained in:
parent
3acc139307
commit
bbec3a7c2e
6 changed files with 137 additions and 22 deletions
|
@ -2,14 +2,16 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Widget.Vector.Button.Text.OnPrimary.LocationLive">
|
<style name="Widget.Vector.Button.Text.OnPrimary.LocationLive">
|
||||||
<item name="android:background">?selectableItemBackground</item>
|
<item name="android:foreground">?selectableItemBackground</item>
|
||||||
|
<item name="android:background">@android:color/transparent</item>
|
||||||
<item name="android:textSize">12sp</item>
|
<item name="android:textSize">12sp</item>
|
||||||
<item name="android:padding">0dp</item>
|
<item name="android:padding">0dp</item>
|
||||||
<item name="android:gravity">center</item>
|
<item name="android:gravity">center</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Vector.Button.Text.LocationLive">
|
<style name="Widget.Vector.Button.Text.LocationLive">
|
||||||
<item name="android:background">?selectableItemBackground</item>
|
<item name="android:foreground">?selectableItemBackground</item>
|
||||||
|
<item name="android:background">@android:color/transparent</item>
|
||||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Body.Medium</item>
|
<item name="android:textAppearance">@style/TextAppearance.Vector.Body.Medium</item>
|
||||||
<item name="android:textColor">?colorError</item>
|
<item name="android:textColor">?colorError</item>
|
||||||
<item name="android:padding">0dp</item>
|
<item name="android:padding">0dp</item>
|
||||||
|
|
|
@ -35,6 +35,8 @@ import im.vector.app.core.utils.DimensionConverter
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
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.home.room.detail.timeline.style.granularRoundedCorners
|
||||||
|
import im.vector.app.features.location.live.LocationLiveMessageBannerView
|
||||||
|
import im.vector.app.features.location.live.LocationLiveMessageBannerViewState
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>() {
|
abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>() {
|
||||||
|
@ -57,12 +59,17 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
renderSendState(holder.view, null)
|
renderSendState(holder.view, null)
|
||||||
|
bindMap(holder)
|
||||||
|
bindLocationLiveBanner(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindMap(holder: Holder) {
|
||||||
val location = locationUrl ?: return
|
val location = locationUrl ?: return
|
||||||
val messageLayout = attributes.informationData.messageLayout
|
val messageLayout = attributes.informationData.messageLayout
|
||||||
val dimensionConverter = DimensionConverter(holder.view.resources)
|
|
||||||
val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
|
val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
|
||||||
messageLayout.cornersRadius.granularRoundedCorners()
|
messageLayout.cornersRadius.granularRoundedCorners()
|
||||||
} else {
|
} else {
|
||||||
|
val dimensionConverter = DimensionConverter(holder.view.resources)
|
||||||
RoundedCorners(dimensionConverter.dpToPx(8))
|
RoundedCorners(dimensionConverter.dpToPx(8))
|
||||||
}
|
}
|
||||||
holder.staticMapImageView.updateLayoutParams {
|
holder.staticMapImageView.updateLayoutParams {
|
||||||
|
@ -88,9 +95,8 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
|
||||||
dataSource: DataSource?,
|
dataSource: DataSource?,
|
||||||
isFirstResource: Boolean): Boolean {
|
isFirstResource: Boolean): Boolean {
|
||||||
locationPinProvider?.create(userId) { pinDrawable ->
|
locationPinProvider?.create(userId) { pinDrawable ->
|
||||||
GlideApp.with(holder.staticMapPinImageView)
|
// we are not using Glide since it does not display it correctly when there is no user photo
|
||||||
.load(pinDrawable)
|
holder.staticMapPinImageView.setImageDrawable(pinDrawable)
|
||||||
.into(holder.staticMapPinImageView)
|
|
||||||
}
|
}
|
||||||
holder.staticMapErrorTextView.isVisible = false
|
holder.staticMapErrorTextView.isVisible = false
|
||||||
return false
|
return false
|
||||||
|
@ -100,12 +106,44 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
|
||||||
.into(holder.staticMapImageView)
|
.into(holder.staticMapImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun bindLocationLiveBanner(holder: Holder) {
|
||||||
|
val messageLayout = attributes.informationData.messageLayout
|
||||||
|
val viewState = if (messageLayout is TimelineMessageLayout.Bubble) {
|
||||||
|
LocationLiveMessageBannerViewState.Emitter(
|
||||||
|
remainingTimeInMillis = 4000 * 1000L,
|
||||||
|
bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius,
|
||||||
|
bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius,
|
||||||
|
isStopButtonCenteredVertically = false
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val dimensionConverter = DimensionConverter(holder.view.resources)
|
||||||
|
val cornerRadius = dimensionConverter.dpToPx(8).toFloat()
|
||||||
|
// LocationLiveMessageBannerViewState.Watcher(
|
||||||
|
// bottomStartCornerRadiusInDp = cornerRadius,
|
||||||
|
// bottomEndCornerRadiusInDp = cornerRadius,
|
||||||
|
// formattedLocalTimeOfEndOfLive = "12:34",
|
||||||
|
// )
|
||||||
|
LocationLiveMessageBannerViewState.Emitter(
|
||||||
|
remainingTimeInMillis = 4000 * 1000L,
|
||||||
|
bottomStartCornerRadiusInDp = cornerRadius,
|
||||||
|
bottomEndCornerRadiusInDp = cornerRadius,
|
||||||
|
isStopButtonCenteredVertically = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
holder.locationLiveMessageBanner.isVisible = true
|
||||||
|
holder.locationLiveMessageBanner.render(viewState)
|
||||||
|
|
||||||
|
// TODO create a dedicated message Item per state: Start, Location, End? Check if inheritance is possible in Epoxy model
|
||||||
|
// TODO adjust Copyright map placement
|
||||||
|
}
|
||||||
|
|
||||||
override fun getViewStubId() = STUB_ID
|
override fun getViewStubId() = STUB_ID
|
||||||
|
|
||||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
val staticMapImageView by bind<ImageView>(R.id.staticMapImageView)
|
val staticMapImageView by bind<ImageView>(R.id.staticMapImageView)
|
||||||
val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView)
|
val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView)
|
||||||
val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView)
|
val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView)
|
||||||
|
val locationLiveMessageBanner by bind<LocationLiveMessageBannerView>(R.id.locationLiveMessageBanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -17,21 +17,42 @@
|
||||||
package im.vector.app.features.location.live
|
package im.vector.app.features.location.live
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.core.utils.TextUtils
|
import im.vector.app.core.utils.TextUtils
|
||||||
import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding
|
import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding
|
||||||
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import org.threeten.bp.Duration
|
import org.threeten.bp.Duration
|
||||||
|
|
||||||
data class LocationLiveMessageBannerViewState(
|
// TODO should it be moved to timeline.item package?
|
||||||
val isStopButtonVisible: Boolean,
|
sealed class LocationLiveMessageBannerViewState(
|
||||||
val remainingTimeInMillis: Long
|
open val bottomStartCornerRadiusInDp: Float,
|
||||||
)
|
open val bottomEndCornerRadiusInDp: Float,
|
||||||
|
) {
|
||||||
|
|
||||||
|
data class Emitter(
|
||||||
|
override val bottomStartCornerRadiusInDp: Float,
|
||||||
|
override val bottomEndCornerRadiusInDp: Float,
|
||||||
|
val remainingTimeInMillis: Long,
|
||||||
|
val isStopButtonCenteredVertically: Boolean
|
||||||
|
) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp)
|
||||||
|
|
||||||
|
data class Watcher(
|
||||||
|
override val bottomStartCornerRadiusInDp: Float,
|
||||||
|
override val bottomEndCornerRadiusInDp: Float,
|
||||||
|
val formattedLocalTimeOfEndOfLive: String,
|
||||||
|
) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp)
|
||||||
|
}
|
||||||
|
|
||||||
class LocationLiveMessageBannerView @JvmOverloads constructor(
|
class LocationLiveMessageBannerView @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -47,12 +68,57 @@ class LocationLiveMessageBannerView @JvmOverloads constructor(
|
||||||
val stopButton: Button
|
val stopButton: Button
|
||||||
get() = binding.locationLiveMessageBannerStop
|
get() = binding.locationLiveMessageBannerStop
|
||||||
|
|
||||||
|
private val background: ImageView
|
||||||
|
get() = binding.locationLiveMessageBannerBackground
|
||||||
|
|
||||||
|
private val title: TextView
|
||||||
|
get() = binding.locationLiveMessageBannerTitle
|
||||||
|
|
||||||
private val subTitle: TextView
|
private val subTitle: TextView
|
||||||
get() = binding.locationLiveMessageBannerSubTitle
|
get() = binding.locationLiveMessageBannerSubTitle
|
||||||
|
|
||||||
fun render(viewState: LocationLiveMessageBannerViewState) {
|
fun render(viewState: LocationLiveMessageBannerViewState) {
|
||||||
stopButton.isVisible = viewState.isStopButtonVisible
|
when (viewState) {
|
||||||
|
is LocationLiveMessageBannerViewState.Emitter -> renderEmitter(viewState)
|
||||||
|
is LocationLiveMessageBannerViewState.Watcher -> renderWatcher(viewState)
|
||||||
|
}
|
||||||
|
|
||||||
|
GlideApp.with(context)
|
||||||
|
.load(ColorDrawable(ThemeUtils.getColor(context, R.attr.colorSurface)))
|
||||||
|
.transform(GranularRoundedCorners(0f, 0f, viewState.bottomEndCornerRadiusInDp, viewState.bottomStartCornerRadiusInDp))
|
||||||
|
.into(background)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderEmitter(viewState: LocationLiveMessageBannerViewState.Emitter) {
|
||||||
|
stopButton.isVisible = true
|
||||||
|
title.text = context.getString(R.string.location_share_live_enabled)
|
||||||
val duration = Duration.ofMillis(viewState.remainingTimeInMillis.coerceAtLeast(0L))
|
val duration = Duration.ofMillis(viewState.remainingTimeInMillis.coerceAtLeast(0L))
|
||||||
subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration))
|
subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration))
|
||||||
|
|
||||||
|
val rootLayout: ConstraintLayout? = (binding.root as? ConstraintLayout)
|
||||||
|
rootLayout?.let { parentLayout ->
|
||||||
|
val constraintSet = ConstraintSet()
|
||||||
|
constraintSet.clone(rootLayout)
|
||||||
|
|
||||||
|
if (viewState.isStopButtonCenteredVertically) {
|
||||||
|
constraintSet.connect(
|
||||||
|
R.id.locationLiveMessageBannerStop,
|
||||||
|
ConstraintSet.BOTTOM,
|
||||||
|
R.id.locationLiveMessageBannerBackground,
|
||||||
|
ConstraintSet.BOTTOM,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
constraintSet.clear(R.id.locationLiveMessageBannerStop, ConstraintSet.BOTTOM)
|
||||||
|
}
|
||||||
|
|
||||||
|
constraintSet.applyTo(parentLayout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderWatcher(viewState: LocationLiveMessageBannerViewState.Watcher) {
|
||||||
|
stopButton.isVisible = false
|
||||||
|
title.text = context.getString(R.string.location_share_live_view)
|
||||||
|
subTitle.text = context.getString(R.string.location_share_live_until, viewState.formattedLocalTimeOfEndOfLive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,4 +45,12 @@
|
||||||
app:layout_constraintTop_toBottomOf="@id/staticMapPinImageView"
|
app:layout_constraintTop_toBottomOf="@id/staticMapPinImageView"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<im.vector.app.features.location.live.LocationLiveMessageBannerView
|
||||||
|
android:id="@+id/locationLiveMessageBanner"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/staticMapImageView"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/staticMapImageView"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/staticMapImageView" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
@ -6,15 +6,16 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
<View
|
<ImageView
|
||||||
android:id="@+id/locationLiveMessageBannerBackground"
|
android:id="@+id/locationLiveMessageBannerBackground"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="50dp"
|
||||||
android:alpha="0.85"
|
android:alpha="0.85"
|
||||||
android:background="?colorSurface"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:background="?colorSurface"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/locationLiveMessageBannerIcon"
|
android:id="@+id/locationLiveMessageBannerIcon"
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="8dp"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:text="@string/location_share_live_enabled"
|
tools:text="@string/location_share_live_enabled"
|
||||||
android:textColor="?colorOnSurface"
|
android:textColor="?colorOnSurface"
|
||||||
app:layout_constraintBottom_toTopOf="@id/locationLiveMessageBannerSubTitle"
|
app:layout_constraintBottom_toTopOf="@id/locationLiveMessageBannerSubTitle"
|
||||||
app:layout_constraintStart_toEndOf="@id/locationLiveMessageBannerIcon"
|
app:layout_constraintStart_toEndOf="@id/locationLiveMessageBannerIcon"
|
||||||
|
@ -57,11 +58,10 @@
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/locationLiveMessageBannerStop"
|
android:id="@+id/locationLiveMessageBannerStop"
|
||||||
style="@style/Widget.Vector.Button.Text.LocationLive"
|
style="@style/Widget.Vector.Button.Text.LocationLive"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="45dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="30dp"
|
||||||
android:layout_marginHorizontal="8dp"
|
android:text="@string/location_share_live_stop"
|
||||||
android:text="@string/location_share_live_stop_long_version"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/locationLiveMessageBannerBackground"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/locationLiveMessageBannerBackground"
|
||||||
app:layout_constraintTop_toTopOf="@id/locationLiveMessageBannerBackground" />
|
app:layout_constraintTop_toTopOf="@id/locationLiveMessageBannerBackground" />
|
||||||
</merge>
|
</merge>
|
||||||
|
|
|
@ -3018,8 +3018,9 @@
|
||||||
<string name="location_timeline_failed_to_load_map">Failed to load map</string>
|
<string name="location_timeline_failed_to_load_map">Failed to load map</string>
|
||||||
<string name="location_share_live_enabled">Live location enabled</string>
|
<string name="location_share_live_enabled">Live location enabled</string>
|
||||||
<string name="location_share_live_started">Loading live location…</string>
|
<string name="location_share_live_started">Loading live location…</string>
|
||||||
|
<string name="location_share_live_view">View live location</string>
|
||||||
|
<string name="location_share_live_until">Live until %1$s</string>
|
||||||
<string name="location_share_live_stop">Stop</string>
|
<string name="location_share_live_stop">Stop</string>
|
||||||
<string name="location_share_live_stop_long_version">Stop sharing</string>
|
|
||||||
<string name="location_share_live_remaining_time">%1$s left</string>
|
<string name="location_share_live_remaining_time">%1$s left</string>
|
||||||
<string name="live_location_sharing_notification_title">${app_name} Live Location</string>
|
<string name="live_location_sharing_notification_title">${app_name} Live Location</string>
|
||||||
<string name="live_location_sharing_notification_description">Location sharing is in progress</string>
|
<string name="live_location_sharing_notification_description">Location sharing is in progress</string>
|
||||||
|
|
Loading…
Reference in a new issue