Skip to content

fix(voip): CallView button grid and correct landscape/dialpad layouts - #7164

Merged
diegolmello merged 4 commits into
feat.voip-lib-newfrom
claude/epic-leakey
Apr 16, 2026
Merged

fix(voip): CallView button grid and correct landscape/dialpad layouts#7164
diegolmello merged 4 commits into
feat.voip-lib-newfrom
claude/epic-leakey

Conversation

@diegolmello

@diegolmello diegolmello commented Apr 16, 2026

Copy link
Copy Markdown
Member

Proposed changes

This PR improves CallView action buttons and fixes landscape behavior to match Figma.

Button grid (original scope)
The six CallButtons (Speaker, Hold, Mute, Message, End, Dialpad) use a Dialpad-style flex: 1 cell so columns stay aligned when labels change length (e.g. Mute/Unmute, End/Cancel). Row gap is tightened and labels use numberOfLines={1}.

Landscape / dialpad (follow-up)
An earlier landscape change incorrectly applied a split layout (caller area | controls) to the whole call screen. That split belongs only to the Dialpad sheet on phones in landscape.

  • CallView is always a vertical stack: header/caller on top, controls in the bottom bar (same as portrait), including phone landscape.
  • Dialpad uses the split layout (input left, keypad right) only when width > height and layoutMode === 'narrow' (smartphone landscape). Tablets (wide) keep the stacked dialpad in both orientations.

Expected UI by device and orientation

Context Call screen (CallView) Dialpad (action sheet)
Phone — portrait Caller/avatar centered; two rows of three action buttons at the bottom. Input on top, 3×4 keypad below.
Phone — landscape Same vertical structure as portrait: caller on top, one horizontal row of six buttons at the bottom (not split left/right). Split: input on the left, keypad on the right.
Tablet — portrait Caller centered; one row of six buttons at the bottom. Stacked: input above keypad.
Tablet — landscape Same as tablet portrait: one row of six buttons at the bottom. Stacked: input above keypad (no split).

Issue(s)

https://rocketchat.atlassian.net/browse/VMUX-79

How to test or reproduce

  1. Place a VoIP call and open CallView.
  2. Phone portrait: confirm a 3×2 button grid; toggle Mute/Hold and confirm columns stay aligned.
  3. Phone landscape: confirm six buttons in one row at the bottom and no caller | controls side-by-side split.
  4. Open Dialpad from CallView:
    • Phone landscape: split layout (input | grid).
    • Phone portrait and tablet (any orientation): stacked dialpad.
  5. On a tablet (width ≥ master–detail breakpoint), confirm one row of six buttons in portrait and landscape.

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Story snapshots under app/views/CallView/**/__snapshots__/ were refreshed for layout changes. yarn test -- --testPathPattern='app/views/CallView' passes with TZ=UTC.

Summary by CodeRabbit

  • Style

    • Reduced spacing between call action buttons for a more compact layout.
  • Refactor

    • Improved responsive layout handling for different screen orientations and device types to enhance consistency across the call interface.

CallButtons weren't grid-aligned because each button's outer View sized
to its label text, causing columns to shift when labels changed
(Mute↔Unmute, End↔Cancel). Adopts the Dialpad's proven flex:1 pattern
so every button occupies an equal-width column regardless of label length.
@coderabbitai

coderabbitai Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The PR refactors landscape-responsive layout handling across CallView components. Previously distributed landscape-specific styles and hooks are consolidated into a unified responsive approach using window dimensions, reducing style duplication and simplifying component logic.

Changes

