feat: allow only one swiped room item at a time - #7719
Merged
Merged
Conversation
* chore: upgrade dependencies and patches to RN 0.86 / Expo 57 * chore: align upgrade toolchain floors and CI checks * chore: update Jest toolchain for RN 0.86 and Expo 57
* fix: adapt app code to React Native 0.86 and Expo 57 * test: await scheduled gesture and floating date callbacks
* fix(ios): support React Native 0.86 and Expo 57 * test: fix jest failures under Node 22 Resolve Expo's lazy fetch global in jest setup so suites that fully mock react-native no longer crash when jest enumerates globals at teardown. Restore real timers only after Testing Library unmounts, so immediates scheduled with fake timers are cancelled with the same clock. * fix(android): adopt Expo 57 autolinking plugin and drop jcenter from patched libraries Expo 57 removed scripts/autolinking.gradle in favour of the expo-autolinking-settings and expo-root-project Gradle plugins. Gradle 9 removed jcenter(), which @react-native-cookies/cookies and react-native-math-view still declared. Bump the iOS Pods cache key so runners stop restoring Local Podspecs generated for the previous React Native version. * fix(android): build with React Native 0.86 codegen and Expo 57 prebuilt modules Bump @react-native/codegen, babel-preset and typescript-config to 0.86.3 so libraries stop resolving the stale 0.80 codegen. Build the four patched Expo packages from source, since Expo 57 ships prebuilt Android artifacts that ignore patch-package changes. Patch camera-roll to drop string literal unions from its spec, keyboard-controller for the non-null onConfigurationChanged signature, math-view to drop the removed UIManagerModuleListener import, and expo-font for the nullable loadedFonts field. Declare androidx.localbroadcastmanager explicitly and resolve the WRITE_EXTERNAL_STORAGE maxSdkVersion manifest conflict. * fix: drop undefined request headers rejected by Expo fetch Expo 57 installs its native fetch as the global and rejects header entries whose value is undefined. getServerInfo sent X-Auth-Token and X-User-Id as undefined before login, so every deep link login fell back to the Add workspace screen and all E2E flows failed. * test(e2e): wait for LoginView transition before keyboard navigation * fix(login): keep focus on the password field when submitting with a hardware keyboard * fix(ios): compile the release bundle with the hermesc matching the bundled Hermes runtime * fix(ios): expose room header testIDs to accessibility tree An accessible wrapper around the room header hides its children from XCUITest on React Native 0.86, so Maestro could not find the header title or the header button. Move the accessibility label and role to the pressable header, which already exposes its children. * fix(ios): expose rooms list header testIDs to accessibility tree Same cause as the room header: an accessible wrapper inside the native stack header hides its children from XCUITest, so Maestro could not find the servers list button. * fix(ios): bump react-native-linear-gradient to 2.8.3 to stop zero-size layer crash on mention autocomplete * fix(ios): address upgrade review feedback * fix(ios): exclude Android keyboard flow change
* fix: update Android build for React Native 0.86 * fix: restore Android Gradle 9 compatibility * fix: align React Native codegen version * fix: resolve Android storage permission merge * fix: patch Android modules for React Native 0.86 * fix: restore Android local broadcast dependency * fix: build patched Expo modules from source * fix: raise iOS deployment target to 16.4 for Expo 57 * fix: materialize expo fetch global before jest mocks * fix: keep setImmediate real under jest fake timers * fix: key iOS pods cache by Podfile.lock hash * fix: allow non-modular React includes in framework modules * fix: drop undefined header values before fetch expo/fetch, installed as the global fetch on native by Expo 57, forwards header entries to the native request verbatim, so an undefined value fails the Kotlin cast to Pair<String, String> and rejects the whole request. * fix: build React-Core from source instead of prebuilt * fix: match Expo import access level with generated provider * fix: adopt Expo 57 AppDelegate contract * fix: compile Hermes bytecode with the legacy compiler * fix: expose header test ids on iOS * fix: expose team channels header test id * fix: remove iOS changes from Android upgrade
Removed the three Podfile opt-outs (RCT_USE_PREBUILT_RNCORE, RCT_USE_RN_DEP, RCT_HERMES_V1_ENABLED) and the HERMES_CLI_PATH post_install override, letting React Native 0.86 use its prebuilt React-Core/ReactNativeDependencies tarball and Hermes V1 (250829098.0.17) instead of building from source. Dropped the RCTViewManager accessibility-role patch: it only touched the Paper renderer, the app runs on Fabric, and the file no longer compiles under prebuilt Core. VoiceOver role-update behavior is covered by manual smoke testing, not this patch. RNFB pods forced to static libraries in the pre_install hook to avoid a non-modular-include failure on RNFB 21.x; the fix ships in react-native-firebase >= 26.3.0, so this can be dropped on that bump. SocketRocket now ships vendored inside the ReactNativeDependencies xcframework under a namespaced header path, so SSLPinning.mm's import changed from a flat "SRWebSocket.h" to <SocketRocket/SRWebSocket.h>. Copy Pods Resources phases regenerated by pod install, dropping the boost/glog/RCT-Folly privacy bundles that no longer exist as separate pods.
* fix(e2e): reach login email field by focus instead of Dpad count The keyboard navigation onboarding flow walked to the email field with four blind Remote Dpad Down presses. Removing the decorative OR separator from the accessibility focus chain shortened the chain by one, so the email was typed into the password field and the password into the email field, and login was never submitted. Press Down until login-view-email reports focused, bounded by times: 10, and assert focus before typing. Maestro returns normally when a repeat bound is exhausted, so without the assertion an over-long chain would fail later at the rooms-list-view wait with an unrelated error. The separator only renders when totalServices > 0, so the chain length depends on server config and no fixed count is correct. * fix(e2e): reach password and submit by focus, hide keyboard first Anchoring the email field exposed two more blind steps in the same flow. The hop to the password field assumed Enter moves focus, and the submit assumed Enter on the password field logs in. Neither had been exercised before, because the flow failed earlier. Press Down until each target reports focused, bounded and asserted like the email field. The submit loop needs the soft keyboard hidden first: with the keyboard up the Dpad presses go to the input method and never reach the login screen, so the submit button is never focused.
* chore(deps): bump reanimated to 4.6.0 and worklets to 0.12.2 Reanimated 4.6.0 requires react-native-worklets 0.12.x, so both move together. Worklets drops its caret to match reanimated's exact pin. Reanimated 4.6.0 initializes CSS event handling at import time, which the JS-only fallback used by Jest does not implement, so the mock entry point throws on load. Stubbing setCSSEventHandler at its source keeps the rest of the proxy module real. * chore(ios): update Podfile.lock for reanimated and worklets CocoaPods preserves versions already in Podfile.lock, so pod install would not have picked these up on its own. * chore(ios): bump ffi to 1.17.4 for Xcode 27 compatibility
iOS 27 SDK requires scene-based app lifecycle. The app now uses Expo's ExpoAppSceneDelegate via a new SceneDelegate, with startReactNative and boot splash initialization moved out of the app delegate into the scene delegate.
…-pad The RN 0.86 gesture-handler bump added an Android-only onKeyUp override that intercepts DPAD_CENTER/ENTER/SPACE and activates whichever button currently holds native view focus, bypassing the default performClick() path. Every composer and header icon button is a BorderlessButton, so this silently fired the wrong button (e.g. the header drawer button) on hardware Enter/D-pad and swallowed the key event elsewhere. Removed the hunk; the iOS press-handling hunk is untouched.
Track the currently open swipeable room item in a single UI-thread mutable value (openSwipeItemId) instead of per-item state. Every Touchable reacts to that value and springs itself closed once another row claims it, so only one room can be swiped open at a time. Tapping a room, long-pressing it, or opening its own actions all clear or reassign ownership. Also replaces the abrupt default spring with an explicit, softer config (damping/mass/stiffness) shared by every open/close animation.
openSwipeItem.test.ts only asserted that a shared value keeps what was written to it, which added no coverage. GestureDetector/pan behavior isn't exercisable through the existing jest reanimated mocks (useAnimatedReaction is a no-op), so a real cross-row close test isn't practical here without a broader mocking change. Revert the makeMutable jest mock added for that test since nothing else in the suite needs it.
diegolmello
had a problem deploying
to
approve_e2e_testing
September 23, 2026 12:27 — with
GitHub Actions
Error
diegolmello
had a problem deploying
to
upload_android
September 29, 2026 18:20 — with
GitHub Actions
Error
|
Android Build Available Rocket.Chat 4.77.0.109808 Internal App Sharing: https://play.google.com/apps/test/RQQ8k09hlnQ/ahAO29uNRq_jyY-4-OblYTJn5vVKsyPFBS27NFc-FHwek_W1l9G6N1v49onfCJWvaBGuu0tnihrXmzI-qC-i_qDh2_ |
|
iOS Build Available Rocket.Chat 4.77.0.109809 |
Derive the open side from the resting offset instead of a separate row state, release and register the row inside the gesture worklet, fire the threshold haptic from the pan update, and flip gesture input and output once for right-to-left layouts. Drop the duplicate long-press gesture and skip the snapshot rebuild on the record's initial emission.
diegolmello
had a problem deploying
to
upload_android
September 29, 2026 21:15 — with
GitHub Actions
Error
|
Android Build Available Rocket.Chat 4.77.0.109812 Internal App Sharing: https://play.google.com/apps/test/RQQ8k09hlnQ/ahAO29uNRrzXcidkdWnybWYsmcmfszKhxD4vqJHyosakifYH3n7pyrKwKDLc8DcWOSAIRU2edn6i8ekRgrqPdVDtYV |
|
iOS Build Available Rocket.Chat 4.77.0.109814 |
diegolmello
had a problem deploying
to
upload_android
September 30, 2026 02:18 — with
GitHub Actions
Error
Base automatically changed from
diegolmello/native-1561-rn-upgrade
to
develop
September 30, 2026 13:18
…-room-item-swipe # Conflicts: # app/containers/RoomItem/Actions.tsx # app/containers/RoomItem/Touchable.tsx # app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap
diegolmello
had a problem deploying
to
approve_e2e_testing
September 30, 2026 17:12 — with
GitHub Actions
Error
diegolmello
had a problem deploying
to
ios_build
September 30, 2026 17:15 — with
GitHub Actions
Error
diegolmello
had a problem deploying
to
android_build
September 30, 2026 17:15 — with
GitHub Actions
Error
diegolmello
had a problem deploying
to
approve_e2e_testing
September 30, 2026 17:17 — with
GitHub Actions
Error
diegolmello
had a problem deploying
to
android_build
September 30, 2026 17:20 — with
GitHub Actions
Error
diegolmello
had a problem deploying
to
ios_build
September 30, 2026 17:20 — with
GitHub Actions
Error
diegolmello
requested a deployment
to
approve_e2e_testing
September 30, 2026 20:54 — with
GitHub Actions
Waiting
diegolmello
requested a deployment
to
ios_build
September 30, 2026 20:57 — with
GitHub Actions
Waiting
diegolmello
requested a deployment
to
android_build
September 30, 2026 20:57 — with
GitHub Actions
Waiting
This branch is waiting to be deployed
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.
Proposed changes
Only one room item can be swiped open at a time. With a row open, pressing or tapping another row closes the open row with a smooth spring animation, and that tap does not navigate or open the long-press action sheet; the next tap navigates normally. Tapping the open row itself closes it without navigating. Swiping another row closes the open one while the new row follows the drag. A long swipe (auto-toggle-read/hide) and a long press (action sheet) behave as before.
Swipe actions follow a WhatsApp-style layout:
getSwipeRelease, covered by unit tests.The swipe springs share one critically damped spring config (mass 1, stiffness 150, damping 24.5), so the auto-close animation matches the manual swipe feel, and the close animation starts on the UI thread.
The "currently open row" is tracked in a module-level Reanimated value (
makeMutable), so pressing another row closes the open one directly on the UI thread.The row now refreshes when its room record changes. React Compiler cached the row's read state, title, avatar and invite state on the record object, which WatermelonDB updates in place, so the row kept stale values and the swipe kept sending "mark read" instead of toggling. These values now come from a
useSyncExternalStoresubscription to the record, and the custom memo comparator is removed. Rooms with only unread thread replies count as unread, matching the unread badge.The focused-row highlight only shows in master-detail layout, where the selected room is visible next to the list; on phones a row no longer stays highlighted after returning from its room.
Issue(s)
https://rocketchat.atlassian.net/browse/NATIVE-1676
How to test or reproduce
Verified on the iOS simulator. On the Android emulator, synthetic pan gestures did not open rows, so Android still needs manual verification.
Scrolling that starts on the open row does not close it. A touch that starts on another row (including a scroll) closes it.
Screenshots
Types of changes
Checklist
Further comments
iOS demo (video): tap on another row closes the open row without navigating, then a normal tap navigates.
ios-tap-closes-final.mp4
iOS demo (video): swipe release rules on both sides, full swipes and threshold crossing.
room-swipe-release-rules-short.mp4
Summary by CodeRabbit