mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Add details about events with attachment in the bottomsheet
This commit is contained in:
parent
c502e971a1
commit
edbfc2e2e9
6 changed files with 134 additions and 13 deletions
|
@ -46,6 +46,9 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var body: CharSequence
|
lateinit var body: CharSequence
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var bodyDetails: CharSequence? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var imageContentRenderer: ImageContentRenderer? = null
|
var imageContentRenderer: ImageContentRenderer? = null
|
||||||
|
|
||||||
|
@ -73,6 +76,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
|
||||||
holder.imagePreview.isVisible = data != null
|
holder.imagePreview.isVisible = data != null
|
||||||
holder.body.movementMethod = movementMethod
|
holder.body.movementMethod = movementMethod
|
||||||
holder.body.text = body
|
holder.body.text = body
|
||||||
|
holder.bodyDetails.setTextOrHide(bodyDetails)
|
||||||
body.findPillsAndProcess(coroutineScope) { it.bind(holder.body) }
|
body.findPillsAndProcess(coroutineScope) { it.bind(holder.body) }
|
||||||
holder.timestamp.setTextOrHide(time)
|
holder.timestamp.setTextOrHide(time)
|
||||||
}
|
}
|
||||||
|
@ -86,6 +90,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
|
||||||
val avatar by bind<ImageView>(R.id.bottom_sheet_message_preview_avatar)
|
val avatar by bind<ImageView>(R.id.bottom_sheet_message_preview_avatar)
|
||||||
val sender by bind<TextView>(R.id.bottom_sheet_message_preview_sender)
|
val sender by bind<TextView>(R.id.bottom_sheet_message_preview_sender)
|
||||||
val body by bind<TextView>(R.id.bottom_sheet_message_preview_body)
|
val body by bind<TextView>(R.id.bottom_sheet_message_preview_body)
|
||||||
|
val bodyDetails by bind<TextView>(R.id.bottom_sheet_message_preview_body_details)
|
||||||
val timestamp by bind<TextView>(R.id.bottom_sheet_message_preview_timestamp)
|
val timestamp by bind<TextView>(R.id.bottom_sheet_message_preview_timestamp)
|
||||||
val imagePreview by bind<ImageView>(R.id.bottom_sheet_message_preview_image)
|
val imagePreview by bind<ImageView>(R.id.bottom_sheet_message_preview_image)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.core.utils
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
|
import org.threeten.bp.Duration
|
||||||
import java.util.TreeMap
|
import java.util.TreeMap
|
||||||
|
|
||||||
object TextUtils {
|
object TextUtils {
|
||||||
|
@ -68,4 +69,15 @@ object TextUtils {
|
||||||
Formatter.formatFileSize(context, normalizedSize)
|
Formatter.formatFileSize(context, normalizedSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatDuration(duration: Duration): String {
|
||||||
|
val hours = duration.seconds / 3600
|
||||||
|
val minutes = (duration.seconds % 3600) / 60
|
||||||
|
val seconds = duration.seconds % 60
|
||||||
|
return if (hours > 0) {
|
||||||
|
String.format("%d:%02d:%02d", hours, minutes, seconds)
|
||||||
|
} else {
|
||||||
|
String.format("%02d:%02d", minutes, seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.hardware.camera2.CameraManager
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import im.vector.app.core.services.CallService
|
import im.vector.app.core.services.CallService
|
||||||
import im.vector.app.core.utils.CountUpTimer
|
import im.vector.app.core.utils.CountUpTimer
|
||||||
|
import im.vector.app.core.utils.TextUtils.formatDuration
|
||||||
import im.vector.app.features.call.CameraEventsHandlerAdapter
|
import im.vector.app.features.call.CameraEventsHandlerAdapter
|
||||||
import im.vector.app.features.call.CameraProxy
|
import im.vector.app.features.call.CameraProxy
|
||||||
import im.vector.app.features.call.CameraType
|
import im.vector.app.features.call.CameraType
|
||||||
|
@ -829,17 +830,6 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatDuration(duration: Duration): String {
|
|
||||||
val hours = duration.seconds / 3600
|
|
||||||
val minutes = (duration.seconds % 3600) / 60
|
|
||||||
val seconds = duration.seconds % 60
|
|
||||||
return if (hours > 0) {
|
|
||||||
String.format("%d:%02d:%02d", hours, minutes, seconds)
|
|
||||||
} else {
|
|
||||||
String.format("%02d:%02d", minutes, seconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MxCall.StateListener
|
// MxCall.StateListener
|
||||||
|
|
||||||
override fun onStateUpdate(call: MxCall) {
|
override fun onStateUpdate(call: MxCall) {
|
||||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
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.format.EventDetailsFormatter
|
||||||
import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData
|
import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
||||||
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
||||||
|
@ -53,6 +54,7 @@ class MessageActionsEpoxyController @Inject constructor(
|
||||||
private val imageContentRenderer: ImageContentRenderer,
|
private val imageContentRenderer: ImageContentRenderer,
|
||||||
private val dimensionConverter: DimensionConverter,
|
private val dimensionConverter: DimensionConverter,
|
||||||
private val errorFormatter: ErrorFormatter,
|
private val errorFormatter: ErrorFormatter,
|
||||||
|
private val eventDetailsFormatter: EventDetailsFormatter,
|
||||||
private val dateFormatter: VectorDateFormatter
|
private val dateFormatter: VectorDateFormatter
|
||||||
) : TypedEpoxyController<MessageActionState>() {
|
) : TypedEpoxyController<MessageActionState>() {
|
||||||
|
|
||||||
|
@ -71,6 +73,7 @@ class MessageActionsEpoxyController @Inject constructor(
|
||||||
data(state.timelineEvent()?.buildImageContentRendererData(dimensionConverter.dpToPx(66)))
|
data(state.timelineEvent()?.buildImageContentRendererData(dimensionConverter.dpToPx(66)))
|
||||||
userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
|
userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
|
||||||
body(state.messageBody.linkify(listener))
|
body(state.messageBody.linkify(listener))
|
||||||
|
bodyDetails(eventDetailsFormatter.format(state.timelineEvent()?.root))
|
||||||
time(formattedDate)
|
time(formattedDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.format
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import im.vector.app.core.utils.TextUtils
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isAudioMessage
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isFileMessage
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isImageMessage
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||||
|
import org.threeten.bp.Duration
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class EventDetailsFormatter @Inject constructor(
|
||||||
|
private val context: Context
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun format(event: Event?): CharSequence? {
|
||||||
|
event ?: return null
|
||||||
|
|
||||||
|
if (event.isRedacted()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.isEncrypted() && event.mxDecryptionResult == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
event.isImageMessage() -> formatForImageMessage(event)
|
||||||
|
event.isVideoMessage() -> formatForVideoMessage(event)
|
||||||
|
event.isAudioMessage() -> formatForAudioMessage(event)
|
||||||
|
event.isFileMessage() -> formatForFileMessage(event)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example: "1024 x 720 - 670 kB"
|
||||||
|
*/
|
||||||
|
private fun formatForImageMessage(event: Event): CharSequence? {
|
||||||
|
return event.getClearContent().toModel<MessageImageContent>()?.info
|
||||||
|
?.let { "${it.width} x ${it.height} - ${it.size.asFileSize()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example: "02:45 - 1024 x 720 - 670 kB"
|
||||||
|
*/
|
||||||
|
private fun formatForVideoMessage(event: Event): CharSequence? {
|
||||||
|
return event.getClearContent().toModel<MessageVideoContent>()?.videoInfo
|
||||||
|
?.let { "${it.duration.asDuration()} - ${it.width} x ${it.height} - ${it.size.asFileSize()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example: "02:45 - 670 kB"
|
||||||
|
*/
|
||||||
|
private fun formatForAudioMessage(event: Event): CharSequence? {
|
||||||
|
return event.getClearContent().toModel<MessageAudioContent>()?.audioInfo
|
||||||
|
?.let { "${it.duration.asDuration()} - ${it.size.asFileSize()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example: "670 kB - application/pdf"
|
||||||
|
*/
|
||||||
|
private fun formatForFileMessage(event: Event): CharSequence? {
|
||||||
|
return event.getClearContent().toModel<MessageFileContent>()?.info
|
||||||
|
?.let { "${it.size.asFileSize()} - ${it.mimeType}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Long.asFileSize() = TextUtils.formatFileSize(context, this)
|
||||||
|
private fun Int.asDuration() = TextUtils.formatDuration(Duration.ofMillis(toLong()))
|
||||||
|
}
|
|
@ -76,10 +76,29 @@
|
||||||
android:textColor="?riotx_text_secondary"
|
android:textColor="?riotx_text_secondary"
|
||||||
android:textIsSelectable="false"
|
android:textIsSelectable="false"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@+id/bottom_sheet_message_preview_body_details"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/bottom_sheet_message_preview_avatar"
|
app:layout_constraintStart_toEndOf="@id/bottom_sheet_message_preview_avatar"
|
||||||
app:layout_constraintTop_toBottomOf="@id/bottom_sheet_message_preview_image"
|
app:layout_constraintTop_toBottomOf="@id/bottom_sheet_message_preview_image"
|
||||||
|
app:layout_goneMarginBottom="4dp"
|
||||||
tools:text="Quis harum id autem cumque consequatur laboriosam aliquam sed. Sint accusamus dignissimos nobis ullam earum debitis aspernatur. Sint accusamus dignissimos nobis ullam earum debitis aspernatur." />
|
tools:text="Quis harum id autem cumque consequatur laboriosam aliquam sed. Sint accusamus dignissimos nobis ullam earum debitis aspernatur. Sint accusamus dignissimos nobis ullam earum debitis aspernatur." />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_sheet_message_preview_body_details"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_tertiary"
|
||||||
|
android:textIsSelectable="false"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/bottom_sheet_message_preview_body"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/bottom_sheet_message_preview_body"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/bottom_sheet_message_preview_body"
|
||||||
|
tools:text="1080 x 1024 - 43s - 12kB"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
Loading…
Reference in a new issue