Skip to content

Make resume() and pause() of LivestreamChannelController more flexible #3774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 11, 2025

Conversation

nuno-vieira
Copy link
Member

@nuno-vieira nuno-vieira commented Aug 7, 2025

🔗 Issue Links

Related to: https://linear.app/stream/issue/IOS-1025/create-a-livestream-controller

📝 Summary

  • Fix pending message being added to LivestreamChannelController.messages when in paused state
  • LivestreamChannelController does not resume automatically when a new message is sent; it should be done by the UI
  • Add completion block to resume, to observe errors
  • Fixes copy message action not working in the Demo App Livestream UI
  • Fixes message actions not visible in Dark Mode
  • Improve the pause banner view in the Demo App, to show that the Chat is being resumed
  • Fixes the Demo App livestream controller not scrolling to the bottom when inserting a new message in the paused state
  • Fixes the Demo App livestream controller showing and disappearing multiple times a new message
  • Adds missing test coverage to messages capping

🎨 Showcase

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-08-07.at.16.10.16.mp4

🧪 Manual Testing Notes

Re-test the following list: https://www.notion.so/stream-wiki/iOS-Livestream-2466a5d7f9f680fb9666ea0e0887b22f

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced a dedicated pause banner for livestream chat, providing clearer feedback when chat is paused or resuming.
    • Added the ability to copy chat messages to the clipboard with a simple action.
  • Improvements

    • Enhanced pause and resume handling in livestream chat, preventing overlapping resume operations and improving event responses.
    • Updated chat pause banner appearance for better visibility and consistency with the app’s theme.
    • Adjusted scroll behavior to pause livestream when user scrolls away from the latest message.
    • Streamlined reaction button setup with sorted reactions and improved interaction handling.
  • Bug Fixes

    • Improved message handling to respect pause state, ensuring messages are not added or processed incorrectly during pauses.
    • Removed automatic resuming triggered by new messages from the current user during pause.
  • Tests

    • Added and updated tests to cover pause/resume logic, message limits, and event handling for improved reliability.

@nuno-vieira nuno-vieira requested a review from a team as a code owner August 7, 2025 14:42
Copy link

coderabbitai bot commented Aug 7, 2025

Walkthrough

A dedicated LivestreamPauseBannerView was introduced for chat pause/resume indication in livestreams. The pause/resume logic in DemoLivestreamChatChannelVC and LivestreamChannelController was refactored to centralize state management, prevent concurrent resumes, and clarify event handling. Related UI and test code were updated, and project files were adjusted accordingly.

Changes

Cohort / File(s) Change Summary
Livestream Pause Banner UI
DemoApp/Screens/Livestream/LivestreamPauseBannerView.swift, DemoApp/Screens/Livestream/DemoLivestreamChatChannelVC.swift
Added a new LivestreamPauseBannerView class for pause/resume indication and refactored the chat VC to use it, replacing manual banner logic and updating event handling and scroll logic accordingly.
Livestream Controller Logic
Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift
Introduced isResuming to prevent concurrent resume operations, updated resume() method with completion handler, and refactored event handling to respect pause/resume state more strictly.
Livestream Controller Tests
Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift
Added and updated tests for pause/resume logic, message limits, and event handling, including edge cases for concurrent resumes and pending events.
Livestream Message Actions UI
DemoApp/Screens/Livestream/DemoLivestreamMessageActionsVC.swift
Changed background color to use a custom palette; no logic changes.
Project Configuration
StreamChat.xcodeproj/project.pbxproj
Registered the new LivestreamPauseBannerView.swift file in the Xcode project.
DemoApp UI Cleanup
DemoApp/Screens/Livestream/DemoLivestreamChatMessageListVC.swift, DemoApp/Screens/Livestream/DemoLivestreamReactionsListView.swift
Removed inline comments from SwiftUI views and chat message list VC without altering functionality.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DemoLivestreamChatChannelVC
    participant LivestreamPauseBannerView
    participant LivestreamChannelController

    User->>DemoLivestreamChatChannelVC: Scrolls up (pauses chat)
    DemoLivestreamChatChannelVC->>LivestreamChannelController: pause()
    DemoLivestreamChatChannelVC->>LivestreamPauseBannerView: setState(.paused), setVisible(true)
    
    User->>DemoLivestreamChatChannelVC: Scrolls to bottom or new message arrives
    DemoLivestreamChatChannelVC->>LivestreamPauseBannerView: setState(.resuming)
    DemoLivestreamChatChannelVC->>LivestreamChannelController: resume()
    LivestreamChannelController-->>DemoLivestreamChatChannelVC: Resume completion
    DemoLivestreamChatChannelVC->>LivestreamPauseBannerView: setVisible(false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Create a livestream controller (IOS-1025)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Possibly related PRs

  • Add LivestreamChannelController #3750: Introduces and modifies DemoLivestreamChatChannelVC to integrate the new LivestreamChannelController and event handling, closely related to this PR's changes.

Suggested labels

🌐 SDK: StreamChat (LLC), ✅ Feature, 🐞 Bug, 🟢 QAed

Suggested reviewers

  • laevandus
  • martinmitrevski

Poem

A banner now waves when the chat takes a break,
With pause and resume, it’s easy to partake.
No more confusion, the flow’s crystal clear,
Livestreams are smoother, the bunny’s in cheer!
Hop, hop—code refined, with a banner so bright,
Reviewing these changes will be a delight! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/resume-and-pause--from-livestream-robustness

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
StreamChat.xcodeproj/project.pbxproj (1)

4427-4427: Relative path may be incorrect

The file reference does not specify a sub-directory (it just sets path = LivestreamPauseBannerView.swift).
For files under DemoApp/Screens/Livestream/, the project usually stores a relative path such as:

path = DemoApp/Screens/Livestream/LivestreamPauseBannerView.swift;

Leaving the directory out can break the reference when the file is physically inside a sub-folder, causing Xcode to show it in red for some users.

Recommend setting the full relative path or verifying that the file really sits at the project root.

DemoApp/Screens/Livestream/LivestreamPauseBannerView.swift (3)

15-26: Consider making appearance configurable for better reusability.

The current implementation hardcodes Appearance.default, which limits reusability across different appearance contexts.

Consider making the appearance configurable:

-    private var appearance: Appearance {
-        Appearance.default
-    }
+    private let appearance: Appearance
+    
+    init(appearance: Appearance = .default) {
+        self.appearance = appearance
+        super.init(frame: .zero)
+        setupView()
+    }

24-26: Consider caching the appearance value for better performance.

The computed property accesses Appearance.default each time it's called. While unlikely to be a performance issue, consider caching it as a constant.

-    private var appearance: Appearance {
-        Appearance.default
-    }
+    private let appearance = Appearance.default

58-61: Minor redundancy in initial visibility setup.

Setting both isHidden = true and alpha = 0.0 is redundant since isHidden alone makes the view invisible. While not problematic, it could cause confusion about which property controls visibility.

Consider removing the alpha setting since the setVisible method handles both properties:

 isHidden = true
-alpha = 0.0
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15146fc and 0da4f01.

📒 Files selected for processing (6)
  • DemoApp/Screens/Livestream/DemoLivestreamChatChannelVC.swift (7 hunks)
  • DemoApp/Screens/Livestream/DemoLivestreamMessageActionsVC.swift (1 hunks)
  • DemoApp/Screens/Livestream/LivestreamPauseBannerView.swift (1 hunks)
  • Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift (3 hunks)
  • StreamChat.xcodeproj/project.pbxproj (4 hunks)
  • Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift (5 hunks)
🔇 Additional comments (31)
DemoApp/Screens/Livestream/LivestreamPauseBannerView.swift (12)

8-13: Well-designed banner component structure.

The class design with the internal BannerState enum effectively encapsulates the two distinct states needed for livestream pause management. This aligns well with the PR objective of making pause/resume functionality more flexible.


15-29: LGTM! Proper use of appearance system and encapsulation.

The lazy label initialization with appearance-based styling ensures consistency with the app's theme, and the private(set) access control for currentState provides good encapsulation while allowing external read access.


31-39: Standard and correct initializer implementation.

Both initializers properly call super.init and delegate setup to setupView(), following UIView subclass best practices.


41-62: Comprehensive view setup with good visual design.

The setup method properly configures the banner with theme-aware colors, visual polish (shadow, rounded corners), and appropriate constraints. Starting in a hidden state with the default paused message is a sensible choice.


64-75: Clear state management with user-friendly messaging.

The state management is straightforward and effective. The text messages "Chat paused due to scroll" and "Resuming..." clearly communicate the banner's purpose to users.


77-92: Well-implemented visibility control with proper animation handling.

The method correctly handles both animated and instant transitions, managing both isHidden and alpha properties. The 0.3 second animation duration provides smooth visual feedback without being too slow.


8-13: Well-designed class structure with clear state representation.

The class follows proper encapsulation principles with a clear enum for banner states. The documentation comment effectively describes the component's purpose.


31-39: Initialization methods follow UIKit patterns correctly.

Both convenience and required initializers are properly implemented, calling setupView() consistently.


41-62: Well-structured view setup with proper constraints and styling.

The shadow and corner radius styling creates an appropriate banner appearance. The constraint setup follows best practices with proper padding.


64-75: Clear state management with appropriate text updates.

The method properly updates both internal state and UI. The text messages clearly communicate the banner's purpose to users.


8-13: Well-designed class structure.

The class declaration and enum design are clean and follow Swift best practices. The BannerState enum clearly represents the two distinct states, and the class documentation is appropriate.


31-39: Proper initializer implementation.

Both initializers are correctly implemented following UIView subclassing best practices.

DemoApp/Screens/Livestream/DemoLivestreamMessageActionsVC.swift (3)

54-54: Good consistency improvement using appearance system.

Changing from UIColor.systemBackground to Appearance.default.colorPalette.background ensures visual consistency with other livestream components like the new LivestreamPauseBannerView. This aligns well with the PR's objective to improve the overall livestream UI experience.


54-54: Good consistency improvement using custom appearance system.

This change aligns the background color with the custom appearance palette used throughout the livestream components, ensuring better visual consistency and dark mode support.


54-54: Good consistency improvement.

Changing from UIColor.systemBackground to the appearance system's background color ensures visual consistency across the livestream UI components.

StreamChat.xcodeproj/project.pbxproj (2)

1623-1623: Verify the target membership for the new source file

LivestreamPauseBannerView.swift is added to the Sources build phase.
Double-check that it is only added to the Demo App / Example target and not to the core SDK framework target. Pulling Demo-UI code into the SDK binary will bloat the framework and leak demo-only dependencies.

If SDK targets also appear in this section, untick the file in “Target Membership” or move the file into a Demo-only build phase.


11289-11289: No issues — addition looks consistent

The file is correctly listed in Sources; GUID and ordering follow existing convention.
Nothing else to flag here.

Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift (3)

68-68: LGTM!

Good addition of the isResuming flag to prevent concurrent resume operations.


863-866: LGTM!

Correctly applies message limit only when not paused, preserving all messages during pause state.


869-872: LGTM!

Correctly skips processing of pending messages when paused, aligning with the PR objectives.

Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift (5)

511-524: LGTM!

Test correctly validates the asynchronous resume behavior with proper API simulation.


584-601: LGTM!

Excellent test coverage for preventing concurrent resume operations.


934-970: LGTM!

Comprehensive test for message limiting behavior when not paused.


992-1011: LGTM!

Clear test validating that pending messages are ignored during pause state.


1288-1312: LGTM!

Good test ensuring pending messages don't trigger automatic resume.

DemoApp/Screens/Livestream/DemoLivestreamChatChannelVC.swift (6)

19-19: LGTM!

Good practice to use the same client instance for consistency.


65-65: LGTM!

Good use of a dedicated view component for the pause banner.


261-263: LGTM!

Clear pause trigger logic based on user scroll interaction.


305-308: LGTM!

Standard copy-to-clipboard implementation with user feedback.


384-397: LGTM!

Well-implemented UI-driven resume logic that correctly handles both pending and new message events.


400-406: LGTM!

Clean implementation using the dedicated banner view's API.

@Stream-SDK-Bot
Copy link
Collaborator

SDK Size

title develop branch diff status
StreamChat 8.04 MB 8.04 MB 0 KB 🟢
StreamChatUI 4.86 MB 4.86 MB 0 KB 🟢

Copy link

github-actions bot commented Aug 7, 2025

Public Interface

 public class LivestreamChannelController: DataStoreProvider, EventsControllerDelegate, AppStateObserverDelegate  
-   public func resume()
+   public func resume(completion: (@MainActor(Error?) -> Void)? = nil)

@Stream-SDK-Bot
Copy link
Collaborator

SDK Performance

target metric benchmark branch performance status
MessageList Hitches total duration 10 ms 8.35 ms 16.5% 🔼 🟢
Duration 2.6 s 2.54 s 2.31% 🔼 🟢
Hitch time ratio 4 ms per s 3.29 ms per s 17.75% 🔼 🟢
Frame rate 75 fps 77.99 fps 3.99% 🔼 🟢
Number of hitches 1 1.0 0.0% 🟰 🟢

@Stream-SDK-Bot
Copy link
Collaborator

Stream-SDK-Bot commented Aug 7, 2025

SDK Size

title develop branch diff status
StreamChat 8.04 MB 8.04 MB 0 KB 🟢
StreamChatUI 4.86 MB 4.86 MB 0 KB 🟢

Copy link

sonarqubecloud bot commented Aug 7, 2025

Copy link
Contributor

@martinmitrevski martinmitrevski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! ✅

@nuno-vieira nuno-vieira added the 🤞 Ready For QA A PR that is Ready for QA label Aug 7, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22cf17f and ecb2133.

📒 Files selected for processing (7)
  • CHANGELOG.md (1 hunks)
  • DemoApp/Screens/Livestream/DemoLivestreamChatChannelVC.swift (5 hunks)
  • DemoApp/Screens/Livestream/DemoLivestreamChatMessageListVC.swift (2 hunks)
  • DemoApp/Screens/Livestream/DemoLivestreamMessageActionsVC.swift (4 hunks)
  • DemoApp/Screens/Livestream/DemoLivestreamReactionsListView.swift (0 hunks)
  • Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift (3 hunks)
  • Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift (4 hunks)
💤 Files with no reviewable changes (1)
  • DemoApp/Screens/Livestream/DemoLivestreamReactionsListView.swift
✅ Files skipped from review due to trivial changes (2)
  • DemoApp/Screens/Livestream/DemoLivestreamChatMessageListVC.swift
  • DemoApp/Screens/Livestream/DemoLivestreamMessageActionsVC.swift
🚧 Files skipped from review as they are similar to previous changes (3)
  • CHANGELOG.md
  • Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift
  • Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test E2E UI (Debug) (0)
  • GitHub Check: Test E2E UI (Debug) (1)
🔇 Additional comments (5)
DemoApp/Screens/Livestream/DemoLivestreamChatChannelVC.swift (5)

19-21: LGTM: Improved controller coupling

The change to use livestreamChannelController.client.eventsController() instead of client.eventsController() creates a more explicit dependency relationship and ensures consistency with the livestream controller's client instance.


62-62: LGTM: Clean separation of concerns

Replacing the manual UIView construction with a dedicated LivestreamPauseBannerView improves code organization and reusability. This aligns well with the PR objective of improving the pause banner view.


275-279: LGTM: Clean copy functionality implementation

The copy action implementation is straightforward and follows the existing pattern. Good use of completion block for the alert presentation.


370-376: LGTM: Simplified banner management

The updated showPauseBanner method properly delegates state management to the LivestreamPauseBannerView, which is cleaner than the previous manual approach.


231-233: Pause/resume logic is already idempotent – no race condition found

Both pause() and resume() in LivestreamChannelController use guard statements to prevent duplicate state transitions:

  • pause() returns immediately if isPaused is already true
  • resume() returns immediately if !isPaused or a resume is already in flight (isResuming)

Since these calls originate from the main‐thread scroll events in DemoLivestreamChatChannelVC.swift, they cannot interleave in a way that violates the controller’s internal state. No changes are needed.

@nuno-vieira nuno-vieira merged commit 5417035 into develop Aug 11, 2025
14 checks passed
@nuno-vieira nuno-vieira deleted the fix/resume-and-pause--from-livestream-robustness branch August 11, 2025 14:43
@Stream-SDK-Bot Stream-SDK-Bot mentioned this pull request Aug 13, 2025
@testableapple testableapple added 🟢 QAed A PR that was QAed and removed 🤞 Ready For QA A PR that is Ready for QA labels Aug 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🟢 QAed A PR that was QAed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants