Skip to content

Commit af41134

Browse files
feat(message-item): add AdaptiveMessageItemHeaderRow for responsive header layout (#10709)
2 parents 5ea55e7 + 8172403 commit af41134

4 files changed

Lines changed: 232 additions & 129 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package net.thunderbird.feature.mail.message.list.ui.component.molecule
2+
3+
import android.content.res.Configuration
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.runtime.CompositionLocalProvider
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.graphics.Color
9+
import androidx.compose.ui.platform.LocalConfiguration
10+
import androidx.compose.ui.tooling.preview.PreviewLightDark
11+
import androidx.compose.ui.tooling.preview.PreviewParameter
12+
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
13+
import app.k9mail.core.ui.compose.common.window.WindowSizeClass
14+
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemeLightDark
15+
import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleSmall
16+
import net.thunderbird.core.ui.compose.theme2.MainTheme
17+
import net.thunderbird.feature.mail.message.list.ui.component.config.MessageItemAccountIndicator
18+
import net.thunderbird.feature.mail.message.list.ui.component.config.MessageItemConfiguration
19+
20+
private class AdaptiveMessageItemHeaderRowPreviewData(
21+
val previewName: String,
22+
val configuration: MessageItemConfiguration,
23+
val receivedAt: String,
24+
val screenWidth: Int,
25+
)
26+
27+
private val screenWidths = listOf(
28+
"Small" to (WindowSizeClass.SMALL_MAX_WIDTH - 1),
29+
"Compact" to (WindowSizeClass.COMPACT_MAX_WIDTH - 1),
30+
"Medium" to (WindowSizeClass.MEDIUM_MAX_WIDTH - 1),
31+
"Expanded" to WindowSizeClass.MEDIUM_MAX_WIDTH,
32+
)
33+
34+
private class AdaptiveMessageItemHeaderRowProvider :
35+
CollectionPreviewParameterProvider<AdaptiveMessageItemHeaderRowPreviewData>(
36+
screenWidths.flatMap { (sizeName, width) ->
37+
listOf(
38+
AdaptiveMessageItemHeaderRowPreviewData(
39+
previewName = "$sizeName - Default",
40+
configuration = MessageItemConfiguration(),
41+
receivedAt = "10:30 AM",
42+
screenWidth = width,
43+
),
44+
AdaptiveMessageItemHeaderRowPreviewData(
45+
previewName = "$sizeName - Yesterday timestamp",
46+
configuration = MessageItemConfiguration(),
47+
receivedAt = "Yesterday",
48+
screenWidth = width,
49+
),
50+
AdaptiveMessageItemHeaderRowPreviewData(
51+
previewName = "$sizeName - Date timestamp",
52+
configuration = MessageItemConfiguration(),
53+
receivedAt = "Mar 12, 2026",
54+
screenWidth = width,
55+
),
56+
AdaptiveMessageItemHeaderRowPreviewData(
57+
previewName = "$sizeName - With account indicator",
58+
configuration = MessageItemConfiguration(
59+
accountIndicator = MessageItemAccountIndicator(color = Color(color = 0xFF1565C0)),
60+
),
61+
receivedAt = "9:15 AM",
62+
screenWidth = width,
63+
),
64+
AdaptiveMessageItemHeaderRowPreviewData(
65+
previewName = "$sizeName - With red account indicator",
66+
configuration = MessageItemConfiguration(
67+
accountIndicator = MessageItemAccountIndicator(color = Color(color = 0xFFC62828)),
68+
),
69+
receivedAt = "2:45 PM",
70+
screenWidth = width,
71+
),
72+
AdaptiveMessageItemHeaderRowPreviewData(
73+
previewName = "$sizeName - With green account indicator",
74+
configuration = MessageItemConfiguration(
75+
accountIndicator = MessageItemAccountIndicator(color = Color(color = 0xFF2E7D32)),
76+
),
77+
receivedAt = "Jan 5",
78+
screenWidth = width,
79+
),
80+
)
81+
},
82+
) {
83+
override fun getDisplayName(index: Int): String = values.elementAt(index).previewName
84+
}
85+
86+
@PreviewLightDark
87+
@Composable
88+
private fun AdaptiveMessageItemHeaderRowPreview(
89+
@PreviewParameter(AdaptiveMessageItemHeaderRowProvider::class) data: AdaptiveMessageItemHeaderRowPreviewData,
90+
) {
91+
PreviewWithThemeLightDark {
92+
CompositionLocalProvider(
93+
LocalConfiguration provides Configuration().apply {
94+
screenWidthDp = data.screenWidth
95+
},
96+
) {
97+
AdaptiveMessageItemHeaderRow(
98+
configuration = data.configuration,
99+
receivedAt = data.receivedAt,
100+
firstLine = { TextTitleSmall(data.previewName) },
101+
modifier = Modifier.padding(
102+
horizontal = MainTheme.spacings.default,
103+
vertical = MainTheme.spacings.quadruple,
104+
),
105+
)
106+
}
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package net.thunderbird.feature.mail.message.list.ui.component.molecule
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.FlowRow
5+
import androidx.compose.foundation.layout.IntrinsicSize
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.RowScope
8+
import androidx.compose.foundation.layout.defaultMinSize
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.width
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.movableContentOf
13+
import androidx.compose.runtime.remember
14+
import androidx.compose.ui.Alignment
15+
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.text.style.TextOverflow
17+
import app.k9mail.core.ui.compose.common.window.WindowSizeClass
18+
import app.k9mail.core.ui.compose.common.window.getWindowSizeInfo
19+
import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleSmall
20+
import net.thunderbird.core.ui.compose.theme2.MainTheme
21+
import net.thunderbird.feature.mail.message.list.ui.component.config.MessageItemAccountIndicator
22+
import net.thunderbird.feature.mail.message.list.ui.component.config.MessageItemConfiguration
23+
24+
/**
25+
* Renders an adaptive header row for a message item that adjusts its layout based
26+
* on screen size.
27+
*
28+
* The function determines the appropriate layout by checking the current window
29+
* size class and renders either a [HeaderRowSmall] or [HeaderRow] accordingly.
30+
*
31+
* @param configuration The message item configuration containing display settings,
32+
* including the account indicator to be shown in the header.
33+
* @param receivedAt The timestamp string indicating when the message was received,
34+
* displayed at the end of the header row.
35+
* @param firstLine A composable lambda that provides the custom content to be displayed
36+
* as the first line of the header, typically containing sender information.
37+
* @param modifier Optional modifier to be applied to the header row container.
38+
*/
39+
@Composable
40+
internal fun AdaptiveMessageItemHeaderRow(
41+
configuration: MessageItemConfiguration,
42+
receivedAt: String,
43+
firstLine: @Composable (() -> Unit),
44+
modifier: Modifier = Modifier,
45+
) {
46+
val windowSizeInfo = getWindowSizeInfo()
47+
val isSmallScreen = windowSizeInfo.screenWidthSizeClass == WindowSizeClass.Small
48+
49+
val headerRowContent: @Composable ((RowScope) -> Unit) = rememberAdaptiveHeaderRowContent(
50+
isSmallScreen = isSmallScreen,
51+
accountIndicator = configuration.accountIndicator,
52+
receivedAt = receivedAt,
53+
firstLine = firstLine,
54+
)
55+
if (isSmallScreen) {
56+
HeaderRowSmall(modifier, headerRowContent = headerRowContent)
57+
} else {
58+
HeaderRow(modifier, headerRowContent = headerRowContent)
59+
}
60+
}
61+
62+
@Composable
63+
private fun HeaderRow(
64+
modifier: Modifier = Modifier,
65+
headerRowContent: @Composable ((RowScope) -> Unit),
66+
) {
67+
Row(
68+
horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.default),
69+
modifier = modifier
70+
.defaultMinSize(minHeight = AccountIndicatorIcon.ACCOUNT_INDICATOR_DEFAULT_HEIGHT)
71+
.fillMaxWidth()
72+
.width(intrinsicSize = IntrinsicSize.Max),
73+
) {
74+
headerRowContent(this)
75+
}
76+
}
77+
78+
@Composable
79+
private fun HeaderRowSmall(
80+
modifier: Modifier = Modifier,
81+
headerRowContent: @Composable ((RowScope) -> Unit),
82+
) {
83+
FlowRow(
84+
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.quarter),
85+
horizontalArrangement = Arrangement.Start,
86+
maxLines = 2,
87+
modifier = modifier.defaultMinSize(minHeight = AccountIndicatorIcon.ACCOUNT_INDICATOR_DEFAULT_HEIGHT),
88+
) {
89+
headerRowContent(this)
90+
}
91+
}
92+
93+
@Composable
94+
private fun rememberAdaptiveHeaderRowContent(
95+
isSmallScreen: Boolean,
96+
accountIndicator: MessageItemAccountIndicator?,
97+
receivedAt: String,
98+
firstLine: @Composable (() -> Unit),
99+
): @Composable ((RowScope) -> Unit) = remember(isSmallScreen, accountIndicator, receivedAt, firstLine) {
100+
movableContentOf { scope ->
101+
with(scope) {
102+
Row(
103+
verticalAlignment = Alignment.CenterVertically,
104+
modifier = Modifier
105+
.then(if (isSmallScreen) Modifier.fillMaxWidth() else Modifier.weight(1f))
106+
.defaultMinSize(minHeight = AccountIndicatorIcon.ACCOUNT_INDICATOR_DEFAULT_HEIGHT),
107+
) {
108+
val indicatorColor = accountIndicator?.color
109+
if (indicatorColor != null) {
110+
AccountIndicatorIcon(indicatorColor)
111+
}
112+
firstLine()
113+
}
114+
TextTitleSmall(
115+
text = receivedAt,
116+
maxLines = 1,
117+
overflow = TextOverflow.Visible,
118+
modifier = Modifier.align(Alignment.CenterVertically),
119+
)
120+
}
121+
}
122+
}

feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/molecule/MessageItemHeaderRow.kt

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)