feat(voip): tap-to-hide call controls with animations - #7078
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
app/lib/services/voip/useCallStore.ts (1)
208-210: Consider whethershowControlsis needed.The
showControlsaction is defined but the auto-reveal logic uses inlineset({ controlsVisible: true })instead. This action may be useful for external consumers or future enhancements. If it's not needed externally, you could simplify by removing it, or alternatively refactor the inline sets to use this action for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/lib/services/voip/useCallStore.ts` around lines 208 - 210, The store defines a showControls action but other code uses inline set({ controlsVisible: true }), causing duplication; either remove the showControls action if it has no external usage, or replace the inline set({ controlsVisible: true }) calls with calls to showControls for consistency. Locate the showControls function in useCallStore and the places that call set({ controlsVisible: true }) and choose one approach: delete showControls and keep inline sets (and remove its export), or refactor those inline sets to call showControls so all visibility changes go through the single action.app/lib/services/voip/useCallStore.test.ts (1)
33-35: Forward event payloads in the mock emitter.This helper currently invokes listeners without args, which can mask regressions if store handlers later depend on event payloads.
♻️ Proposed tweak
- const emit = (ev: string) => { - listeners[ev]?.forEach(fn => fn()); - }; + const emit = (ev: string, ...args: unknown[]) => { + listeners[ev]?.forEach(fn => fn(...args)); + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/lib/services/voip/useCallStore.test.ts` around lines 33 - 35, The mock emitter function emit currently calls listeners without forwarding payloads; update the emit helper in useCallStore.test.ts so its signature accepts variadic payloads (e.g., emit(ev: string, ...args)) and invoke each listener with those payloads (fn(...args)); also adjust the listeners typing if necessary so listener functions can receive arguments, ensuring tests exercise real event payload handling used by the store handlers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/views/CallView/components/CallButtons.tsx`:
- Around line 30-33: The animated worklet in containerStyle captures
controlsVisible at creation time so it won't react to zustand updates; convert
the zustand boolean (from useControlsVisible) into a reanimated shared value
(useSharedValue) and sync it in a useEffect when the selector changes, then
reference that shared value inside useAnimatedStyle so opacity/translateY use
withTiming against the shared value (keep CONTROLS_ANIMATION_DURATION as-is) to
restore reactivity.
In `@app/views/CallView/components/CallerInfo.tsx`:
- Around line 17-20: The animated style uses the plain JS boolean
controlsVisible inside useAnimatedStyle which harms Reanimated performance;
convert controlsVisible into a Reanimated shared value and reference that inside
callerRowStyle. Specifically, create a useSharedValue (e.g.,
controlsVisibleShared), update it when the React prop/store changes (via
useEffect or a derived value) and then replace references to controlsVisible in
the useAnimatedStyle callback for callerRowStyle with
controlsVisibleShared.value while keeping CONTROLS_ANIMATION_DURATION and the
same withTiming logic.
---
Nitpick comments:
In `@app/lib/services/voip/useCallStore.test.ts`:
- Around line 33-35: The mock emitter function emit currently calls listeners
without forwarding payloads; update the emit helper in useCallStore.test.ts so
its signature accepts variadic payloads (e.g., emit(ev: string, ...args)) and
invoke each listener with those payloads (fn(...args)); also adjust the
listeners typing if necessary so listener functions can receive arguments,
ensuring tests exercise real event payload handling used by the store handlers.
In `@app/lib/services/voip/useCallStore.ts`:
- Around line 208-210: The store defines a showControls action but other code
uses inline set({ controlsVisible: true }), causing duplication; either remove
the showControls action if it has no external usage, or replace the inline set({
controlsVisible: true }) calls with calls to showControls for consistency.
Locate the showControls function in useCallStore and the places that call set({
controlsVisible: true }) and choose one approach: delete showControls and keep
inline sets (and remove its export), or refactor those inline sets to call
showControls so all visibility changes go through the single action.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 38495b52-7fdb-4b65-86d3-caa69559972d
⛔ Files ignored due to path filters (3)
app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snapis excluded by!**/*.snapapp/views/CallView/__snapshots__/index.test.tsx.snapis excluded by!**/*.snapapp/views/CallView/components/__snapshots__/CallerInfo.test.tsx.snapis excluded by!**/*.snap
📒 Files selected for processing (11)
app/containers/MediaCallHeader/MediaCallHeader.test.tsxapp/containers/MediaCallHeader/MediaCallHeader.tsxapp/lib/services/voip/useCallStore.test.tsapp/lib/services/voip/useCallStore.tsapp/views/CallView/components/CallButtons.test.tsxapp/views/CallView/components/CallButtons.tsxapp/views/CallView/components/CallerInfo.test.tsxapp/views/CallView/components/CallerInfo.tsxapp/views/CallView/index.test.tsxapp/views/CallView/styles.tsprogress-controls-animation.md
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-03-10T15:21:45.098Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7046
File: app/containers/InAppNotification/NotifierComponent.stories.tsx:46-75
Timestamp: 2026-03-10T15:21:45.098Z
Learning: In `app/containers/InAppNotification/NotifierComponent.tsx` (React Native, Rocket.Chat), `NotifierComponent` is exported as a Redux-connected component via `connect(mapStateToProps)`. The `isMasterDetail` prop is automatically injected from `state.app.isMasterDetail` and does not need to be passed explicitly at call sites or in Storybook stories that use the default (connected) export.
Applied to files:
app/views/CallView/components/CallerInfo.test.tsxapp/views/CallView/components/CallButtons.tsxapp/views/CallView/components/CallerInfo.tsx
🔇 Additional comments (13)
app/views/CallView/styles.ts (1)
5-6: LGTM!Good practice extracting the animation duration as a shared constant. This ensures consistent timing across all animated components (
CallButtons,CallerInfo,MediaCallHeader) and makes future adjustments easy.app/lib/services/voip/useCallStore.ts (1)
204-210: LGTM - clean state management implementation.The
toggleControlsVisibleandshowControlsactions follow Zustand patterns correctly. Auto-revealing controls onstateChange,trackStateChange, andtoggleFocusprovides good UX by ensuring users see the controls during important call events.app/views/CallView/components/CallButtons.tsx (1)
52-55: LGTM for the Animated.View setup.The
pointerEventstoggle is a good practice to prevent accidental taps on hidden controls, and maintainingtestIDensures testability.app/views/CallView/components/CallerInfo.test.tsx (1)
57-70: LGTM - good test coverage for toggle behavior.The test properly verifies the complete toggle cycle (true → false → true) and directly accesses store state which is appropriate for testing Zustand stores.
app/containers/MediaCallHeader/MediaCallHeader.test.tsx (1)
179-210: LGTM - comprehensive test coverage for pointer events behavior.The three test cases properly cover the visibility matrix:
focused=true, controlsVisible=false→pointerEvents='none'focused=true, controlsVisible=true→pointerEvents='auto'focused=false→pointerEvents='auto'(header always interactive when collapsed)This correctly validates the conditional hiding logic.
app/views/CallView/index.test.tsx (1)
94-95: LGTM - testID references updated consistently.All assertions correctly updated from
caller-infotocaller-info-toggleto match the renamed/restructuredCallerInfocomponent.Also applies to: 105-106, 135-138, 302-303, 314-315
app/views/CallView/components/CallerInfo.tsx (1)
26-35: LGTM - clean implementation of tap-to-hide with correct separation.The avatar correctly remains outside the animated view (always visible), while only the caller name row animates. The
Pressableprovides the expected tap target covering the entire caller info area.app/containers/MediaCallHeader/MediaCallHeader.tsx (2)
33-38: Logic is correct; same animated style note applies.The
shouldHide = focused && !controlsVisiblelogic correctly ensures:
- Header hides only when the CallView is focused AND controls are toggled off
- Header stays visible when collapsed (not focused), regardless of
controlsVisibleThis matches the PR objective: "collapsed header bar remains visible."
50-61: LGTM - well-structured animated header.The Animated.View correctly applies the combined styles and manages pointer events. The empty state branch appropriately remains a plain View since no animation is needed when there's no call.
app/lib/services/voip/useCallStore.test.ts (2)
57-118: Good coverage forcontrolsVisiblelifecycle.The suite exercises default state, action transitions, reset behavior, focus toggling, and auto-reveal on call events—this is solid coverage for the new store contract.
174-174: Nice fix in stale-timer setup.Passing
createMockCall('x').callkeeps the test aligned withsetCallinput shape while preserving the helper’s event emitter API.app/views/CallView/components/CallButtons.test.tsx (1)
41-63: Targeted interaction-gating tests look good.Verifying
pointerEventsfor both hidden and visible states gives clear coverage for preventing ghost taps when controls are hidden.progress-controls-animation.md (1)
1-123: Well-structured rollout documentation.The slice breakdown, demos, and decision log are clear and actionable for both implementation tracking and QA validation.
Add toggle/show controls visibility for tap-to-hide animation support in CallView. Includes convenience selector and animation duration constant.
Wrap CallerInfo with Pressable to toggle controlsVisible on tap. Animate caller name row with fade + slide using Reanimated. Avatar remains always visible and centered.
Wrap CallButtons in Animated.View with opacity and translateY animations driven by controlsVisible store state. Set pointerEvents to 'none' when hidden to block ghost taps.
Update 'caller-info' to 'caller-info-toggle' to match the testID rename from the CallerInfo toggle implementation.
Replace active-call View with Animated.View that slides up and fades out when controlsVisible is false. Animation only applies when focused (expanded call view); collapsed header bar remains always visible.
Show controls automatically when stateChange, trackStateChange events fire or when toggling focus, so users never miss important state updates.
- Call showControls() from stateChange, trackStateChange, and toggleFocus instead of inlining controlsVisible in set() patches - Mock media emitter forwards variadic args in useCallStore tests - Add test and JSDoc for controls visibility / animation consumers Made-with: Cursor
The MediaCallHeader used translateY + opacity to hide, but its surfaceNeutral background remained visible as a dark bar. Animate backgroundColor and borderBottomColor to transparent so CallView shows through seamlessly.
…emove progress doc Merge controlsVisible: true into existing Zustand set() calls instead of separate showControls() to avoid double renders. Update stale MediaCallHeader snapshots and remove planning doc from repo.
45f3ef2 to
a8ae63c
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/views/CallView/components/CallButtons.tsx (1)
37-41: Placeholderalert()for Message functionality.The TODO is acknowledged. The
alert('Message')is fine for development but should be replaced with actual navigation before shipping.Would you like me to help scaffold the navigation to the chat room once the implementation details are known?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/views/CallView/components/CallButtons.tsx` around lines 37 - 41, The placeholder alert in handleMessage should be replaced with actual navigation to the chat room: remove alert('Message') and call the app's navigation method to open the RoomView (use the previously commented Navigation.navigate('RoomView', { rid, t: 'd' }) or the project's navigation helper). Ensure handleMessage has access to the caller room id (rid) and required navigation object (import or receive Navigation from props/context) and keep the route params { rid, t: 'd' } when invoking Navigation.navigate so tapping Message opens the correct chat room.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@app/views/CallView/components/CallButtons.tsx`:
- Around line 37-41: The placeholder alert in handleMessage should be replaced
with actual navigation to the chat room: remove alert('Message') and call the
app's navigation method to open the RoomView (use the previously commented
Navigation.navigate('RoomView', { rid, t: 'd' }) or the project's navigation
helper). Ensure handleMessage has access to the caller room id (rid) and
required navigation object (import or receive Navigation from props/context) and
keep the route params { rid, t: 'd' } when invoking Navigation.navigate so
tapping Message opens the correct chat room.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 312e07ae-8a70-4f5f-8637-44d86d5c7275
⛔ Files ignored due to path filters (3)
app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snapis excluded by!**/*.snapapp/views/CallView/__snapshots__/index.test.tsx.snapis excluded by!**/*.snapapp/views/CallView/components/__snapshots__/CallerInfo.test.tsx.snapis excluded by!**/*.snap
📒 Files selected for processing (11)
app/containers/MediaCallHeader/MediaCallHeader.test.tsxapp/containers/MediaCallHeader/MediaCallHeader.tsxapp/lib/services/voip/MediaSessionInstance.test.tsapp/lib/services/voip/useCallStore.test.tsapp/lib/services/voip/useCallStore.tsapp/views/CallView/components/CallButtons.test.tsxapp/views/CallView/components/CallButtons.tsxapp/views/CallView/components/CallerInfo.test.tsxapp/views/CallView/components/CallerInfo.tsxapp/views/CallView/index.test.tsxapp/views/CallView/styles.ts
💤 Files with no reviewable changes (1)
- app/lib/services/voip/MediaSessionInstance.test.ts
✅ Files skipped from review due to trivial changes (4)
- app/views/CallView/styles.ts
- app/views/CallView/index.test.tsx
- app/containers/MediaCallHeader/MediaCallHeader.test.tsx
- app/views/CallView/components/CallButtons.test.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- app/containers/MediaCallHeader/MediaCallHeader.tsx
- app/lib/services/voip/useCallStore.test.ts
- app/lib/services/voip/useCallStore.ts
- app/views/CallView/components/CallerInfo.tsx
- app/views/CallView/components/CallerInfo.test.tsx
📜 Review details
⏰ 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: ESLint and Test / run-eslint-and-test
- GitHub Check: format
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-17T15:56:22.578Z
Learnt from: OtavioStasiak
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6499
File: app/containers/ServerItem/index.tsx:34-36
Timestamp: 2025-12-17T15:56:22.578Z
Learning: In the Rocket.Chat React Native codebase, for radio button components on iOS, include the selection state ("Selected"/"Unselected") in the accessibilityLabel instead of using accessibilityState={{ checked: hasCheck }}, because iOS VoiceOver has known issues with accessibilityRole="radio" + accessibilityState that prevent correct state announcement.
Applied to files:
app/views/CallView/components/CallButtons.tsx
📚 Learning: 2026-03-04T20:13:17.288Z
Learnt from: divyanshu-patil
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6957
File: ios/RocketChat Watch App/Views/MessageComposerView.swift:37-55
Timestamp: 2026-03-04T20:13:17.288Z
Learning: In the WatchOS app (ios/RocketChat Watch App) for Rocket.Chat React Native, using SwiftUI `Button` inside a `ScrollView` on WatchOS causes accidental message sends because button tap targets can be triggered during scroll gestures. `Text` with `.onTapGesture` is the preferred pattern for tappable items in scroll views on WatchOS. To preserve accessibility, add `.accessibilityAddTraits(.isButton)` and `.accessibilityLabel()` to the `Text` element instead.
Applied to files:
app/views/CallView/components/CallButtons.tsx
📚 Learning: 2026-03-10T15:21:45.098Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7046
File: app/containers/InAppNotification/NotifierComponent.stories.tsx:46-75
Timestamp: 2026-03-10T15:21:45.098Z
Learning: In `app/containers/InAppNotification/NotifierComponent.tsx` (React Native, Rocket.Chat), `NotifierComponent` is exported as a Redux-connected component via `connect(mapStateToProps)`. The `isMasterDetail` prop is automatically injected from `state.app.isMasterDetail` and does not need to be passed explicitly at call sites or in Storybook stories that use the default (connected) export.
Applied to files:
app/views/CallView/components/CallButtons.tsx
🔇 Additional comments (3)
app/views/CallView/components/CallButtons.tsx (3)
28-33: Reanimated reactivity with zustand state was previously flagged.The concern about
useAnimatedStylenot automatically reacting to plain JavaScript values (from the zustand selector) was already raised in a past review. The PR objectives indicate this is an intentional architectural choice where components re-render to drive the animated style rather than mirroring state into shared values.If animations appear janky or don't trigger reliably in testing, consider the suggested pattern of syncing to a shared value via
useEffect.
3-8: LGTM on imports.Clean import organization with centralized animation duration constant from styles.
52-55: Good use ofpointerEventsfor interaction gating.Setting
pointerEvents="none"when controls are hidden prevents accidental button presses during or after the hide animation. Style composition order is correct.
…/Decline (#7215) * merge feat.voip-lib * feat(voip): enhance call handling with UUID mapping and event listeners * Base call UI * feat(voip): integrate Zustand for call state management and enhance CallView UI * feat(voip): add simulateCall function for mock call handling in UI development * refactor(CallView): update button handlers and improve UI responsiveness * Add pause-shape-unfilled icon * Base CallHeader * toggleFocus * collapse buttons * Header components * Hide header when no call * Timer * Add use memo * Add voice call item on sidebar * cleanup * Temp use @rocket.chat/media-signaling from .tgz * cleanup * Check module and permissions to enable voip * Refactor stop method to use optional chaining for media signal listeners * voip push first test * Add VoIP call handling with pending call management - Implemented VoIP push notification handling in index.js, including storing call info for later processing. - Added CallKeep event handlers for answering and ending calls from a cold start. - Introduced a new CallIdUUID module to convert call IDs to deterministic UUIDs for compatibility with CallKit. - Created a pending call store to manage incoming calls when the app is not fully initialized. - Updated deep linking actions to include VoIP call handling. - Enhanced MediaSessionInstance to process pending calls and manage call states effectively. * Remove pending store and create getInitialEvents on app/index * Attempt to make iOS calls work from cold state * lint and format * Patch callkeep ios * Temp send iOS voip push token on gcm * Temp fix require cycle * chore: format code and fix lint issues [skip ci] * CallIDUUID module on android and voip push * Add setCallUUID on useCallStore to persist calls accepted on native Android * remove callkeep from notification * Android Incoming Call UI POC * Refactor VoIP handling: Migrate VoIP-related classes to a new package structure, removing deprecated modules and consolidating functionality. Update imports in MainApplication and NotificationIntentHandler to reflect changes. This cleanup enhances code organization and prepares for future VoIP feature enhancements. * Remove VoipForegroundService * cleanup and use caller instead of callerName * Cleanup and make iOS build again * Refactor VoIP handling: Remove unused event emissions for call answered and declined, switch from SharedPreferences to in-memory storage for pending VoIP call data, and update method signatures for better clarity. This cleanup enhances performance and prepares for future VoIP feature improvements. * Refactor VoIP handling: Introduce a new VoipPayload class to encapsulate call data, streamline notification processing, and enhance method signatures across the VoIP module. This update improves code clarity and prepares for future feature enhancements. * Migrate react-native-voip-push-notifications to VoipModule * Refactor VoIP module: Update package structure by moving VoipTurboPackage to the main package and removing the obsolete NativeVoipSpec class. Adjust imports in MainApplication and VoipModule to reflect these changes, enhancing code organization and maintainability. * Unify emitters * Move CallKeep listeners from MediaSessionInstance to getInitialEvents * Clear callkeep on endcall * Unify getInitialEvents logic * getInitialEvents -> MediaCallEvents * chore: format code and fix lint issues [skip ci] * feat(Android): Add full screen incoming call (#6977) * feat: Update call UI (#6990) * feat: Handle audio routing, e.g., Bluetooth headset vs. internal speaker switching (#6992) * fix: empty space when not on call (#6993) * feat: Dialpad (#7000) * action: organized translations * feat: start call (#7024) * chore: format code and fix lint issues * feat: Pre flight (#7038) * action: organized translations * feat: Receive voip push notifications from backend (#7045) * feat: Refactor media session handling and improve disconnect logic (#7065) * feat: Control incoming call from native (#7066) * feat: Voice message blocks (#7057) * feat: native accept success event (#7068) * feat(voip): call waiting, busy detection, and videoconf blocking (#7077) * action: organized translations * feat(voip): tap-to-hide call controls with animations (#7078) * feat(voip): navigate to call DM from message button and header (#7082) * feat(voip): tablet and landscape layout (#7110) * chore: develop into feat.voip-lib-new (RN 81 + Expo 54 + reanimated 4 + true-sheet + iOS 26) (#7114) * chore: format code and fix lint issues * feat(voip): android landscape layout for IncomingCallActivity (#7116) * Update agents files * feat(voip): Support a11y (#7106) * Fix content cutting on iOS on some edge cases * pods * Ignore .worktrees on jest * chore: Merge develop into feat.voip-lib-new (#7129) * fix(voip): show CallKit UI when call is active in background (#7128) * chore: Update media-signaling to 0.2.0 (#7153) * feat(voip): migrate iOS accept/reject from DDP to REST (#7124) * Fix icons * feat(voip): migrate Android accept/reject from DDP to REST (#7127) * test(voip): integration tests for CallView pipeline (#7161) * feat(voip): display video conf provider as subtitle (#7160) * fix(voip): CallView button grid and correct landscape/dialpad layouts (#7164) * fix(voip): prevent stale MMKV cache on Android first-install accept MMKVKeyManager.initialize ran in MainApplication.onCreate before the JS engine started and opened the default MMKV file via the Tencent 1.2 JAR when it was still empty. Tencent caches instances per-ID in a singleton registry, so that empty-state view was held for the rest of the process. JS later wrote credentials through react-native-mmkv (MMKV Core 2.0), which has its own separate registry. When a VoIP push arrived, Ejson.getMMKV() got the cached empty Tencent instance and reported "No userId found in MMKV for server". Closing and reopening the app cleared the cache, which is why only the very first call after install failed. Drop the open/verify block — the encryption key is already cached from SecureKeystore, so no MMKV handle is needed here. The first Tencent instance is now created inside Ejson.getMMKV() after JS has written, so it scans the file fresh. * fix(voip): prevent duplicate ringtone on Android incoming call (#7158) * fix(voip): set explicit snaps for NewMediaCall bottom sheet (#7165) * Update app/lib/services/voip/MediaSessionStore.ts Co-authored-by: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> * fix: make startVoipFork reactive to permissions-changed (#7151) * fix(android): remove MediaProjectionService from merged manifest (#7190) * fix(voip): Phone account creation (#7170) * feat: add Enable Mobile Ringing toggle in user preferences (#7155) * fix(voip): ship blockers for PushKit, licensing, outbound calls, push tokens (#7167) * fix(android): Play Store mic discoverability, safer FCM logs, avatar auth via headers (#7171) * fix(ios): serialize VoipService bridge statics (#7169) * fix(voip): Android DDP thread safety and VoipPayload bundle parity (#7168) * chore(voip): dead-code and hygiene sweep (#7174) * refactor(voip): decouple navigateToCallRoom from Redux and backfill REST/connect tests (#7176) * test(voip): tighten ringing endCall assertion and add VideoConf VoIP-lock saga coverage (#7177) * fix(ios): harden VoIP DDP WebSocket client on receive failures and TLS (#7173) * refactor(voip): MediaCallEvents Redux adapters and resetVoipState (#7178) * refactor(voip): decouple peer autocomplete from Redux; simplify NewMediaCall (#7175) * fix(ios): add NS_SWIFT_NAME to Challenge.runChallenge for Swift 6.2 compatibility Swift 6.2 (Xcode 26.x / macos-26 runner) auto-renames the Objective-C method runChallenge:didReceiveChallenge:completionHandler: to run(_:didReceive:completionHandler:) when imported into Swift. Add NS_SWIFT_NAME to explicitly pin the Swift import name, preventing the compiler from applying its heuristics. This keeps the existing Swift call site in DDPClient.swift working without changes. * fix(ios): cancel old URLSession/webSocketTask before reconnecting in DDPClient.connect (#7197) * fix(ios): add NSLock to nativeAcceptHandledCallIds and 10s REST timeout to handleNativeAccept (#7198) * feat(android): create VoipCallService with FOREGROUND_SERVICE_MICROPHONE (#7199) * fix(android): start VoipCallService on accept, stop on hangup/timeout, install end-call listener (#7200) * fix(voip): enable DM nav for users with SIP extension (#7203) * fix(android): handle null VoiceConnection in answerIncomingCall, notify JS (#7201) * fix(voip): resolve closure capture ordering in handleNativeAccept (#7209) * fix(android): integrate VoIP modules with SSL-pinned OkHttpClient (#7208) * fix(push): gate id and voipToken behind server version checks, fix VideoConf caller extra (#7210) * fix(voip): remove sensitive data from production logs (#7207) * fix(android): remove isRunning guard + add double-tap guard on Accept/Decline - VoipCallService: remove if (!isRunning) guard, call startForeground unconditionally (idempotent on Android, fixes Android 14+ foreground service requirement) - IncomingCallActivity: add AtomicBoolean guard on handleAccept/handleDecline to prevent double-tap from triggering multiple service starts --------- Co-authored-by: diegolmello <diegolmello@users.noreply.github.com> Co-authored-by: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com>
Proposed changes
This PR adds tap-to-hide (and tap-to-show) VoIP call controls on the in-call screen, with Reanimated opacity and translation transitions and a single source of truth in the call Zustand store.
User-facing behavior
Pressablearound avatar + name) toggles whether secondary controls are visible.pointerEventsis set tononeso taps do not hit invisible controls.controlsVisible, so the mini header bar remains usable.Store and lifecycle
controlsVisibleflag (defaulttrue) withtoggleControlsVisible,showControls, and selectoruseControlsVisiblefor components that animate or gate interaction.showControls()is the single path for forcing controls back on after important events:stateChangeandtrackStateChangeon the media call emitter, andtoggleFocuswhen switching between expanded and collapsed call UI. That keeps “force visible” in one place and avoids scatteringcontrolsVisiblepatches next to unrelatedset({ … })updates.reset()restores initial store state (includingcontrolsVisible) like other call UI fields.Shared animation timing
CONTROLS_ANIMATION_DURATION(300 ms) is defined once in CallView styles and reused by CallButtons, CallerInfo, and MediaCallHeader so timing stays consistent.Tests
stateChange/trackStateChange. The in-memory media-call mock’semitforwards variadic arguments to listeners (aligned with real emitters) with a test that locks that behavior in.caller-info-toggletestID, pointerEvents when controls are hidden vs visible (CallButtons and MediaCallHeader), and existing CallView flows.Architecture note (Zustand vs Reanimated shared values)
controlsVisiblestays in Zustand. Subscribers re-render when it changes;useAnimatedStylereads the boolean on those updates. This PR does not mirror that flag into a Reanimated shared value for these animations—intentionally, to avoid duplicating state and to keep the feature easy to follow in one store.Issue(s)
https://rocketchat.atlassian.net/browse/VMUX-19
How to test or reproduce
yarn test. Pay particular attention toapp/lib/services/voip/useCallStore.test.ts, CallView-related tests,CallerInfo,CallButtons, andMediaCallHeadertests.Screenshots
controls.webm
Types of changes
Checklist
Further comments
Suggested reply if a reviewer asks to drive these animations only from Reanimated shared values synced via
useEffect: We keepcontrolsVisiblein Zustand; components that subscribe re-render when it changes, which updatesuseAnimatedStyle. We are not mirroring this flag into shared values for this feature.Summary by CodeRabbit
New Features
Tests