Next Release - #3830
Merged
Merged
Next Release#3830
Conversation
## π― Goal From iOS 27 every iPhone app is resizable β Split View, and the fold/unfold of iPhone Duo (ships 2026-10-23). This makes the SDK and SampleApp correct on those devices. No device detection is involved anywhere: Duo is treated as a resizable window, not a device to branch on. Linear: [RN-429](https://linear.app/stream/issue/RN-429/iphone-duo-ios-27-resizable-window-readiness) **The two defects that matter:** 1. **Safe-area insets are asymmetric, and `top` is zero.** Measured on device: `top: 0, bottom: 34, left: 0, right: 84`, on *both* Duo displays. iOS 27 stacks the status bar, Dynamic Island, toolbars and tab bars down a single vertical edge β an 84pt reserved column. Nothing in the SDK or SampleApp read `insets.left` / `insets.right`, so channel-row timestamps, the header compose button, unread badges and the scroll-to-bottom button rendered underneath the system indicators. 2. **`inactive` was treated as `background`.** In Split View a *visible* app sits `inactive`. `useAppStateListener` conflated the two, so `useIsOnline` called `client.closeConnection()` and showed the offline banner **while the user was reading the chat** (on by default via `closeConnectionOnBackground`). Typing indicators stopped the same way, and `KeyboardCompatibleView` had its own copy of the bug that dropped the keyboard listeners. ## π Implementation details ### Horizontal insets: the integrator wraps, the SDK insets only what a wrapper cannot reach An integrator wraps their screens in `<SafeAreaView edges={['left', 'right']}>`. `develop` has no horizontal inset handling at all, so any integrator supporting landscape on a notched phone already does this β and adding SDK padding on top would silently double-inset exactly them on a minor upgrade. Leaving in-tree components alone is a no-op for everyone else. A wrapper also narrows the whole subtree, so absolutely positioned children (which ignore their container's padding) come along for free. The SDK insets itself only where a consumer's wrapper cannot reach: - **React Native `Modal`s**, which render in their own native window β `BottomSheetModal` (new `useHorizontalInsets`) and the channel details `Modal`. - **Image gallery header and footer**, hosted by `OverlayProvider` at the app root. They use `SafeAreaView` `edges` rather than raw inset values, so a nested instance contributes nothing when an ancestor has already narrowed the subtree. - `SafeAreaViewWrapper` now passes all four edges. `useHorizontalInsets` keeps `left` and `right` separate (they differ on iOS 27) and omits a side whose inset is 0, because a `paddingLeft: 0` longhand would override a `padding` shorthand set by the component or a consumer theme. ### Grid and tile sizing A wrapper can supply layout, but not a number. New `useWindowContentWidth` (window width minus the horizontal insets) sizes the channel-details media grid and its skeleton, the attachment picker grid and tiles, and the image gallery grid. It degrades safely: an unwrapped app gets slightly small tiles, where plain window width would overflow for anyone who wraps. Grids stay at 3 columns. `ImageGrid` remounts on a column-count change, since `FlatList` rejects `numColumns` changes on a mounted list. ### App state `useAppStateListener` and `KeyboardCompatibleView` now treat only a real `background` transition as backgrounding, each callback firing once per transition. This also stops the connection dropping during Control Center and the app switcher. The previous test *codified* the bug and was rewritten. ### Smaller fixes - **`StreamShimmerView`** reads `traitCollection.displayScale` instead of a once-cached `UIScreen.main.scale`, and per-view `window.windowScene.activationState` instead of the app-wide `applicationState`. - **`MessageItemView`** reads the window, not the screen. `screen` β `window` in both directions: an 834pt iPad screen behind a 417pt window, and the Duo's 466pt outer screen behind its 951pt inner window. - **`useScreenDimensions`** is marked `@deprecated` in favour of `useViewport` β same API, measured against the window. Behaviour unchanged; removal in the next major. ### SampleApp Every screen wraps itself in `<SafeAreaView edges={['left', 'right']}>`, with the header inside the wrapper. The hand-rolled `insets.left/right` padding in `ScreenHeader` and on the channel-list search bar is gone. `BottomTabs` keeps its own horizontal padding, because it is the navigator's tab bar and renders outside every screen. The toast is sized as a percentage of its container instead of the window. ### Measured geometry, since the published figures are wrong | Device / mode | window (pt) | screen (pt) | insets t/b/l/r | | --- | --- | --- | --- | | Duo, outer display, folded | 466 Γ 678 @3x | 466 Γ 678 @3x | 0 / 34 / 0 / 84 | | Duo, inner display, unfolded, landscape | **951 Γ 669 @3x** | **466 Γ 678 @3x** | 0 / 34 / 0 / 84 | | iPad Pro 11", iPhone-only app in a compatibility window | **417** Γ 1210 @2x | **834** Γ 1210 @2x | 32 / 20 / 0 / 0 | The inner display is 669 Γ 951pt, not the widely cited 626 Γ 890: 669 Γ 3 = 2007 and 951 Γ 3 = 2853, matching the framebuffer exactly. ## π¨ UI Changes On the Duo, nothing the app draws enters the 84pt reserved column any more, on either display. Previously the row timestamps, the header compose button, the unread badges and the scroll-to-bottom button (406β446pt) all sat inside it. Folded, content now ends at the 382pt boundary (466 β 84). Unfolded, it ends at 867pt (951 β 84) or before. On phones without horizontal insets in portrait there is no visible change. In landscape, content now clears the Dynamic Island or camera cutout on whichever side it is. ## π§ͺ Testing | Gate | Result | | --- | --- | | `yarn lint` | exit 0, zero warnings | | `cd package && yarn test:typecheck` | exit 0 | | `yarn build` | succeeds (the strict type gate) | | `yarn test:unit` | 185 suites passed (2 skipped), 1506 tests passed (14 skipped), 12 snapshots | **On device.** The same 13-screen tour on every device: channel list, channel, attachment picker, channel details, Photos & Videos, gallery, gallery grid, Threads, Mentions, Drafts, Reminders, Pinned Messages and Files. Each screen was captured, and the inset regions were measured from pixels. | Device | Orientations | Result | | --- | --- | --- | | iPhone Duo (iOS 27.1), outer display | portrait | reserved column clear on every screen except the six headers below (captured before their fix) | | iPhone Duo, inner display (unfolded) | landscape | reserved column clear on every screen; live unfold re-lays out without a relaunch | | iPhone 18 Pro Max (iOS 27.0) | portrait + landscape | 62pt side strips clear; portrait pixel-identical to the previous verified head, apart from content | | Pixel 10 emulator (Android 17, gesture nav) | portrait + both landscape rotations | 54dp cutout strip clear on whichever side it is; no double padding on the other side | | Pixel 10 Pro Fold emulator (Android 17) | unfolded + folded, 3 orientations each | cover-screen cutout strip clear; media grid and attachment picker re-lay out on fold and are pixel-identical after unfolding again | In every run the only thing inside an inset region is the gallery grid's dimmed backdrop, which is intended. This device pass caught one regression from the inset simplification: six SampleApp screens (Mentions, Drafts, Reminders, Photos & Videos, Pinned Messages, Files) rendered their header outside the new wrapper. The Duo's compose button went under the system controls, and titles centred on the full window. It is fixed in `fix(SampleApp): wrap screen headers in the horizontal safe area` and re-verified on the Duo inner display, the Pro Max and both Android emulators. The Duo outer display has not been re-captured since the fix; it has the same root cause. **Not verified:** the Duo inner display in portrait (the simulator pins the unfolded scene to landscape); Android 3-button navigation, where the nav bar sits on a side in landscape; the toast, thread detail, message actions, polls and the keyboard; Expo. ## Known trade-offs and follow-ups - **The media and picker grids are 3 columns.** On the Duo's inner display and on the Pixel Fold unfolded, the middle column crosses the fold. Apple's Duo guidance asks for an even column count, but develop has 3 columns too, so this is not a regression. - **Rotating the media grid keeps the pixel offset, not the row.** Landscape's larger tiles land you several rows earlier. `develop` behaves the same way. - **ExpoMessaging does not launch on iOS 27.** Expo SDK 57's prebuild template predates the scene lifecycle. This needs SDK 58 or a config plugin, and is out of scope here. - **Two-pane layout on the Duo's inner display.** Apple's HIG presents this as the main adaptation for that display. For us it means `ChannelList` + `Channel` side by side, which needs a product decision. - **Native chrome in SampleApp.** Verified empirically: iOS relocates real native bars into the reserved column. SampleApp forgoes that by using `headerShown: false` and a custom JS tab bar. - **Unrelated, spotted in passing:** the Drafts list renders a literal `{{count}} Files` (`useMessagePreviewText.tsx:105`). ## βοΈ Checklist - [x] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [x] PR targets the `develop` branch - [x] Documentation is updated (JSDoc on the new hooks, `@deprecated` on `useScreenDimensions`) - [ ] New code is tested in main example apps, including all possible scenarios - [x] SampleApp iOS β iPhone Duo (iOS 27.1, both displays) and iPhone 18 Pro Max (iOS 27.0), portrait and landscape - [x] SampleApp Android β Pixel 10 and Pixel 10 Pro Fold emulators (Android 17), portrait and both landscape directions, folded and unfolded - [ ] Expo iOS and Android β untouched; `ExpoMessaging` does not launch on iOS 27 at all (see above) --------- Co-authored-by: Ivan Sekovanikj <ivan.sekovanikj@getstream.io>
β¦cribe (#3827) ## π― Goal A message or draft with no text and no attachments rendered a bogus attachment summary as its preview. It showed up in the SampleApp Drafts list as a literal `{{count}} Files` during the iPhone Duo device pass (RN-429). The same hook also feeds the channel-list last-message preview, the thread list, the reply preview and the SampleApp Reminders list. The easiest way to hit it: in a channel, long-press a message β **Reply**, type nothing, and go back. `stream-chat` saves a draft that has only a `quoted_message_id`, so the Drafts tab shows `{{count}} Files`. Linear: [RN-432](https://linear.app/stream/issue/RN-432/message-preview-shows-0-files-0-audios-for-messages-with-nothing-to) Β· Same fix on `V10`: #3826 ## π Implementation details `useMessagePreviewText` had no exit for "nothing to describe", so it fell through to its attachment wording: | `message.attachments` | before | after | | --- | --- | --- | | absent | literal `{{count}} Files`: `count` is undefined, so no `_one`/`_other` key matches | `''` | | `[]` | "0 Audios": `onlyAudio` is `0 === 0` | `''` | It now returns an empty preview once the text check has passed and there are no attachments. The sibling `useMessagePreviewIcon` already returns `null` in that case, and it guards `onlyAudio` with `audios?.length &&` for exactly this reason. Deleted messages, polls, locations, giphys and text all return before the new check, so they are unaffected. Mixed attachments still read "N Files". ## π¨ UI Changes The rows that showed `{{count}} Files` or "0 Audios" now show an empty subtitle, the same as their icon, which was already absent. No other visual change. ## π§ͺ Testing New `package/src/hooks/messagePreview/__tests__/useMessagePreviewText.test.tsx`: - **Regression:** an absent `attachments` and an empty one both return `''`. Both failed before the fix. - **Unchanged behaviour:** mixed attachments give "2 Files", audio-only gives "2 Audios", and text wins over attachments. The counted cases use `waitFor`, because translations load asynchronously in tests. | Gate | Result | | --- | --- | | `yarn lint` | exit 0 | | `cd package && yarn test:typecheck` | exit 0 | | `yarn build` | succeeds | | `yarn test:unit` | 186 suites, 1511 passed (14 skipped), 12 snapshots | Not checked in the running app. The change is covered by the hook tests. ## βοΈ Checklist - [x] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [x] PR targets the `develop` branch - [x] Documentation is updated β not needed, no API change - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android
## π― Goal Should resolve [this GH issue](#3824). Linear ticket: https://linear.app/stream/issue/RN-430/messagecomposer-reserves-72dp-of-extra-bottom-space-on-first-mount-of ## π Implementation details <!-- Provide a description of the implementation --> ## π¨ UI Changes <!-- Add relevant screenshots --> <details> <summary>iOS</summary> <table> <thead> <tr> <td>Before</td> <td>After</td> </tr> </thead> <tbody> <tr> <td> <!--<img src="" /> --> </td> <td> <!--<img src="" /> --> </td> </tr> </tbody> </table> </details> <details> <summary>Android</summary> <table> <thead> <tr> <td>Before</td> <td>After</td> </tr> </thead> <tbody> <tr> <td> <!--<img src="" /> --> </td> <td> <!--<img src="" /> --> </td> </tr> </tbody> </table> </details> ## π§ͺ Testing <!-- Explain how this change can be tested (or why it can't be tested) --> ## βοΈ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android
Next releasesv9.9.29.9.2 (2026-09-25)Bug Fixes
sampleapp@v4.15.24.15.2 (2026-09-25)Bug Fixes |
Contributor
SDK Size
|
szuperaz
approved these changes
Sep 25, 2026
Contributor
|
π This PR is included in version 9.9.2 π The release is available on:
Your semantic-release bot π¦π |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
π― Goal
π Implementation details
π¨ UI Changes
iOS
Android
π§ͺ Testing
βοΈ Checklist
developbranch