Cohort / File(s) Summary
Style Updates
app/views/CallView/styles.ts
Removed landscape-specific styles (contentContainerLandscape, callerInfoContainerLandscape, buttonsContainerLandscape, buttonsRowLandscape); added new actionButtonCell style with flex centering; reduced buttonsRow gap from 48 to 24.
Component Layout Refactoring
app/views/CallView/components/CallButtons.tsx, app/views/CallView/components/Dialpad/Dialpad.tsx, app/views/CallView/index.tsx
Removed useResponsiveLayout hook usage; consolidated single/dual-row rendering logic in CallButtons via computed singleRow flag; updated Dialpad to determine landscape layout via window dimensions and layoutMode check; unified container styling to always use base styles without landscape variants.
Component Minor Updates
app/views/CallView/components/CallActionButton.tsx, app/views/CallView/components/CallerInfo.tsx
Applied actionButtonCell styling to button container; enforced single-line text rendering; fixed avatar size to 120 in CallerInfo regardless of orientation.
Test Updates
app/views/CallView/components/CallButtons.test.tsx, app/views/CallView/components/Dialpad/Dialpad.test.tsx, app/views/CallView/index.test.tsx
Introduced setStoreState helper and mocked react-native-incall-manager in CallButtons tests; added mockResponsiveLayout helper in Dialpad tests; shifted assertions to validate testID presence, row counts, and state-driven UI changes; added phone landscape dimension tests to verify responsive behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing CallView button grid alignment and correcting landscape/dialpad layouts, which is the primary focus of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@diegolmello
diegolmello changed the base branch from develop to feat.voip-lib-new April 16, 2026 14:16
…hone only

- Remove CallerInfo | CallButtons side-by-side split on landscape
- CallButtons: single row when wide or landscape; portrait phone keeps 2x3
- Dialpad: split input|grid only when narrow + landscape (phone)
- CallerInfo: fixed 120px avatar; drop landscape-only styles
- Tests and snapshots updated

Made-with: Cursor
@diegolmello diegolmello changed the title fix(voip): align CallButtons in a 3×2 grid fix(voip): CallView button grid and correct landscape/dialpad layouts Apr 16, 2026
@diegolmello
diegolmello had a problem deploying to experimental_android_build April 16, 2026 18:54 — with GitHub Actions Failure
@diegolmello
diegolmello had a problem deploying to official_android_build April 16, 2026 18:54 — with GitHub Actions Failure
@diegolmello
diegolmello had a problem deploying to experimental_ios_build April 16, 2026 18:54 — with GitHub Actions Failure

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
app/views/CallView/components/CallButtons.test.tsx (1)

141-144: Verify accessibility state prop access.

