Skip to content

Commit c5da273

Browse files
Improve date display, forward timestamps, and group creation flow (#281)
Date separators in conversations currently show raw dates like "4 May" regardless of how recent the message is. This makes it hard to quickly gauge message recency at a glance. This change updates `formatChatDayLabel()` to display relative labels — "Today", "Yesterday", or the weekday name — for messages within the past week, falling back to the full date for older messages. The year-boundary edge case (e.g. Dec 31 → Jan 1) is handled correctly. Forwarded messages only showed the time of the original post, which loses context when the forward is from a different day. The forward header now shows the date alongside the time for non-today forwards (e.g. "3 May, 14:30"). Creating a group with only yourself was impossible because the member selection step required at least one other user before allowing the user to proceed. Both official Telegram clients allow this. The `isNotEmpty()` gate on the member list has been removed so users can continue to the group info screen with zero members selected. Closes #87, #90, #149
1 parent 725f1d3 commit c5da273

7 files changed

Lines changed: 68 additions & 19 deletions

File tree

presentation/src/main/java/org/monogram/presentation/features/chats/conversation/ui/ChatDateLabel.kt

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.monogram.presentation.features.chats.conversation.ui
22

3+
import android.content.Context
4+
import org.monogram.presentation.R
35
import java.text.SimpleDateFormat
46
import java.util.Calendar
57
import java.util.Date
@@ -15,13 +17,36 @@ fun isTodayTimestamp(timestamp: Int, locale: Locale = Locale.getDefault()): Bool
1517
calendar.get(Calendar.DAY_OF_YEAR) == todayDayOfYear
1618
}
1719

18-
fun formatChatDayLabel(timestamp: Int, locale: Locale = Locale.getDefault()): String {
20+
fun formatChatDayLabel(timestamp: Int, context: Context, locale: Locale = Locale.getDefault()): String {
1921
val date = Date(timestamp.toLong() * 1000)
20-
val calendar = Calendar.getInstance(locale)
21-
val currentYear = calendar.get(Calendar.YEAR)
22-
calendar.time = date
23-
val messageYear = calendar.get(Calendar.YEAR)
22+
val now = Calendar.getInstance(locale)
23+
val msgCal = Calendar.getInstance(locale).apply { time = date }
24+
25+
val nowYear = now.get(Calendar.YEAR)
26+
val nowDay = now.get(Calendar.DAY_OF_YEAR)
27+
val msgYear = msgCal.get(Calendar.YEAR)
28+
val msgDay = msgCal.get(Calendar.DAY_OF_YEAR)
29+
30+
if (nowYear == msgYear) {
31+
val dayDiff = nowDay - msgDay
32+
return when {
33+
dayDiff == 0 -> context.getString(R.string.chat_date_today)
34+
dayDiff == 1 -> context.getString(R.string.chat_date_yesterday)
35+
dayDiff in 2..6 -> SimpleDateFormat("EEEE", locale).format(date)
36+
else -> SimpleDateFormat("d MMMM", locale).format(date)
37+
}
38+
}
39+
40+
if (nowYear - msgYear == 1 && msgDay >= 359 && nowDay <= 6) {
41+
val daysInMsgYear = msgCal.getActualMaximum(Calendar.DAY_OF_YEAR)
42+
val dayDiff = (daysInMsgYear - msgDay) + nowDay
43+
return when {
44+
dayDiff == 0 -> context.getString(R.string.chat_date_today)
45+
dayDiff == 1 -> context.getString(R.string.chat_date_yesterday)
46+
dayDiff in 2..6 -> SimpleDateFormat("EEEE", locale).format(date)
47+
else -> SimpleDateFormat("d MMMM yyyy", locale).format(date)
48+
}
49+
}
2450

25-
val pattern = if (messageYear == currentYear) "d MMMM" else "d MMMM yyyy"
26-
return SimpleDateFormat(pattern, locale).format(date)
51+
return SimpleDateFormat("d MMMM yyyy", locale).format(date)
2752
}

presentation/src/main/java/org/monogram/presentation/features/chats/conversation/ui/DateSeparator.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import androidx.compose.material3.Text
1010
import androidx.compose.runtime.Composable
1111
import androidx.compose.ui.Alignment
1212
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.platform.LocalContext
1314
import androidx.compose.ui.unit.dp
1415

1516
@Composable
1617
fun DateSeparator(timestamp: Int) {
17-
val text = formatChatDayLabel(timestamp)
18+
val context = LocalContext.current
19+
val text = formatChatDayLabel(timestamp, context)
1820
Box(
1921
modifier = Modifier
2022
.fillMaxWidth()

presentation/src/main/java/org/monogram/presentation/features/chats/conversation/ui/content/ChatContentList.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import androidx.compose.ui.geometry.Offset
7373
import androidx.compose.ui.graphics.Color
7474
import androidx.compose.ui.graphics.graphicsLayer
7575
import androidx.compose.ui.graphics.luminance
76+
import androidx.compose.ui.platform.LocalContext
7677
import androidx.compose.ui.platform.LocalDensity
7778
import androidx.compose.ui.res.stringResource
7879
import androidx.compose.ui.semantics.contentDescription
@@ -191,6 +192,7 @@ fun ChatContentList(
191192
) {
192193
val isComments = state.isComments
193194
val appearance = state.toAppearanceConfig()
195+
val context = LocalContext.current
194196
val density = LocalDensity.current
195197
val isScrolling by remember(scrollState) { derivedStateOf { scrollState.isScrollInProgress } }
196198
val latestState by rememberUpdatedState(state)
@@ -306,7 +308,7 @@ fun ChatContentList(
306308
viewportTopOffset = viewportTopOffset
307309
)
308310
?: return@derivedStateOf null
309-
formatChatDayLabel(anchor.mainTimestamp())
311+
formatChatDayLabel(anchor.mainTimestamp(), context)
310312
}
311313
}
312314

presentation/src/main/java/org/monogram/presentation/features/chats/conversation/ui/message/ForwardContent.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import org.monogram.domain.models.ForwardInfo
3333
import org.monogram.domain.models.ForwardOriginType
3434
import org.monogram.presentation.core.ui.Avatar
3535
import org.monogram.presentation.core.util.DateFormatManager
36+
import org.monogram.presentation.features.chats.conversation.ui.isTodayTimestamp
3637

3738
@Composable
3839
fun ForwardContent(
@@ -132,9 +133,16 @@ fun ForwardContent(
132133
)
133134

134135
if (forwardInfo.date > 0) {
136+
val forwardDateText = remember(forwardInfo.date, timeFormat) {
137+
if (isTodayTimestamp(forwardInfo.date)) {
138+
formatTime(forwardInfo.date, timeFormat)
139+
} else {
140+
formatForwardDate(forwardInfo.date, timeFormat)
141+
}
142+
}
135143
Spacer(modifier = Modifier.width(6.dp))
136144
Text(
137-
text = formatTime(forwardInfo.date, timeFormat),
145+
text = forwardDateText,
138146
style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp),
139147
color = contentColor.copy(alpha = 0.56f),
140148
maxLines = 1

presentation/src/main/java/org/monogram/presentation/features/chats/conversation/ui/message/MessageUtils.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import org.monogram.presentation.features.chats.conversation.ui.channel.formatVi
5555
import java.io.File
5656
import java.text.BreakIterator
5757
import java.text.SimpleDateFormat
58+
import java.util.Calendar
5859
import java.util.Date
5960
import java.util.Locale
6061
import kotlin.math.log10
@@ -67,6 +68,17 @@ val LocalLinkHandler = staticCompositionLocalOf<(String) -> Unit> {
6768
fun formatTime(ts: Int, timeFormat: String): String =
6869
SimpleDateFormat(timeFormat, Locale.getDefault()).format(Date(ts.toLong() * 1000))
6970

71+
fun formatForwardDate(ts: Int, timeFormat: String): String {
72+
val date = Date(ts.toLong() * 1000)
73+
val locale = Locale.getDefault()
74+
val cal = Calendar.getInstance(locale).apply { time = date }
75+
val now = Calendar.getInstance(locale)
76+
val datePattern = if (cal.get(Calendar.YEAR) == now.get(Calendar.YEAR)) "d MMM" else "d MMM yyyy"
77+
val datePart = SimpleDateFormat(datePattern, locale).format(date)
78+
val timePart = SimpleDateFormat(timeFormat, locale).format(date)
79+
return "$datePart, $timePart"
80+
}
81+
7082
fun formatDuration(seconds: Int): String {
7183
val m = seconds / 60
7284
val s = seconds % 60

presentation/src/main/java/org/monogram/presentation/features/chats/creation/DefaultNewChatComponent.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -225,15 +225,13 @@ class DefaultNewChatComponent(
225225

226226
when (currentState.step) {
227227
NewChatComponent.Step.GROUP_MEMBERS -> {
228-
if (currentState.selectedUserIds.isNotEmpty()) {
229-
_state.update {
230-
it.copy(
231-
step = NewChatComponent.Step.GROUP_INFO,
232-
searchQuery = "",
233-
searchResults = emptyList(),
234-
validationError = null
235-
)
236-
}
228+
_state.update {
229+
it.copy(
230+
step = NewChatComponent.Step.GROUP_INFO,
231+
searchQuery = "",
232+
searchResults = emptyList(),
233+
validationError = null
234+
)
237235
}
238236
}
239237

presentation/src/main/res/values/string.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,8 @@
21022102
<string name="logs_media_unsupported">Unsupported message</string>
21032103

21042104
<!-- Date and Formats -->
2105+
<string name="chat_date_today">Today</string>
2106+
<string name="chat_date_yesterday">Yesterday</string>
21052107
<string name="format_duration">%1$02d:%2$02d</string>
21062108

21072109
<!-- Views Count -->

0 commit comments

Comments
 (0)