Skip to content

Commit 4082250

Browse files
feat(message-list): add configuration classes for message item presentation and layout (#10685)
2 parents b311a24 + 7774b9b commit 4082250

10 files changed

Lines changed: 322 additions & 75 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package net.thunderbird.feature.mail.message.list.ui.component.config
2+
3+
import androidx.compose.ui.graphics.Color
4+
5+
/**
6+
* Represents a visual account indicator displayed in a message item.
7+
*
8+
* The indicator helps users quickly identify which account the message
9+
* is associated with in the unified inbox.
10+
* ```
11+
* Message Item structure:
12+
* ┌───────────┬──────────────────────┬──────────┐
13+
* │ Leading │ Primary Line [X] │ Trailing │
14+
* │ Area ├──────────────────────┤ Area │
15+
* │ │ Secondary Line │ │
16+
* │ │ Excerpt Line │ │
17+
* └───────────┴──────────────────────┴──────────┘
18+
* [X] = Position where this indicator is rendered.
19+
* ```
20+
*
21+
* @property color The color used to render the account indicator,
22+
* unique to each account.
23+
*/
24+
data class MessageItemAccountIndicator(val color: Color)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package net.thunderbird.feature.mail.message.list.ui.component.config
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.remember
5+
import kotlinx.collections.immutable.PersistentList
6+
import kotlinx.collections.immutable.persistentListOf
7+
import kotlinx.collections.immutable.toPersistentList
8+
import net.thunderbird.feature.mail.message.list.preferences.MessageListPreferences
9+
import net.thunderbird.feature.mail.message.list.ui.component.molecule.MessageConversationCounterBadgeColor
10+
import net.thunderbird.feature.mail.message.list.ui.state.MessageItemUi
11+
12+
/**
13+
* Configuration class that defines the visual presentation and layout settings for
14+
* a message item.
15+
*
16+
* ```
17+
* Message Item structure:
18+
* ┌───────────┬──────────────────────┬──────────┐
19+
* │ Leading │ Primary Line │ Trailing │
20+
* │ Area ├──────────────────────┤ Area │
21+
* │ │ Secondary Line │ │
22+
* │ │ Excerpt Line │ │
23+
* └───────────┴──────────────────────┴──────────┘
24+
* ```
25+
*
26+
* @property maxExcerptLines The maximum number of lines to display for the message
27+
* excerpt before truncation.
28+
* @property swapPrimaryLineWithSecondaryLine When true, the primary and secondary
29+
* lines of the message display are swapped in their positions.
30+
* @property leadingConfiguration Configuration for elements displayed on the leading
31+
* edge of the message item, such as badges and avatars.
32+
* @property accountIndicator Optional account indicator that can be displayed to
33+
* distinguish messages from different accounts.
34+
* @property secondaryLineConfiguration Configuration for the secondary line of the
35+
* message item, including any leading items to display.
36+
* @property excerptLineConfiguration Configuration for the excerpt/preview line of
37+
* the message, including any leading items to display.
38+
* @property trailingConfiguration Configuration for elements displayed on the trailing
39+
* edge of the message item, such as badges and action buttons.
40+
*/
41+
data class MessageItemConfiguration(
42+
val maxExcerptLines: Int = 2,
43+
val swapPrimaryLineWithSecondaryLine: Boolean = false,
44+
val leadingConfiguration: MessageItemLeadingConfiguration = MessageItemLeadingConfiguration(),
45+
val accountIndicator: MessageItemAccountIndicator? = null,
46+
val secondaryLineConfiguration: MessageSublineConfiguration = MessageSublineConfiguration(),
47+
val excerptLineConfiguration: MessageSublineConfiguration = MessageSublineConfiguration(),
48+
val trailingConfiguration: MessageItemTrailingConfiguration = MessageItemTrailingConfiguration(),
49+
)
50+
51+
/**
52+
* Remembers and creates a configuration for a message item based on current parameters.
53+
*
54+
* @param messageItemUi The UI state of the message item containing information about state,
55+
* senders, content, and metadata flags needed to build the configuration.
56+
* @param preferences The user's message list preferences that control display options such as
57+
* number of excerpt lines and other visual settings.
58+
* @param color The color scheme for the conversation counter badge, defining container,
59+
* content, and border colors.
60+
* @param accountIndicator Optional account indicator containing the color to display for
61+
* account identification in unified inbox view. Can be null if no indicator is needed.
62+
* @return A MessageItemConfiguration instance containing all layout and display settings
63+
* for rendering the message item.
64+
*/
65+
@Composable
66+
internal fun rememberMessageItemConfiguration(
67+
messageItemUi: MessageItemUi,
68+
preferences: MessageListPreferences,
69+
color: MessageConversationCounterBadgeColor,
70+
accountIndicator: MessageItemAccountIndicator?,
71+
): MessageItemConfiguration = remember(messageItemUi, preferences) {
72+
val leadingItems = buildLeadingItems(messageItemUi, color)
73+
MessageItemConfiguration(
74+
maxExcerptLines = preferences.excerptLines,
75+
leadingConfiguration = MessageItemLeadingConfiguration(
76+
badgeStyle = messageItemUi.state.toBadgeStyle(),
77+
avatar = messageItemUi.senders.avatar,
78+
avatarColor = messageItemUi.senders.color,
79+
),
80+
accountIndicator = accountIndicator,
81+
secondaryLineConfiguration = MessageSublineConfiguration(
82+
leadingItems = if (preferences.excerptLines == 0) leadingItems else persistentListOf(),
83+
),
84+
excerptLineConfiguration = MessageSublineConfiguration(
85+
leadingItems = if (preferences.excerptLines > 0) leadingItems else persistentListOf(),
86+
),
87+
trailingConfiguration = MessageItemTrailingConfiguration(
88+
elements = buildTrailingElementsList(preferences, messageItemUi),
89+
),
90+
)
91+
}
92+
93+
private fun buildTrailingElementsList(
94+
preferences: MessageListPreferences,
95+
messageItemUi: MessageItemUi,
96+
): PersistentList<MessageItemTrailingElement> = buildList {
97+
if (messageItemUi.encrypted) {
98+
add(MessageItemTrailingElement.EncryptedBadge)
99+
}
100+
if (preferences.showFavouriteButton) {
101+
add(MessageItemTrailingElement.FavouriteIconButton(favourite = messageItemUi.starred))
102+
}
103+
}.toPersistentList()
104+
105+
private fun buildLeadingItems(
106+
messageItemUi: MessageItemUi,
107+
color: MessageConversationCounterBadgeColor,
108+
): PersistentList<MessageSublineLeadingIndicator> = buildList {
109+
if (messageItemUi.threadCount > 1) {
110+
add(
111+
MessageSublineLeadingIndicator.ConversationCounterBadge(
112+
count = messageItemUi.threadCount,
113+
color = color,
114+
),
115+
)
116+
}
117+
if (messageItemUi.hasAttachments) {
118+
add(MessageSublineLeadingIndicator.AttachmentIcon)
119+
}
120+
}.toPersistentList()
121+
122+
private fun MessageItemUi.State.toBadgeStyle(): MessageBadgeStyle? = when (this) {
123+
MessageItemUi.State.New -> MessageBadgeStyle.New
124+
MessageItemUi.State.Unread -> MessageBadgeStyle.Unread
125+
MessageItemUi.State.Read -> null
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package net.thunderbird.feature.mail.message.list.ui.component.config
2+
3+
import androidx.compose.ui.graphics.Color
4+
import net.thunderbird.feature.mail.message.list.ui.state.Avatar
5+
6+
/**
7+
* Configuration for the leading area of a message item.
8+
*
9+
* This class defines the visual elements displayed at the start of a message item,
10+
* such as status badges and sender avatars.
11+
*
12+
* ```
13+
* Message Item structure:
14+
* ┌───────────┬──────────────────────┬──────────┐
15+
* │ Leading │ Primary Line │ Trailing │
16+
* │ Area ├──────────────────────┤ Area │
17+
* │ [X] │ Secondary Line │ │
18+
* │ │ Excerpt Line │ │
19+
* └───────────┴──────────────────────┴──────────┘
20+
* [X] = Position where this configuration is rendered.
21+
* ```
22+
*
23+
* @property badgeStyle The style of the badge indicating message status (e.g., unread, new).
24+
* @property avatar The visual representation of the sender, such as an image, monogram, or icon.
25+
* @property avatarColor The background or tint color applied to the avatar.
26+
* @see MessageItemConfiguration
27+
* @see MessageBadgeStyle
28+
*/
29+
data class MessageItemLeadingConfiguration(
30+
val badgeStyle: MessageBadgeStyle? = null,
31+
val avatar: Avatar? = null,
32+
val avatarColor: Color? = null,
33+
)
34+
35+
/**
36+
* Defines the visual style variants for message status badges.
37+
*/
38+
enum class MessageBadgeStyle { New, Unread }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package net.thunderbird.feature.mail.message.list.ui.component.config
2+
3+
import androidx.compose.runtime.Immutable
4+
import kotlinx.collections.immutable.ImmutableList
5+
import kotlinx.collections.immutable.persistentListOf
6+
7+
/**
8+
* Configuration class that defines the elements displayed on the trailing edge of a message item.
9+
*
10+
* This data class encapsulates the configuration for visual elements that appear at the end
11+
* (trailing edge) of a message item, such as badges and action buttons. The elements are
12+
* displayed in the order specified in the list.
13+
*
14+
* ```
15+
* Message Item structure:
16+
* ┌───────────┬──────────────────────────┬──────────┐
17+
* │ Leading │ Primary Line │ Trailing │
18+
* │ Area ├──────────────────────────┤ Area │
19+
* │ │ Secondary Line │ [X] │
20+
* │ │ Excerpt Line │ │
21+
* └───────────┴──────────────────────────┴──────────┘
22+
* [X] = Position where this configuration is rendered.
23+
* ```
24+
*
25+
* @property elements An immutable list of trailing elements to be displayed.
26+
* Defaults to an empty list if not specified.
27+
* @see MessageItemConfiguration
28+
*/
29+
data class MessageItemTrailingConfiguration(
30+
val elements: ImmutableList<MessageItemTrailingElement> = persistentListOf(),
31+
)
32+
33+
/**
34+
* Represents the types of elements that can be displayed on the trailing edge of a message item.
35+
*/
36+
@Immutable
37+
sealed interface MessageItemTrailingElement {
38+
data object EncryptedBadge : MessageItemTrailingElement
39+
data class FavouriteIconButton(val favourite: Boolean) : MessageItemTrailingElement
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package net.thunderbird.feature.mail.message.list.ui.component.config
2+
3+
import androidx.compose.runtime.Immutable
4+
import kotlinx.collections.immutable.ImmutableList
5+
import kotlinx.collections.immutable.persistentListOf
6+
import net.thunderbird.feature.mail.message.list.ui.component.molecule.MessageConversationCounterBadgeColor
7+
8+
/**
9+
* Configuration that defines the leading elements displayed on a subline (Secondary or Excerpt)
10+
* within a message list item.
11+
*
12+
* This class determines which visual indicators, such as attachment icons or conversation
13+
* counters, appear at the start of the line. Items are rendered in the order they appear
14+
* in the list.
15+
*
16+
* ```
17+
* Message Item structure:
18+
* ┌───────────┬──────────────────────────┬──────────┐
19+
* │ Leading │ Primary Line │ Trailing │
20+
* │ Area ├──────────────────────────┤ Area │
21+
* │ │ Secondary Line [X] │ │
22+
* │ │ Excerpt Line [X] │ │
23+
* └───────────┴──────────────────────────┴──────────┘
24+
* [X] = Position where this configuration is rendered.
25+
* ```
26+
*
27+
* @property leadingItems The list of indicators to be displayed at the leading edge of the line.
28+
* @see MessageItemConfiguration
29+
* @see MessageSublineLeadingIndicator
30+
*/
31+
data class MessageSublineConfiguration(
32+
val leadingItems: ImmutableList<MessageSublineLeadingIndicator> = persistentListOf(),
33+
)
34+
35+
/**
36+
* Sealed interface representing visual elements that can be displayed at the
37+
* start of the secondary and excerpt lines within a message item.
38+
*/
39+
@Immutable
40+
sealed interface MessageSublineLeadingIndicator {
41+
/**
42+
* Represents a visual element displaying an icon indicator for messages with
43+
* attachments.
44+
*/
45+
data object AttachmentIcon : MessageSublineLeadingIndicator
46+
47+
/**
48+
* Represents a badge displaying the count of messages in a conversation thread.
49+
*
50+
* @property count The number of messages in the conversation thread to be displayed
51+
* on the badge
52+
* @property color The color scheme defining the visual appearance of the counter
53+
* badge
54+
*/
55+
data class ConversationCounterBadge(
56+
val count: Int,
57+
val color: MessageConversationCounterBadgeColor,
58+
) : MessageSublineLeadingIndicator
59+
}

feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/state/MessageItemAttachment.kt

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

feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/state/MessageItemUi.kt

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,30 @@
11
package net.thunderbird.feature.mail.message.list.ui.state
22

33
import androidx.compose.runtime.Immutable
4-
import kotlinx.collections.immutable.ImmutableList
5-
import kotlinx.collections.immutable.persistentListOf
6-
import net.thunderbird.feature.mail.message.list.preferences.MessageListPreferences
74

85
/**
96
* Represents the UI state for a single message item in the message list.
107
*
118
* This data class encapsulates all the information required to render a message item,
129
* including its display state, identifiers, sender/recipient details, content snippets, and metadata.
1310
*
14-
* @param state The current display state of the message (e.g., Read, Unread, Selected).
15-
* @param id The unique identifier for the message.
16-
* @param folderId The identifier of the folder containing this message.
17-
* @param account The account to which this message belongs.
11+
* @property state The current display state of the message (e.g., Read, Unread, Selected).
12+
* @property id The unique identifier for the message.
13+
* @property folderId The identifier of the folder containing this message.
14+
* @property account The account to which this message belongs.
1815
* @property senders The composed representation of the message sender(s) with display name,
1916
* styling, and avatar.
20-
* @param subject The subject line of the message.
21-
* @param excerpt A short snippet or preview of the message body.
22-
* @param formattedReceivedAt A user-friendly, formatted string representing when the message was received.
23-
* @param attachments A list of attachments included in the message.
24-
* @param starred A flag indicating whether the message is marked as starred/important.
25-
* @param encrypted A flag indicating whether the message is encrypted.
26-
* @param answered A flag indicating whether the message has been replied to.
27-
* @param forwarded A flag indicating whether the message has been forwarded.
28-
* @param conversations A list of related messages in the same conversation thread.
29-
* Defaults to an empty list. Always empty if [MessageListPreferences.groupConversations] is `false`
30-
* @param isActive A flag indicating whether the message is currently active. Defaults to `false`.
17+
* @property subject The subject line of the message.
18+
* @property excerpt A short snippet or preview of the message body.
19+
* @property formattedReceivedAt A user-friendly, formatted string representing when the message was received.
20+
* @property hasAttachments Whether the message contains one or more attachments.
21+
* @property starred A flag indicating whether the message is marked as starred/important.
22+
* @property encrypted A flag indicating whether the message is encrypted.
23+
* @property answered A flag indicating whether the message has been replied to.
24+
* @property forwarded A flag indicating whether the message has been forwarded.
25+
* @property threadCount The number of messages in the thread. A value of 0-1 indicates a single
26+
* message (not threaded).
27+
* @property isActive A flag indicating whether the message is currently active. Defaults to `false`.
3128
* **NOTE:** Only available when Home Screen is on Split mode.
3229
*/
3330
@Immutable
@@ -40,13 +37,13 @@ data class MessageItemUi(
4037
val subject: String,
4138
val excerpt: String,
4239
val formattedReceivedAt: String,
43-
val attachments: ImmutableList<MessageItemAttachment>,
40+
val hasAttachments: Boolean,
4441
val starred: Boolean,
4542
val encrypted: Boolean,
4643
val answered: Boolean,
4744
val forwarded: Boolean,
4845
val selected: Boolean,
49-
val conversations: ImmutableList<MessageItemUi> = persistentListOf(),
46+
val threadCount: Int = 0,
5047
val isActive: Boolean = false,
5148
) {
5249
/**

0 commit comments

Comments
 (0)