The test accesses props.accessibilityState?.disabled directly. This works with React Native Testing Library, but be aware that the exact prop structure depends on how Pressable implements disabled. If tests become flaky, consider using toBeDisabled() matcher from @testing-library/jest-native as an alternative.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/views/CallView/components/CallButtons.test.tsx` around lines 141 - 144,
Test currently reads the disabled state via props.accessibilityState?.disabled
on elements fetched with getByTestId('call-view-speaker'), 'call-view-hold',
'call-view-mute', and 'call-view-dialpad'; replace those assertions with the
jest-native matcher toBeDisabled() (e.g.
expect(getByTestId('call-view-speaker')).toBeDisabled()) to more robustly verify
disabled state and add or ensure the
'@testing-library/jest-native/extend-expect' import/config is present so
toBeDisabled() is available.
app/views/CallView/components/Dialpad/Dialpad.test.tsx (1)

78-79: Minor: Consider caching the actual module to avoid repeated jest.requireActual calls.

The jest.requireActual call in beforeEach runs for every test, which is slightly inefficient. You could hoist the actual module to module scope.

♻️ Optional optimization
+const actualResponsiveLayout = jest.requireActual('../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout');
+
 jest.mock('../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout', () => {
 	const actual = jest.requireActual('../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout');
 	return {
 		...actual,
 		useResponsiveLayout: jest.fn(() => actual.useResponsiveLayout())
 	};
 });

Then in beforeEach:

 beforeEach(() => {
 	(useCallLayoutMode as jest.Mock).mockReturnValue({ layoutMode: 'narrow' });
-	const actual = jest.requireActual('../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout');
-	(useResponsiveLayout as jest.Mock).mockImplementation(() => actual.useResponsiveLayout());
+	(useResponsiveLayout as jest.Mock).mockImplementation(() => actualResponsiveLayout.useResponsiveLayout());
 	useCallStore.getState().reset();
 	sendDTMFMock.mockClear();
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/views/CallView/components/Dialpad/Dialpad.test.tsx` around lines 78 - 79,
The test repeatedly calls
jest.requireActual('../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout')
inside beforeEach which is inefficient; hoist the actual module to module scope
by assigning the result of jest.requireActual(...) to a top-level const (e.g.,
actualUseResponsiveLayoutModule) and then change the beforeEach
mockImplementation to call actualUseResponsiveLayoutModule.useResponsiveLayout()
so (useResponsiveLayout as jest.Mock).mockImplementation(...) uses the cached
module instead of requiring it each test.
🤖 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.test.tsx`:
- Around line 141-144: Test currently reads the disabled state via
props.accessibilityState?.disabled on elements fetched with
getByTestId('call-view-speaker'), 'call-view-hold', 'call-view-mute', and
'call-view-dialpad'; replace those assertions with the jest-native matcher
toBeDisabled() (e.g. expect(getByTestId('call-view-speaker')).toBeDisabled()) to
more robustly verify disabled state and add or ensure the
'@testing-library/jest-native/extend-expect' import/config is present so
toBeDisabled() is available.

In `@app/views/CallView/components/Dialpad/Dialpad.test.tsx`:
- Around line 78-79: The test repeatedly calls
jest.requireActual('../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout')
inside beforeEach which is inefficient; hoist the actual module to module scope
by assigning the result of jest.requireActual(...) to a top-level const (e.g.,
actualUseResponsiveLayoutModule) and then change the beforeEach
mockImplementation to call actualUseResponsiveLayoutModule.useResponsiveLayout()
so (useResponsiveLayout as jest.Mock).mockImplementation(...) uses the cached
module instead of requiring it each test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2b41fa8a-e83e-4ecb-9170-15f7f8830078

📥 Commits

Reviewing files that changed from the base of the PR and between 80663c7 and 27e039c.

⛔ Files ignored due to path filters (4)
  • app/views/CallView/__snapshots__/index.test.tsx.snap is excluded by !**/*.snap
  • app/views/CallView/components/Dialpad/__snapshots__/Dialpad.test.tsx.snap is excluded by !**/*.snap
  • app/views/CallView/components/__snapshots__/CallActionButton.test.tsx.snap is excluded by !**/*.snap
  • app/views/CallView/components/__snapshots__/CallerInfo.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (9)
  • app/views/CallView/components/CallActionButton.tsx
  • app/views/CallView/components/CallButtons.test.tsx
  • app/views/CallView/components/CallButtons.tsx
  • app/views/CallView/components/CallerInfo.tsx
  • app/views/CallView/components/Dialpad/Dialpad.test.tsx
  • app/views/CallView/components/Dialpad/Dialpad.tsx
  • app/views/CallView/index.test.tsx
  • app/views/CallView/index.tsx
  • app/views/CallView/styles.ts
📜 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). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Configure Prettier with tabs, single quotes, 130 character width, no trailing commas, arrow parens avoid, and bracket same line

Files:

  • app/views/CallView/index.tsx
  • app/views/CallView/components/CallerInfo.tsx
  • app/views/CallView/components/Dialpad/Dialpad.tsx
  • app/views/CallView/components/CallButtons.tsx
  • app/views/CallView/index.test.tsx
  • app/views/CallView/components/CallButtons.test.tsx
  • app/views/CallView/styles.ts
  • app/views/CallView/components/Dialpad/Dialpad.test.tsx
  • app/views/CallView/components/CallActionButton.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint with @rocket.chat/eslint-config base configuration including React, React Native, TypeScript, and Jest plugins

Files:

  • app/views/CallView/index.tsx
  • app/views/CallView/components/CallerInfo.tsx
  • app/views/CallView/components/Dialpad/Dialpad.tsx
  • app/views/CallView/components/CallButtons.tsx
  • app/views/CallView/index.test.tsx
  • app/views/CallView/components/CallButtons.test.tsx
  • app/views/CallView/styles.ts
  • app/views/CallView/components/Dialpad/Dialpad.test.tsx
  • app/views/CallView/components/CallActionButton.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript with strict mode enabled and configure baseUrl to app/ for import resolution

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Files:

  • app/views/CallView/index.tsx
  • app/views/CallView/components/CallerInfo.tsx
  • app/views/CallView/components/Dialpad/Dialpad.tsx
  • app/views/CallView/components/CallButtons.tsx
  • app/views/CallView/index.test.tsx
  • app/views/CallView/components/CallButtons.test.tsx
  • app/views/CallView/styles.ts
  • app/views/CallView/components/Dialpad/Dialpad.test.tsx
  • app/views/CallView/components/CallActionButton.tsx
app/views/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place screen components in app/views/ directory

Files:

  • app/views/CallView/index.tsx
  • app/views/CallView/components/CallerInfo.tsx
  • app/views/CallView/components/Dialpad/Dialpad.tsx
  • app/views/CallView/components/CallButtons.tsx
  • app/views/CallView/index.test.tsx
  • app/views/CallView/components/CallButtons.test.tsx
  • app/views/CallView/styles.ts
  • app/views/CallView/components/Dialpad/Dialpad.test.tsx
  • app/views/CallView/components/CallActionButton.tsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/views/CallView/index.tsx
  • app/views/CallView/components/CallerInfo.tsx
  • app/views/CallView/components/Dialpad/Dialpad.tsx
  • app/views/CallView/components/CallButtons.tsx
  • app/views/CallView/index.test.tsx
  • app/views/CallView/components/CallButtons.test.tsx
  • app/views/CallView/styles.ts
  • app/views/CallView/components/Dialpad/Dialpad.test.tsx
  • app/views/CallView/components/CallActionButton.tsx
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/hooks/useResponsiveLayout/**/*.{ts,tsx} : Use responsive layout with master-detail on tablets and single stack on phones via useResponsiveLayout hook
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/hooks/useResponsiveLayout/**/*.{ts,tsx} : Use responsive layout with master-detail on tablets and single stack on phones via useResponsiveLayout hook

Applied to files:

  • app/views/CallView/index.tsx
  • app/views/CallView/components/CallerInfo.tsx
  • app/views/CallView/components/Dialpad/Dialpad.tsx
  • app/views/CallView/components/CallButtons.tsx
  • app/views/CallView/index.test.tsx
  • app/views/CallView/styles.ts
  • app/views/CallView/components/Dialpad/Dialpad.test.tsx
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate

Applied to files:

  • app/views/CallView/components/CallerInfo.tsx
  • app/views/CallView/components/CallButtons.tsx
  • app/views/CallView/index.test.tsx
  • app/views/CallView/components/CallButtons.test.tsx
  • app/views/CallView/components/CallActionButton.tsx
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/index.tsx : Configure Redux provider, theme, navigation, and notifications in app/index.tsx

Applied to files:

  • app/views/CallView/components/CallerInfo.tsx
📚 Learning: 2026-03-30T15:49:30.957Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6875
File: app/containers/RoomItem/Actions.tsx:12-12
Timestamp: 2026-03-30T15:49:30.957Z
Learning: In RocketChat/Rocket.Chat.ReactNative, `react-native-worklets` version 0.6.1 does NOT export a built-in Jest mock (e.g., no `react-native-worklets/lib/module/mock`). The correct Jest mock approach for this version is to add a manual mock in `jest.setup.js`: `jest.mock('react-native-worklets', () => ({ scheduleOnRN: jest.fn((fn, ...args) => fn(...args)) }))`.

Applied to files:

  • app/views/CallView/index.test.tsx
  • app/views/CallView/components/Dialpad/Dialpad.test.tsx
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to index.js : Register app entry point in index.js with conditional Storybook loading

Applied to files:

  • app/views/CallView/components/Dialpad/Dialpad.test.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/Dialpad/Dialpad.test.tsx
🔇 Additional comments (12)
app/views/CallView/index.tsx (1)

20-25: LGTM!

The simplification of the SafeAreaView styling by removing landscape-conditional logic is clean and aligns with the PR's consolidation of responsive layout handling into the child components.

app/views/CallView/components/CallActionButton.tsx (1)

61-78: LGTM!

The flex: 1 cell wrapper via styles.actionButtonCell ensures equal-width columns regardless of label content, and numberOfLines={1} prevents text wrapping that could cause layout instability. This directly addresses the root cause of the grid alignment issue.

app/views/CallView/components/CallerInfo.tsx (1)

27-36: LGTM!

The simplification to use a consistent layout and fixed avatar size (120) removes unnecessary landscape branching. This aligns with the PR's consolidation of responsive layout handling.

app/views/CallView/components/Dialpad/Dialpad.tsx (1)

51-81: LGTM!

The isPhoneLandscape condition correctly identifies phone landscape mode (narrow layout with width > height) to render the side-by-side input/grid layout. This is appropriately distinct from the CallButtons layout logic since the Dialpad has different layout requirements.

app/views/CallView/index.test.tsx (2)

12-17: LGTM!

The configurable mockWindowHeight alongside mockWindowWidth enables proper testing of both portrait and landscape orientations, supporting the new layout logic verification.


430-441: Good test coverage for phone landscape.

This test correctly verifies that phone landscape (narrow layout with width > height) renders buttons in a single row, matching the new singleRow logic in CallButtons.

app/views/CallView/components/CallButtons.tsx (2)

31-33: Clean unification of single-row condition.

The singleRow logic (layoutMode === 'wide' || isLandscape) elegantly handles both tablet/wide layouts and phone landscape, ensuring a single row of buttons when horizontal space is available.


118-172: LGTM!

The simplified container styling and conditional row rendering is clean. The consistent use of styles.buttonsRow for both layouts (with the flex: 1 cell wrappers in CallActionButton) ensures stable grid alignment regardless of label content changes.

app/views/CallView/styles.ts (1)

61-69: Core fix for grid alignment.

The gap: 24 reduction combined with the new actionButtonCell style (flex: 1, alignItems: 'center') is the key fix. Equal-width flex cells ensure columns don't shift when labels change (e.g., "Mute" ↔ "Unmute"), matching the Dialpad's existing cell-wrapper pattern.

app/views/CallView/components/CallButtons.test.tsx (1)

60-181: Comprehensive test rewrite.

The test suite provides good coverage of:

  • Layout behavior (narrow 2-row, wide 1-row, phone landscape 1-row)
  • Button rendering and labels
  • Disabled states during ringing
  • Dynamic label changes (mute/unmute, cancel/end)

The approach of testing via testIDs and state-driven assertions is clean and maintainable.

app/views/CallView/components/Dialpad/Dialpad.test.tsx (2)

157-206: Well-structured layout tests covering all key scenarios.

The tests comprehensively verify the split layout behavior:

  • Narrow + landscape → split layout ✓
  • Wide + portrait → no split layout ✓
  • Wide + landscape → no split layout ✓
  • Narrow + portrait → no split layout ✓

This aligns well with the PR objective of rendering a 3×2 grid in portrait and single row in landscape for narrow layouts.


64-73: LGTM: Clean helper function for responsive layout mocking.

The mockResponsiveLayout helper is well-designed with appropriate default values for layout-focused tests. Using the imported constants (BASE_ROW_HEIGHT, BASE_ROW_HEIGHT_CONDENSED) ensures consistency with the actual implementation.

@diegolmello
diegolmello merged commit 1e2e6e8 into feat.voip-lib-new Apr 16, 2026
6 of 11 checks passed
@diegolmello
diegolmello deleted the claude/epic-leakey branch April 16, 2026 18:55
diegolmello added a commit that referenced this pull request Apr 22, 2026
…/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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant