Commit cf481f5
authored
Redesign channel list, swipe actions and actions bottom sheet (#6181)
* feat(compose): update ChannelsScreen and SearchInput to Figma tokens
- ChannelsScreen: search section padding top 8dp→16dp, horizontal 12dp→16dp (spacingMd), bottom 8dp (spacingXs)
- SearchInput DefaultSearchLeadingIcon: 16x16dp size, leading 16dp padding, tint textLowEmphasis→textTertiary
- SearchInput DefaultSearchLabel: typography body→bodyDefault, color textLowEmphasis→textTertiary
- SearchInput: add 48dp minHeight, innerPadding start=spacingXs/end=spacingMd/v=spacingSm
- InputField: border color borders→borderCoreDefault
* feat(compose): update ChannelListHeader to Figma spec
- Container: padding 8dp→spacingSm (12dp), add spacedBy(spacingXs) gap between children
- Add 1dp bottom border (borderCoreDefault) replacing drop shadow
- Elevation default: headerElevation (4dp)→0.dp
- Title typography: title3Bold→headingSmall, color: textHighEmphasis→textPrimary
- Title padding: 16.dp→spacingMd (token-aligned)
- Leading avatar: 40dp visual wrapped in 48dp hit target Box
- Trailing button: accentPrimary bg, CircleShape, 0dp elevation, 48dp hit target
* feat(compose): restructure ChannelItem layout per Figma
- Leading: avatar 40dp→channelListItemAvatarSize (48dp), all padding spacingMd (16dp)
- Center content major restructure: timestamp+badge moved from trailing→title row, delivery status moved→message row
- Channel name: bodyBold+fontSize16→bodyDefault, textHighEmphasis→textPrimary
- Mute icon: moved inline into title row, textLowEmphasis→textTertiary
- Message preview: body→captionDefault, textLowEmphasis→textSecondary
- Typing indicator: gap 6dp→spacing2xs, body→captionDefault, textLowEmphasis→textSecondary
- Trailing content: replaced with empty Spacer stub (spacingMd=16dp) for API compatibility
* feat(compose): update UnreadCountIndicator, Timestamp, and delivery status
- UnreadCountIndicator: errorAccent (red)→accentPrimary (blue), add 2dp presenceBorder border, min 18dp→20dp, RoundedCornerShape(9)→pill (50), captionBold→numericExtraLarge, Color.White→badgeTextOnAccent
- Timestamp: default style footnote→captionDefault (14sp), textLowEmphasis→textTertiary, add lineHeightNormal (20sp)
- StreamDimens: add channelListItemAvatarSize=48.dp field (channelAvatarSize=40.dp retained for header)
* feat(compose): update SearchResultItem and SelectedChannelMenu tokens
- SearchResultItem leading: avatar 40dp→48dp, padding channelItemHorizontalPadding→spacingMd (16dp)
- SearchResultItem center: bodyBold+fontSize16→bodyDefault, textHighEmphasis→textPrimary, body→captionDefault, textLowEmphasis→textSecondary, add 4dp gap
- SearchResultItem trailing: padding updated to spacingMd, timestamp footnote→captionDefault+textTertiary+20sp lineHeight, alignment Bottom→CenterVertically
- SearchResultItem container Row: add Arrangement.spacedBy(spacingMd) for 16dp avatar-content gap
- SelectedChannelMenu header: title3Bold→headingSmall, textHighEmphasis→textPrimary, footnoteBold→captionDefault, textLowEmphasis→textSecondary
- ChannelOptions: all non-destructive options textHighEmphasis→textPrimary, textLowEmphasis→textSecondary (delete option unchanged)
- ChatComponentFactory ChannelOptionsItem: bodyBold→bodyDefault
- StreamHorizontalDivider: borders→borderCoreSurfaceSubtle
* fix(compose): swap header icon from pencil to plus per Figma spec
The Figma design uses a plus icon for the new channel button,
not the pencil/edit icon. Switched from stream_compose_ic_new_chat
to stream_compose_ic_add with explicit 24dp sizing.
* refactor(compose): decouple SearchInput from InputField
Move SearchInput to use BasicTextField directly, matching the
pattern used by the new message composer. This removes the
dependency on InputField and its composer-specific theme
(inputBackground), making the search bar transparent so it
inherits the parent background color. Works correctly in both
light and dark mode.
* fix(compose): update SearchInput radius and fix icon clipping
Use radius3xl (24dp) matching the composer's MessageInputShape
instead of radiusLg (12dp). Move padding into decorationBox so
the search icon isn't clipped by the border/clip modifiers.
* fix(compose): reduce header plus icon from 24dp to 20dp
Match the composer's icon size (icon/size/md = 20dp) for
consistent icon sizing across the SDK.
* fix: update search leading icon default paddings
* fix(compose): replace search icon with Figma stroked variant
Replace the filled 24dp search icon with the stroked 20dp
variant from Figma (IconMagnifyingGlassSearch), matching the
new design system's stroked icon style.
* fix(compose): center plus icon in header button with 10dp padding
Add Box with 10dp padding and center alignment inside the Surface,
matching Figma button/padding-x/icon-only/md and button/padding-y/md
tokens. The icon was not centered and appeared oversized.
* feat(compose): add attachment type icons and message states to channel list preview
Add inline preview icons for photo, video, file, link, and location
attachment types in the channel list message preview. Handle deleted
messages, error state indicator, and empty chat placeholder per Figma
spec. Voice messages and location now include sender name prefix for
group chats.
* fix(compose): show deleted message preview in channel list
The getPreviewMessage function filters out deleted messages, so the
formatter's isDeleted check was never reached. Add
getLastMessageIncludingDeleted to pass deleted messages to the formatter.
Suppress delivery status icon for deleted messages.
* fix(compose): ensure preview icons fill placeholder bounds
Add Modifier.fillMaxSize() to all inline preview icons so they
properly scale to the 16sp placeholder regardless of their intrinsic
drawable size (some are 12dp, mic is 20dp, poll is 24dp).
* fix(compose): use TextCenter alignment for preview icons instead of fillMaxSize
Revert fillMaxSize() on icons — the real issue is PlaceholderVerticalAlign.Center
centers in the line box (16sp) rather than the text glyphs (14sp). TextCenter
fixes the vertical alignment against the caption text.
* feat(compose): show sender name in group chat previews and fix icon alignment
- getSenderDisplayName now returns the sender's name for group chats
(was only returning "You" for current user, null for everyone else)
- Add isDirectMessaging parameter to formatMessagePreview so the formatter
knows when to suppress the sender name (DMs only)
- ChannelItem passes isOneToOne to the formatter
- Reduce icon placeholder from 16sp to 14sp (matching font size) with
Modifier.size(14.dp) to prevent text baseline displacement
* fix(compose): align channel list item with Figma spec
- Configurable mute icon position (inline title / trailing bottom)
- DM typing shows "Typing" without name; group shows proper name formats
- Remove "You:" prefix from DM outgoing non-deleted messages
- Replace hardcoded mute icon size with design token
* fix(compose): use inline poll icon instead of emoji in channel list preview
Replace hardcoded 📊 emoji with ic_poll drawable via appendInlineContent,
matching the pattern used by all other attachment type previews.
* fix(compose): resolve CI failures in channel list redesign
Fix detekt MaxLineLength/LongMethod/MagicNumber violations, spotless
formatting, and docs compile error from formatMessagePreview signature.
* fix(compose): update delivery status icons to Figma stroked style, add poll sender prefix and chatTextRead token
Replace filled delivery status icons (clock, single check, double check)
with stroked outlines per Figma spec. Add sender name prefix for poll
messages in group chats. Add chatTextRead semantic color token for read
status icon tint.
* fix(compose): reduce unread badge text from 14sp to 12sp
Badge number felt oversized inside the 20dp pill with 2dp border.
Switched from numericExtraLarge (14sp) to numericLarge (12sp) for
better visual proportion.
* feat(compose): add focus and selected interaction states to ChannelItem
Add visual feedback for focus (blue rounded border) and selected
(tinted background) states on channel list items, matching the Figma
design system interaction states.
* fix(compose): inset channel item card shape, fix badge proportions
- Add 4dp horizontal padding before border/clip/background so the
rounded selected/focused card has visible inset from list edges
- Remove 2dp white border from UnreadCountIndicator (was for avatar
overlap, not standalone badge in title row)
- Switch badge text from numericLarge (12sp) to numericMedium (10sp)
which is the style designed for unread count indicators
* feat(compose): add swipe-to-reveal actions on channel list items
Add Pin/Unpin, Mute/Unmute, and Delete swipe actions behind channel
list items using AnchoredDraggable from Compose Foundation 1.9.0.
- SwipeableChannelItem wrapper with RTL support
- SwipeRevealCoordinator for single-open-at-a-time behavior
- DefaultChannelSwipeActions respecting channel capabilities
- Overridable via ChatComponentFactory.ChannelSwipeActions()
- Configurable via ChannelListConfig.swipeActionsEnabled
- isPinned added to ChannelItemState
* refactor(compose): rework swipe actions to match Figma spec
DM channels show Archive + More, group channels show Mute + More.
Primary action resolved via fallback priority (DM: Archive→Mute→Pin,
Group: Mute→Archive→Pin). "More" opens the channel options bottom
sheet via selectChannel.
Add SwipeActionStyle enum (Primary/Secondary/Destructive) for
slot-based coloring. Fix missing background() on SwipeActionItem.
* fix(compose): fix swipe-to-reveal not responding to gestures
SideEffect does not track state reads for recomposition, so when
onSizeChanged updated actionsWidthPx during layout, the anchor
setup block never re-ran — leaving AnchoredDraggableState with no
anchors and making the swipe gesture a no-op.
Switch to LaunchedEffect(actionsWidthPx) so the key is tracked
during composition and anchors are set after layout measurement.
Also add height(IntrinsicSize.Min) to the outer Box so the
background action Row can fillMaxHeight() properly inside
LazyColumn's unbounded height context.
* fix(compose): wire swipe actions in sample's custom factory
CustomChatComponentFactory overrode ChannelListItemContent without
the SwipeableChannelItem wrapper, making swipe a no-op in the sample.
- Add SwipeableChannelItem wrapper to the sample's override
- Make LocalSwipeRevealCoordinator public so consumers who override
ChannelListItemContent can check swipe availability
* fix(compose): guard against NaN offset before anchors are set
AnchoredDraggableState.offset returns Float.NaN when no anchors
exist yet. roundToInt() throws on NaN in Kotlin. Guard with isNaN
check, defaulting to 0 offset.
* fix(compose): hide swipe actions behind opaque foreground
The ChannelItem's 4dp card inset left the action Row visible
through the transparent margins. Add a backgroundColor param to
SwipeableChannelItem that paints the foreground layer opaque,
covering the actions when the item is in the closed position.
* fix(compose): correct modifier order so background slides with content
background() must be after offset() so the opaque layer moves with
the channel item, covering actions when closed but revealing them
when swiped open.
* fix(compose): swap swipe action order — More left, primary right
Place the secondary More action first (leftmost) and the primary
action (Archive/Mute) second (rightmost) to match the Figma spec.
* refactor(actions): unify ChannelAction into self-describing interface
Rewrite ChannelAction from sealed class to interface carrying icon,
label, capability, confirmation popup, and execution handler. This
eliminates scattered when-switches, removes ChannelOptionState, and
enables custom channel actions across Compose and XML surfaces.
- ChannelAction: sealed class → interface with icon/label/onAction
- Add ConfirmationPopup data class for deferred destructive actions
- Copy 10 icon drawables to ui-common for cross-module access
- Remove Cancel object (dismissal via onDismiss callbacks)
- ViewModel: performChannelAction → executeOrConfirm/confirmPendingAction
- Compose: delete ChannelOptionState, buildDefaultChannelActions returns
List<ChannelAction> with ViewModel handlers bound
- ChannelsScreen: generic confirmation dialog from action.confirmationPopup
- ChatComponentFactory: ChannelOptionState refs → ChannelAction
- Swipe actions: build ChannelAction objects directly
- XML: factory returns List<ChannelAction>, delete ChannelOptionItem
- Update tests, sample app, docs snippets, API dump
* fix(compose): remove unused isOneToOne and re-record snapshots
Remove dead `isOneToOne` extension (Detekt UnusedPrivateMember) and
re-record ChannelItemTest Paparazzi snapshots.
* fix(compose): apply spotless formatting and re-record snapshots
Fix spotless violation in UnreadCountIndicator and re-record
ChannelItemTest + ChannelListTest Paparazzi snapshots.
* fix(e2e): update channel preview assertion for sender name prefix
Channel preview now shows sender name for non-DM participant messages.
Update assertMessageInChannelPreview to expect "ParticipantName: text"
when fromCurrentUser is false.
* feat(icons): update channel action and list icons to Figma design system
Replace legacy Material-style icons with filled variants from Stream
Foundations. All icons now use 16x16 viewport with filled paths for
consistent rendering. Mute/unmute switched from speaker to bell icons.
* fix(icons): correct mute/unmute and add icon mappings
Mute/unmute: use IconMute (speaker) and IconVolumeFull instead of bell
icons. Add: use IconPlusLarge (plus) instead of IconEditBigSolid (pen).
* fix(channels): move Leave to end as destructive action
Reorder channel actions: ViewInfo, Mute, Pin, Archive, Leave, Delete.
Destructive actions (Leave, Delete) are now grouped last. Mark
LeaveGroup as isDestructive = true.
* feat(channels): redesign bottom sheet header with group avatar
Replace horizontal member list with compact header: channel avatar
(left) + channel name + member count/status (right). Matches Figma
Channel Detail Sheet design.
* fix(channels): use headingSmall for channel name, icon-only swipe actions
Channel list title: bodyDefault → headingSmall (semi-bold) to match
Figma. Swipe actions: remove text label, show icon only in a centered
Box.
* fix(swipe): use backgroundCoreSurface for secondary action
Light gray background with dark icon instead of dark slate500 with
white icon. Matches Figma swipe action design.
* fix(channels): reduce bottom sheet action item size
Item height 56dp → 48dp, icon container 56dp → 48dp with 12dp padding
(24dp icon). More compact to match Figma design.
* fix(channels): use StreamTokens for bottom sheet action dimensions
Icon: spacingXl (24dp) with spacingSm (12dp) horizontal padding.
Replaces hardcoded 48dp/12dp values with design system tokens.
* fix(channels): remove dividers between bottom sheet action items
No dividers between channel options per Figma design.
* fix(channels): align bottom sheet items with Figma List Item spec
Row height 48dp → 44dp, horizontal padding spacing2xs (4dp), icon 24dp
with 8dp end gap. Matches Mobile / List Item Figma component.
* fix(channels): increase bottom sheet item horizontal padding to spacingMd
spacing2xs (4dp) → spacingMd (16dp) to match visible Figma padding.
* fix(channels): add bottom padding to channel options list
spacingXs (8dp) bottom content padding for breathing room below last
action item.
* fix(compose): re-record Paparazzi snapshots after v7 merge
* fix(compose): use collectAsState instead of StateFlow.value in composition
* feat(channels): skeleton loading, empty state redesign
- Replace spinner with shimmer skeleton items (8 placeholder rows
matching channel item layout: avatar + title/timestamp + message)
- Redesign empty state: 32dp icon, "No conversations yet" copy,
optional "Start a chat" CTA wired to header action
- Update ChatComponentFactory.ChannelListEmptyContent with
onStartChatClick callback
- ChannelsScreen passes onHeaderActionClick as empty state CTA
* fix(channels): address PR review feedback from Petar and CodeRabbit
- Fix deleted messages filtered out in channel preview (missing isDeleted predicate)
- Fix hardcoded icon color breaking XML SDK dark mode (tint with text style color)
- Fix ConcurrentModificationException in SwipeRevealCoordinator (snapshot registry)
- Add contentDescription to IsErrorIcon for accessibility
- Remove isPinned from ChannelItemState (derive from Channel.isPinned extension)
- Move animateItem from inner ChannelItem to outer SwipeableChannelItem
- Simplify header trailing content nesting and use AvatarSize constants
- Clip avatar clickable area to CircleShape for proper ripple shape
- Add text overflow for members status in SelectedChannelMenu
- Remove migration artifact comments from SearchResultItem
- Fix SwipeActionItem label KDoc (contentDescription, not visible text)
- Add @OptIn(ExperimentalFoundationApi) to SwipeableChannelItem
- Remove .planning directory from git tracking
* fix(channels): address PR review round 2 — API fixes and CodeRabbit feedback
- Separate onStartChatClick from onHeaderActionClick in ChannelsScreen (P4+P5)
- Rename onChannelOptionClick → onChannelOptionConfirm for semantic clarity (P3+P15)
- Remove deprecated 2-param formatMessagePreview overload (v7 breaking change)
- Move animateItem() to outer SwipeableChannelItem in sample CustomChatComponentFactory
- Add dark mode snapshot for muted channel trailing bottom test
- Make poll preview strings translatable
* fix(channels): address PR review round 3 — Gian's feedback
- Remove leftover comment and unnecessary Box wrapper in header
- Use applyIf extension for focus/selected modifiers in ChannelItem
- Use AvatarSize.ExtraLarge constant instead of raw 48.dp
- Extract TitleRow and MessageRow composables from center content
- Remove verbose section-divider comments
- Delete accidentally committed GAPS.md
- Replace raw dp values with StreamTokens in empty state
- Replace OutlinedButton with StreamTextButton secondaryOutline
- Remove RowScope extension from DefaultChannelSwipeActions
- Wrap swipe action resolution in remember with resources
- Make swipe actions self-executing via onAction + rememberUpdatedState
- Replace hardcoded Color.White with theme tokens in SwipeActionItem
- Replace RoundedCornerShape(50) with CircleShape in UnreadCountIndicator
- Update public API dump
* fix(channels): fix compose tests and re-record Paparazzi snapshots
Loading state changed from CircularProgressIndicator to skeleton shimmer.
Update ChannelsScreenTest and ChatsScreenTest to assert on testTag instead
of ProgressBarRangeInfo. Re-record all affected snapshots.
* fix(channels): resolve Detekt findings in channel list components
Reduce parameter count in TitleRow/MessageRow by passing channelItemState
directly. Extract swipe action builders to shorten rememberPrimarySwipeAction.
Wrap long KDoc lines in ChatComponentFactory.
* fix(channels): update E2E test for deleted message preview
Channel preview now shows 'Message deleted' instead of falling back
to the previous message, matching the redesigned behavior.
* fix(compose-sample): revert deleted message E2E test assertion
Mock server hard-deletes messages regardless of hard_delete=false param,
so getLastMessageIncludingDeleted() cannot find the removed message.
Revert to asserting the previous message ("Old") is shown instead.
* fix(compose): address Andre's PR feedback
- MuteIndicatorPosition: rename SCREAMING_SNAKE_CASE to PascalCase
(InlineTitle, TrailingBottom) per new enum convention
- Revert stream_compose_ic_add.xml to original (shared drawable)
- Replace Surface with StreamButton in header trailing content
- Update API dump for enum rename
* fix(compose): add missing Color import in ChannelListHeader
* fix(compose): fix import ordering and re-record snapshots
- Fix Detekt import ordering violation in ChannelListHeader.kt
- Re-record Paparazzi snapshots after v7 merge and StreamButton change1 parent e322a2a commit cf481f5
135 files changed
Lines changed: 2659 additions & 884 deletions
File tree
- stream-chat-android-compose-sample/src
- androidTestE2eDebug/kotlin/io/getstream/chat/android/compose
- robots
- tests
- main/java/io/getstream/chat/android/compose/sample
- feature/channel/list
- ui/component
- stream-chat-android-compose
- api
- src
- main
- java/io/getstream/chat/android/compose
- state/channels/list
- ui
- channels
- header
- info
- list
- components
- channels
- messages/preview/internal
- theme
- threads
- util
- viewmodel/channels
- res
- drawable
- values
- test
- kotlin/io/getstream/chat/android/compose
- ui
- channels
- chats
- viewmodel/channels
- snapshots/images
- stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose
- channels
- general
- stream-chat-android-ui-common
- api
- src/main
- kotlin/io/getstream/chat/android/ui/common/state/channels/actions
- res
- drawable
- values
- stream-chat-android-ui-components/src
- main/kotlin/io/getstream/chat/android/ui/feature/channels/actions/internal
- test/snapshots/images
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
Lines changed: 6 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| |||
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
33 | | - | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
34 | 39 | | |
35 | 40 | | |
36 | 41 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
190 | 190 | | |
191 | 191 | | |
192 | 192 | | |
193 | | - | |
| 193 | + | |
194 | 194 | | |
195 | 195 | | |
196 | 196 | | |
| |||
Lines changed: 10 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
| 87 | + | |
87 | 88 | | |
88 | 89 | | |
89 | 90 | | |
| |||
381 | 382 | | |
382 | 383 | | |
383 | 384 | | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
384 | 392 | | |
385 | 393 | | |
386 | 394 | | |
387 | 395 | | |
388 | 396 | | |
389 | 397 | | |
390 | 398 | | |
391 | | - | |
392 | 399 | | |
393 | 400 | | |
394 | | - | |
| 401 | + | |
| 402 | + | |
395 | 403 | | |
396 | 404 | | |
397 | 405 | | |
| |||
Lines changed: 34 additions & 16 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
| 29 | + | |
28 | 30 | | |
29 | 31 | | |
30 | 32 | | |
| |||
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
61 | 79 | | |
62 | 80 | | |
0 commit comments