Skip to content

Commit

Permalink
feat: grouping message dates (WPB-1733) (#2964)
Browse files Browse the repository at this point in the history
Signed-off-by: alexandreferris <[email protected]>
  • Loading branch information
alexandreferris authored May 6, 2024
1 parent a0ae53d commit 8e17489
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.wire.android.ui.home.conversations

import android.annotation.SuppressLint
import android.net.Uri
import android.text.format.DateUtils
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandIn
Expand All @@ -28,6 +29,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
Expand All @@ -46,9 +48,11 @@ import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand Down Expand Up @@ -142,8 +146,11 @@ import com.wire.android.ui.home.messagecomposer.state.MessageComposerStateHolder
import com.wire.android.ui.home.messagecomposer.state.rememberMessageComposerStateHolder
import com.wire.android.ui.legalhold.dialog.subject.LegalHoldSubjectMessageDialog
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.MessageDateTimeGroup
import com.wire.android.util.normalizeLink
import com.wire.android.util.permission.PermissionDenialType
import com.wire.android.util.serverDate
import com.wire.android.util.ui.UIText
import com.wire.android.util.ui.openDownloadFolder
import com.wire.kalium.logic.NetworkFailure
Expand All @@ -164,6 +171,8 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.datetime.Instant
import java.util.Date
import java.util.Locale
import kotlin.time.Duration.Companion.milliseconds

/**
Expand Down Expand Up @@ -977,6 +986,7 @@ fun MessageList(
) {
val prevItemCount = remember { mutableStateOf(lazyPagingMessages.itemCount) }
val readLastMessageAtStartTriggered = remember { mutableStateOf(false) }
val currentTime by currentTimeInMillisFlow.collectAsState(initial = System.currentTimeMillis())

LaunchedEffect(lazyPagingMessages.itemCount) {
if (lazyPagingMessages.itemCount > prevItemCount.value && selectedMessageId == null) {
Expand Down Expand Up @@ -1048,6 +1058,23 @@ fun MessageList(
val showAuthor = rememberShouldShowHeader(index, message, lazyPagingMessages)
val useSmallBottomPadding = rememberShouldHaveSmallBottomPadding(index, message, lazyPagingMessages)

if (index > 0) {
val previousMessage = lazyPagingMessages[index - 1] ?: message

val currentGroup = message.header.messageTime.getFormattedDateGroup(now = currentTime)
val previousGroup = previousMessage.header.messageTime.getFormattedDateGroup(now = currentTime)

if (currentGroup != previousGroup) {
previousMessage.header.messageTime.utcISO.serverDate()?.let { serverDate ->
MessageGroupDateTime(
messageDateTime = serverDate,
messageDateTimeGroup = previousGroup,
now = currentTime
)
}
}
}

MessageContainerItem(
message = message,
conversationDetailsData = conversationDetailsData,
Expand Down Expand Up @@ -1075,8 +1102,7 @@ fun MessageList(
}
),
isSelectedMessage = (message.header.messageId == selectedMessageId),
isInteractionAvailable = interactionAvailability == InteractionAvailability.ENABLED,
currentTimeInMillisFlow = currentTimeInMillisFlow
isInteractionAvailable = interactionAvailability == InteractionAvailability.ENABLED
)
}
}
Expand All @@ -1085,6 +1111,76 @@ fun MessageList(
)
}

@Composable
private fun MessageGroupDateTime(
now: Long,
messageDateTime: Date,
messageDateTimeGroup: MessageDateTimeGroup?
) {
val context = LocalContext.current

val timeString = when (messageDateTimeGroup) {
is MessageDateTimeGroup.Now -> context.resources.getString(R.string.message_datetime_now)
is MessageDateTimeGroup.Within30Minutes -> DateUtils.getRelativeTimeSpanString(
messageDateTime.time,
now,
DateUtils.MINUTE_IN_MILLIS
).toString()
is MessageDateTimeGroup.Daily -> {
when (messageDateTimeGroup.type) {
MessageDateTimeGroup.Daily.Type.Today -> DateUtils.getRelativeDateTimeString(
context,
messageDateTime.time,
DateUtils.DAY_IN_MILLIS,
DateUtils.DAY_IN_MILLIS,
0
).toString()
MessageDateTimeGroup.Daily.Type.Yesterday ->
DateUtils.getRelativeDateTimeString(
context,
messageDateTime.time,
DateUtils.DAY_IN_MILLIS,
DateUtils.DAY_IN_MILLIS * 2,
0
).toString()
MessageDateTimeGroup.Daily.Type.WithinWeek -> DateUtils.formatDateTime(
context,
messageDateTime.time,
DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME
)
MessageDateTimeGroup.Daily.Type.NotWithinWeekButSameYear -> DateUtils.formatDateTime(
context,
messageDateTime.time,
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME
)
MessageDateTimeGroup.Daily.Type.Other -> DateUtils.formatDateTime(
context,
messageDateTime.time,
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_SHOW_TIME
)
}
}
null -> ""
}

Row(
Modifier
.fillMaxWidth()
.background(color = colorsScheme().divider)
.padding(
top = dimensions().spacing6x,
bottom = dimensions().spacing6x,
start = dimensions().spacing56x
)
) {
Text(
text = timeString.uppercase(Locale.getDefault()),
color = colorsScheme().secondaryText,
style = MaterialTheme.wireTypography.title03,
)
}
}

private fun updateLastReadMessage(
lastVisibleMessage: UIMessage,
lastUnreadMessageInstant: Instant?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ import com.wire.kalium.logic.data.asset.AssetTransferStatus
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.UserId
import kotlinx.collections.immutable.PersistentMap
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

@OptIn(ExperimentalFoundationApi::class)
@Suppress("ComplexMethod")
Expand Down Expand Up @@ -79,8 +77,7 @@ fun MessageContainerItem(
shouldDisplayFooter: Boolean = true,
onReplyClickable: Clickable? = null,
isSelectedMessage: Boolean = false,
isInteractionAvailable: Boolean = true,
currentTimeInMillisFlow: Flow<Long> = flow { },
isInteractionAvailable: Boolean = true
) {
val selfDeletionTimerState = rememberSelfDeletionTimer(message.header.messageStatus.expirationStatus)
if (
Expand Down Expand Up @@ -158,8 +155,7 @@ fun MessageContainerItem(
shouldDisplayMessageStatus = shouldDisplayMessageStatus,
shouldDisplayFooter = shouldDisplayFooter,
selfDeletionTimerState = selfDeletionTimerState,
useSmallBottomPadding = useSmallBottomPadding,
currentTimeInMillisFlow = currentTimeInMillisFlow
useSmallBottomPadding = useSmallBottomPadding
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import androidx.compose.material3.SwipeToDismissBoxState
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand Down Expand Up @@ -84,7 +83,6 @@ import com.wire.android.ui.home.conversations.model.MessageHeader
import com.wire.android.ui.home.conversations.model.MessageImage
import com.wire.android.ui.home.conversations.model.MessageSource
import com.wire.android.ui.home.conversations.model.MessageStatus
import com.wire.android.ui.home.conversations.model.MessageTime
import com.wire.android.ui.home.conversations.model.UIMessage
import com.wire.android.ui.home.conversations.model.UIMessageContent
import com.wire.android.ui.home.conversations.model.UIQuotedMessage
Expand All @@ -96,15 +94,12 @@ import com.wire.android.ui.home.conversations.model.messagetypes.location.Locati
import com.wire.android.ui.theme.Accent
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.MessageDateTime
import com.wire.android.util.launchGeoIntent
import com.wire.kalium.logic.data.asset.AssetTransferStatus
import com.wire.kalium.logic.data.asset.isSaved
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.UserId
import kotlinx.collections.immutable.PersistentMap
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.math.absoluteValue
import kotlin.math.min

Expand Down Expand Up @@ -136,7 +131,6 @@ fun RegularMessageItem(
onReplyClickable: Clickable? = null,
isInteractionAvailable: Boolean = true,
useSmallBottomPadding: Boolean = false,
currentTimeInMillisFlow: Flow<Long> = flow { },
selfDeletionTimerState: SelfDeletionTimerHelper.SelfDeletionTimerState = SelfDeletionTimerHelper.SelfDeletionTimerState.NotExpirable
): Unit = with(message) {
val onSwipe = remember(message) { { onSwipedToReply(message) } }
Expand All @@ -161,7 +155,7 @@ fun RegularMessageItem(
Column {
if (showAuthor) {
Spacer(modifier = Modifier.height(dimensions().avatarClickablePadding))
MessageAuthorRow(messageHeader = message.header, currentTimeInMillisFlow)
MessageAuthorRow(messageHeader = message.header)
Spacer(modifier = Modifier.height(dimensions().spacing4x))
}
if (selfDeletionTimerState is SelfDeletionTimerHelper.SelfDeletionTimerState.Expirable) {
Expand Down Expand Up @@ -447,7 +441,7 @@ fun MessageExpireLabel(messageContent: UIMessageContent?, assetTransferStatus: A
}

@Composable
private fun MessageAuthorRow(messageHeader: MessageHeader, currentTimeInMillisFlow: Flow<Long>) {
private fun MessageAuthorRow(messageHeader: MessageHeader) {
with(messageHeader) {
Row(verticalAlignment = Alignment.CenterVertically) {
Row(
Expand All @@ -470,8 +464,7 @@ private fun MessageAuthorRow(messageHeader: MessageHeader, currentTimeInMillisFl
}
}
MessageTimeLabel(
messageTime = messageHeader.messageTime,
currentTimeInMillisFlow = currentTimeInMillisFlow,
messageTime = messageHeader.messageTime.formattedDate,
modifier = Modifier.padding(start = dimensions().spacing6x)
)
}
Expand Down Expand Up @@ -510,35 +503,11 @@ private fun MessageFooter(

@Composable
private fun MessageTimeLabel(
messageTime: MessageTime,
currentTimeInMillisFlow: Flow<Long>,
messageTime: String,
modifier: Modifier = Modifier
) {

val currentTime by currentTimeInMillisFlow.collectAsState(initial = System.currentTimeMillis())

val messageDateTime = messageTime.formattedDate(now = currentTime)

val context = LocalContext.current

val timeString = when (messageDateTime) {
is MessageDateTime.Now -> context.resources.getString(R.string.message_datetime_now)
is MessageDateTime.Within30Minutes -> context.resources.getQuantityString(
R.plurals.message_datetime_minutes_ago,
messageDateTime.minutes,
messageDateTime.minutes
)

is MessageDateTime.Today -> context.resources.getString(R.string.message_datetime_today, messageDateTime.time)
is MessageDateTime.Yesterday -> context.resources.getString(R.string.message_datetime_yesterday, messageDateTime.time)
is MessageDateTime.WithinWeek -> context.resources.getString(R.string.message_datetime_other, messageDateTime.date)
is MessageDateTime.NotWithinWeekButSameYear -> context.resources.getString(R.string.message_datetime_other, messageDateTime.date)
is MessageDateTime.Other -> context.resources.getString(R.string.message_datetime_other, messageDateTime.date)
null -> ""
}

Text(
text = timeString,
text = messageTime,
style = MaterialTheme.typography.labelSmall.copy(color = MaterialTheme.wireColorScheme.secondaryText),
maxLines = 1,
modifier = modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import com.wire.android.ui.home.messagecomposer.SelfDeletionDuration
import com.wire.android.ui.markdown.MarkdownConstants
import com.wire.android.ui.theme.Accent
import com.wire.android.util.Copyable
import com.wire.android.util.MessageDateTime
import com.wire.android.util.MessageDateTimeGroup
import com.wire.android.util.groupedUIMessageDateTime
import com.wire.android.util.ui.LocalizedStringResource
import com.wire.android.util.ui.UIText
import com.wire.android.util.uiMessageDateTime
Expand Down Expand Up @@ -622,7 +623,8 @@ enum class MessageSource {
}

data class MessageTime(val utcISO: String) {
fun formattedDate(now: Long): MessageDateTime? = utcISO.uiMessageDateTime(now = now)
val formattedDate: String = utcISO.uiMessageDateTime() ?: ""
fun getFormattedDateGroup(now: Long): MessageDateTimeGroup? = utcISO.groupedUIMessageDateTime(now = now)
}

@Stable
Expand Down
Loading

0 comments on commit 8e17489

Please sign in to comment.