From b3fef1a54c86697f23ba652e53749224731a5132 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 9 Jul 2026 16:42:50 -0400 Subject: [PATCH] feat(send/contactlist): update cell layout arrangement Signed-off-by: Brandon McAnsh --- .../core/src/main/res/values/strings.xml | 2 + .../screens/components/ContactList.kt | 102 ++++++++++++++---- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml index 2cbddb0b4..e34b874a9 100644 --- a/apps/flipcash/core/src/main/res/values/strings.xml +++ b/apps/flipcash/core/src/main/res/values/strings.xml @@ -828,4 +828,6 @@ You sent %1$s You received %1$s + Unknown Contact + \ No newline at end of file diff --git a/apps/flipcash/features/direct-send/src/main/kotlin/com/flipcash/app/directsend/internal/screens/components/ContactList.kt b/apps/flipcash/features/direct-send/src/main/kotlin/com/flipcash/app/directsend/internal/screens/components/ContactList.kt index d09a55218..ebd434db7 100644 --- a/apps/flipcash/features/direct-send/src/main/kotlin/com/flipcash/app/directsend/internal/screens/components/ContactList.kt +++ b/apps/flipcash/features/direct-send/src/main/kotlin/com/flipcash/app/directsend/internal/screens/components/ContactList.kt @@ -1,5 +1,6 @@ package com.flipcash.app.directsend.internal.screens.components +import android.text.format.DateFormat import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -33,6 +34,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter @@ -43,6 +45,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewWrapper import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import com.flipcash.app.contacts.ui.ContactAvatar import com.flipcash.app.core.android.extensions.launchAppSettings @@ -58,7 +61,12 @@ import com.getcode.theme.extraLarge import com.getcode.theme.extraSmall import com.getcode.ui.core.verticalScrollStateGradient import com.getcode.ui.theme.CodeButton +import com.getcode.util.formatLocalized import com.getcode.util.permissions.PermissionResult +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlin.time.Clock +import kotlin.time.Instant @Composable internal fun ContactList( @@ -128,6 +136,7 @@ internal fun ContactList( lastMessagePreview = item.lastMessagePreview, unreadCount = item.unreadCount, showDivider = !isLastInSection, + lastActivity = item.lastActivity, ) { onItemClick(item) } @@ -321,10 +330,12 @@ private fun ContactRowItem( isOnFlipcash: Boolean, modifier: Modifier = Modifier, lastMessagePreview: String? = null, + lastActivity: Instant? = null, unreadCount: Int = 0, showDivider: Boolean = true, onClick: () -> Unit, ) { + val isActiveChat = lastMessagePreview != null Column( modifier = modifier .fillMaxWidth() @@ -343,17 +354,9 @@ private fun ContactRowItem( Row( verticalAlignment = Alignment.CenterVertically, ) { - if (unreadCount > 0) { - UnreadBadge( - modifier = Modifier.padding( - start = CodeTheme.dimens.grid.x1, - end = CodeTheme.dimens.grid.x1, - ), - count = unreadCount - ) - } else { - Box(modifier = Modifier.requiredWidth(CodeTheme.dimens.inset)) - } + // Spacer used by the unread indicator that we moved + // left for spacing compatibility and easy add back later + Box(modifier = Modifier.requiredWidth(CodeTheme.dimens.inset)) ContactAvatar( contact = contact, modifier = Modifier @@ -369,17 +372,50 @@ private fun ContactRowItem( horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1), ) { Text( - modifier = Modifier.weight(1f, fill = false), + modifier = Modifier.weight(1f), text = contact.displayName, style = CodeTheme.typography.textMedium, color = CodeTheme.colors.textMain, ) - if (contact.isUnknown) { + + when { + contact.isUnknown -> { + Text( + modifier = Modifier.background( + color = CodeTheme.colors.surfaceVariant, + shape = CircleShape, + ).padding( + vertical = CodeTheme.dimens.grid.x1, + horizontal = CodeTheme.dimens.grid.x2, + ), + text = stringResource(R.string.label_unknownContact), + style = CodeTheme.typography.caption.copy(fontSize = 10.sp, lineHeight = 10.sp), + color = CodeTheme.colors.textSecondary, + ) + } + lastActivity != null && isActiveChat -> { + Text( + text = formatLastActivity(lastActivity), + style = CodeTheme.typography.caption, + color = CodeTheme.colors.textSecondary, + ) + } + } + + if (unreadCount > 0) { + UnreadBadge( + modifier = Modifier.padding( + start = CodeTheme.dimens.grid.x1, + end = CodeTheme.dimens.grid.x1, + ), + count = unreadCount + ) + } else if (isActiveChat) { Icon( - modifier = Modifier.size(CodeTheme.dimens.staticGrid.x3), - painter = painterResource(R.drawable.ic_unknown_contact), + modifier = Modifier.scale(0.6f), + painter = painterResource(id = R.drawable.ic_chevron_right), contentDescription = null, - tint = CodeTheme.colors.textMain, + tint = CodeTheme.colors.textSecondary, ) } } @@ -388,7 +424,6 @@ private fun ContactRowItem( if (showSubtitle) { Text( - modifier = Modifier.padding(top = CodeTheme.dimens.grid.x1), text = if (isOnFlipcash && !lastMessagePreview.isNullOrEmpty()) { lastMessagePreview } else { @@ -403,11 +438,13 @@ private fun ContactRowItem( } if (isOnFlipcash) { - Icon( - painter = painterResource(id = R.drawable.ic_chevron_right), - contentDescription = null, - tint = CodeTheme.colors.textSecondary, - ) + if (!isActiveChat) { + Icon( + painter = painterResource(id = R.drawable.ic_chevron_right), + contentDescription = null, + tint = CodeTheme.colors.textSecondary, + ) + } } else { Text( modifier = Modifier @@ -510,6 +547,26 @@ private fun EmptySearchState( } } +@Composable +private fun formatLastActivity(instant: Instant): String { + val context = LocalContext.current + val is24Hour = DateFormat.is24HourFormat(context) + val tz = TimeZone.currentSystemDefault() + val todayDate = Clock.System.now().toLocalDateTime(tz).date + val messageDate = instant.toLocalDateTime(tz).date + val dayDiff = todayDate.toEpochDays() - messageDate.toEpochDays() + + val time = instant.formatLocalized("h:mm a", is24Hour = is24Hour, if24Hour = "H:mm") + + return when { + dayDiff == 0L -> time + dayDiff == 1L -> stringResource(R.string.label_chatReceipt_yesterday) + dayDiff in 2L..6L -> instant.formatLocalized("EEEE") + messageDate.year == todayDate.year -> instant.formatLocalized("MMM d") + else -> instant.formatLocalized("MMM d, yyyy") + } +} + @Preview @PreviewWrapper(FlipcashThemeWrapper::class) @Composable @@ -532,6 +589,7 @@ private fun ContactListPreview() { displayNumber = "(555) 000-${1000 + i}", ), isOnFlipcash = true, + unreadCount = 12, ) } val otherContacts = fakeNames.drop(6).mapIndexed { i, name ->