Skip to content

Swipe-to-reveal actions on channel list items#6188

Merged
aleksandar-apostolov merged 25 commits intoredesign/channel-listfrom
redesign/channel-list-swipe-actions
Feb 25, 2026
Merged

Swipe-to-reveal actions on channel list items#6188
aleksandar-apostolov merged 25 commits intoredesign/channel-listfrom
redesign/channel-list-swipe-actions

Conversation

@aleksandar-apostolov
Copy link
Contributor

@aleksandar-apostolov aleksandar-apostolov commented Feb 25, 2026

Goal

Add swipe-to-reveal actions on channel list items, matching the Figma spec. DM channels get More + Archive/Unarchive, group channels get More + Mute/Unmute. Provides a slot-based styling system and full customization support via ChatComponentFactory.

Implementation

Swipe Mechanism

  • SwipeableChannelItem wraps channel items with AnchoredDraggableState-based swipe gesture
  • SwipeRevealCoordinator ensures only one item is open at a time
  • Default positional threshold: 50% of actions width; velocity threshold: 125dp

Default Actions

Channel Type Left (Secondary) Right (Primary)
DM More (gray) Archive/Unarchive (blue)
Group More (gray) Mute/Unmute (blue)
  • Fallback priority when preferred action is unavailable:
    • DM: Archive → Mute → Pin
    • Group: Mute → Archive → Pin
  • "More" opens the channel options bottom sheet (same as long-press)

Slot-Based Styling (SwipeActionStyle)

Style Background Content
Primary accentPrimary (blue) White
Secondary accentNeutral (gray) White
Destructive accentError (red) White

ChannelAction Refactor

  • ChannelAction is now a self-describing interface with icon, label, confirmationPopup
  • Each action (Archive, Mute, Pin, Delete, etc.) carries its own metadata for rendering in swipe actions, bottom sheets, and dialogs

Customization

Two tiers via ChatComponentFactory:

  1. ChannelSwipeActions() — replace which actions appear (keeps swipe mechanism)
  2. ChannelListItemContent() — full control over swipe wrapper + item

Configuration

ChatTheme(
    config = ChatConfig(
        channelList = ChannelListConfig(swipeActionsEnabled = false), // kill switch
    ),
)

New Files

File Purpose
SwipeableChannelItem.kt AnchoredDraggableState-based swipe wrapper
SwipeRevealCoordinator.kt Single-open coordination + CompositionLocals
SwipeRevealValue.kt Closed / Open enum
SwipeActionItem.kt Individual action button (icon + label)
SwipeActionStyle.kt Slot-based color resolution
DefaultChannelSwipeActions.kt Default DM/Group action logic with fallback

Testing

  • On-device tested across DM and group channels
  • Verified swipe gestures, single-open coordination, action execution, auto-close
  • Verified fallback priority when capabilities are restricted
  • Sample app's CustomChatComponentFactory updated to include swipe wrapper

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
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.
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.
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
AnchoredDraggableState.offset returns Float.NaN when no anchors
exist yet. roundToInt() throws on NaN in Kotlin. Guard with isNaN
check, defaulting to 0 offset.
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.
background() must be after offset() so the opaque layer moves with
the channel item, covering actions when closed but revealing them
when swiped open.
Place the secondary More action first (leftmost) and the primary
action (Archive/Mute) second (rightmost) to match the Figma spec.
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
@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@aleksandar-apostolov aleksandar-apostolov changed the title feat(compose): swipe-to-reveal actions on channel list items Swipe-to-reveal actions on channel list items Feb 25, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.69 MB 0.44 MB 🟡
stream-chat-android-ui-components 10.60 MB 10.97 MB 0.37 MB 🟡
stream-chat-android-compose 12.81 MB 11.94 MB -0.87 MB 🚀

Remove dead `isOneToOne` extension (Detekt UnusedPrivateMember) and
re-record ChannelItemTest Paparazzi snapshots.
Resolve conflicts from channel-list merge (which included v7/AND-1089
design system migration). Update legacy token references:
- footnoteBold → captionEmphasis (SwipeActionItem)
- errorAccent → accentError (ChatComponentFactory)
- overlay → backgroundCoreScrim (SelectedChannelMenu)
- highlight → backgroundCoreHighlight (CustomChatComponentFactory)
- appBackground → backgroundCoreApp (CustomChatComponentFactory)
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.
Mute/unmute: use IconMute (speaker) and IconVolumeFull instead of bell
icons. Add: use IconPlusLarge (plus) instead of IconEditBigSolid (pen).
Reorder channel actions: ViewInfo, Mute, Pin, Archive, Leave, Delete.
Destructive actions (Leave, Delete) are now grouped last. Mark
LeaveGroup as isDestructive = true.
Replace horizontal member list with compact header: channel avatar
(left) + channel name + member count/status (right). Matches Figma
Channel Detail Sheet design.
…ions

Channel list title: bodyDefault → headingSmall (semi-bold) to match
Figma. Swipe actions: remove text label, show icon only in a centered
Box.
Light gray background with dark icon instead of dark slate500 with
white icon. Matches Figma swipe action design.
Item height 56dp → 48dp, icon container 56dp → 48dp with 12dp padding
(24dp icon). More compact to match Figma design.
Icon: spacingXl (24dp) with spacingSm (12dp) horizontal padding.
Replaces hardcoded 48dp/12dp values with design system tokens.
No dividers between channel options per Figma design.
Row height 48dp → 44dp, horizontal padding spacing2xs (4dp), icon 24dp
with 8dp end gap. Matches Mobile / List Item Figma component.
…ngMd

spacing2xs (4dp) → spacingMd (16dp) to match visible Figma padding.
spacingXs (8dp) bottom content padding for breathing room below last
action item.
@aleksandar-apostolov aleksandar-apostolov merged commit aec1cb4 into redesign/channel-list Feb 25, 2026
10 of 14 checks passed
@aleksandar-apostolov aleksandar-apostolov deleted the redesign/channel-list-swipe-actions branch February 25, 2026 14:10
@aleksandar-apostolov
Copy link
Contributor Author

Consolidated into #6181 — combined the channel list redesign and swipe actions into a single PR since most changes here (icons, bottom sheet, ChannelAction refactor) belong to the broader redesign scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:breaking-change Breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant