Implement month separator for the Gallery.
Improve day separator rendering in the timeline. Use Today, Yesterday, and the name of the day if less than 7 days and do not render the year for the current year. Improve date format for the media viewer. Rework how date and time are computed. ActionListView: Time can take more space, so update the layout.
This commit is contained in:
parent
5515c938d4
commit
da272ddb07
60 changed files with 1271 additions and 351 deletions
|
|
@ -53,6 +53,7 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint
|
|||
senderName = null,
|
||||
senderAvatar = null,
|
||||
dateSent = null,
|
||||
dateSentFull = null,
|
||||
),
|
||||
mediaSource = MediaSource(url = avatarUrl),
|
||||
thumbnailSource = null,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ fun MediaDetailsBottomSheet(
|
|||
}
|
||||
SectionText(
|
||||
title = stringResource(R.string.screen_media_details_uploaded_on),
|
||||
text = state.mediaInfo.dateSent.orEmpty(),
|
||||
text = state.mediaInfo.dateSentFull.orEmpty(),
|
||||
)
|
||||
SectionText(
|
||||
title = stringResource(R.string.screen_media_details_filename),
|
||||
|
|
|
|||
|
|
@ -10,12 +10,15 @@ package io.element.android.libraries.mediaviewer.impl.details
|
|||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.mediaviewer.api.anImageMediaInfo
|
||||
|
||||
fun aMediaDetailsBottomSheetState(): MediaBottomSheetState.MediaDetailsBottomSheetState {
|
||||
fun aMediaDetailsBottomSheetState(
|
||||
dateSentFull: String = "December 6, 2024 at 12:59",
|
||||
): MediaBottomSheetState.MediaDetailsBottomSheetState {
|
||||
return MediaBottomSheetState.MediaDetailsBottomSheetState(
|
||||
eventId = EventId("\$eventId"),
|
||||
canDelete = true,
|
||||
mediaInfo = anImageMediaInfo(
|
||||
senderName = "Alice",
|
||||
dateSentFull = dateSentFull,
|
||||
),
|
||||
thumbnailSource = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
package io.element.android.libraries.mediaviewer.impl.gallery
|
||||
|
||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
import io.element.android.libraries.dateformatter.api.toHumanReadableDuration
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
||||
|
|
@ -45,13 +46,20 @@ import javax.inject.Inject
|
|||
class EventItemFactory @Inject constructor(
|
||||
private val fileSizeFormatter: FileSizeFormatter,
|
||||
private val fileExtensionExtractor: FileExtensionExtractor,
|
||||
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
||||
private val dateFormatter: DateFormatter,
|
||||
) {
|
||||
fun create(
|
||||
currentTimelineItem: MatrixTimelineItem.Event,
|
||||
): MediaItem.Event? {
|
||||
val event = currentTimelineItem.event
|
||||
val sentTime = lastMessageTimestampFormatter.format(currentTimelineItem.event.timestamp)
|
||||
val dateSent = dateFormatter.format(
|
||||
currentTimelineItem.event.timestamp,
|
||||
mode = DateFormatterMode.Day,
|
||||
)
|
||||
val dateSentFull = dateFormatter.format(
|
||||
timestamp = currentTimelineItem.event.timestamp,
|
||||
mode = DateFormatterMode.Full,
|
||||
)
|
||||
return when (val content = event.content) {
|
||||
CallNotifyContent,
|
||||
is FailedToParseMessageLikeContent,
|
||||
|
|
@ -90,7 +98,8 @@ class EventItemFactory @Inject constructor(
|
|||
senderId = event.sender,
|
||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||
dateSent = sentTime,
|
||||
dateSent = dateSent,
|
||||
dateSentFull = dateSentFull,
|
||||
),
|
||||
mediaSource = type.source,
|
||||
)
|
||||
|
|
@ -106,7 +115,8 @@ class EventItemFactory @Inject constructor(
|
|||
senderId = event.sender,
|
||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||
dateSent = sentTime,
|
||||
dateSent = dateSent,
|
||||
dateSentFull = dateSentFull,
|
||||
),
|
||||
mediaSource = type.source,
|
||||
)
|
||||
|
|
@ -122,7 +132,8 @@ class EventItemFactory @Inject constructor(
|
|||
senderId = event.sender,
|
||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||
dateSent = sentTime,
|
||||
dateSent = dateSent,
|
||||
dateSentFull = dateSentFull,
|
||||
),
|
||||
mediaSource = type.source,
|
||||
thumbnailSource = null,
|
||||
|
|
@ -139,7 +150,8 @@ class EventItemFactory @Inject constructor(
|
|||
senderId = event.sender,
|
||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||
dateSent = sentTime,
|
||||
dateSent = dateSent,
|
||||
dateSentFull = dateSentFull,
|
||||
),
|
||||
mediaSource = type.source,
|
||||
thumbnailSource = null,
|
||||
|
|
@ -156,7 +168,8 @@ class EventItemFactory @Inject constructor(
|
|||
senderId = event.sender,
|
||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||
dateSent = sentTime,
|
||||
dateSent = dateSent,
|
||||
dateSentFull = dateSentFull,
|
||||
),
|
||||
mediaSource = type.source,
|
||||
thumbnailSource = type.info?.thumbnailSource,
|
||||
|
|
@ -174,7 +187,8 @@ class EventItemFactory @Inject constructor(
|
|||
senderId = event.sender,
|
||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||
dateSent = sentTime,
|
||||
dateSent = dateSent,
|
||||
dateSentFull = dateSentFull,
|
||||
),
|
||||
mediaSource = type.source,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,19 +7,24 @@
|
|||
|
||||
package io.element.android.libraries.mediaviewer.impl.gallery
|
||||
|
||||
import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class VirtualItemFactory @Inject constructor(
|
||||
private val daySeparatorFormatter: DaySeparatorFormatter,
|
||||
private val dateFormatter: DateFormatter,
|
||||
) {
|
||||
fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? {
|
||||
return when (val virtual = timelineItem.virtual) {
|
||||
is VirtualTimelineItem.DayDivider -> MediaItem.DateSeparator(
|
||||
id = timelineItem.uniqueId,
|
||||
formattedDate = daySeparatorFormatter.format(virtual.timestamp)
|
||||
formattedDate = dateFormatter.format(
|
||||
timestamp = virtual.timestamp,
|
||||
mode = DateFormatterMode.Month,
|
||||
useRelative = true,
|
||||
)
|
||||
)
|
||||
VirtualTimelineItem.LastForwardIndicator -> null
|
||||
is VirtualTimelineItem.LoadingIndicator -> MediaItem.LoadingIndicator(
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class AndroidLocalMediaFactory @Inject constructor(
|
|||
senderName = mediaInfo.senderName,
|
||||
senderAvatar = mediaInfo.senderAvatar,
|
||||
dateSent = mediaInfo.dateSent,
|
||||
dateSentFull = mediaInfo.dateSentFull,
|
||||
)
|
||||
|
||||
override fun createFromUri(
|
||||
|
|
@ -63,6 +64,7 @@ class AndroidLocalMediaFactory @Inject constructor(
|
|||
senderName = null,
|
||||
senderAvatar = null,
|
||||
dateSent = null,
|
||||
dateSentFull = null,
|
||||
)
|
||||
|
||||
private fun createFromUri(
|
||||
|
|
@ -75,6 +77,7 @@ class AndroidLocalMediaFactory @Inject constructor(
|
|||
senderName: String?,
|
||||
senderAvatar: String?,
|
||||
dateSent: String?,
|
||||
dateSentFull: String?,
|
||||
): LocalMedia {
|
||||
val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream
|
||||
val fileName = name ?: context.getFileName(uri) ?: ""
|
||||
|
|
@ -92,6 +95,7 @@ class AndroidLocalMediaFactory @Inject constructor(
|
|||
senderName = senderName,
|
||||
senderAvatar = senderAvatar,
|
||||
dateSent = dateSent,
|
||||
dateSentFull = dateSentFull,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||
import io.element.android.libraries.matrix.api.media.AudioDetails
|
||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
|
|
@ -162,7 +161,8 @@ class DefaultEventItemFactoryTest {
|
|||
senderId = A_USER_ID,
|
||||
senderName = "alice",
|
||||
senderAvatar = null,
|
||||
dateSent = A_FORMATTED_DATE,
|
||||
dateSent = "0 Day false",
|
||||
dateSentFull = "0 Full false",
|
||||
),
|
||||
mediaSource = MediaSource(""),
|
||||
)
|
||||
|
|
@ -209,7 +209,8 @@ class DefaultEventItemFactoryTest {
|
|||
senderId = A_USER_ID,
|
||||
senderName = "alice",
|
||||
senderAvatar = null,
|
||||
dateSent = A_FORMATTED_DATE,
|
||||
dateSent = "0 Day false",
|
||||
dateSentFull = "0 Full false",
|
||||
),
|
||||
mediaSource = MediaSource(""),
|
||||
thumbnailSource = null,
|
||||
|
|
@ -253,7 +254,8 @@ class DefaultEventItemFactoryTest {
|
|||
senderId = A_USER_ID,
|
||||
senderName = "alice",
|
||||
senderAvatar = null,
|
||||
dateSent = A_FORMATTED_DATE,
|
||||
dateSent = "0 Day false",
|
||||
dateSentFull = "0 Full false",
|
||||
),
|
||||
mediaSource = MediaSource(""),
|
||||
)
|
||||
|
|
@ -301,7 +303,8 @@ class DefaultEventItemFactoryTest {
|
|||
senderId = A_USER_ID,
|
||||
senderName = "alice",
|
||||
senderAvatar = null,
|
||||
dateSent = A_FORMATTED_DATE,
|
||||
dateSent = "0 Day false",
|
||||
dateSentFull = "0 Full false",
|
||||
),
|
||||
mediaSource = MediaSource(""),
|
||||
thumbnailSource = null,
|
||||
|
|
@ -350,7 +353,8 @@ class DefaultEventItemFactoryTest {
|
|||
senderId = A_USER_ID,
|
||||
senderName = "alice",
|
||||
senderAvatar = null,
|
||||
dateSent = A_FORMATTED_DATE,
|
||||
dateSent = "0 Day false",
|
||||
dateSentFull = "0 Full false",
|
||||
),
|
||||
mediaSource = MediaSource(""),
|
||||
)
|
||||
|
|
@ -397,7 +401,8 @@ class DefaultEventItemFactoryTest {
|
|||
senderId = A_USER_ID,
|
||||
senderName = "alice",
|
||||
senderAvatar = null,
|
||||
dateSent = A_FORMATTED_DATE,
|
||||
dateSent = "0 Day false",
|
||||
dateSentFull = "0 Full false",
|
||||
),
|
||||
mediaSource = MediaSource(""),
|
||||
thumbnailSource = null,
|
||||
|
|
@ -409,5 +414,5 @@ class DefaultEventItemFactoryTest {
|
|||
private fun createEventItemFactory() = EventItemFactory(
|
||||
fileSizeFormatter = FakeFileSizeFormatter(),
|
||||
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
|
||||
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE),
|
||||
dateFormatter = FakeDateFormatter(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery
|
|||
import android.net.Uri
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
||||
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
||||
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
|
||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
|
|
@ -254,12 +252,12 @@ class MediaGalleryPresenterTest {
|
|||
timelineMediaItemsFactory = TimelineMediaItemsFactory(
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
virtualItemFactory = VirtualItemFactory(
|
||||
daySeparatorFormatter = FakeDaySeparatorFormatter(),
|
||||
dateFormatter = FakeDateFormatter(),
|
||||
),
|
||||
eventItemFactory = EventItemFactory(
|
||||
fileSizeFormatter = FakeFileSizeFormatter(),
|
||||
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
|
||||
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE),
|
||||
dateFormatter = FakeDateFormatter(),
|
||||
),
|
||||
),
|
||||
localMediaFactory = localMediaFactory,
|
||||
|
|
|
|||
|
|
@ -27,11 +27,15 @@ class AndroidLocalMediaFactoryTest {
|
|||
@Test
|
||||
fun `test AndroidLocalMediaFactory`() {
|
||||
val sut = createAndroidLocalMediaFactory()
|
||||
val result = sut.createFromMediaFile(aMediaFile(), anImageMediaInfo(
|
||||
senderId = A_USER_ID,
|
||||
senderName = A_USER_NAME,
|
||||
dateSent = "12:34",
|
||||
))
|
||||
val result = sut.createFromMediaFile(
|
||||
mediaFile = aMediaFile(),
|
||||
mediaInfo = anImageMediaInfo(
|
||||
senderId = A_USER_ID,
|
||||
senderName = A_USER_NAME,
|
||||
dateSent = "12:34",
|
||||
dateSentFull = "full",
|
||||
)
|
||||
)
|
||||
assertThat(result.uri.toString()).endsWith("aPath")
|
||||
assertThat(result.info).isEqualTo(
|
||||
MediaInfo(
|
||||
|
|
@ -43,7 +47,8 @@ class AndroidLocalMediaFactoryTest {
|
|||
senderId = A_USER_ID,
|
||||
senderName = A_USER_NAME,
|
||||
senderAvatar = null,
|
||||
dateSent = "12:34"
|
||||
dateSent = "12:34",
|
||||
dateSentFull = "full"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue