Add details about events with attachment in the bottomsheet

This commit is contained in:
Benoit Marty 2021-05-04 13:50:49 +02:00
parent c502e971a1
commit edbfc2e2e9
6 changed files with 134 additions and 13 deletions

View file

@ -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)
} }

View file

@ -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)
}
}
} }

View file

@ -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) {

View file

@ -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)
} }

View file

@ -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()))
}

View file

@ -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"
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. " /> 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." />
<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>