Merge pull request #4026 from element-hq/feature/bma/monthSeparators
Implement month separator for the Gallery, and improve date rendering.
This commit is contained in:
commit
cf4bc53ef3
100 changed files with 1704 additions and 403 deletions
|
|
@ -30,5 +30,6 @@ appId: ${MAESTRO_APP_ID}
|
||||||
# assert there's 1 member and 2 invitees
|
# assert there's 1 member and 2 invitees
|
||||||
- tapOn: "Back"
|
- tapOn: "Back"
|
||||||
- scroll
|
- scroll
|
||||||
|
- scroll
|
||||||
- tapOn: "Leave room"
|
- tapOn: "Leave room"
|
||||||
- tapOn: "Leave"
|
- tapOn: "Leave"
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.architecture.overlay.Overlay
|
import io.element.android.libraries.architecture.overlay.Overlay
|
||||||
import io.element.android.libraries.architecture.overlay.operation.hide
|
import io.element.android.libraries.architecture.overlay.operation.hide
|
||||||
import io.element.android.libraries.architecture.overlay.operation.show
|
import io.element.android.libraries.architecture.overlay.operation.show
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
|
@ -97,6 +99,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider,
|
private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider,
|
||||||
private val timelineController: TimelineController,
|
private val timelineController: TimelineController,
|
||||||
private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint,
|
private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint,
|
||||||
|
private val dateFormatter: DateFormatter,
|
||||||
) : BaseFlowNode<MessagesFlowNode.NavTarget>(
|
) : BaseFlowNode<MessagesFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = plugins.filterIsInstance<MessagesEntryPoint.Params>().first().initialTarget.toNavTarget(),
|
initialElement = plugins.filterIsInstance<MessagesEntryPoint.Params>().first().initialTarget.toNavTarget(),
|
||||||
|
|
@ -436,7 +439,14 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
senderId = event.senderId,
|
senderId = event.senderId,
|
||||||
senderName = event.safeSenderName,
|
senderName = event.safeSenderName,
|
||||||
senderAvatar = event.senderAvatar.url,
|
senderAvatar = event.senderAvatar.url,
|
||||||
dateSent = event.sentTime,
|
dateSent = dateFormatter.format(
|
||||||
|
event.sentTimeMillis,
|
||||||
|
mode = DateFormatterMode.Day,
|
||||||
|
),
|
||||||
|
dateSentFull = dateFormatter.format(
|
||||||
|
timestamp = event.sentTimeMillis,
|
||||||
|
mode = DateFormatterMode.Full,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
mediaSource = mediaSource,
|
mediaSource = mediaSource,
|
||||||
thumbnailSource = thumbnailSource,
|
thumbnailSource = thumbnailSource,
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ import io.element.android.features.messages.impl.timeline.model.event.canBeCopie
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.canBeForwarded
|
import io.element.android.features.messages.impl.timeline.model.event.canBeForwarded
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.canReact
|
import io.element.android.features.messages.impl.timeline.model.event.canReact
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
|
@ -64,6 +66,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val userSendFailureFactory: VerifiedUserSendFailureFactory,
|
private val userSendFailureFactory: VerifiedUserSendFailureFactory,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
|
private val dateFormatter: DateFormatter,
|
||||||
) : ActionListPresenter {
|
) : ActionListPresenter {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
@ContributesBinding(RoomScope::class)
|
@ContributesBinding(RoomScope::class)
|
||||||
|
|
@ -131,6 +134,11 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||||
if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != VerifiedUserSendFailure.None) {
|
if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != VerifiedUserSendFailure.None) {
|
||||||
target.value = ActionListState.Target.Success(
|
target.value = ActionListState.Target.Success(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
|
sentTimeFull = dateFormatter.format(
|
||||||
|
timelineItem.sentTimeMillis,
|
||||||
|
DateFormatterMode.Full,
|
||||||
|
useRelative = true,
|
||||||
|
),
|
||||||
displayEmojiReactions = displayEmojiReactions,
|
displayEmojiReactions = displayEmojiReactions,
|
||||||
verifiedUserSendFailure = verifiedUserSendFailure,
|
verifiedUserSendFailure = verifiedUserSendFailure,
|
||||||
actions = actions.toImmutableList()
|
actions = actions.toImmutableList()
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ data class ActionListState(
|
||||||
data class Loading(val event: TimelineItem.Event) : Target
|
data class Loading(val event: TimelineItem.Event) : Target
|
||||||
data class Success(
|
data class Success(
|
||||||
val event: TimelineItem.Event,
|
val event: TimelineItem.Event,
|
||||||
|
val sentTimeFull: String,
|
||||||
val displayEmojiReactions: Boolean,
|
val displayEmojiReactions: Boolean,
|
||||||
val verifiedUserSendFailure: VerifiedUserSendFailure,
|
val verifiedUserSendFailure: VerifiedUserSendFailure,
|
||||||
val actions: ImmutableList<TimelineItemAction>,
|
val actions: ImmutableList<TimelineItemAction>,
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
event = aTimelineItemEvent(
|
event = aTimelineItemEvent(
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(),
|
||||||
|
|
@ -49,6 +50,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
displayNameAmbiguous = true,
|
displayNameAmbiguous = true,
|
||||||
timelineItemReactions = reactionsState,
|
timelineItemReactions = reactionsState,
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(
|
actions = aTimelineItemActionList(
|
||||||
|
|
@ -62,6 +64,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
content = aTimelineItemVideoContent(),
|
content = aTimelineItemVideoContent(),
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(
|
actions = aTimelineItemActionList(
|
||||||
|
|
@ -75,6 +78,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
content = aTimelineItemFileContent(),
|
content = aTimelineItemFileContent(),
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(
|
actions = aTimelineItemActionList(
|
||||||
|
|
@ -88,6 +92,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
content = aTimelineItemAudioContent(),
|
content = aTimelineItemAudioContent(),
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(
|
actions = aTimelineItemActionList(
|
||||||
|
|
@ -101,6 +106,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
content = aTimelineItemVoiceContent(caption = null),
|
content = aTimelineItemVoiceContent(caption = null),
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(
|
actions = aTimelineItemActionList(
|
||||||
|
|
@ -114,6 +120,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
content = aTimelineItemLocationContent(),
|
content = aTimelineItemLocationContent(),
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(),
|
||||||
|
|
@ -125,6 +132,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
content = aTimelineItemLocationContent(),
|
content = aTimelineItemLocationContent(),
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(),
|
||||||
|
|
@ -136,6 +144,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
content = aTimelineItemPollContent(),
|
content = aTimelineItemPollContent(),
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemPollActionList(),
|
actions = aTimelineItemPollActionList(),
|
||||||
|
|
@ -147,6 +156,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
timelineItemReactions = reactionsState,
|
timelineItemReactions = reactionsState,
|
||||||
messageShield = MessageShield.UnknownDevice(isCritical = true)
|
messageShield = MessageShield.UnknownDevice(isCritical = true)
|
||||||
),
|
),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(),
|
||||||
|
|
@ -155,6 +165,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
anActionListState(
|
anActionListState(
|
||||||
target = ActionListState.Target.Success(
|
target = ActionListState.Target.Success(
|
||||||
event = aTimelineItemEvent(),
|
event = aTimelineItemEvent(),
|
||||||
|
sentTimeFull = "January 1, 1970 at 12:00 AM",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = anUnsignedDeviceSendFailure(),
|
verifiedUserSendFailure = anUnsignedDeviceSendFailure(),
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(),
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,7 @@ private fun ActionListViewContent(
|
||||||
Column {
|
Column {
|
||||||
MessageSummary(
|
MessageSummary(
|
||||||
event = target.event,
|
event = target.event,
|
||||||
|
sentTimeFull = target.sentTimeFull,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
|
|
@ -245,7 +246,11 @@ private fun ActionListViewContent(
|
||||||
|
|
||||||
@Suppress("MultipleEmitters") // False positive
|
@Suppress("MultipleEmitters") // False positive
|
||||||
@Composable
|
@Composable
|
||||||
private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modifier) {
|
private fun MessageSummary(
|
||||||
|
event: TimelineItem.Event,
|
||||||
|
sentTimeFull: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
val content: @Composable () -> Unit
|
val content: @Composable () -> Unit
|
||||||
val icon: @Composable () -> Unit = { Avatar(avatarData = event.senderAvatar.copy(size = AvatarSize.MessageActionSender)) }
|
val icon: @Composable () -> Unit = { Avatar(avatarData = event.senderAvatar.copy(size = AvatarSize.MessageActionSender)) }
|
||||||
val contentStyle = ElementTheme.typography.fontBodyMdRegular.copy(color = MaterialTheme.colorScheme.secondary)
|
val contentStyle = ElementTheme.typography.fontBodyMdRegular.copy(color = MaterialTheme.colorScheme.secondary)
|
||||||
|
|
@ -300,20 +305,23 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
|
||||||
icon()
|
icon()
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
SenderName(
|
Row {
|
||||||
senderId = event.senderId,
|
SenderName(
|
||||||
senderProfile = event.senderProfile,
|
modifier = Modifier.weight(1f),
|
||||||
senderNameMode = SenderNameMode.ActionList,
|
senderId = event.senderId,
|
||||||
)
|
senderProfile = event.senderProfile,
|
||||||
|
senderNameMode = SenderNameMode.ActionList,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Text(
|
||||||
|
text = sentTimeFull,
|
||||||
|
style = ElementTheme.typography.fontBodyXsRegular,
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
textAlign = TextAlign.End,
|
||||||
|
)
|
||||||
|
}
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
Text(
|
|
||||||
event.sentTime,
|
|
||||||
style = ElementTheme.typography.fontBodyXsRegular,
|
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
|
||||||
textAlign = TextAlign.End,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemGrou
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
|
||||||
import io.element.android.libraries.core.bool.orTrue
|
import io.element.android.libraries.core.bool.orTrue
|
||||||
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.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
@ -32,14 +33,13 @@ import io.element.android.libraries.matrix.api.timeline.item.event.getDisambigua
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import java.text.DateFormat
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class TimelineItemEventFactory @AssistedInject constructor(
|
class TimelineItemEventFactory @AssistedInject constructor(
|
||||||
@Assisted private val config: TimelineItemsFactoryConfig,
|
@Assisted private val config: TimelineItemsFactoryConfig,
|
||||||
private val contentFactory: TimelineItemContentFactory,
|
private val contentFactory: TimelineItemContentFactory,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
private val dateFormatter: DateFormatter,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
) {
|
) {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
@ -57,9 +57,10 @@ class TimelineItemEventFactory @AssistedInject constructor(
|
||||||
val groupPosition =
|
val groupPosition =
|
||||||
computeGroupPosition(currentTimelineItem, timelineItems, index)
|
computeGroupPosition(currentTimelineItem, timelineItems, index)
|
||||||
val senderProfile = currentTimelineItem.event.senderProfile
|
val senderProfile = currentTimelineItem.event.senderProfile
|
||||||
val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT)
|
val sentTime = dateFormatter.format(
|
||||||
val sentTime = timeFormatter.format(Date(currentTimelineItem.event.timestamp))
|
timestamp = currentTimelineItem.event.timestamp,
|
||||||
|
mode = DateFormatterMode.TimeOnly,
|
||||||
|
)
|
||||||
val senderAvatarData = AvatarData(
|
val senderAvatarData = AvatarData(
|
||||||
id = currentSender.value,
|
id = currentSender.value,
|
||||||
name = senderProfile.getDisambiguatedDisplayName(currentSender),
|
name = senderProfile.getDisambiguatedDisplayName(currentSender),
|
||||||
|
|
@ -78,6 +79,7 @@ class TimelineItemEventFactory @AssistedInject constructor(
|
||||||
isMine = currentTimelineItem.event.isOwn,
|
isMine = currentTimelineItem.event.isOwn,
|
||||||
isEditable = currentTimelineItem.event.isEditable,
|
isEditable = currentTimelineItem.event.isEditable,
|
||||||
canBeRepliedTo = currentTimelineItem.event.canBeRepliedTo,
|
canBeRepliedTo = currentTimelineItem.event.canBeRepliedTo,
|
||||||
|
sentTimeMillis = currentTimelineItem.event.timestamp,
|
||||||
sentTime = sentTime,
|
sentTime = sentTime,
|
||||||
groupPosition = groupPosition,
|
groupPosition = groupPosition,
|
||||||
reactionsState = currentTimelineItem.computeReactionsState(),
|
reactionsState = currentTimelineItem.computeReactionsState(),
|
||||||
|
|
@ -106,7 +108,6 @@ class TimelineItemEventFactory @AssistedInject constructor(
|
||||||
if (!config.computeReactions) {
|
if (!config.computeReactions) {
|
||||||
return TimelineItemReactions(reactions = persistentListOf())
|
return TimelineItemReactions(reactions = persistentListOf())
|
||||||
}
|
}
|
||||||
val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT)
|
|
||||||
var aggregatedReactions = this.event.reactions.map { reaction ->
|
var aggregatedReactions = this.event.reactions.map { reaction ->
|
||||||
// Sort reactions within an aggregation by timestamp descending.
|
// Sort reactions within an aggregation by timestamp descending.
|
||||||
// This puts the most recent at the top, useful in cases like the
|
// This puts the most recent at the top, useful in cases like the
|
||||||
|
|
@ -121,7 +122,10 @@ class TimelineItemEventFactory @AssistedInject constructor(
|
||||||
AggregatedReactionSender(
|
AggregatedReactionSender(
|
||||||
senderId = it.senderId,
|
senderId = it.senderId,
|
||||||
timestamp = date,
|
timestamp = date,
|
||||||
sentTime = timeFormatter.format(date),
|
sentTime = dateFormatter.format(
|
||||||
|
it.timestamp,
|
||||||
|
DateFormatterMode.TimeOrDate,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.toImmutableList()
|
.toImmutableList()
|
||||||
|
|
@ -157,7 +161,10 @@ class TimelineItemEventFactory @AssistedInject constructor(
|
||||||
url = roomMember?.avatarUrl,
|
url = roomMember?.avatarUrl,
|
||||||
size = AvatarSize.TimelineReadReceipt,
|
size = AvatarSize.TimelineReadReceipt,
|
||||||
),
|
),
|
||||||
formattedDate = lastMessageTimestampFormatter.format(receipt.timestamp)
|
formattedDate = dateFormatter.format(
|
||||||
|
receipt.timestamp,
|
||||||
|
mode = DateFormatterMode.TimeOrDate,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.toImmutableList()
|
.toImmutableList()
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,20 @@ package io.element.android.features.messages.impl.timeline.factories.virtual
|
||||||
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
||||||
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.item.virtual.VirtualTimelineItem
|
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineItemDaySeparatorFactory @Inject constructor(private val daySeparatorFormatter: DaySeparatorFormatter) {
|
class TimelineItemDaySeparatorFactory @Inject constructor(
|
||||||
|
private val dateFormatter: DateFormatter,
|
||||||
|
) {
|
||||||
fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel {
|
fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel {
|
||||||
val formattedDate = daySeparatorFormatter.format(virtualItem.timestamp)
|
val formattedDate = dateFormatter.format(
|
||||||
|
timestamp = virtualItem.timestamp,
|
||||||
|
mode = DateFormatterMode.Day,
|
||||||
|
useRelative = true,
|
||||||
|
)
|
||||||
return TimelineItemDaySeparatorModel(
|
return TimelineItemDaySeparatorModel(
|
||||||
formattedDate = formattedDate
|
formattedDate = formattedDate
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ sealed interface TimelineItem {
|
||||||
val senderProfile: ProfileTimelineDetails,
|
val senderProfile: ProfileTimelineDetails,
|
||||||
val senderAvatar: AvatarData,
|
val senderAvatar: AvatarData,
|
||||||
val content: TimelineItemEventContent,
|
val content: TimelineItemEventContent,
|
||||||
|
val sentTimeMillis: Long = 0L,
|
||||||
val sentTime: String = "",
|
val sentTime: String = "",
|
||||||
val isMine: Boolean = false,
|
val isMine: Boolean = false,
|
||||||
val isEditable: Boolean,
|
val isEditable: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,7 @@ class MessagesViewTest {
|
||||||
actionListState = anActionListState(
|
actionListState = anActionListState(
|
||||||
target = ActionListState.Target.Success(
|
target = ActionListState.Target.Success(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
|
sentTimeFull = "",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
actions = persistentListOf(TimelineItemAction.Edit),
|
actions = persistentListOf(TimelineItemAction.Edit),
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
|
|
@ -399,6 +400,7 @@ class MessagesViewTest {
|
||||||
actionListState = anActionListState(
|
actionListState = anActionListState(
|
||||||
target = ActionListState.Target.Success(
|
target = ActionListState.Target.Success(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
|
sentTimeFull = "",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(TimelineItemAction.Edit),
|
actions = persistentListOf(TimelineItemAction.Edit),
|
||||||
|
|
@ -427,6 +429,7 @@ class MessagesViewTest {
|
||||||
actionListState = anActionListState(
|
actionListState = anActionListState(
|
||||||
target = ActionListState.Target.Success(
|
target = ActionListState.Target.Success(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
|
sentTimeFull = "",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = aChangedIdentitySendFailure(),
|
verifiedUserSendFailure = aChangedIdentitySendFailure(),
|
||||||
actions = persistentListOf(),
|
actions = persistentListOf(),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
||||||
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
||||||
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
|
@ -86,6 +87,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -128,6 +130,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -170,6 +173,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -215,6 +219,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -263,6 +268,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -308,6 +314,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -355,6 +362,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -403,6 +411,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -448,6 +457,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -496,6 +506,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -542,6 +553,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -592,6 +604,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -641,6 +654,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -691,6 +705,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -738,6 +753,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = stateEvent,
|
event = stateEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -808,6 +824,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -855,6 +872,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -909,6 +927,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -1006,6 +1025,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -1046,6 +1066,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -1089,6 +1110,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -1131,6 +1153,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -1174,6 +1197,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -1214,6 +1238,7 @@ class ActionListPresenterTest {
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
|
sentTimeFull = "0 Full true",
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
|
|
@ -1268,6 +1293,7 @@ private fun createActionListPresenter(
|
||||||
initialState = mapOf(
|
initialState = mapOf(
|
||||||
FeatureFlags.MediaCaptionCreation.key to allowCaption,
|
FeatureFlags.MediaCaptionCreation.key to allowCaption,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
dateFormatter = FakeDateFormatter(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,7 @@ import io.element.android.features.messages.impl.utils.FakeTextPillificationHelp
|
||||||
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
|
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
|
||||||
import io.element.android.features.poll.test.pollcontent.FakePollContentStateFactory
|
import io.element.android.features.poll.test.pollcontent.FakePollContentStateFactory
|
||||||
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
|
||||||
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||||
|
|
@ -80,7 +79,7 @@ internal fun TestScope.aTimelineItemsFactory(
|
||||||
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(),
|
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(),
|
||||||
),
|
),
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(),
|
dateFormatter = FakeDateFormatter(),
|
||||||
permalinkParser = FakePermalinkParser(),
|
permalinkParser = FakePermalinkParser(),
|
||||||
config = config
|
config = config
|
||||||
)
|
)
|
||||||
|
|
@ -88,7 +87,7 @@ internal fun TestScope.aTimelineItemsFactory(
|
||||||
},
|
},
|
||||||
virtualItemFactory = TimelineItemVirtualFactory(
|
virtualItemFactory = TimelineItemVirtualFactory(
|
||||||
daySeparatorFactory = TimelineItemDaySeparatorFactory(
|
daySeparatorFactory = TimelineItemDaySeparatorFactory(
|
||||||
FakeDaySeparatorFormatter()
|
FakeDateFormatter()
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
timelineItemGrouper = TimelineItemGrouper(),
|
timelineItemGrouper = TimelineItemGrouper(),
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ package io.element.android.features.poll.impl.history.model
|
||||||
|
|
||||||
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory
|
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
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.MatrixTimelineItem
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
@ -18,7 +19,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class PollHistoryItemsFactory @Inject constructor(
|
class PollHistoryItemsFactory @Inject constructor(
|
||||||
private val pollContentStateFactory: PollContentStateFactory,
|
private val pollContentStateFactory: PollContentStateFactory,
|
||||||
private val daySeparatorFormatter: DaySeparatorFormatter,
|
private val dateFormatter: DateFormatter,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
) {
|
) {
|
||||||
suspend fun create(timelineItems: List<MatrixTimelineItem>): PollHistoryItems = withContext(dispatchers.computation) {
|
suspend fun create(timelineItems: List<MatrixTimelineItem>): PollHistoryItems = withContext(dispatchers.computation) {
|
||||||
|
|
@ -45,7 +46,11 @@ class PollHistoryItemsFactory @Inject constructor(
|
||||||
val pollContent = timelineItem.event.content as? PollContent ?: return null
|
val pollContent = timelineItem.event.content as? PollContent ?: return null
|
||||||
val pollContentState = pollContentStateFactory.create(timelineItem.event, pollContent)
|
val pollContentState = pollContentStateFactory.create(timelineItem.event, pollContent)
|
||||||
PollHistoryItem(
|
PollHistoryItem(
|
||||||
formattedDate = daySeparatorFormatter.format(timelineItem.event.timestamp),
|
formattedDate = dateFormatter.format(
|
||||||
|
timestamp = timelineItem.event.timestamp,
|
||||||
|
mode = DateFormatterMode.Day,
|
||||||
|
useRelative = true
|
||||||
|
),
|
||||||
state = pollContentState
|
state = pollContentState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import io.element.android.features.poll.impl.history.model.PollHistoryItemsFacto
|
||||||
import io.element.android.features.poll.impl.model.DefaultPollContentStateFactory
|
import io.element.android.features.poll.impl.model.DefaultPollContentStateFactory
|
||||||
import io.element.android.features.poll.test.actions.FakeEndPollAction
|
import io.element.android.features.poll.test.actions.FakeEndPollAction
|
||||||
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
|
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
|
|
@ -161,7 +161,7 @@ class PollHistoryPresenterTest {
|
||||||
sendPollResponseAction: SendPollResponseAction = FakeSendPollResponseAction(),
|
sendPollResponseAction: SendPollResponseAction = FakeSendPollResponseAction(),
|
||||||
pollHistoryItemFactory: PollHistoryItemsFactory = PollHistoryItemsFactory(
|
pollHistoryItemFactory: PollHistoryItemsFactory = PollHistoryItemsFactory(
|
||||||
pollContentStateFactory = DefaultPollContentStateFactory(FakeMatrixClient()),
|
pollContentStateFactory = DefaultPollContentStateFactory(FakeMatrixClient()),
|
||||||
daySeparatorFormatter = FakeDaySeparatorFormatter(),
|
dateFormatter = FakeDateFormatter(),
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
),
|
),
|
||||||
): PollHistoryPresenter {
|
): PollHistoryPresenter {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ package io.element.android.features.roomlist.impl.datasource
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType
|
import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType
|
||||||
import io.element.android.libraries.core.extensions.orEmpty
|
import io.element.android.libraries.core.extensions.orEmpty
|
||||||
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.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
|
|
@ -22,7 +23,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomListRoomSummaryFactory @Inject constructor(
|
class RoomListRoomSummaryFactory @Inject constructor(
|
||||||
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
private val dateFormatter: DateFormatter,
|
||||||
private val roomLastMessageFormatter: RoomLastMessageFormatter,
|
private val roomLastMessageFormatter: RoomLastMessageFormatter,
|
||||||
) {
|
) {
|
||||||
fun create(roomSummary: RoomSummary): RoomListRoomSummary {
|
fun create(roomSummary: RoomSummary): RoomListRoomSummary {
|
||||||
|
|
@ -36,7 +37,11 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||||
numberOfUnreadMentions = roomInfo.numUnreadMentions,
|
numberOfUnreadMentions = roomInfo.numUnreadMentions,
|
||||||
numberOfUnreadNotifications = roomInfo.numUnreadNotifications,
|
numberOfUnreadNotifications = roomInfo.numUnreadNotifications,
|
||||||
isMarkedUnread = roomInfo.isMarkedUnread,
|
isMarkedUnread = roomInfo.isMarkedUnread,
|
||||||
timestamp = lastMessageTimestampFormatter.format(roomSummary.lastMessageTimestamp),
|
timestamp = dateFormatter.format(
|
||||||
|
timestamp = roomSummary.lastMessageTimestamp,
|
||||||
|
mode = DateFormatterMode.TimeOrDate,
|
||||||
|
useRelative = true,
|
||||||
|
),
|
||||||
lastMessage = roomSummary.lastMessage?.let { message ->
|
lastMessage = roomSummary.lastMessage?.let { message ->
|
||||||
roomLastMessageFormatter.format(message.event, roomInfo.isDm)
|
roomLastMessageFormatter.format(message.event, roomInfo.isDm)
|
||||||
}.orEmpty(),
|
}.orEmpty(),
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,8 @@ import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||||
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
||||||
import io.element.android.libraries.androidutils.system.DateTimeObserver
|
import io.element.android.libraries.androidutils.system.DateTimeObserver
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||||
|
|
@ -188,6 +187,7 @@ class RoomListPresenterTest {
|
||||||
createRoomListRoomSummary(
|
createRoomListRoomSummary(
|
||||||
numberOfUnreadMentions = 1,
|
numberOfUnreadMentions = 1,
|
||||||
numberOfUnreadMessages = 2,
|
numberOfUnreadMessages = 2,
|
||||||
|
timestamp = "0 TimeOrDate true",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
|
@ -633,9 +633,7 @@ class RoomListPresenterTest {
|
||||||
networkMonitor: NetworkMonitor = FakeNetworkMonitor(),
|
networkMonitor: NetworkMonitor = FakeNetworkMonitor(),
|
||||||
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
||||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||||
lastMessageTimestampFormatter: LastMessageTimestampFormatter = FakeLastMessageTimestampFormatter().apply {
|
dateFormatter: DateFormatter = FakeDateFormatter(),
|
||||||
givenFormat(A_FORMATTED_DATE)
|
|
||||||
},
|
|
||||||
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||||
|
|
@ -652,7 +650,7 @@ class RoomListPresenterTest {
|
||||||
roomListDataSource = RoomListDataSource(
|
roomListDataSource = RoomListDataSource(
|
||||||
roomListService = client.roomListService,
|
roomListService = client.roomListService,
|
||||||
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
||||||
lastMessageTimestampFormatter = lastMessageTimestampFormatter,
|
dateFormatter = dateFormatter,
|
||||||
roomLastMessageFormatter = roomLastMessageFormatter,
|
roomLastMessageFormatter = roomLastMessageFormatter,
|
||||||
),
|
),
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.roomlist.impl.FakeDateTimeObserver
|
import io.element.android.features.roomlist.impl.FakeDateTimeObserver
|
||||||
import io.element.android.libraries.androidutils.system.DateTimeObserver
|
import io.element.android.libraries.androidutils.system.DateTimeObserver
|
||||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
|
|
@ -30,12 +30,12 @@ class RoomListDataSourceTest {
|
||||||
postAllRooms(listOf(aRoomSummary()))
|
postAllRooms(listOf(aRoomSummary()))
|
||||||
}
|
}
|
||||||
val dateTimeObserver = FakeDateTimeObserver()
|
val dateTimeObserver = FakeDateTimeObserver()
|
||||||
val lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter()
|
var dateFormatterResult = "Today"
|
||||||
lastMessageTimestampFormatter.givenFormat("Today")
|
val dateFormatter = FakeDateFormatter({ _, _, _ -> dateFormatterResult })
|
||||||
val roomListDataSource = createRoomListDataSource(
|
val roomListDataSource = createRoomListDataSource(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
||||||
lastMessageTimestampFormatter = lastMessageTimestampFormatter,
|
dateFormatter = dateFormatter,
|
||||||
),
|
),
|
||||||
dateTimeObserver = dateTimeObserver,
|
dateTimeObserver = dateTimeObserver,
|
||||||
)
|
)
|
||||||
|
|
@ -47,7 +47,7 @@ class RoomListDataSourceTest {
|
||||||
val initialRoomList = awaitItem()
|
val initialRoomList = awaitItem()
|
||||||
assertThat(initialRoomList).isNotEmpty()
|
assertThat(initialRoomList).isNotEmpty()
|
||||||
assertThat(initialRoomList.first().timestamp).isEqualTo("Today")
|
assertThat(initialRoomList.first().timestamp).isEqualTo("Today")
|
||||||
lastMessageTimestampFormatter.givenFormat("Yesterday")
|
dateFormatterResult = "Yesterday"
|
||||||
// Trigger a date change
|
// Trigger a date change
|
||||||
dateTimeObserver.given(DateTimeObserver.Event.DateChanged(Instant.MIN, Instant.now()))
|
dateTimeObserver.given(DateTimeObserver.Event.DateChanged(Instant.MIN, Instant.now()))
|
||||||
// Check there is a new list and it's not the same as the previous one
|
// Check there is a new list and it's not the same as the previous one
|
||||||
|
|
@ -64,12 +64,12 @@ class RoomListDataSourceTest {
|
||||||
postAllRooms(listOf(aRoomSummary()))
|
postAllRooms(listOf(aRoomSummary()))
|
||||||
}
|
}
|
||||||
val dateTimeObserver = FakeDateTimeObserver()
|
val dateTimeObserver = FakeDateTimeObserver()
|
||||||
val lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter()
|
var dateFormatterResult = "Today"
|
||||||
lastMessageTimestampFormatter.givenFormat("Today")
|
val dateFormatter = FakeDateFormatter({ _, _, _ -> dateFormatterResult })
|
||||||
val roomListDataSource = createRoomListDataSource(
|
val roomListDataSource = createRoomListDataSource(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
||||||
lastMessageTimestampFormatter = lastMessageTimestampFormatter,
|
dateFormatter = dateFormatter,
|
||||||
),
|
),
|
||||||
dateTimeObserver = dateTimeObserver,
|
dateTimeObserver = dateTimeObserver,
|
||||||
)
|
)
|
||||||
|
|
@ -80,7 +80,7 @@ class RoomListDataSourceTest {
|
||||||
val initialRoomList = awaitItem()
|
val initialRoomList = awaitItem()
|
||||||
assertThat(initialRoomList).isNotEmpty()
|
assertThat(initialRoomList).isNotEmpty()
|
||||||
assertThat(initialRoomList.first().timestamp).isEqualTo("Today")
|
assertThat(initialRoomList.first().timestamp).isEqualTo("Today")
|
||||||
lastMessageTimestampFormatter.givenFormat("Yesterday")
|
dateFormatterResult = "Yesterday"
|
||||||
// Trigger a timezone change
|
// Trigger a timezone change
|
||||||
dateTimeObserver.given(DateTimeObserver.Event.TimeZoneChanged)
|
dateTimeObserver.given(DateTimeObserver.Event.TimeZoneChanged)
|
||||||
// Check there is a new list and it's not the same as the previous one
|
// Check there is a new list and it's not the same as the previous one
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,14 @@
|
||||||
|
|
||||||
package io.element.android.features.roomlist.impl.datasource
|
package io.element.android.features.roomlist.impl.datasource
|
||||||
|
|
||||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
|
|
||||||
fun aRoomListRoomSummaryFactory(
|
fun aRoomListRoomSummaryFactory(
|
||||||
lastMessageTimestampFormatter: LastMessageTimestampFormatter = LastMessageTimestampFormatter { _ -> "Today" },
|
dateFormatter: DateFormatter = FakeDateFormatter { _, _, _ -> "Today" },
|
||||||
roomLastMessageFormatter: RoomLastMessageFormatter = RoomLastMessageFormatter { _, _ -> "Hey" }
|
roomLastMessageFormatter: RoomLastMessageFormatter = RoomLastMessageFormatter { _, _ -> "Hey" }
|
||||||
) = RoomListRoomSummaryFactory(
|
) = RoomListRoomSummaryFactory(
|
||||||
lastMessageTimestampFormatter = lastMessageTimestampFormatter,
|
dateFormatter = dateFormatter,
|
||||||
roomLastMessageFormatter = roomLastMessageFormatter
|
roomLastMessageFormatter = roomLastMessageFormatter
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
package io.element.android.features.roomlist.impl.model
|
package io.element.android.features.roomlist.impl.model
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
|
@ -84,6 +83,7 @@ internal fun createRoomListRoomSummary(
|
||||||
isFavorite: Boolean = false,
|
isFavorite: Boolean = false,
|
||||||
displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM,
|
displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM,
|
||||||
heroes: List<AvatarData> = emptyList(),
|
heroes: List<AvatarData> = emptyList(),
|
||||||
|
timestamp: String? = null,
|
||||||
) = RoomListRoomSummary(
|
) = RoomListRoomSummary(
|
||||||
id = A_ROOM_ID.value,
|
id = A_ROOM_ID.value,
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -92,7 +92,7 @@ internal fun createRoomListRoomSummary(
|
||||||
numberOfUnreadMessages = numberOfUnreadMessages,
|
numberOfUnreadMessages = numberOfUnreadMessages,
|
||||||
numberOfUnreadNotifications = numberOfUnreadNotifications,
|
numberOfUnreadNotifications = numberOfUnreadNotifications,
|
||||||
isMarkedUnread = isMarkedUnread,
|
isMarkedUnread = isMarkedUnread,
|
||||||
timestamp = A_FORMATTED_DATE,
|
timestamp = timestamp,
|
||||||
lastMessage = "",
|
lastMessage = "",
|
||||||
avatarData = AvatarData(id = A_ROOM_ID.value, name = A_ROOM_NAME, size = AvatarSize.RoomListItem),
|
avatarData = AvatarData(id = A_ROOM_ID.value, name = A_ROOM_NAME, size = AvatarSize.RoomListItem),
|
||||||
displayType = displayType,
|
displayType = displayType,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory
|
import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory
|
||||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
|
@ -143,7 +143,7 @@ fun TestScope.createRoomListSearchPresenter(
|
||||||
dataSource = RoomListSearchDataSource(
|
dataSource = RoomListSearchDataSource(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
roomSummaryFactory = aRoomListRoomSummaryFactory(
|
roomSummaryFactory = aRoomListRoomSummaryFactory(
|
||||||
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(),
|
dateFormatter = FakeDateFormatter(),
|
||||||
roomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
roomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||||
),
|
),
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.features.verifysession.impl.incoming.IncomingVerificationState.Step
|
import io.element.android.features.verifysession.impl.incoming.IncomingVerificationState.Step
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
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.matrix.api.verification.SessionVerificationRequestDetails
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||||
|
|
@ -37,7 +38,7 @@ class IncomingVerificationPresenter @AssistedInject constructor(
|
||||||
@Assisted private val navigator: IncomingVerificationNavigator,
|
@Assisted private val navigator: IncomingVerificationNavigator,
|
||||||
private val sessionVerificationService: SessionVerificationService,
|
private val sessionVerificationService: SessionVerificationService,
|
||||||
private val stateMachine: IncomingVerificationStateMachine,
|
private val stateMachine: IncomingVerificationStateMachine,
|
||||||
private val dateFormatter: LastMessageTimestampFormatter,
|
private val dateFormatter: DateFormatter,
|
||||||
) : Presenter<IncomingVerificationState> {
|
) : Presenter<IncomingVerificationState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
@ -59,7 +60,10 @@ class IncomingVerificationPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
||||||
val formattedSignInTime = remember {
|
val formattedSignInTime = remember {
|
||||||
dateFormatter.format(sessionVerificationRequestDetails.firstSeenTimestamp)
|
dateFormatter.format(
|
||||||
|
timestamp = sessionVerificationRequestDetails.firstSeenTimestamp,
|
||||||
|
mode = DateFormatterMode.TimeOrDate,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val step by remember {
|
val step by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,8 @@ package io.element.android.features.verifysession.impl.incoming
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData
|
import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData
|
||||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
|
||||||
import io.element.android.libraries.matrix.api.core.FlowId
|
import io.element.android.libraries.matrix.api.core.FlowId
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
|
|
@ -56,7 +55,7 @@ class IncomingVerificationPresenterTest {
|
||||||
IncomingVerificationState.Step.Initial(
|
IncomingVerificationState.Step.Initial(
|
||||||
deviceDisplayName = "a device name",
|
deviceDisplayName = "a device name",
|
||||||
deviceId = A_DEVICE_ID,
|
deviceId = A_DEVICE_ID,
|
||||||
formattedSignInTime = A_FORMATTED_DATE,
|
formattedSignInTime = "567 TimeOrDate false",
|
||||||
isWaiting = false,
|
isWaiting = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -119,7 +118,7 @@ class IncomingVerificationPresenterTest {
|
||||||
IncomingVerificationState.Step.Initial(
|
IncomingVerificationState.Step.Initial(
|
||||||
deviceDisplayName = "a device name",
|
deviceDisplayName = "a device name",
|
||||||
deviceId = A_DEVICE_ID,
|
deviceId = A_DEVICE_ID,
|
||||||
formattedSignInTime = A_FORMATTED_DATE,
|
formattedSignInTime = "567 TimeOrDate false",
|
||||||
isWaiting = false,
|
isWaiting = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -178,7 +177,7 @@ class IncomingVerificationPresenterTest {
|
||||||
IncomingVerificationState.Step.Initial(
|
IncomingVerificationState.Step.Initial(
|
||||||
deviceDisplayName = "a device name",
|
deviceDisplayName = "a device name",
|
||||||
deviceId = A_DEVICE_ID,
|
deviceId = A_DEVICE_ID,
|
||||||
formattedSignInTime = A_FORMATTED_DATE,
|
formattedSignInTime = "567 TimeOrDate false",
|
||||||
isWaiting = false,
|
isWaiting = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -210,7 +209,7 @@ class IncomingVerificationPresenterTest {
|
||||||
IncomingVerificationState.Step.Initial(
|
IncomingVerificationState.Step.Initial(
|
||||||
deviceDisplayName = "a device name",
|
deviceDisplayName = "a device name",
|
||||||
deviceId = A_DEVICE_ID,
|
deviceId = A_DEVICE_ID,
|
||||||
formattedSignInTime = A_FORMATTED_DATE,
|
formattedSignInTime = "567 TimeOrDate false",
|
||||||
isWaiting = false,
|
isWaiting = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -281,7 +280,7 @@ class IncomingVerificationPresenterTest {
|
||||||
sessionVerificationRequestDetails: SessionVerificationRequestDetails = aSessionVerificationRequestDetails,
|
sessionVerificationRequestDetails: SessionVerificationRequestDetails = aSessionVerificationRequestDetails,
|
||||||
navigator: IncomingVerificationNavigator = IncomingVerificationNavigator { lambdaError() },
|
navigator: IncomingVerificationNavigator = IncomingVerificationNavigator { lambdaError() },
|
||||||
service: SessionVerificationService = FakeSessionVerificationService(),
|
service: SessionVerificationService = FakeSessionVerificationService(),
|
||||||
dateFormatter: LastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE),
|
dateFormatter: DateFormatter = FakeDateFormatter(),
|
||||||
) = IncomingVerificationPresenter(
|
) = IncomingVerificationPresenter(
|
||||||
sessionVerificationRequestDetails = sessionVerificationRequestDetails,
|
sessionVerificationRequestDetails = sessionVerificationRequestDetails,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
package io.element.android.libraries.core.extensions
|
package io.element.android.libraries.core.extensions
|
||||||
|
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
fun Boolean.toOnOff() = if (this) "ON" else "OFF"
|
fun Boolean.toOnOff() = if (this) "ON" else "OFF"
|
||||||
fun Boolean.to01() = if (this) "1" else "0"
|
fun Boolean.to01() = if (this) "1" else "0"
|
||||||
|
|
||||||
|
|
@ -68,3 +70,16 @@ fun String.replacePrefix(oldPrefix: String, newPrefix: String): String {
|
||||||
fun String.withBrackets(prefix: String = "(", suffix: String = ")"): String {
|
fun String.withBrackets(prefix: String = "(", suffix: String = ")"): String {
|
||||||
return "$prefix$this$suffix"
|
return "$prefix$this$suffix"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalize the string.
|
||||||
|
*/
|
||||||
|
fun String.safeCapitalize(): String {
|
||||||
|
return replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) {
|
||||||
|
it.titlecase(Locale.getDefault())
|
||||||
|
} else {
|
||||||
|
it.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.api
|
||||||
|
|
||||||
|
interface DateFormatter {
|
||||||
|
fun format(
|
||||||
|
timestamp: Long?,
|
||||||
|
mode: DateFormatterMode = DateFormatterMode.Full,
|
||||||
|
useRelative: Boolean = false,
|
||||||
|
): String
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DateFormatterMode {
|
||||||
|
/**
|
||||||
|
* Full date and time.
|
||||||
|
* Example:
|
||||||
|
* "April 6, 1980 at 6:35 PM"
|
||||||
|
* Format can be shorter when useRelative is true.
|
||||||
|
* Example:
|
||||||
|
* "6:35 PM"
|
||||||
|
*/
|
||||||
|
Full,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only month and year.
|
||||||
|
* Example:
|
||||||
|
* "April 1980"
|
||||||
|
* "This month" can be returned when useRelative is true.
|
||||||
|
* Example:
|
||||||
|
* "This month"
|
||||||
|
*/
|
||||||
|
Month,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only day.
|
||||||
|
* Example:
|
||||||
|
* "Sunday 6 April"
|
||||||
|
* "Today", "Yesterday" and day of week can be returned when useRelative is true.
|
||||||
|
*/
|
||||||
|
Day,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time if same day, else date.
|
||||||
|
*/
|
||||||
|
TimeOrDate,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only time whatever the day.
|
||||||
|
*/
|
||||||
|
TimeOnly,
|
||||||
|
}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.api
|
|
||||||
|
|
||||||
interface DaySeparatorFormatter {
|
|
||||||
fun format(timestamp: Long): String
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.api
|
|
||||||
|
|
||||||
fun interface LastMessageTimestampFormatter {
|
|
||||||
fun format(timestamp: Long?): String
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@ import extension.setupAnvil
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAnvil()
|
setupAnvil()
|
||||||
|
|
@ -16,15 +16,30 @@ setupAnvil()
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.libraries.dateformatter.impl"
|
namespace = "io.element.android.libraries.dateformatter.impl"
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.dagger)
|
implementation(libs.dagger)
|
||||||
|
implementation(projects.libraries.core)
|
||||||
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.di)
|
implementation(projects.libraries.di)
|
||||||
|
implementation(projects.libraries.uiStrings)
|
||||||
|
implementation(projects.services.toolbox.api)
|
||||||
|
|
||||||
api(projects.libraries.dateformatter.api)
|
api(projects.libraries.dateformatter.api)
|
||||||
api(libs.datetime)
|
api(libs.datetime)
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(projects.libraries.dateformatter.test)
|
testImplementation(projects.libraries.dateformatter.test)
|
||||||
|
testImplementation(projects.services.toolbox.test)
|
||||||
|
testImplementation(projects.tests.testutils)
|
||||||
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.libraries.core.extensions.safeCapitalize
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
interface DateFormatterDay {
|
||||||
|
fun format(
|
||||||
|
timestamp: Long,
|
||||||
|
useRelative: Boolean,
|
||||||
|
): String
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultDateFormatterDay @Inject constructor(
|
||||||
|
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||||
|
private val dateFormatters: DateFormatters,
|
||||||
|
) : DateFormatterDay {
|
||||||
|
override fun format(
|
||||||
|
timestamp: Long,
|
||||||
|
useRelative: Boolean,
|
||||||
|
): String {
|
||||||
|
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||||
|
val today = localDateTimeProvider.providesNow()
|
||||||
|
return if (useRelative) {
|
||||||
|
val dayDiff = today.date.toEpochDays() - dateToFormat.date.toEpochDays()
|
||||||
|
when (dayDiff) {
|
||||||
|
0 -> dateFormatters.getRelativeDay(timestamp, "Today")
|
||||||
|
1 -> dateFormatters.getRelativeDay(timestamp, "Yesterday")
|
||||||
|
else -> if (dayDiff < 7) {
|
||||||
|
dateFormatters.formatDateWithDay(dateToFormat)
|
||||||
|
} else {
|
||||||
|
if (today.year == dateToFormat.year) {
|
||||||
|
dateFormatters.formatDateWithFullFormatNoYear(dateToFormat)
|
||||||
|
} else {
|
||||||
|
dateFormatters.formatDateWithFullFormat(dateToFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (today.year == dateToFormat.year) {
|
||||||
|
dateFormatters.formatDateWithFullFormatNoYear(dateToFormat)
|
||||||
|
} else {
|
||||||
|
dateFormatters.formatDateWithFullFormat(dateToFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.safeCapitalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DateFormatterFull @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||||
|
private val dateFormatters: DateFormatters,
|
||||||
|
private val dateFormatterDay: DateFormatterDay,
|
||||||
|
) {
|
||||||
|
fun format(
|
||||||
|
timestamp: Long,
|
||||||
|
useRelative: Boolean,
|
||||||
|
): String {
|
||||||
|
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||||
|
val time = dateFormatters.formatTime(dateToFormat)
|
||||||
|
return if (useRelative) {
|
||||||
|
val now = localDateTimeProvider.providesNow()
|
||||||
|
if (now.date == dateToFormat.date) {
|
||||||
|
time
|
||||||
|
} else {
|
||||||
|
val dateStr = dateFormatterDay.format(timestamp, true)
|
||||||
|
stringProvider.getString(R.string.common_date_date_at_time, dateStr, time)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val dateStr = dateFormatters.formatDateWithFullFormat(dateToFormat)
|
||||||
|
stringProvider.getString(R.string.common_date_date_at_time, dateStr, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import io.element.android.libraries.core.extensions.safeCapitalize
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DateFormatterMonth @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||||
|
private val dateFormatters: DateFormatters,
|
||||||
|
) {
|
||||||
|
fun format(
|
||||||
|
timestamp: Long,
|
||||||
|
useRelative: Boolean,
|
||||||
|
): String {
|
||||||
|
val today = localDateTimeProvider.providesNow()
|
||||||
|
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||||
|
return if (useRelative && dateToFormat.month == today.month && dateToFormat.year == today.year) {
|
||||||
|
stringProvider.getString(R.string.common_date_this_month)
|
||||||
|
} else {
|
||||||
|
dateFormatters.formatDateWithMonthAndYear(dateToFormat)
|
||||||
|
}
|
||||||
|
.safeCapitalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
* Please see LICENSE in the repository root for full details.
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
|
@ -7,18 +7,16 @@
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.impl
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
|
||||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
|
||||||
import io.element.android.libraries.di.AppScope
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
class DateFormatterTime @Inject constructor(
|
||||||
class DefaultLastMessageTimestampFormatter @Inject constructor(
|
|
||||||
private val localDateTimeProvider: LocalDateTimeProvider,
|
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||||
private val dateFormatters: DateFormatters,
|
private val dateFormatters: DateFormatters,
|
||||||
) : LastMessageTimestampFormatter {
|
) {
|
||||||
override fun format(timestamp: Long?): String {
|
fun format(
|
||||||
if (timestamp == null) return ""
|
timestamp: Long,
|
||||||
|
useRelative: Boolean,
|
||||||
|
): String {
|
||||||
val currentDate = localDateTimeProvider.providesNow()
|
val currentDate = localDateTimeProvider.providesNow()
|
||||||
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||||
val isSameDay = currentDate.date == dateToFormat.date
|
val isSameDay = currentDate.date == dateToFormat.date
|
||||||
|
|
@ -30,7 +28,7 @@ class DefaultLastMessageTimestampFormatter @Inject constructor(
|
||||||
dateFormatters.formatDate(
|
dateFormatters.formatDate(
|
||||||
dateToFormat = dateToFormat,
|
dateToFormat = dateToFormat,
|
||||||
currentDate = currentDate,
|
currentDate = currentDate,
|
||||||
useRelative = true
|
useRelative = useRelative,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DateFormatterTimeOnly @Inject constructor(
|
||||||
|
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||||
|
private val dateFormatters: DateFormatters,
|
||||||
|
) {
|
||||||
|
fun format(
|
||||||
|
timestamp: Long,
|
||||||
|
): String {
|
||||||
|
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||||
|
return dateFormatters.formatTime(dateToFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,57 +7,64 @@
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.impl
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
import android.text.format.DateFormat
|
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.datetime.toInstant
|
import kotlinx.datetime.toInstant
|
||||||
import kotlinx.datetime.toJavaLocalDate
|
import kotlinx.datetime.toJavaLocalDate
|
||||||
import kotlinx.datetime.toJavaLocalDateTime
|
import kotlinx.datetime.toJavaLocalDateTime
|
||||||
|
import timber.log.Timber
|
||||||
import java.time.Period
|
import java.time.Period
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.time.format.FormatStyle
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
class DateFormatters @Inject constructor(
|
class DateFormatters @Inject constructor(
|
||||||
private val locale: Locale,
|
localeChangeObserver: LocaleChangeObserver,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val timeZoneProvider: TimezoneProvider,
|
private val timeZoneProvider: TimezoneProvider,
|
||||||
) {
|
locale: Locale,
|
||||||
private val onlyTimeFormatter: DateTimeFormatter by lazy {
|
) : LocaleChangeListener {
|
||||||
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(locale)
|
init {
|
||||||
|
localeChangeObserver.addListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dateWithMonthFormatter: DateTimeFormatter by lazy {
|
private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(locale)
|
||||||
val pattern = DateFormat.getBestDateTimePattern(locale, "d MMM") ?: "d MMM"
|
|
||||||
DateTimeFormatter.ofPattern(pattern, locale)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val dateWithYearFormatter: DateTimeFormatter by lazy {
|
override fun onLocaleChange() {
|
||||||
val pattern = DateFormat.getBestDateTimePattern(locale, "dd.MM.yyyy") ?: "dd.MM.yyyy"
|
Timber.w("Locale changed, updating formatters")
|
||||||
DateTimeFormatter.ofPattern(pattern, locale)
|
dateTimeFormatters = DateTimeFormatters(Locale.getDefault())
|
||||||
}
|
|
||||||
|
|
||||||
private val dateWithFullFormatFormatter: DateTimeFormatter by lazy {
|
|
||||||
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun formatTime(localDateTime: LocalDateTime): String {
|
internal fun formatTime(localDateTime: LocalDateTime): String {
|
||||||
return onlyTimeFormatter.format(localDateTime.toJavaLocalDateTime())
|
return dateTimeFormatters.onlyTimeFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatDateWithMonthAndYear(localDateTime: LocalDateTime): String {
|
||||||
|
return dateTimeFormatters.dateWithMonthAndYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun formatDateWithMonth(localDateTime: LocalDateTime): String {
|
internal fun formatDateWithMonth(localDateTime: LocalDateTime): String {
|
||||||
return dateWithMonthFormatter.format(localDateTime.toJavaLocalDateTime())
|
return dateTimeFormatters.dateWithMonthFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatDateWithDay(localDateTime: LocalDateTime): String {
|
||||||
|
return dateTimeFormatters.dateWithDayFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun formatDateWithYear(localDateTime: LocalDateTime): String {
|
internal fun formatDateWithYear(localDateTime: LocalDateTime): String {
|
||||||
return dateWithYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
return dateTimeFormatters.dateWithYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun formatDateWithFullFormat(localDateTime: LocalDateTime): String {
|
internal fun formatDateWithFullFormat(localDateTime: LocalDateTime): String {
|
||||||
return dateWithFullFormatFormatter.format(localDateTime.toJavaLocalDateTime())
|
return dateTimeFormatters.dateWithFullFormatFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatDateWithFullFormatNoYear(localDateTime: LocalDateTime): String {
|
||||||
|
return dateTimeFormatters.dateWithFullFormatNoYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun formatDate(
|
internal fun formatDate(
|
||||||
|
|
@ -75,12 +82,12 @@ class DateFormatters @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRelativeDay(ts: Long): String {
|
internal fun getRelativeDay(ts: Long, default: String = ""): String {
|
||||||
return DateUtils.getRelativeTimeSpanString(
|
return DateUtils.getRelativeTimeSpanString(
|
||||||
ts,
|
ts,
|
||||||
clock.now().toEpochMilliseconds(),
|
clock.now().toEpochMilliseconds(),
|
||||||
DateUtils.DAY_IN_MILLIS,
|
DateUtils.DAY_IN_MILLIS,
|
||||||
DateUtils.FORMAT_SHOW_WEEKDAY
|
DateUtils.FORMAT_SHOW_WEEKDAY
|
||||||
)?.toString() ?: ""
|
)?.toString() ?: default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.format.FormatStyle
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class DateTimeFormatters(
|
||||||
|
private val locale: Locale,
|
||||||
|
) {
|
||||||
|
val onlyTimeFormatter: DateTimeFormatter by lazy {
|
||||||
|
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateWithMonthAndYearFormatter: DateTimeFormatter by lazy {
|
||||||
|
val pattern = bestDateTimePattern("MMMM YYYY")
|
||||||
|
DateTimeFormatter.ofPattern(pattern, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateWithMonthFormatter: DateTimeFormatter by lazy {
|
||||||
|
val pattern = bestDateTimePattern("d MMM")
|
||||||
|
DateTimeFormatter.ofPattern(pattern, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateWithDayFormatter: DateTimeFormatter by lazy {
|
||||||
|
val pattern = bestDateTimePattern("EEEE")
|
||||||
|
DateTimeFormatter.ofPattern(pattern, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateWithYearFormatter: DateTimeFormatter by lazy {
|
||||||
|
val pattern = bestDateTimePattern("dd.MM.yyyy")
|
||||||
|
DateTimeFormatter.ofPattern(pattern, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateWithFullFormatFormatter: DateTimeFormatter by lazy {
|
||||||
|
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateWithFullFormatNoYearFormatter: DateTimeFormatter by lazy {
|
||||||
|
val pattern = DateFormat.getBestDateTimePattern(locale, "EEEE d MMMM") ?: "EEEE d MMMM"
|
||||||
|
DateTimeFormatter.ofPattern(pattern, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bestDateTimePattern(pattern: String): String {
|
||||||
|
return DateFormat.getBestDateTimePattern(locale, pattern) ?: pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultDateFormatter @Inject constructor(
|
||||||
|
private val dateFormatterFull: DateFormatterFull,
|
||||||
|
private val dateFormatterMonth: DateFormatterMonth,
|
||||||
|
private val dateFormatterDay: DateFormatterDay,
|
||||||
|
private val dateFormatterTime: DateFormatterTime,
|
||||||
|
private val dateFormatterTimeOnly: DateFormatterTimeOnly,
|
||||||
|
) : DateFormatter {
|
||||||
|
override fun format(
|
||||||
|
timestamp: Long?,
|
||||||
|
mode: DateFormatterMode,
|
||||||
|
useRelative: Boolean,
|
||||||
|
): String {
|
||||||
|
timestamp ?: return ""
|
||||||
|
return when (mode) {
|
||||||
|
DateFormatterMode.Full -> {
|
||||||
|
dateFormatterFull.format(timestamp, useRelative)
|
||||||
|
}
|
||||||
|
DateFormatterMode.Month -> {
|
||||||
|
dateFormatterMonth.format(timestamp, useRelative)
|
||||||
|
}
|
||||||
|
DateFormatterMode.Day -> {
|
||||||
|
dateFormatterDay.format(timestamp, useRelative)
|
||||||
|
}
|
||||||
|
DateFormatterMode.TimeOrDate -> {
|
||||||
|
dateFormatterTime.format(timestamp, useRelative)
|
||||||
|
}
|
||||||
|
DateFormatterMode.TimeOnly -> {
|
||||||
|
dateFormatterTimeOnly.format(timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.impl
|
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
|
||||||
import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter
|
|
||||||
import io.element.android.libraries.di.AppScope
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
|
||||||
class DefaultDaySeparatorFormatter @Inject constructor(
|
|
||||||
private val localDateTimeProvider: LocalDateTimeProvider,
|
|
||||||
private val dateFormatters: DateFormatters,
|
|
||||||
) : DaySeparatorFormatter {
|
|
||||||
override fun format(timestamp: Long): String {
|
|
||||||
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
|
||||||
// TODO use relative formatting once iOS uses it too
|
|
||||||
return dateFormatters.formatDateWithFullFormat(dateToFormat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.Build
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
fun interface LocaleChangeObserver {
|
||||||
|
fun addListener(listener: LocaleChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LocaleChangeListener {
|
||||||
|
fun onLocaleChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultLocaleChangeObserver @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
) : LocaleChangeObserver {
|
||||||
|
init {
|
||||||
|
registerReceiver(object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
listeners.forEach(LocaleChangeListener::onLocaleChange)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private val listeners = mutableSetOf<LocaleChangeListener>()
|
||||||
|
|
||||||
|
override fun addListener(listener: LocaleChangeListener) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerReceiver(receiver: BroadcastReceiver) {
|
||||||
|
val filter = IntentFilter()
|
||||||
|
filter.addAction(Intent.ACTION_LOCALE_CHANGED)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
filter.addAction(Intent.ACTION_APPLICATION_LOCALE_CHANGED)
|
||||||
|
}
|
||||||
|
context.registerReceiver(receiver, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
data class DateForPreview(
|
||||||
|
val semantic: String,
|
||||||
|
val date: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
val dateForPreviewToday = DateForPreview(
|
||||||
|
semantic = "Today",
|
||||||
|
date = "1980-04-06T18:35:24.00Z",
|
||||||
|
)
|
||||||
|
|
||||||
|
val dateForPreviews = listOf(
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "Now",
|
||||||
|
date = dateForPreviewToday.date,
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One second ago",
|
||||||
|
date = "1980-04-06T18:35:23.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One minute ago",
|
||||||
|
date = "1980-04-06T18:34:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One hour ago",
|
||||||
|
date = "1980-04-06T17:35:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One day ago",
|
||||||
|
date = "1980-04-05T18:35:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "Two days ago",
|
||||||
|
date = "1980-04-04T18:35:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One month ago",
|
||||||
|
date = "1980-03-06T18:35:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One year ago",
|
||||||
|
date = "1979-04-06T18:35:24.00Z",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
|
|
||||||
|
class DateFormatterModeProvider : PreviewParameterProvider<DateFormatterMode> {
|
||||||
|
override val values: Sequence<DateFormatterMode>
|
||||||
|
get() = DateFormatterMode.entries.asSequence()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.intl.Locale
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatter
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.designsystem.utils.allBooleans
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun DateFormatterModeViewPreview(
|
||||||
|
@PreviewParameter(DateFormatterModeProvider::class) dateFormatterMode: DateFormatterMode,
|
||||||
|
) = ElementPreview {
|
||||||
|
DateFormatterModeView(dateFormatterMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DateFormatterModeView(
|
||||||
|
mode: DateFormatterMode,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val composeLocale = Locale.current
|
||||||
|
val dateFormatter = remember {
|
||||||
|
createFormatter(
|
||||||
|
context = context,
|
||||||
|
currentDate = dateForPreviewToday.date,
|
||||||
|
locale = java.util.Locale.Builder()
|
||||||
|
.setLanguageTag(composeLocale.toLanguageTag())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(4.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Mode $mode / $composeLocale",
|
||||||
|
style = ElementTheme.typography.fontHeadingSmMedium
|
||||||
|
)
|
||||||
|
val today = Instant.parse(dateForPreviewToday.date).toEpochMilliseconds()
|
||||||
|
Text(
|
||||||
|
text = "Today is: ${dateFormatter.format(today, DateFormatterMode.Full, useRelative = false)}",
|
||||||
|
style = ElementTheme.typography.fontHeadingSmMedium,
|
||||||
|
)
|
||||||
|
dateForPreviews.forEach { dateForPreview ->
|
||||||
|
DateForPreviewItem(
|
||||||
|
dateForPreview = dateForPreview,
|
||||||
|
dateFormatter = dateFormatter,
|
||||||
|
mode = mode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DateForPreviewItem(
|
||||||
|
dateForPreview: DateForPreview,
|
||||||
|
dateFormatter: DefaultDateFormatter,
|
||||||
|
mode: DateFormatterMode,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(2.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 8.dp),
|
||||||
|
text = dateForPreview.semantic,
|
||||||
|
style = ElementTheme.typography.fontBodyMdMedium,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
)
|
||||||
|
val ts = Instant.parse(dateForPreview.date).toEpochMilliseconds()
|
||||||
|
Row {
|
||||||
|
Column {
|
||||||
|
listOf("Absolute:", "Relative:").forEach { label ->
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Column {
|
||||||
|
allBooleans.forEach { useRelative ->
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
text = dateFormatter.format(ts, mode, useRelative),
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatterFull
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatterMonth
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatterTime
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatterTimeOnly
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatters
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatter
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatterDay
|
||||||
|
import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create DefaultDateFormatter and set current time to the provided date.
|
||||||
|
*/
|
||||||
|
fun createFormatter(
|
||||||
|
context: Context,
|
||||||
|
currentDate: String,
|
||||||
|
locale: Locale,
|
||||||
|
): DefaultDateFormatter {
|
||||||
|
val clock = PreviewClock().apply { givenInstant(Instant.parse(currentDate)) }
|
||||||
|
val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC }
|
||||||
|
val dateFormatters = DateFormatters(
|
||||||
|
localeChangeObserver = {},
|
||||||
|
clock = clock,
|
||||||
|
timeZoneProvider = { TimeZone.UTC },
|
||||||
|
locale = locale,
|
||||||
|
)
|
||||||
|
val stringProvider = PreviewStringProvider(context.resources)
|
||||||
|
val dateFormatterDay = DefaultDateFormatterDay(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
)
|
||||||
|
return DefaultDateFormatter(
|
||||||
|
dateFormatterFull = DateFormatterFull(
|
||||||
|
stringProvider = stringProvider,
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
dateFormatterDay = dateFormatterDay,
|
||||||
|
),
|
||||||
|
dateFormatterMonth = DateFormatterMonth(
|
||||||
|
stringProvider = stringProvider,
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
dateFormatterDay = dateFormatterDay,
|
||||||
|
dateFormatterTime = DateFormatterTime(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
dateFormatterTimeOnly = DateFormatterTimeOnly(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
|
class PreviewClock : Clock {
|
||||||
|
private var instant: Instant = Instant.fromEpochMilliseconds(0)
|
||||||
|
|
||||||
|
fun givenInstant(instant: Instant) {
|
||||||
|
this.instant = instant
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun now(): Instant = instant
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.PluralsRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
|
||||||
|
class PreviewStringProvider(
|
||||||
|
private val resources: Resources
|
||||||
|
) : StringProvider {
|
||||||
|
override fun getString(@StringRes resId: Int): String {
|
||||||
|
return resources.getString(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
|
||||||
|
return resources.getString(resId, *formatArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String {
|
||||||
|
return resources.getQuantityString(resId, quantity, *formatArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="common_date_date_at_time">"%1$s à %2$s"</string>
|
||||||
|
<string name="common_date_this_month">"Ce mois-ci"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="common_date_date_at_time">"%1$s at %2$s"</string>
|
||||||
|
<string name="common_date_this_month">"This month"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,260 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@Config(qualifiers = "fr")
|
||||||
|
class DefaultDateFormatterFrTest {
|
||||||
|
@Test
|
||||||
|
fun `test null`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts: Long? = null
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts)).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test epoch`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts = 0L
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("1 janvier 1970 à 00:00")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Janvier 1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("1 janvier 1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("01.01.1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("00:00")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test epoch relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts = 0L
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("1 janvier 1970 à 00:00")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Janvier 1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("1 janvier 1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("01.01.1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("00:00")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test now`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test now relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one second before`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:35:23.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one second before relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:35:23.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one minute before`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:34:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 18:34")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("18:34")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:34")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one minute before relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:34:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("18:34")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("18:34")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:34")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one hour before`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T17:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1980 à 17:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Dimanche 6 avril")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("17:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("17:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one hour before relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T17:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("17:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Aujourd’hui")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("17:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("17:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one day before same time`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-05T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("5 avril 1980 à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Samedi 5 avril")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("5 avr.")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one day before same time relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-05T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Hier à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Hier")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("Hier")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test two days before same time`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-04T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("4 avril 1980 à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Vendredi 4 avril")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("4 avr.")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test two days before same time relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-04T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Vendredi à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Ce mois-ci")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Vendredi")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("4 avr.")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one month before same time`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-03-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 mars 1980 à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Mars 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Jeudi 6 mars")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6 mars")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one month before same time relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-03-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Jeudi 6 mars à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Mars 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Jeudi 6 mars")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6 mars")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one year before same time`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1979-04-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("6 avril 1979 à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("Avril 1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("6 avril 1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("06.04.1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one year before same time relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1979-04-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6 avril 1979 à 18:35")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("Avril 1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("6 avril 1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("06.04.1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("18:35")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,260 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@Config(qualifiers = "en")
|
||||||
|
class DefaultDateFormatterTest {
|
||||||
|
@Test
|
||||||
|
fun `test null`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts: Long? = null
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts)).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test epoch`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts = 0L
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("January 1, 1970 at 12:00 AM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("January 1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("January 1, 1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("01.01.1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("12:00 AM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test epoch relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts = 0L
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("January 1, 1970 at 12:00 AM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("January 1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("January 1, 1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("01.01.1970")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("12:00 AM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test now`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday 6 April")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test now relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one second before`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:35:23.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday 6 April")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one second before relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:35:23.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one minute before`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:34:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 6:34 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday 6 April")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6:34 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:34 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one minute before relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T18:34:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("6:34 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6:34 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:34 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one hour before`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T17:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1980 at 5:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Sunday 6 April")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("5:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("5:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one hour before relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-06T17:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("5:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Today")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("5:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("5:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one day before same time`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-05T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 5, 1980 at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Saturday 5 April")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("5 Apr")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one day before same time relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-05T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Yesterday at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Yesterday")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("Yesterday")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test two days before same time`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-04T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 4, 1980 at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Friday 4 April")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("4 Apr")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test two days before same time relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-04-04T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Friday at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("This month")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Friday")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("4 Apr")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one month before same time`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-03-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("March 6, 1980 at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("March 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("Thursday 6 March")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("6 Mar")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one month before same time relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1980-03-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("Thursday 6 March at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("March 1980")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("Thursday 6 March")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("6 Mar")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one year before same time`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1979-04-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full)).isEqualTo("April 6, 1979 at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month)).isEqualTo("April 1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day)).isEqualTo("April 6, 1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate)).isEqualTo("06.04.1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test one year before same time relative`() {
|
||||||
|
val now = "1980-04-06T18:35:24.00Z"
|
||||||
|
val dat = "1979-04-06T18:35:24.00Z"
|
||||||
|
val ts = Instant.parse(dat).toEpochMilliseconds()
|
||||||
|
val formatter = createFormatter(now)
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Full, true)).isEqualTo("April 6, 1979 at 6:35 PM")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Month, true)).isEqualTo("April 1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.Day, true)).isEqualTo("April 6, 1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOrDate, true)).isEqualTo("06.04.1979")
|
||||||
|
assertThat(formatter.format(ts, DateFormatterMode.TimeOnly, true)).isEqualTo("6:35 PM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.impl
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
|
||||||
import io.element.android.libraries.dateformatter.test.FakeClock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.datetime.TimeZone
|
|
||||||
import kotlinx.datetime.toLocalDateTime
|
|
||||||
import org.junit.Test
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class DefaultLastMessageTimestampFormatterTest {
|
|
||||||
@Test
|
|
||||||
fun `test null`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
assertThat(formatter.format(null)).isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test epoch`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
assertThat(formatter.format(0)).isEqualTo("01.01.1970")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test now`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val dat = "1980-04-06T18:35:24.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("6:35 PM")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test one second before`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val dat = "1980-04-06T18:35:23.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("6:35 PM")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test one minute before`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val dat = "1980-04-06T18:34:24.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("6:34 PM")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test one hour before`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val dat = "1980-04-06T17:35:24.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("5:35 PM")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test one day before same time`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val dat = "1980-04-05T18:35:24.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
// TODO DateUtils.getRelativeTimeSpanString returns null.
|
|
||||||
assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test one month before same time`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val dat = "1980-03-06T18:35:24.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("6 Mar")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test one year before same time`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val dat = "1979-04-06T18:35:24.00Z"
|
|
||||||
val formatter = createFormatter(now)
|
|
||||||
assertThat(formatter.format(Instant.parse(dat).toEpochMilliseconds())).isEqualTo("06.04.1979")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `test full format`() {
|
|
||||||
val now = "1980-04-06T18:35:24.00Z"
|
|
||||||
val dat = "1979-04-06T18:35:24.00Z"
|
|
||||||
val clock = FakeClock().apply { givenInstant(Instant.parse(now)) }
|
|
||||||
val dateFormatters = DateFormatters(Locale.US, clock) { TimeZone.UTC }
|
|
||||||
assertThat(dateFormatters.formatDateWithFullFormat(Instant.parse(dat).toLocalDateTime(TimeZone.UTC))).isEqualTo("Friday, April 6, 1979")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create DefaultLastMessageFormatter and set current time to the provided date.
|
|
||||||
*/
|
|
||||||
private fun createFormatter(@Suppress("SameParameterValue") currentDate: String): LastMessageTimestampFormatter {
|
|
||||||
val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) }
|
|
||||||
val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC }
|
|
||||||
val dateFormatters = DateFormatters(Locale.US, clock) { TimeZone.UTC }
|
|
||||||
return DefaultLastMessageTimestampFormatter(localDateTimeProvider, dateFormatters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import io.element.android.tests.testutils.InstrumentationStringProvider
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create DefaultDateFormatter and set current time to the provided date.
|
||||||
|
*/
|
||||||
|
fun createFormatter(currentDate: String): DefaultDateFormatter {
|
||||||
|
val clock = FakeClock().apply { givenInstant(Instant.parse(currentDate)) }
|
||||||
|
val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC }
|
||||||
|
val dateFormatters = DateFormatters(
|
||||||
|
localeChangeObserver = {},
|
||||||
|
clock = clock,
|
||||||
|
timeZoneProvider = { TimeZone.UTC },
|
||||||
|
locale = Locale.getDefault(),
|
||||||
|
)
|
||||||
|
val stringProvider = InstrumentationStringProvider()
|
||||||
|
val dateFormatterDay = DefaultDateFormatterDay(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
)
|
||||||
|
return DefaultDateFormatter(
|
||||||
|
dateFormatterFull = DateFormatterFull(
|
||||||
|
stringProvider = stringProvider,
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
dateFormatterDay = dateFormatterDay,
|
||||||
|
),
|
||||||
|
dateFormatterMonth = DateFormatterMonth(
|
||||||
|
stringProvider = stringProvider,
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
dateFormatterDay = dateFormatterDay,
|
||||||
|
dateFormatterTime = DateFormatterTime(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
dateFormatterTimeOnly = DateFormatterTimeOnly(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE in the repository root for full details.
|
* Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.test
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.test
|
||||||
|
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
|
|
||||||
|
class FakeDateFormatter(
|
||||||
|
private val formatLambda: (Long?, DateFormatterMode, Boolean) -> String = { timestamp, mode, useRelative ->
|
||||||
|
"$timestamp $mode $useRelative"
|
||||||
|
},
|
||||||
|
) : DateFormatter {
|
||||||
|
override fun format(
|
||||||
|
timestamp: Long?,
|
||||||
|
mode: DateFormatterMode,
|
||||||
|
useRelative: Boolean,
|
||||||
|
): String {
|
||||||
|
return formatLambda(timestamp, mode, useRelative)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.test
|
|
||||||
|
|
||||||
import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter
|
|
||||||
|
|
||||||
class FakeDaySeparatorFormatter : DaySeparatorFormatter {
|
|
||||||
private var format = ""
|
|
||||||
|
|
||||||
fun givenFormat(format: String) {
|
|
||||||
this.format = format
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun format(timestamp: Long): String {
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.test
|
|
||||||
|
|
||||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
|
||||||
|
|
||||||
const val A_FORMATTED_DATE = "formatted_date"
|
|
||||||
|
|
||||||
class FakeLastMessageTimestampFormatter(
|
|
||||||
var format: String = "",
|
|
||||||
) : LastMessageTimestampFormatter {
|
|
||||||
fun givenFormat(format: String) {
|
|
||||||
this.format = format
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun format(timestamp: Long?): String {
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -235,7 +235,7 @@ class RustMatrixRoom(
|
||||||
RoomMessageEventMessageType.VIDEO,
|
RoomMessageEventMessageType.VIDEO,
|
||||||
RoomMessageEventMessageType.AUDIO,
|
RoomMessageEventMessageType.AUDIO,
|
||||||
),
|
),
|
||||||
dateDividerMode = DateDividerMode.DAILY,
|
dateDividerMode = DateDividerMode.MONTHLY,
|
||||||
).let { inner ->
|
).let { inner ->
|
||||||
createTimeline(inner, mode = Timeline.Mode.MEDIA)
|
createTimeline(inner, mode = Timeline.Mode.MEDIA)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ data class MediaInfo(
|
||||||
val senderName: String?,
|
val senderName: String?,
|
||||||
val senderAvatar: String?,
|
val senderAvatar: String?,
|
||||||
val dateSent: String?,
|
val dateSent: String?,
|
||||||
|
val dateSentFull: String?,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
fun anImageMediaInfo(
|
fun anImageMediaInfo(
|
||||||
|
|
@ -30,6 +31,7 @@ fun anImageMediaInfo(
|
||||||
caption: String? = null,
|
caption: String? = null,
|
||||||
senderName: String? = null,
|
senderName: String? = null,
|
||||||
dateSent: String? = null,
|
dateSent: String? = null,
|
||||||
|
dateSentFull: String? = null,
|
||||||
): MediaInfo = MediaInfo(
|
): MediaInfo = MediaInfo(
|
||||||
filename = "an image file.jpg",
|
filename = "an image file.jpg",
|
||||||
caption = caption,
|
caption = caption,
|
||||||
|
|
@ -40,12 +42,14 @@ fun anImageMediaInfo(
|
||||||
senderName = senderName,
|
senderName = senderName,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = dateSent,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aVideoMediaInfo(
|
fun aVideoMediaInfo(
|
||||||
caption: String? = null,
|
caption: String? = null,
|
||||||
senderName: String? = null,
|
senderName: String? = null,
|
||||||
dateSent: String? = null,
|
dateSent: String? = null,
|
||||||
|
dateSentFull: String? = null,
|
||||||
): MediaInfo = MediaInfo(
|
): MediaInfo = MediaInfo(
|
||||||
filename = "a video file.mp4",
|
filename = "a video file.mp4",
|
||||||
caption = caption,
|
caption = caption,
|
||||||
|
|
@ -56,6 +60,7 @@ fun aVideoMediaInfo(
|
||||||
senderName = senderName,
|
senderName = senderName,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = dateSent,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aPdfMediaInfo(
|
fun aPdfMediaInfo(
|
||||||
|
|
@ -63,6 +68,7 @@ fun aPdfMediaInfo(
|
||||||
caption: String? = null,
|
caption: String? = null,
|
||||||
senderName: String? = null,
|
senderName: String? = null,
|
||||||
dateSent: String? = null,
|
dateSent: String? = null,
|
||||||
|
dateSentFull: String? = null,
|
||||||
): MediaInfo = MediaInfo(
|
): MediaInfo = MediaInfo(
|
||||||
filename = filename,
|
filename = filename,
|
||||||
caption = caption,
|
caption = caption,
|
||||||
|
|
@ -73,12 +79,14 @@ fun aPdfMediaInfo(
|
||||||
senderName = senderName,
|
senderName = senderName,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = dateSent,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun anApkMediaInfo(
|
fun anApkMediaInfo(
|
||||||
senderId: UserId? = UserId("@alice:server.org"),
|
senderId: UserId? = UserId("@alice:server.org"),
|
||||||
senderName: String? = null,
|
senderName: String? = null,
|
||||||
dateSent: String? = null,
|
dateSent: String? = null,
|
||||||
|
dateSentFull: String? = null,
|
||||||
): MediaInfo = MediaInfo(
|
): MediaInfo = MediaInfo(
|
||||||
filename = "an apk file.apk",
|
filename = "an apk file.apk",
|
||||||
caption = null,
|
caption = null,
|
||||||
|
|
@ -89,11 +97,13 @@ fun anApkMediaInfo(
|
||||||
senderName = senderName,
|
senderName = senderName,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = dateSent,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun anAudioMediaInfo(
|
fun anAudioMediaInfo(
|
||||||
senderName: String? = null,
|
senderName: String? = null,
|
||||||
dateSent: String? = null,
|
dateSent: String? = null,
|
||||||
|
dateSentFull: String? = null,
|
||||||
): MediaInfo = MediaInfo(
|
): MediaInfo = MediaInfo(
|
||||||
filename = "an audio file.mp3",
|
filename = "an audio file.mp3",
|
||||||
caption = null,
|
caption = null,
|
||||||
|
|
@ -104,4 +114,5 @@ fun anAudioMediaInfo(
|
||||||
senderName = senderName,
|
senderName = senderName,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = dateSent,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint
|
||||||
senderName = null,
|
senderName = null,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = null,
|
dateSent = null,
|
||||||
|
dateSentFull = null,
|
||||||
),
|
),
|
||||||
mediaSource = MediaSource(url = avatarUrl),
|
mediaSource = MediaSource(url = avatarUrl),
|
||||||
thumbnailSource = null,
|
thumbnailSource = null,
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ fun MediaDetailsBottomSheet(
|
||||||
}
|
}
|
||||||
SectionText(
|
SectionText(
|
||||||
title = stringResource(R.string.screen_media_details_uploaded_on),
|
title = stringResource(R.string.screen_media_details_uploaded_on),
|
||||||
text = state.mediaInfo.dateSent.orEmpty(),
|
text = state.mediaInfo.dateSentFull.orEmpty(),
|
||||||
)
|
)
|
||||||
SectionText(
|
SectionText(
|
||||||
title = stringResource(R.string.screen_media_details_filename),
|
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.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.mediaviewer.api.anImageMediaInfo
|
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(
|
return MediaBottomSheetState.MediaDetailsBottomSheetState(
|
||||||
eventId = EventId("\$eventId"),
|
eventId = EventId("\$eventId"),
|
||||||
canDelete = true,
|
canDelete = true,
|
||||||
mediaInfo = anImageMediaInfo(
|
mediaInfo = anImageMediaInfo(
|
||||||
senderName = "Alice",
|
senderName = "Alice",
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
),
|
),
|
||||||
thumbnailSource = null,
|
thumbnailSource = null,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
package io.element.android.libraries.mediaviewer.impl.gallery
|
package io.element.android.libraries.mediaviewer.impl.gallery
|
||||||
|
|
||||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
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.dateformatter.api.toHumanReadableDuration
|
||||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
||||||
|
|
@ -45,13 +46,20 @@ import javax.inject.Inject
|
||||||
class EventItemFactory @Inject constructor(
|
class EventItemFactory @Inject constructor(
|
||||||
private val fileSizeFormatter: FileSizeFormatter,
|
private val fileSizeFormatter: FileSizeFormatter,
|
||||||
private val fileExtensionExtractor: FileExtensionExtractor,
|
private val fileExtensionExtractor: FileExtensionExtractor,
|
||||||
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
private val dateFormatter: DateFormatter,
|
||||||
) {
|
) {
|
||||||
fun create(
|
fun create(
|
||||||
currentTimelineItem: MatrixTimelineItem.Event,
|
currentTimelineItem: MatrixTimelineItem.Event,
|
||||||
): MediaItem.Event? {
|
): MediaItem.Event? {
|
||||||
val event = currentTimelineItem.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) {
|
return when (val content = event.content) {
|
||||||
CallNotifyContent,
|
CallNotifyContent,
|
||||||
is FailedToParseMessageLikeContent,
|
is FailedToParseMessageLikeContent,
|
||||||
|
|
@ -90,7 +98,8 @@ class EventItemFactory @Inject constructor(
|
||||||
senderId = event.sender,
|
senderId = event.sender,
|
||||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||||
dateSent = sentTime,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
),
|
),
|
||||||
mediaSource = type.source,
|
mediaSource = type.source,
|
||||||
)
|
)
|
||||||
|
|
@ -106,7 +115,8 @@ class EventItemFactory @Inject constructor(
|
||||||
senderId = event.sender,
|
senderId = event.sender,
|
||||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||||
dateSent = sentTime,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
),
|
),
|
||||||
mediaSource = type.source,
|
mediaSource = type.source,
|
||||||
)
|
)
|
||||||
|
|
@ -122,7 +132,8 @@ class EventItemFactory @Inject constructor(
|
||||||
senderId = event.sender,
|
senderId = event.sender,
|
||||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||||
dateSent = sentTime,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
),
|
),
|
||||||
mediaSource = type.source,
|
mediaSource = type.source,
|
||||||
thumbnailSource = null,
|
thumbnailSource = null,
|
||||||
|
|
@ -139,7 +150,8 @@ class EventItemFactory @Inject constructor(
|
||||||
senderId = event.sender,
|
senderId = event.sender,
|
||||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||||
dateSent = sentTime,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
),
|
),
|
||||||
mediaSource = type.source,
|
mediaSource = type.source,
|
||||||
thumbnailSource = null,
|
thumbnailSource = null,
|
||||||
|
|
@ -156,7 +168,8 @@ class EventItemFactory @Inject constructor(
|
||||||
senderId = event.sender,
|
senderId = event.sender,
|
||||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||||
dateSent = sentTime,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
),
|
),
|
||||||
mediaSource = type.source,
|
mediaSource = type.source,
|
||||||
thumbnailSource = type.info?.thumbnailSource,
|
thumbnailSource = type.info?.thumbnailSource,
|
||||||
|
|
@ -174,7 +187,8 @@ class EventItemFactory @Inject constructor(
|
||||||
senderId = event.sender,
|
senderId = event.sender,
|
||||||
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
senderName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||||
senderAvatar = event.senderProfile.getAvatarUrl(),
|
senderAvatar = event.senderProfile.getAvatarUrl(),
|
||||||
dateSent = sentTime,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
),
|
),
|
||||||
mediaSource = type.source,
|
mediaSource = type.source,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,24 @@
|
||||||
|
|
||||||
package io.element.android.libraries.mediaviewer.impl.gallery
|
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.MatrixTimelineItem
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VirtualItemFactory @Inject constructor(
|
class VirtualItemFactory @Inject constructor(
|
||||||
private val daySeparatorFormatter: DaySeparatorFormatter,
|
private val dateFormatter: DateFormatter,
|
||||||
) {
|
) {
|
||||||
fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? {
|
fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? {
|
||||||
return when (val virtual = timelineItem.virtual) {
|
return when (val virtual = timelineItem.virtual) {
|
||||||
is VirtualTimelineItem.DayDivider -> MediaItem.DateSeparator(
|
is VirtualTimelineItem.DayDivider -> MediaItem.DateSeparator(
|
||||||
id = timelineItem.uniqueId,
|
id = timelineItem.uniqueId,
|
||||||
formattedDate = daySeparatorFormatter.format(virtual.timestamp)
|
formattedDate = dateFormatter.format(
|
||||||
|
timestamp = virtual.timestamp,
|
||||||
|
mode = DateFormatterMode.Month,
|
||||||
|
useRelative = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
VirtualTimelineItem.LastForwardIndicator -> null
|
VirtualTimelineItem.LastForwardIndicator -> null
|
||||||
is VirtualTimelineItem.LoadingIndicator -> MediaItem.LoadingIndicator(
|
is VirtualTimelineItem.LoadingIndicator -> MediaItem.LoadingIndicator(
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class AndroidLocalMediaFactory @Inject constructor(
|
||||||
senderName = mediaInfo.senderName,
|
senderName = mediaInfo.senderName,
|
||||||
senderAvatar = mediaInfo.senderAvatar,
|
senderAvatar = mediaInfo.senderAvatar,
|
||||||
dateSent = mediaInfo.dateSent,
|
dateSent = mediaInfo.dateSent,
|
||||||
|
dateSentFull = mediaInfo.dateSentFull,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun createFromUri(
|
override fun createFromUri(
|
||||||
|
|
@ -63,6 +64,7 @@ class AndroidLocalMediaFactory @Inject constructor(
|
||||||
senderName = null,
|
senderName = null,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = null,
|
dateSent = null,
|
||||||
|
dateSentFull = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createFromUri(
|
private fun createFromUri(
|
||||||
|
|
@ -75,6 +77,7 @@ class AndroidLocalMediaFactory @Inject constructor(
|
||||||
senderName: String?,
|
senderName: String?,
|
||||||
senderAvatar: String?,
|
senderAvatar: String?,
|
||||||
dateSent: String?,
|
dateSent: String?,
|
||||||
|
dateSentFull: String?,
|
||||||
): LocalMedia {
|
): LocalMedia {
|
||||||
val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream
|
val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream
|
||||||
val fileName = name ?: context.getFileName(uri) ?: ""
|
val fileName = name ?: context.getFileName(uri) ?: ""
|
||||||
|
|
@ -92,6 +95,7 @@ class AndroidLocalMediaFactory @Inject constructor(
|
||||||
senderName = senderName,
|
senderName = senderName,
|
||||||
senderAvatar = senderAvatar,
|
senderAvatar = senderAvatar,
|
||||||
dateSent = dateSent,
|
dateSent = dateSent,
|
||||||
|
dateSentFull = dateSentFull,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
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.FakeDateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
|
||||||
import io.element.android.libraries.matrix.api.media.AudioDetails
|
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.AudioInfo
|
||||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||||
|
|
@ -162,7 +161,8 @@ class DefaultEventItemFactoryTest {
|
||||||
senderId = A_USER_ID,
|
senderId = A_USER_ID,
|
||||||
senderName = "alice",
|
senderName = "alice",
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = A_FORMATTED_DATE,
|
dateSent = "0 Day false",
|
||||||
|
dateSentFull = "0 Full false",
|
||||||
),
|
),
|
||||||
mediaSource = MediaSource(""),
|
mediaSource = MediaSource(""),
|
||||||
)
|
)
|
||||||
|
|
@ -209,7 +209,8 @@ class DefaultEventItemFactoryTest {
|
||||||
senderId = A_USER_ID,
|
senderId = A_USER_ID,
|
||||||
senderName = "alice",
|
senderName = "alice",
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = A_FORMATTED_DATE,
|
dateSent = "0 Day false",
|
||||||
|
dateSentFull = "0 Full false",
|
||||||
),
|
),
|
||||||
mediaSource = MediaSource(""),
|
mediaSource = MediaSource(""),
|
||||||
thumbnailSource = null,
|
thumbnailSource = null,
|
||||||
|
|
@ -253,7 +254,8 @@ class DefaultEventItemFactoryTest {
|
||||||
senderId = A_USER_ID,
|
senderId = A_USER_ID,
|
||||||
senderName = "alice",
|
senderName = "alice",
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = A_FORMATTED_DATE,
|
dateSent = "0 Day false",
|
||||||
|
dateSentFull = "0 Full false",
|
||||||
),
|
),
|
||||||
mediaSource = MediaSource(""),
|
mediaSource = MediaSource(""),
|
||||||
)
|
)
|
||||||
|
|
@ -301,7 +303,8 @@ class DefaultEventItemFactoryTest {
|
||||||
senderId = A_USER_ID,
|
senderId = A_USER_ID,
|
||||||
senderName = "alice",
|
senderName = "alice",
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = A_FORMATTED_DATE,
|
dateSent = "0 Day false",
|
||||||
|
dateSentFull = "0 Full false",
|
||||||
),
|
),
|
||||||
mediaSource = MediaSource(""),
|
mediaSource = MediaSource(""),
|
||||||
thumbnailSource = null,
|
thumbnailSource = null,
|
||||||
|
|
@ -350,7 +353,8 @@ class DefaultEventItemFactoryTest {
|
||||||
senderId = A_USER_ID,
|
senderId = A_USER_ID,
|
||||||
senderName = "alice",
|
senderName = "alice",
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = A_FORMATTED_DATE,
|
dateSent = "0 Day false",
|
||||||
|
dateSentFull = "0 Full false",
|
||||||
),
|
),
|
||||||
mediaSource = MediaSource(""),
|
mediaSource = MediaSource(""),
|
||||||
)
|
)
|
||||||
|
|
@ -397,7 +401,8 @@ class DefaultEventItemFactoryTest {
|
||||||
senderId = A_USER_ID,
|
senderId = A_USER_ID,
|
||||||
senderName = "alice",
|
senderName = "alice",
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = A_FORMATTED_DATE,
|
dateSent = "0 Day false",
|
||||||
|
dateSentFull = "0 Full false",
|
||||||
),
|
),
|
||||||
mediaSource = MediaSource(""),
|
mediaSource = MediaSource(""),
|
||||||
thumbnailSource = null,
|
thumbnailSource = null,
|
||||||
|
|
@ -409,5 +414,5 @@ class DefaultEventItemFactoryTest {
|
||||||
private fun createEventItemFactory() = EventItemFactory(
|
private fun createEventItemFactory() = EventItemFactory(
|
||||||
fileSizeFormatter = FakeFileSizeFormatter(),
|
fileSizeFormatter = FakeFileSizeFormatter(),
|
||||||
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
|
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 android.net.Uri
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
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.FakeDateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
|
|
||||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
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.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
|
@ -254,12 +252,12 @@ class MediaGalleryPresenterTest {
|
||||||
timelineMediaItemsFactory = TimelineMediaItemsFactory(
|
timelineMediaItemsFactory = TimelineMediaItemsFactory(
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
virtualItemFactory = VirtualItemFactory(
|
virtualItemFactory = VirtualItemFactory(
|
||||||
daySeparatorFormatter = FakeDaySeparatorFormatter(),
|
dateFormatter = FakeDateFormatter(),
|
||||||
),
|
),
|
||||||
eventItemFactory = EventItemFactory(
|
eventItemFactory = EventItemFactory(
|
||||||
fileSizeFormatter = FakeFileSizeFormatter(),
|
fileSizeFormatter = FakeFileSizeFormatter(),
|
||||||
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
|
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
|
||||||
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(A_FORMATTED_DATE),
|
dateFormatter = FakeDateFormatter(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
localMediaFactory = localMediaFactory,
|
localMediaFactory = localMediaFactory,
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,15 @@ class AndroidLocalMediaFactoryTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test AndroidLocalMediaFactory`() {
|
fun `test AndroidLocalMediaFactory`() {
|
||||||
val sut = createAndroidLocalMediaFactory()
|
val sut = createAndroidLocalMediaFactory()
|
||||||
val result = sut.createFromMediaFile(aMediaFile(), anImageMediaInfo(
|
val result = sut.createFromMediaFile(
|
||||||
senderId = A_USER_ID,
|
mediaFile = aMediaFile(),
|
||||||
senderName = A_USER_NAME,
|
mediaInfo = anImageMediaInfo(
|
||||||
dateSent = "12:34",
|
senderId = A_USER_ID,
|
||||||
))
|
senderName = A_USER_NAME,
|
||||||
|
dateSent = "12:34",
|
||||||
|
dateSentFull = "full",
|
||||||
|
)
|
||||||
|
)
|
||||||
assertThat(result.uri.toString()).endsWith("aPath")
|
assertThat(result.uri.toString()).endsWith("aPath")
|
||||||
assertThat(result.info).isEqualTo(
|
assertThat(result.info).isEqualTo(
|
||||||
MediaInfo(
|
MediaInfo(
|
||||||
|
|
@ -43,7 +47,8 @@ class AndroidLocalMediaFactoryTest {
|
||||||
senderId = A_USER_ID,
|
senderId = A_USER_ID,
|
||||||
senderName = A_USER_NAME,
|
senderName = A_USER_NAME,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = "12:34"
|
dateSent = "12:34",
|
||||||
|
dateSentFull = "full"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,8 @@ class FakeLocalMediaFactory(
|
||||||
senderId = null,
|
senderId = null,
|
||||||
senderName = null,
|
senderName = null,
|
||||||
senderAvatar = null,
|
senderAvatar = null,
|
||||||
dateSent = null
|
dateSent = null,
|
||||||
|
dateSentFull = null,
|
||||||
)
|
)
|
||||||
return aLocalMedia(uri, mediaInfo)
|
return aLocalMedia(uri, mediaInfo)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
|
implementation(projects.services.toolbox.api)
|
||||||
implementation(libs.test.turbine)
|
implementation(libs.test.turbine)
|
||||||
implementation(libs.molecule.runtime)
|
implementation(libs.molecule.runtime)
|
||||||
implementation(libs.androidx.compose.ui.test.junit)
|
implementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.tests.testutils
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
|
||||||
|
class InstrumentationStringProvider : StringProvider {
|
||||||
|
private val resource = InstrumentationRegistry.getInstrumentation().context.resources
|
||||||
|
override fun getString(resId: Int): String {
|
||||||
|
return resource.getString(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getString(resId: Int, vararg formatArgs: Any?): String {
|
||||||
|
return resource.getString(resId, *formatArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any?): String {
|
||||||
|
return resource.getQuantityString(resId, quantity, *formatArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e67a540966100272311381e87011149cdb15c8191a6f2bbc40d1febff999c431
|
oid sha256:dbef9e8e887fa493ca9b875e64dc164bf35dd84ad25b84a1ad9b6fd523b26c38
|
||||||
size 24067
|
size 27280
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:65023f7233f112547ccd0850c321b2d6610cfb6a1371c494f68722e75874f871
|
oid sha256:c1a0bc9c83b7e01f9492443433781509c0d899b397652fc7e9b7a539d8ce0412
|
||||||
size 45915
|
size 48219
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e2c2e01bd133c7b84e381141b6baf22783cb585cb3af9f790785dbf8aeaeeed6
|
oid sha256:3a624587095825c971186288a0ad5a40ddfcadc8f4b9f92b1102d8ae20ca3bda
|
||||||
size 47644
|
size 49821
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9bd6fb63059cebc7dc7b850b5aa73549ab9846eda68b1b6d9a4f8e0716d42c3c
|
oid sha256:4cdcc38bfae43298654c3e09d7ab1ca1e0d2fd32157a464539a360f3943f4f75
|
||||||
size 40419
|
size 42839
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6b4e4a075bcb4b95455c27ce2e5f9f1bdac4a1aca22ad944dd53fdbaeb6ca970
|
oid sha256:a56b3e488ceecd0bfb763d60b6827f7d8c460fa198d380a502ff0d97b13c9bc0
|
||||||
size 44550
|
size 45962
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1a2bc7e9098301d17e72a61a7baf01e52aa11566e1da4ffe3b43a66fa37652b2
|
oid sha256:600f28097c6ef8c54fe136d9bb05b8b800d693bff23f238a4e3e0216ee9918ff
|
||||||
size 42103
|
size 44404
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f097af2773ff2ecf70a69e4292dc118b4c8616f1c8f979e7d121e86a16ef3072
|
oid sha256:054cfe9290f91cf0f5d17e8f7c49e71eb50a2bb17067b1edc3c29f89410f76e8
|
||||||
size 38506
|
size 40806
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8f49870a5333cbaf1c6c3ac5ddcb85290d04efc79ffec93d8dc39b59f9712820
|
oid sha256:814bb524af65d077e3610e8cb920e5207aa808111a530357e8687831bd9c0413
|
||||||
size 42391
|
size 44687
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:883f160afa3fc3d2c0be5098205ea616875084ee0f122ebf994d0fee53a8ecd1
|
oid sha256:4da93eb0b9fcea861ae80d6e14bc852acaacea52402b1b41e8c982b9c06b2b63
|
||||||
size 39847
|
size 42105
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7790c892daeee8910ce17caac2c957d09fffec0d41a51b3adc1d5bef8dcee1a1
|
oid sha256:4886407cfe23d5d16aadeef4226698957dad99dfc4d9ac5c184fe64347b3ee41
|
||||||
size 42261
|
size 44524
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:bc7b733680a0d86ee1231345e58641fb3a208da21de62a50f624d9ae04c6e140
|
oid sha256:3c0d8491dbf0b6f50ae6be5c7aaf0d77b9eaa3a31e7763f120d6e08993c1c74b
|
||||||
size 31661
|
size 34028
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:20504466241e36817557812f6b378aceab9c2e271a596bd3d037c6be41af7c54
|
oid sha256:64a6d1a6af14176c933387a7a36de0d7392b1cd31cb04fcc5c6a282317874aac
|
||||||
size 23624
|
size 26597
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:48028e4c3ca7e9b871683165f029430bcd4e0fc2411e4ffc83a93abb641d96a2
|
oid sha256:f92ebc24de4dd34b84fcbbcc656a369d4a79bca160666c2fd4d32b5516c2f8fb
|
||||||
size 45132
|
size 47245
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b040fe47521f5d9d76d7892412bb2b5cf7e2b93b951f5de5f772503797fb6b24
|
oid sha256:2a4a3730a941a4c6863c116b401ea7e4d5c2cef3fe4098e353954c4f93aa660a
|
||||||
size 46548
|
size 48771
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6db046a97f14f2df3db2415d71a02ffd6e706fe73def67c21f0d844be279da59
|
oid sha256:77521de94c04228ca0fb5d09cb7809527cf0dc0acc52a271a8fa4738a752aa66
|
||||||
size 39617
|
size 42028
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7ee53243923d7b5adcc2094d3fe6bb87be00a179cae11966dcb230f2c3e8246e
|
oid sha256:171ef67bdc5a1b6d921ecbdb13e3c79dfab073d7289862181a69515e6063c4c5
|
||||||
size 43681
|
size 45246
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b88e54ba4743e1e245b5a4c7d6b4045f816241f6cfc8716b8cede0b62666222b
|
oid sha256:1a8328afeab339104a83db16d7b4b894c060ff7e80c2b5c4d6ec64d3bc4cdc3a
|
||||||
size 41286
|
size 43613
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d88b4d82bb0d416aff7b32647479181ac9702c36c08370e7671d6e5ef80687c7
|
oid sha256:d575d08141bf446de510162f5dc1e04fccf4831649495bdf94945bd8957768a6
|
||||||
size 37825
|
size 40201
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b3d4dd1766a3f46acd86bdd5ec920c56a5e5f897c81c1477df10ba59dcd3d5b7
|
oid sha256:606379fcb517afa4583ac03fcb2af4f5649a7fc064df841f442c19f5a71f629b
|
||||||
size 41554
|
size 43863
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:842fbea74e53c8804fd0a3e9fe96dcb21b5c91a099e1de33d8ec983fd9a8a80a
|
oid sha256:d63dec242ea479beb9a7bf0a58c6f31e2bba84df0a7db51fb07ea46612944d9a
|
||||||
size 39112
|
size 41355
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1311ec5e008b44d81e6556164f780c57dea3e4721cc47caccdbf337dd4027eb7
|
oid sha256:f5421b7b5ce8441213b1323b4cd88c2f5aff6689ecd091b5be581fe5690cd611
|
||||||
size 41402
|
size 43685
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d2cf27d2f053f33c67d944a8f45e0206165b2db2f3b959eb9e0bb943f84fe6a1
|
oid sha256:e1f4600552aebd3a567251a85105a423882942b23e96f7958238a9b5ba0bb8fc
|
||||||
size 30657
|
size 33137
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d0506879a20bd64cb3a4ea41c93dfa78da1ca3b0c2728ce3044caa56f6648584
|
||||||
|
size 105611
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2e78aa3521464a53c000298dbd4ef51d4d0ea1d3c75b7bb8dcbd933701bab36b
|
||||||
|
size 84060
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f9eec4fc7e72588f957cd7d741e29b8eaed71555fc0d66c43e6ae69cc64b924c
|
||||||
|
size 87650
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d4ae4608296b5bb24c128572cc6b80379a9e3cd12ec89db9693c301e427f6ae5
|
||||||
|
size 82330
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e125730c22fcbc843fc0445e0af06d48b7dc31896053b5a7341b6dde84526eb8
|
||||||
|
size 82540
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4f3557280a4010e7ffdb6f22c11561573780c0b24c27e264073d3a3899169014
|
oid sha256:2c7ba307cf21056623bf35c8558809fdbc6deaacf4e9365a99ffcf829e8d9188
|
||||||
size 29659
|
size 34477
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:63efb9744640b71cfde672821c7c1ea8b33f9c3c2e2ad4d5f41149b9749b31c2
|
oid sha256:724bff130c547e7f0065ccb4c1b3319162ded2e6c1c1db666f4e08e01289a5a0
|
||||||
size 28016
|
size 32776
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:983e505a211c92ae92e091f9ba7cc43a655d5f3ce6d6bcf70971d43984507326
|
oid sha256:25ebb7460550b31bf2cebfca7bdab32e5f89e327b68f6687bf490e5d14cb9220
|
||||||
size 36153
|
size 40950
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4e7a43195bd617ee952ea084e6b17a7e08e8b3634e8f3ce7df6e98f067ab08dc
|
oid sha256:39ecc9a50285df492417f5f22adcc391be2dcad0cc388efb756274c83aba077d
|
||||||
size 34296
|
size 39030
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8a6916d2441316cb3ef55ee4c6a3b3ba9134d246d72c27aa3871408a6b9e59fc
|
oid sha256:2d5f183f53f9e8d0dbbae473f2f853f4372dbff15b1d6ea17e78b4770781fa34
|
||||||
size 30716
|
size 35468
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,12 @@
|
||||||
".*voice_message_tooltip"
|
".*voice_message_tooltip"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name" : ":libraries:dateformatter:impl",
|
||||||
|
"includeRegex" : [
|
||||||
|
"common\\.date\\..*"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name" : ":libraries:permissions:api",
|
"name" : ":libraries:permissions:api",
|
||||||
"includeRegex" : [
|
"includeRegex" : [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue