Skip to content

Conversation

@deepak0x
Copy link

@deepak0x deepak0x commented Jan 9, 2026

Issues Addressed


Demo

🎥 Preview Video
https://github.com/user-attachments/assets/43e70c7d-5328-48ab-81bb-2c0be7ac407f


How to Test

  1. Open User Preferences → Font Size.
  2. Adjust the font size slider and verify:
    • Preview text scaling matches the actual UI scaling.
    • Avatar size updates correctly with font size changes.

Screenshots (With Font Size Changes)

Preview UI State

Type of Change

  • Bugfix (non-breaking change)
  • Improvement
  • New feature
  • Documentation update

Checklist

  • Read the CONTRIBUTING guidelines
  • Signed the CLA
  • Lint and unit tests pass locally
  • Added new tests (not applicable)
  • Added documentation (not applicable)
  • Dependent downstream changes merged

- Apply scaleFontSize to all Text components throughout the application
- Update Directory pages (DirectoryItem, DirectoryOptions)
- Update Reactions components (Reactions, ReactionsList, AllTab)
- Update ListPicker components across multiple views
- Update RoomInfoView components (ItemLabel, RoomInfoTag, Direct, Livechat)
- Update various view components (JoinCode, LoadMore, AuthLoadingView, etc.)
- Fix TypeScript errors by using object spread for style props
- Ensure all user-facing text respects font size preference setting
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

Walkthrough

Adds app-wide font-size support: extends useResponsiveLayout with persistent font-size preferences and scaleFontSize, updates many UI components to consume scaleFontSize, adds Font Size picker UI and navigation routes, and adjusts RoomItem styles to handle scaled fonts.

Changes

Cohort / File(s) Summary
Core responsive hook
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx, app/lib/constants/keys.ts
Persist font-size preference, compute composite fontScale, add scaleFontSize(size) to context, expose FONT_SIZE_OPTIONS and related constants, and adjust derived row heights with clamping.
Font-size UI & navigation
app/views/UserPreferencesView/FontSizePicker.tsx, app/views/FontSizePickerView/index.tsx, app/views/UserPreferencesView/index.tsx, app/stacks/InsideStack.tsx, app/stacks/types.ts, app/stacks/MasterDetailStack/types.ts
Add FontSizePicker component and FontSizePickerView screen, wire into preferences and navigation, preview and persist chosen font size via user preferences.
Mass typography updates
app/containers/..., app/views/..., app/containers/markdown/..., app/containers/message/...
~100+ components updated to import useResponsiveLayout and replace hard-coded fontSize/lineHeight with scaleFontSize(...); primarily presentation-only changes.
RoomItem layout stability
app/containers/RoomItem/styles.ts, app/containers/RoomItem/*.tsx
Add minWidth/flexShrink/overflow rules, rename markdownTextlastMessageContainer, add titleText, wrap elements to avoid overflow with scaled fonts.
ActionSheet & API tweak
app/containers/ActionSheet/*
Replace static sizes with scaleFontSize, remove explicit sizes from styles, and add hide(): void to IActionSheetItem interface.
Stories & test fixtures
app/containers/RoomItem/RoomItem.stories.tsx, app/containers/message/Message.stories.tsx, app/views/NewServerView/.../ServersHistoryItem.stories.tsx
Add scaleFontSize to mock ResponsiveLayout provider values for stories.
Localization
app/i18n/locales/en.json
Add strings for font-size options and descriptions (Small, Normal, Large, Extra Large, Font Size label/description).
Minor / Misc
app/views/SelectListView.tsx, app/containers/RoomHeader/*, app/containers/RoomsListView/*, app/views/UserPreferencesView/ListPicker.tsx, ...
Many files updated to consume scaleFontSize, small layout/text-wrapping adjustments, and formatting tweaks.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as FontSizePickerView
  participant Storage as UserPreferencesStorage
  participant Hook as useResponsiveLayout
  participant App as App Components

  User->>UI: open Font Size screen & adjust slider
  UI->>Storage: persist FONT_SIZE_PREFERENCES_KEY
  Storage-->>Hook: storage change event
  Hook->>App: update context (fontScale, scaleFontSize, row heights)
  App->>App: components re-render using new scaleFontSize values
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • OtavioStasiak

Poem

🐰
I nibble keys and scale each line,
A slider hop to make each letter fine.
From tiny notes to headings bold,
I tune the font so stories hold.
Hop, read, and smile—your text fits gold.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main change: applying font size scaling across all components, which is the core objective.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #2575: global font-size setting in User Preferences, responsive font sizing across all components, consistent scaling on all screens, and improved accessibility.
Out of Scope Changes check ✅ Passed All changes are directly aligned with font size scaling implementation. Layout adjustments (minWidth, flexShrink) support responsive typography without introducing unrelated functionality.
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.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/views/PickerView.tsx (1)

17-24: Remove redundant fontSize from StyleSheet.

The fontSize: 16 in the noResult style (line 19) is now redundant since line 113 applies fontSize: scaleFontSize(16) inline, which overrides it. Remove the static fontSize to avoid confusion and maintain a single source of truth.

♻️ Proposed fix
 const styles = StyleSheet.create({
 	noResult: {
-		fontSize: 16,
 		paddingVertical: 56,
 		...sharedStyles.textSemibold,
 		...sharedStyles.textAlignCenter
 	}
 });
🤖 Fix all issues with AI agents
In @app/containers/Button/index.tsx:
- Around line 84-92: The dynamic font size object in textStyle is placed before
the static styles so styles.text or styles.smallText can override fontSize;
update textStyle to apply static styles first (styles.smallText or styles.text,
plus styleText) and place the dynamic object ({ color: ..., fontSize: ...,
lineHeight: ... }) last so the computed fontSize from scaleFontSize(fontSize) or
small logic takes precedence (refer to textStyle, styles.text, styles.smallText,
styleText, scaleFontSize, and fontSize).

In @app/containers/MessageComposer/components/RecordAudio/Duration.tsx:
- Around line 8-9: The duration label style spreads sharedStyles.textRegular
before applying the scaled fontSize, so sharedStyles.textRegular can override
the computed size; in the Duration component locate the Text/style objects that
use sharedStyles.textRegular (e.g., in the render and the other instance around
lines 34-42) and change the spread order so the computed/scaled fontSize is
applied after the spread (i.e., spread sharedStyles.textRegular first, then set
fontSize: scaledFontSize) so the scaled value takes precedence.

In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 44-59: The polling setInterval in useResponsiveLayout should be
removed and replaced with MMKV's addOnValueChangedListener to avoid unnecessary
re-renders; register userPreferences.addOnValueChangedListener((key) => { if
(key === FONT_SIZE_PREFERENCES_KEY) setUpdateTrigger(t => t + 1) }) in the
useEffect that currently sets the interval, store the returned subscription and
call its remove() in the cleanup, and remove the empty if (updateTrigger) block
and its comment; continue to read customFontSize from
userPreferences.getString(FONT_SIZE_PREFERENCES_KEY) so the component re-reads
on updates triggered by the listener.
🧹 Nitpick comments (21)
app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx (1)

42-42: Consider removing duplicate fontSize from StyleSheet.

The static fontSize: 16 in styles.text (line 29) is now overridden by the inline fontSize: scaleFontSize(16). You can remove the static value to avoid confusion.

♻️ Proposed cleanup
 const styles = StyleSheet.create({
 	// ...
 	text: {
 		flex: 1,
-		fontSize: 16,
 		...sharedStyles.textRegular
 	}
 });
app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx (1)

96-96: Font scaling applied correctly.

The scaleFontSize function is properly applied to both fontSize and lineHeight for both title text components, which ensures proper text rendering at different scale factors.

♻️ Optional: Clean up redundant static styles

The static fontSize and lineHeight values in styles.leftTitle (lines 21-22) and styles.rightTitle (lines 29-30) are now always overridden by the dynamic inline styles and could be removed to avoid confusion:

 leftTitle: {
   ...sharedStyles.textMedium,
-  fontSize: 16,
-  lineHeight: 24
 },
 rightTitle: {
   ...sharedStyles.textRegular,
-  fontSize: 16,
-  lineHeight: 24
 },

Also applies to: 101-101

app/views/SelectedUsersView/Header.tsx (1)

14-20: Remove the redundant hardcoded fontSize: 12 from styles.selectedText.

Right now styles.selectedText.fontSize is always overridden by the inline { fontSize: scaleFontSize(12) }, which can cause confusion during future edits.

Proposed cleanup
 const styles = StyleSheet.create({
 	selectedText: {
 		marginLeft: 16,
 		marginBottom: 12,
-		fontSize: 12,
 		...sharedStyles.textRegular
 	},

Also applies to: 50-52

app/views/UserPreferencesView/ListPicker.tsx (1)

76-76: Dynamic font sizing correctly applied.

The scaleFontSize(16) is correctly applied to the label text, ensuring it respects the user's font size preference. The style array spreading correctly overrides the static fontSize from styles.title.

♻️ Optional: Remove redundant static fontSize

Since the fontSize is now dynamically set at line 76, the static fontSize: 16 in styles.title (line 13) is always overridden and could be removed for clarity:

 const styles = StyleSheet.create({
-	title: { ...sharedStyles.textRegular, fontSize: 16 }
+	title: { ...sharedStyles.textRegular }
 });

This is a minor cleanup and not required, as the current implementation works correctly.

app/views/RoomView/LoadMore/index.tsx (1)

75-75: Dynamic font sizing correctly applied to load more text.

The scaleFontSize(16) is correctly applied within the style array, ensuring the "Load More" text respects the user's font size preference.

♻️ Optional: Remove redundant static fontSize

Since fontSize is now dynamically set at line 75, the static fontSize: 16 in styles.text (line 23) is always overridden and could be removed:

 const styles = StyleSheet.create({
 	button: {
 		paddingVertical: 16,
 		alignItems: 'center',
 		justifyContent: 'center'
 	},
 	text: {
-		fontSize: 16,
 		...sharedStyles.textMedium
 	}
 });

This is optional cleanup; the current implementation functions correctly.

app/views/AuthLoadingView.tsx (1)

33-33: Dynamic font sizing correctly applied to loading text.

The scaleFontSize(16) is correctly applied to the loading message text, ensuring it respects the user's font size preference. This improves accessibility across the authentication flow.

♻️ Optional: Remove redundant static fontSize

Since fontSize is now dynamically set at line 33, the static fontSize: 16 in styles.text (line 17) is always overridden and could be removed:

 const styles = StyleSheet.create({
 	container: {
 		flex: 1,
 		justifyContent: 'center',
 		alignItems: 'center'
 	},
 	text: {
-		fontSize: 16,
 		paddingTop: 10,
 		...sharedStyles.textRegular,
 		...sharedStyles.textAlignCenter
 	}
 });

This is optional cleanup; the current implementation works as intended.

app/containers/message/Components/Attachments/Reply.tsx (2)

103-114: Reply header “time” text may still be unscaled (inconsistent font preference).
author_name/title now use scaleFontSize(16), but the adjacent time Text relies on messageStyles.time (which may be fixed-size). Consider explicitly scaling that Text too for consistency.


185-198: key={field.title} can collide; prefer a stable unique key.
If multiple fields share the same title (or title is empty), React reconciliation can misbehave. Consider using a composite key (e.g., title + index) or a real field id if available.

app/containers/UserGeneratedContentRules.tsx (1)

9-52: Typography scaling looks correct; consider link accessibility + lineHeight sanity.

  • For the pressable “Terms_of_Service” / “Privacy_Policy” Text, consider adding accessibilityRole="link" (and possibly accessibilityHint) to better convey semantics to screen readers.
  • Please sanity-check that scaleFontSize(22) yields a sensible lineHeight relative to scaleFontSize(14) across all font scale settings.
app/containers/message/Time.tsx (1)

7-23: Good: time now respects font scaling; consider guarding ts if “now” is undesirable.
If ts can be missing in some paths, you may want to return null (or '') rather than formatting the current time implicitly.

app/containers/RoomItem/Wrapper.tsx (1)

2-29: Consider hoisting borderWidth out of render + verify Android rendering of 0.5 borders (may look uneven).

If this is intended as a consistent “1px-ish” divider, computing it once avoids recreating the number each render.

Proposed tweak
 import React from 'react';
 import { StyleSheet, View } from 'react-native';
@@
 import { useResponsiveLayout } from '../../lib/hooks/useResponsiveLayout/useResponsiveLayout';
 
+const BORDER_WIDTH = Math.max(StyleSheet.hairlineWidth || 0, 0.5);
+
 const Wrapper = ({ accessibilityLabel, children, displayMode, ...props }: IWrapperProps): React.ReactElement => {
 	const { colors } = useTheme();
 	const { rowHeight, rowHeightCondensed } = useResponsiveLayout();
-	const borderWidth = Math.max(StyleSheet.hairlineWidth || 0, 0.5);
 	return (
@@
 					{
 						borderColor: colors.strokeLight,
-						borderBottomWidth: borderWidth
+						borderBottomWidth: BORDER_WIDTH
 					}
 				]}>
app/views/RoomInfoView/Livechat.tsx (1)

16-22: Consider removing redundant fontSize from StyleSheet.

The fontSize: 16 defined in styles.title (line 18) is now overridden by the inline fontSize: scaleFontSize(16) on line 27, making the static value in the StyleSheet redundant.

♻️ Proposed cleanup
 const styles = StyleSheet.create({
 	title: {
-		fontSize: 16,
 		paddingHorizontal: 20,
 		...sharedStyles.textMedium
 	}
 });

Also applies to: 26-27

app/views/UserNotificationPreferencesView/ListPicker.tsx (1)

13-18: Consider removing redundant fontSize from StyleSheet.

The fontSize: 16 defined in styles.pickerText (line 16) is now overridden by the inline fontSize: scaleFontSize(16) on line 71, making the static value redundant.

♻️ Proposed cleanup
 const styles = StyleSheet.create({
 	pickerText: {
-		...sharedStyles.textRegular,
-		fontSize: 16
+		...sharedStyles.textRegular
 	}
 });

Also applies to: 40-40, 71-71

app/views/RegisterView/index.tsx (1)

52-52: LGTM! Responsive typography correctly applied.

The scaleFontSize hook is properly used for both the Sign Up title and the login prompt text, ensuring these user-facing elements respect font size preferences. The inline styling approach is consistent with existing color styling in this component.

Optional: Extract responsive font styles to styles object

For improved maintainability, you could extract the font sizing to the styles object, though the current inline approach is acceptable:

const styles = StyleSheet.create({
  // ... existing styles
  titleText: {
    fontSize: 24,
    lineHeight: 36
  },
  bottomText: {
    fontSize: 14,
    lineHeight: 22
  }
});

// Then in useStyle or component:
const responsiveStyles = {
  titleText: {
    ...styles.titleText,
    fontSize: scaleFontSize(styles.titleText.fontSize),
    lineHeight: scaleFontSize(styles.titleText.lineHeight)
  }
  // ...
};

This keeps the base values in one place while still applying responsive scaling.

Also applies to: 182-182, 299-299

app/containers/RoomItem/Title.tsx (1)

13-21: Consider memoizing the inline style object.

The inline style object { color: colors.fontTitlesLabels, fontSize: scaleFontSize(17) } is recreated on every render. For better performance, consider using useMemo:

const titleStyle = useMemo(
  () => ({ color: colors.fontTitlesLabels, fontSize: scaleFontSize(17) }),
  [colors.fontTitlesLabels, scaleFontSize]
);

However, if this pattern is consistent across the PR and performance testing shows no issues, the current approach is acceptable.

app/containers/ReactionsList/AllTab.tsx (1)

11-12: Default reactions to [] to avoid FlatList receiving undefined.
This keeps behavior consistent and avoids edge cases across RN versions.

Proposed diff
-const AllTab = ({ reactions, getCustomEmoji }: IAllTabProps): React.ReactElement => (
+const AllTab = ({ reactions = [], getCustomEmoji }: IAllTabProps): React.ReactElement => (
 	<View style={styles.allTabContainer} testID='reactionsListAllTab'>
 		<FlatList
 			data={reactions}
 			contentContainerStyle={styles.listContainer}
 			renderItem={({ item }) => <AllReactionsListItem item={item} getCustomEmoji={getCustomEmoji} />}
 			keyExtractor={item => item.emoji}
 		/>
 	</View>
 );

Also applies to: 25-26, 51-60, 65-72

app/views/DiscussionsView/DiscussionDetails.tsx (1)

26-30: Remove unused fontSize from static styles.

The detailText style defines fontSize: 10, but this is now overridden inline with scaleFontSize(10) on lines 51 and 58. The static fontSize is dead code and creates confusion about which value is actually used.

♻️ Proposed fix to remove unused fontSize
 	detailText: {
-		fontSize: 10,
 		marginLeft: 2,
 		...sharedStyles.textSemibold
 	}
app/containers/InAppNotification/NotifierComponent.tsx (1)

117-122: Consider calculating lineHeight as a ratio of fontSize.

Currently, both fontSize and lineHeight are scaled independently using scaleFontSize. While this works, it means the ratio between them changes if scaleFontSize applies non-linear scaling. A more typical approach would be to calculate lineHeight as a ratio of the scaled fontSize:

const roomNameFontSize = scaleFontSize(17);
const roomNameLineHeight = roomNameFontSize * 1.18; // maintains ratio

const messageFontSize = scaleFontSize(14);
const messageLineHeight = messageFontSize * 1.21; // maintains ratio

This ensures consistent line spacing regardless of font scaling. However, if the current approach is intentional per design system requirements, it can remain as-is.

app/views/MediaAutoDownloadView/ListPicker.tsx (1)

19-23: Remove redundant fontSize and lineHeight from static styles.

The static styles define fontSize: 16 and lineHeight: 24 (lines 21-22, 29-30), but these values are always overridden inline with dynamic values (lines 98, 103). Remove the static declarations to eliminate duplication and potential confusion.

♻️ Proposed cleanup
 const styles = StyleSheet.create({
 	leftTitleContainer: {
 		flex: 1,
 		justifyContent: 'center',
 		alignItems: 'flex-start'
 	},
 	leftTitle: {
-		...sharedStyles.textMedium,
-		fontSize: 16,
-		lineHeight: 24
+		...sharedStyles.textMedium
 	},
 	rightContainer: {
 		flex: 1
 	},
 	rightTitle: {
-		...sharedStyles.textRegular,
-		fontSize: 16,
-		lineHeight: 24
+		...sharedStyles.textRegular
 	},
 	rightTitleContainer: {
 		flex: 1,
 		justifyContent: 'center',
 		alignItems: 'flex-end'
 	}
 });

Also applies to: 27-31, 98-98, 103-103

app/views/UserPreferencesView/FontSizePicker.tsx (1)

14-16: Remove redundant fontSize from static styles.

The static fontSize: 16 is overridden by scaleFontSize(16) at line 67. Remove it to avoid confusion.

Suggested fix
 const styles = StyleSheet.create({
-	title: { ...sharedStyles.textRegular, fontSize: 16 }
+	title: { ...sharedStyles.textRegular }
 });
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)

61-63: Minor: Redundant ternary check.

customFontSize is already guaranteed to have a value from line 55's default assignment. The ternary on line 62 is unnecessary.

Suggested fix
-	const fontSizeMultiplier = customFontSize ? parseFloat(customFontSize) : 1.0;
+	const fontSizeMultiplier = parseFloat(customFontSize);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ede2569 and 674dfd6.

📒 Files selected for processing (96)
  • app/containers/ActionSheet/ActionSheetContentWithInputAndSubmit/index.tsx
  • app/containers/ActionSheet/BottomSheetContent.tsx
  • app/containers/ActionSheet/Item.tsx
  • app/containers/ActionSheet/styles.ts
  • app/containers/AppVersion.tsx
  • app/containers/BackgroundContainer/index.tsx
  • app/containers/Button/index.tsx
  • app/containers/CallHeader.tsx
  • app/containers/ChangePasswordRequired.tsx
  • app/containers/Chip/index.tsx
  • app/containers/CollapsibleText/index.tsx
  • app/containers/DirectoryItem/index.tsx
  • app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx
  • app/containers/Header/components/HeaderTitle/index.tsx
  • app/containers/Header/components/HeaderTitle/styles.ts
  • app/containers/InAppNotification/NotifierComponent.tsx
  • app/containers/List/ListHeader.tsx
  • app/containers/List/ListInfo.tsx
  • app/containers/List/ListItem.tsx
  • app/containers/MessageActions/Header.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/MessageComposer/components/EmojiSearchbar.tsx
  • app/containers/MessageComposer/components/Quotes/Quote.tsx
  • app/containers/MessageComposer/components/RecordAudio/Duration.tsx
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/containers/MessageComposer/components/SendThreadToChannel.tsx
  • app/containers/MessageSeparator.tsx
  • app/containers/OrSeparator.tsx
  • app/containers/ReactionsList/AllTab.tsx
  • app/containers/ReactionsList/UsersList.tsx
  • app/containers/ReactionsList/index.tsx
  • app/containers/RoomItem/LastMessage.tsx
  • app/containers/RoomItem/RoomItem.stories.tsx
  • app/containers/RoomItem/RoomItem.tsx
  • app/containers/RoomItem/Tag.tsx
  • app/containers/RoomItem/Title.tsx
  • app/containers/RoomItem/UpdatedAt.tsx
  • app/containers/RoomItem/Wrapper.tsx
  • app/containers/RoomItem/styles.ts
  • app/containers/SearchHeader.tsx
  • app/containers/TextInput/FormTextInput.tsx
  • app/containers/ThreadDetails.tsx
  • app/containers/Toast.tsx
  • app/containers/UnreadBadge/index.tsx
  • app/containers/UserGeneratedContentRules.tsx
  • app/containers/UserItem.tsx
  • app/containers/markdown/components/Heading.tsx
  • app/containers/markdown/components/InlineCode.tsx
  • app/containers/markdown/components/Paragraph.tsx
  • app/containers/markdown/components/Plain.tsx
  • app/containers/markdown/components/Preview.tsx
  • app/containers/markdown/components/code/CodeLine.tsx
  • app/containers/markdown/components/inline/Link.tsx
  • app/containers/markdown/components/mentions/AtMention.tsx
  • app/containers/markdown/components/mentions/Hashtag.tsx
  • app/containers/markdown/styles.ts
  • app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
  • app/containers/message/Components/Attachments/Reply.tsx
  • app/containers/message/Components/Attachments/Video.tsx
  • app/containers/message/Message.stories.tsx
  • app/containers/message/Reactions.tsx
  • app/containers/message/Time.tsx
  • app/containers/message/Urls.tsx
  • app/containers/message/User.tsx
  • app/i18n/locales/en.json
  • app/lib/constants/keys.ts
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
  • app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx
  • app/views/AuthLoadingView.tsx
  • app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx
  • app/views/CreateChannelView/RoomSettings/SwitchItem.tsx
  • app/views/DirectoryView/Options.tsx
  • app/views/DiscussionsView/DiscussionDetails.tsx
  • app/views/ForgotPasswordView.tsx
  • app/views/LoginView/UserForm.tsx
  • app/views/MediaAutoDownloadView/ListPicker.tsx
  • app/views/NewServerView/components/ServersHistoryItem/ServersHistoryItem.stories.tsx
  • app/views/NotificationPreferencesView/index.tsx
  • app/views/PickerView.tsx
  • app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx
  • app/views/RegisterView/index.tsx
  • app/views/RoomInfoView/Direct.tsx
  • app/views/RoomInfoView/Livechat.tsx
  • app/views/RoomInfoView/components/ItemLabel.tsx
  • app/views/RoomInfoView/components/RoomInfoTag.tsx
  • app/views/RoomInfoView/components/RoomInfoViewTitle.tsx
  • app/views/RoomView/JoinCode.tsx
  • app/views/RoomView/LoadMore/index.tsx
  • app/views/RoomsListView/components/Header.tsx
  • app/views/SelectedUsersView/Header.tsx
  • app/views/SendEmailConfirmationView.tsx
  • app/views/SetUsernameView.tsx
  • app/views/UserNotificationPreferencesView/ListPicker.tsx
  • app/views/UserPreferencesView/FontSizePicker.tsx
  • app/views/UserPreferencesView/ListPicker.tsx
  • app/views/UserPreferencesView/index.tsx
💤 Files with no reviewable changes (2)
  • app/containers/ActionSheet/styles.ts
  • app/containers/Header/components/HeaderTitle/styles.ts
🧰 Additional context used
🧬 Code graph analysis (78)
app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/PickerView.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/RoomInfoView/components/RoomInfoTag.tsx (4)
app/containers/CustomIcon/index.tsx (2)
  • TIconsName (21-21)
  • CustomIcon (47-47)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/views/UserNotificationPreferencesView/ListPicker.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/ReactionsList/UsersList.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/SelectedUsersView/Header.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/views/RoomInfoView/components/RoomInfoViewTitle.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/OrSeparator.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/MessageComposer/components/Quotes/Quote.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/RoomItem/UpdatedAt.tsx (3)
app/containers/RoomItem/interfaces.ts (1)
  • IUpdatedAtProps (36-40)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/UnreadBadge/index.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/markdown/components/InlineCode.tsx (3)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/views/RoomInfoView/Direct.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/ReactionsList/index.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/markdown/components/Heading.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/markdown/components/mentions/AtMention.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/markdown/components/mentions/Hashtag.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/message/Urls.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/RoomInfoView/Livechat.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/ThreadDetails.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/views/RoomsListView/components/Header.tsx (3)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Header/components/HeaderTitle/styles.ts (1)
  • styles (5-21)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/markdown/components/code/CodeLine.tsx (2)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/MediaAutoDownloadView/ListPicker.tsx (3)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Header/components/HeaderTitle/styles.ts (1)
  • styles (5-21)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/RoomItem/Wrapper.tsx (4)
app/containers/RoomItem/interfaces.ts (1)
  • IWrapperProps (42-57)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/MessageComposer/components/SendThreadToChannel.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/UserItem.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/List/ListInfo.tsx (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Header/components/HeaderTitle/styles.ts (1)
  • styles (5-21)
app/containers/List/styles.ts (1)
  • styles (3-7)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/BackgroundContainer/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/message/Reactions.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/ReactionsList/AllTab.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/DirectoryItem/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/message/Components/Attachments/Video.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/MessageSeparator.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/message/Time.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/MessageComposer/components/EmojiSearchbar.tsx (3)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/UserGeneratedContentRules.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/MessageComposer/components/RecordAudio/Duration.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/CollapsibleText/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/MessageActions/Header.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/ActionSheet/Item.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Header/components/HeaderTitle/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/views/DirectoryView/Options.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/RoomItem/LastMessage.tsx (1)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/ChangePasswordRequired.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/UserPreferencesView/ListPicker.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/SearchHeader.tsx (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Header/components/HeaderTitle/styles.ts (1)
  • styles (5-21)
app/containers/TextInput/TextInput.tsx (1)
  • TextInput (18-29)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/views/SetUsernameView.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Chip/index.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/RoomInfoView/components/ItemLabel.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/InAppNotification/NotifierComponent.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/message/Components/Attachments/Reply.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/message/User.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/List/ListHeader.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/ActionSheet/BottomSheetContent.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/RoomItem/RoomItem.tsx (1)
app/containers/RoomItem/RoomItem.stories.tsx (1)
  • LastMessage (106-133)
app/containers/MessageComposer/components/ComposerInput.tsx (3)
app/containers/MessageComposer/interfaces.ts (3)
  • IInputSelection (15-18)
  • IComposerInput (24-31)
  • IComposerInputProps (33-35)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/RoomView/JoinCode.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Header/components/HeaderTitle/styles.ts (1)
  • styles (5-21)
app/containers/RoomItem/Tag.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/CallHeader.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/NotificationPreferencesView/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/markdown/components/Plain.tsx (2)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/LoginView/UserForm.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/RoomItem/Title.tsx (4)
app/containers/RoomItem/interfaces.ts (1)
  • ITitleProps (30-34)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Header/components/HeaderTitle/styles.ts (1)
  • styles (5-21)
app/containers/Button/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/markdown/components/Paragraph.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
app/containers/ActionSheet/ActionSheetContentWithInputAndSubmit/index.tsx (3)
app/containers/Header/components/HeaderTitle/styles.ts (1)
  • styles (5-21)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/Toast.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/markdown/components/inline/Link.tsx (4)
app/containers/markdown/components/inline/index.ts (1)
  • Link (6-6)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/List/ListItem.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/views/CreateChannelView/RoomSettings/SwitchItem.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/views/DiscussionsView/DiscussionDetails.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/views/RoomView/LoadMore/index.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/AppVersion.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)

Copy link
Member

@diegolmello diegolmello left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution!

Left a few comments.
You should also solve issues reported by CodeRabbit and add screenshots/videos on your PR description.

@deepak0x
Copy link
Author

deepak0x commented Jan 9, 2026

thanks for the review
actually i am still working on it..
will solve the issues... address your changes and update this pr

…UI alignment

- Fix channel header text size scaling in RoomHeader component
- Add font size scaling to RoomActionsView (Actions page) title and description
- Fix vertical centering of room title and description in Actions page
- Add font size scaling to AudioPlayer components (Seek, PlaybackSpeed)
- Add font size scaling to SelectListView info text
- Fix List.Item rightContainer layout to display text and arrow horizontally
- Replace MarkdownPreview with direct Text component in RoomActionsView for better alignment
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @app/containers/AudioPlayer/PlaybackSpeed.tsx:
- Around line 26-30: The accessibilityLabel for the NativeButton currently
formats playbackSpeed as "${playbackSpeed} x" (with a space) while the visible
Text shows "{playbackSpeed}x" (no space); update the accessibilityLabel call in
PlaybackSpeed (the NativeButton accessibilityLabel prop / i18n.t invocation) to
use the same formatting as the UI (e.g., `${playbackSpeed}x` or the
corresponding i18n key value) so the spoken/accessible string exactly matches
the displayed Text content.

In @app/views/FontSizePickerView/index.tsx:
- Around line 31-33: The interface IFontSizePickerViewProps references the route
name 'FontSizePickerView' which isn't declared on ProfileStackParamList; add a
matching entry to ProfileStackParamList (e.g., FontSizePickerView: undefined or
the appropriate params object) so the navigation prop type resolves, then
rebuild/type-check to confirm IFontSizePickerViewProps compiles correctly.

In @app/views/RoomActionsView/index.tsx:
- Around line 82-89: RoomTitle currently types the theme prop as string, which
allows invalid keys when indexing themes; change the prop type to the actual
theme key type (e.g., use keyof typeof themes or a shared ThemeName/ThemeType)
so the component signature becomes RoomTitle({ children, theme }: { children:
React.ReactNode; theme: keyof typeof themes }) (or use the project-wide Theme
type), then update any related usages to satisfy the stricter type; keep the
lookup themes[theme] and adjust imports if you introduce or reuse a theme type.
- Around line 91-105: RoomDescription improperly types props and uses an
unnecessary null coalescing; change the prop types so msg is string | undefined
and theme uses the same theme type as RoomTitle (replace theme: string with the
shared Theme type used by RoomTitle), remove the fallback in
usePreviewFormatText (usePreviewFormatText(msg) instead of
usePreviewFormatText(msg ?? '')), and keep the early return if !msg; update any
imports/types as needed to reference the Theme type and adjust the
RoomDescription signature accordingly.
🧹 Nitpick comments (6)
app/containers/AudioPlayer/PlaybackSpeed.tsx (1)

10-16: Avoid potential crash if ResponsiveLayoutContext is missing (unsafe destructure).

If useResponsiveLayout() can ever return undefined (e.g., tests/storybook or a future render path outside the provider), const { scaleFontSize } = useResponsiveLayout(); will throw.

Proposed safer pattern (local fallback)
 import NativeButton from '../NativeButton';
 import { useResponsiveLayout } from '../../lib/hooks/useResponsiveLayout/useResponsiveLayout';

 const PlaybackSpeed = () => {
 	const [playbackSpeed, setPlaybackSpeed] = useUserPreferences<number>(AUDIO_PLAYBACK_SPEED, AVAILABLE_SPEEDS[1]);
 	const { colors } = useTheme();
-	const { scaleFontSize } = useResponsiveLayout();
+	const responsiveLayout = useResponsiveLayout();
+	const scaleFontSize = responsiveLayout?.scaleFontSize ?? ((size: number) => size);
app/views/SelectListView.tsx (1)

23-30: Well-implemented responsive font sizing!

The InfoText component correctly applies responsive typography using scaleFontSize. The implementation follows React best practices with hooks and proper style merging.

♻️ Optional: Move inline style to styles object

For consistency and potential performance benefits, consider moving the inline backgroundColor style to the styles object:

-const InfoText = ({ text, theme }: { text: string; theme: TSupportedThemes }) => {
-	const { scaleFontSize } = useResponsiveLayout();
-	return (
-		<View style={{ backgroundColor: themes[theme].surfaceRoom }}>
-			<Text style={[styles.buttonText, { color: themes[theme].fontDefault, fontSize: scaleFontSize(16) }]}>{I18n.t(text)}</Text>
-		</View>
-	);
-};
+const InfoText = ({ text, theme }: { text: string; theme: TSupportedThemes }) => {
+	const { scaleFontSize } = useResponsiveLayout();
+	return (
+		<View style={[styles.infoContainer, { backgroundColor: themes[theme].surfaceRoom }]}>
+			<Text style={[styles.buttonText, { color: themes[theme].fontDefault, fontSize: scaleFontSize(16) }]}>{I18n.t(text)}</Text>
+		</View>
+	);
+};

And add to the styles object:

 const styles = StyleSheet.create({
+	infoContainer: {
+		// Add any static container styles here if needed
+	},
 	buttonText: {
 		margin: 16,
 		...sharedStyles.textRegular
 	}
 });
app/views/FontSizePickerView/index.tsx (4)

47-48: Extract hardcoded font scale threshold to a constant.

The 1.3 threshold is repeated twice and should be extracted to a named constant for maintainability and consistency with the codebase.

♻️ Refactor to use a constant

At the top of the file, after the existing constants:

 const FONT_SIZE_LABELS = {
 	[FONT_SIZE_OPTIONS.SMALL]: 'Small',
 	[FONT_SIZE_OPTIONS.NORMAL]: 'Normal',
 	[FONT_SIZE_OPTIONS.LARGE]: 'Large',
 	[FONT_SIZE_OPTIONS.EXTRA_LARGE]: 'Extra_Large'
 };
+
+const LARGE_FONT_SCALE_THRESHOLD = 1.3;

Then update lines 47-48:

 	const previewContextValue = {
 		fontScale: previewFontScale,
 		width: 0,
 		height: 0,
-		isLargeFontScale: previewFontScale > 1.3,
-		fontScaleLimited: previewFontScale > 1.3 ? 1.3 : previewFontScale,
+		isLargeFontScale: previewFontScale > LARGE_FONT_SCALE_THRESHOLD,
+		fontScaleLimited: previewFontScale > LARGE_FONT_SCALE_THRESHOLD ? LARGE_FONT_SCALE_THRESHOLD : previewFontScale,
 		rowHeight: 75 * previewFontScale,
 		rowHeightCondensed: 60 * previewFontScale,
 		scaleFontSize: previewScaleFontSize
 	};

76-76: Remove redundant fallback after I18n.t().

The || 'This is an example message...' fallback is redundant since I18n.t() already has a defaultValue parameter that returns the same string. If the translation is missing, the defaultValue will be used.

♻️ Simplify the expression
-{I18n.t('Font_Size_Example_Message', { defaultValue: 'This is an example message to preview how the text size will look.' }) || 'This is an example message to preview how the text size will look.'}
+{I18n.t('Font_Size_Example_Message', { defaultValue: 'This is an example message to preview how the text size will look.' })}

127-127: Remove redundant fallback after I18n.t().

Same issue as line 76: the || 'Adjust the font size...' fallback is redundant since the defaultValue parameter already provides this fallback.

♻️ Simplify the expression
-{I18n.t('Font_Size_Description', { defaultValue: 'Adjust the font size for all text in the application.' }) || 'Adjust the font size for all text in the application.'}
+{I18n.t('Font_Size_Description', { defaultValue: 'Adjust the font size for all text in the application.' })}

168-173: Simplify the current size label logic.

Two minor improvements:

  1. Line 169: The trailing space after "Current size" is used for spacing, but using marginRight or the existing gap: 8 would be more semantic.
  2. Line 172: The logic I18n.t(currentLabel || 'Normal', {...}) ?? currentLabel is convoluted. Since I18n.t() has a defaultValue, the ?? currentLabel fallback is unnecessary.
♻️ Proposed improvements
 <View style={styles.currentSizeContainer}>
 	<Text style={[styles.currentSizeLabel, { color: colors.fontHint, fontSize: scaleFontSize(12) }]}>
-		{I18n.t('Current_Size', { defaultValue: 'Current size' })}: 
+		{I18n.t('Current_Size', { defaultValue: 'Current size' })}:
 	</Text>
 	<Text style={[styles.currentSizeValue, { color: colors.fontTitlesLabels, fontSize: scaleFontSize(14) }]}>
-		{I18n.t(currentLabel || 'Normal', { defaultValue: currentLabel || 'Normal' }) ?? currentLabel}
+		{I18n.t(currentLabel, { defaultValue: currentLabel })}
 	</Text>
 </View>

Note: currentLabel already has a fallback to 'Normal' on line 114, so the extra || 'Normal' checks are redundant.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 674dfd6 and ab258a9.

📒 Files selected for processing (9)
  • app/containers/AudioPlayer/PlaybackSpeed.tsx
  • app/containers/AudioPlayer/Seek.tsx
  • app/containers/List/ListItem.tsx
  • app/containers/RoomHeader/RoomHeader.tsx
  • app/views/FontSizePickerView/index.tsx
  • app/views/RoomActionsView/index.tsx
  • app/views/RoomActionsView/styles.ts
  • app/views/SelectListView.tsx
  • app/views/UserPreferencesView/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/views/UserPreferencesView/index.tsx
🧰 Additional context used
🧬 Code graph analysis (6)
app/views/FontSizePickerView/index.tsx (6)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (3)
  • FONT_SIZE_OPTIONS (32-37)
  • useResponsiveLayout (85-89)
  • ResponsiveLayoutContext (22-22)
app/stacks/types.ts (1)
  • ProfileStackParamList (190-202)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (63-90)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
app/containers/AudioPlayer/PlaybackSpeed.tsx (3)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (63-90)
app/containers/AudioPlayer/constants.ts (2)
  • AUDIO_PLAYBACK_SPEED (9-9)
  • AVAILABLE_SPEEDS (1-1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/AudioPlayer/Seek.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/RoomHeader/RoomHeader.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/containers/Header/components/HeaderTitle/styles.ts (1)
  • styles (5-21)
app/views/RoomActionsView/index.tsx (3)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/lib/methods/helpers/helpers.ts (1)
  • getRoomTitle (43-60)
app/containers/List/ListItem.tsx (3)
app/containers/List/constants.ts (1)
  • PADDING_HORIZONTAL (1-1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (85-89)
app/lib/constants/colors.ts (1)
  • colors (280-302)
🔇 Additional comments (15)
app/containers/RoomHeader/RoomHeader.tsx (3)

86-117: LGTM! Font scaling correctly applied to SubTitle.

The implementation properly integrates scaleFontSize from useResponsiveLayout and applies it consistently across all subtitle rendering paths (typing indicator, custom render function, and markdown preview).


119-133: Excellent accessibility improvement with isLargeFontScale.

The font scaling is correctly applied, and the conditional numberOfLines on Line 126 is a thoughtful touch—allowing 2 lines when isLargeFontScale is true prevents excessive truncation for users with large font preferences.


178-196: LGTM! Consistent font scaling in tmid subtitle path.

The addition of scaleFontSize at Line 178 and its application at Line 191 ensures that the subtitle in the thread/discussion context (tmid branch) receives the same responsive font treatment as the main subtitle path, maintaining consistency across all rendering scenarios.

app/views/SelectListView.tsx (2)

21-21: LGTM!

The import of useResponsiveLayout is correctly added to support responsive font sizing in the new InfoText component.


108-111: LGTM!

The renderInfoText method correctly delegates to the new InfoText component, passing the required text and theme props.

app/containers/AudioPlayer/Seek.tsx (2)

17-17: LGTM! Responsive font sizing correctly integrated.

The import and hook usage follow the established pattern for implementing responsive typography across the app. The scaleFontSize function is properly extracted from useResponsiveLayout() and ready for use in the component.

Also applies to: 48-48


116-116: LGTM! Duration label now respects font size preference.

The styled font size application is correct. Using scaleFontSize(14) ensures the audio duration label text will scale appropriately based on the user's font size preference setting, fulfilling the PR's accessibility objective.

app/containers/List/ListItem.tsx (4)

20-20: LGTM! Proper hook usage for responsive font sizing.

The useResponsiveLayout hook is correctly imported and scaleFontSize is properly destructured alongside the existing fontScale. This aligns well with the PR objective of applying font size scaling across all components.

Also applies to: 136-136


186-186: Font size preference takes precedence over custom styles.

The dynamic fontSize: scaleFontSize(16) is applied after styleTitle, which means any custom fontSize passed via the styleTitle prop will be overridden. This ensures the user's font size preference is always respected, but it may affect consumers who were passing custom font sizes.

This behavior is likely intentional for consistency with the font size preference feature.


196-196: LGTM! Subtitle styling correctly applies responsive font size.

The subtitle properly combines base styles from styles.subtitle (preserving sharedStyles.textRegular) with themed color and dynamic font size. The implementation is consistent with the title's approach.


36-39: The project uses React Native 0.79.4, which is well above the minimum version (0.71) required for the gap property. The code is compatible and correct.

Likely an incorrect or invalid review comment.

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

16-29: LGTM!

The style updates appropriately support the new typography components with vertical centering and font padding control.

app/views/RoomActionsView/index.tsx (1)

815-829: Component usage looks correct.

The new RoomTitle and RoomDescription components are integrated properly. The type safety improvements suggested in previous comments will handle potential undefined values for topic and member.statusText.

app/views/FontSizePickerView/index.tsx (2)

182-281: LGTM! Styles are well-organized.

The styles follow React Native best practices with appropriate use of flexbox, responsive padding/margins, and the newer gap property for spacing. The visual hierarchy is clear with proper use of shadows and elevation for depth.


11-11: No issue found. The import statement at line 11 correctly imports FONT_SIZE_OPTIONS, ResponsiveLayoutContext, and useResponsiveLayout from app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx. All three exports are properly defined and exported from that file, and the import path is valid.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @app/containers/Button/index.tsx:
- Around line 87-91: The disabled text color currently always uses
colors.buttonPrimaryDisabled; update the color expression in the Button render
to choose the token by button type: when isDisabled is true use isPrimary ?
colors.buttonPrimaryDisabled : colors.buttonSecondaryDisabled, otherwise keep
resolvedTextColor; modify the object that sets color (the snippet that
references isDisabled, resolvedTextColor and colors) so it mirrors the existing
background logic and uses the type-specific disabled token.

In @app/stacks/MasterDetailStack/index.tsx:
- Around line 49-50: Remove the extra ModalStack registration and import for
FontSizePickerView in MasterDetailStack: delete the import of FontSizePickerView
and the ModalStack.Screen entry that registers it (the duplicated registration
around the ModalStack/Screen block), since FontSizePickerView is already
registered as ProfileStack.Screen in InsideStack.tsx and is navigated to from
UserPreferencesView; leave all ProfileStack types and InsideStack.tsx
registration unchanged to preserve correct param types.

In @app/views/UserPreferencesView/FontSizePicker.tsx:
- Around line 56-63: The avatar can overflow because styles.avatarContainer has
a fixed width (36) while Avatar.size uses previewScaleFontSize(36); update the
container to use a dynamic width that matches the scaled avatar by changing the
View using styles.avatarContainer to combine the static style with an inline
width: { width: previewScaleFontSize(36) } (e.g., <View
style={[styles.avatarContainer, { width: previewScaleFontSize(36) }]}>), or
remove the fixed width from styles.avatarContainer; apply the same change to the
other occurrence where Avatar and styles.avatarContainer are used.
- Around line 33-50: The preview scaling is inconsistent: ExampleMessage builds
previewFontScale (systemFontScale * previewFontSize) but
previewContextValue.scaleFontSize uses previewFontSize only; update
scaleFontSize to use the computed preview scale (and respect the limited value
if desired) so sizes match previewContextValue (e.g., set scaleFontSize: (size:
number) => size * previewFontScale or size *
previewContextValue.fontScaleLimited), leaving previewFontScale,
previewContextValue and previewScaleFontSize identifiers intact.
🧹 Nitpick comments (9)
app/views/RoomActionsView/index.tsx (1)

90-104: Optimize hook invocation to skip unnecessary work.

When msg is falsy, usePreviewFormatText is still called with an empty string, and then the component returns null, discarding the computed formattedText. Consider refactoring to avoid this wasted computation.

♻️ Proposed optimization
 const RoomDescription = ({ msg, theme }: { msg: string | undefined; theme: TSupportedThemes }) => {
 	const { scaleFontSize } = useResponsiveLayout();
-	const formattedText = usePreviewFormatText(msg || '');
 	if (!msg) {
 		return null;
 	}
+	const formattedText = usePreviewFormatText(msg);
 	return (
 		<Text
 			style={[styles.roomDescription, { color: themes[theme].fontSecondaryInfo, fontSize: scaleFontSize(13), lineHeight: scaleFontSize(18) }]}
 			numberOfLines={1}
 			ellipsizeMode='tail'>
 			{formattedText}
 		</Text>
 	);
 };

Note: This moves the hook call after the null check. However, this violates the Rules of Hooks if msg can change between renders in a way that affects whether the hook is called. If usePreviewFormatText must be called unconditionally (per Rules of Hooks), then the current implementation is correct but you might consider memoizing or optimizing the hook itself to handle empty strings efficiently.

Alternatively, if the hook cannot be called conditionally, the current approach is acceptable and the hook should handle empty strings efficiently internally.

app/containers/Button/index.tsx (1)

8-9: styleText is effectively prevented from overriding fontSize/color
Because the computed object is last in textStyle, any styleText passed by callers can’t override color/fontSize/lineHeight. If styleText is meant to be an override escape hatch, swap the order (or selectively merge).

Proposed adjustment (keep disabled styles, but allow caller overrides)
-	const textStyle = [
-		small ? styles.smallText : styles.text,
-		styleText,
-		{ 
-			color: isDisabled ? colors.buttonPrimaryDisabled : resolvedTextColor, 
-			fontSize: small ? scaleFontSize(12) : scaleFontSize(fontSize),
-			lineHeight: small ? scaleFontSize(18) : undefined
-		}
-	];
+	const computedTextStyle = {
+		color: isDisabled ? colors.buttonPrimaryDisabled : resolvedTextColor,
+		fontSize: small ? scaleFontSize(12) : scaleFontSize(fontSize),
+		lineHeight: small ? scaleFontSize(18) : undefined
+	};
+	const textStyle = [small ? styles.smallText : styles.text, computedTextStyle, styleText];

Also applies to: 84-92

app/views/UserPreferencesView/index.tsx (1)

38-49: Avoid float-key mapping + re-creating labels map on every render
FONT_SIZE_LABELS is re-created each render and indexes by parseFloat(...). Consider moving the map outside the component and keying by the stored string (e.g., '0.9' | '1' | '1.1' | '1.2') to avoid float representation edge cases (like '1.0' vs '1').

app/views/UserPreferencesView/FontSizePicker.tsx (2)

89-91: Persisted value matching is brittle ('1' vs '1.0')
findIndex(opt => opt.toString() === fontSize) will fail if a previously stored value uses a different string representation. Safer: compare numbers (parseFloat(fontSize)), or normalize once.


126-138: Slider accessibility metadata is missing
Consider setting accessibilityLabel (and potentially accessibilityValue) so screen readers announce what the slider controls and the current selection.

app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (4)

27-28: Clarify constant names: BASE_ROW_HEIGHT_SMALL_FONT is larger, not smaller.

BASE_ROW_HEIGHT_SMALL_FONT (82) is larger than BASE_ROW_HEIGHT (75), which contradicts the "SMALL_FONT" label. These constants provide extra vertical space when fonts are small to prevent overly compressed layouts.

Consider renaming to reflect their purpose more clearly:

  • BASE_ROW_HEIGHT_SMALL_FONTBASE_ROW_HEIGHT_FOR_SMALL_FONTS or BASE_ROW_HEIGHT_MINIMUM
  • Or add an inline comment explaining the intentional inversion

61-65: Add comment explaining row height compensation for small fonts.

The logic calculates larger base heights when fontScale < 0.9, then multiplies by the small fontScale. This maintains minimum usable row heights but is not immediately obvious.

📝 Suggested inline documentation
+	// When fontScale is very small (< 0.9), use larger base heights to prevent
+	// overly compressed layouts. The base is still scaled by fontScale to maintain
+	// proportionality, but the larger starting point ensures minimum usability.
 	const isSmallFont = fontScale < SMALL_FONT_THRESHOLD;
 	const baseRowHeight = isSmallFont ? BASE_ROW_HEIGHT_SMALL_FONT : BASE_ROW_HEIGHT;
 	const baseRowHeightCondensed = isSmallFont ? BASE_ROW_HEIGHT_CONDENSED_SMALL_FONT : BASE_ROW_HEIGHT_CONDENSED;
 	const rowHeight = baseRowHeight * fontScale;
 	const rowHeightCondensed = baseRowHeightCondensed * fontScale;

29-29: Consider exporting SMALL_FONT_THRESHOLD for consistency.

FONT_SIZE_OPTIONS is exported (line 31) but SMALL_FONT_THRESHOLD (line 29) is not, despite both being related font size constants. If other components need to apply small-font-specific logic, exporting the threshold would maintain consistency.

Also applies to: 31-36


45-54: API pattern is correct, but consider adding explicit error handling in the listener callback.

The addOnValueChangedListener API returns an object with a .remove() method, and the cleanup pattern correctly removes the listener on unmount. However, while userPreferences.getString() has internal error handling, wrapping the listener callback logic in a try-catch would provide additional robustness:

useEffect(() => {
	const listener = initializeStorage.addOnValueChangedListener((changedKey: string) => {
		try {
			if (changedKey === FONT_SIZE_PREFERENCES_KEY) {
				const newFontSize = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY) || FONT_SIZE_OPTIONS.NORMAL.toString();
				setCustomFontSize(newFontSize);
			}
		} catch (error) {
			console.warn('Error updating font size preference:', error);
		}
	});
	
	return () => listener.remove();
}, []);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ab258a9 and 35d82e0.

📒 Files selected for processing (15)
  • app/containers/AudioPlayer/PlaybackSpeed.tsx
  • app/containers/Button/index.tsx
  • app/containers/MessageComposer/components/RecordAudio/Duration.tsx
  • app/containers/RoomItem/Wrapper.tsx
  • app/containers/RoomItem/styles.ts
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
  • app/stacks/InsideStack.tsx
  • app/stacks/MasterDetailStack/index.tsx
  • app/stacks/MasterDetailStack/types.ts
  • app/stacks/types.ts
  • app/views/RoomActionsView/index.tsx
  • app/views/RoomsListView/index.tsx
  • app/views/SelectedUsersView/Header.tsx
  • app/views/UserPreferencesView/FontSizePicker.tsx
  • app/views/UserPreferencesView/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/containers/MessageComposer/components/RecordAudio/Duration.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/UserPreferencesView/FontSizePicker.tsx
  • app/containers/AudioPlayer/PlaybackSpeed.tsx
🧬 Code graph analysis (8)
app/views/RoomActionsView/index.tsx (4)
app/theme.tsx (1)
  • TSupportedThemes (8-8)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (86-103)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/lib/methods/helpers/helpers.ts (1)
  • getRoomTitle (43-60)
app/views/UserPreferencesView/FontSizePicker.tsx (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (3)
  • FONT_SIZE_OPTIONS (31-36)
  • useResponsiveLayout (86-103)
  • ResponsiveLayoutContext (22-22)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (63-90)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
app/containers/Button/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (86-103)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/views/UserPreferencesView/index.tsx (4)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (2)
  • useResponsiveLayout (86-103)
  • FONT_SIZE_OPTIONS (31-36)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (63-90)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
app/views/RoomsListView/index.tsx (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (86-103)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
app/containers/AudioPlayer/PlaybackSpeed.tsx (2)
app/containers/AudioPlayer/constants.ts (2)
  • AUDIO_PLAYBACK_SPEED (9-9)
  • AVAILABLE_SPEEDS (1-1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (86-103)
app/views/SelectedUsersView/Header.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (86-103)
app/lib/constants/colors.ts (1)
  • themes (304-304)
🔇 Additional comments (18)
app/views/RoomActionsView/index.tsx (3)

15-15: LGTM: Imports are correct.

The new imports support the responsive font sizing feature properly.

Also applies to: 33-33, 77-77


81-88: LGTM: RoomTitle component correctly implements dynamic font sizing.

The component properly integrates the responsive layout hook and applies theme colors.


814-828: LGTM: Components are integrated correctly.

The conditional rendering logic properly handles different room types, and the theme prop is correctly passed to both RoomTitle and RoomDescription. The defensive check on line 828 ensures member.statusText is only rendered when it exists.

app/containers/RoomItem/styles.ts (4)

10-72: Excellent truncation-safe layout patterns.

The addition of minWidth: 0 across flex containers (flex, centerContainer, title, titleText, row, titleContainer, lastMessageContainer), combined with overflow: 'hidden' and strategic flexShrink values, implements best practices for text truncation in React Native flex layouts. These constraints ensure that long text content will truncate properly rather than overflow or break the layout.

Key improvements:

  • minWidth: 0 enables nested flex children to shrink below their content size
  • flexShrink: 0 on date and tagContainer prevents these UI elements from collapsing
  • New titleText style with flexShrink: 1 allows the title to shrink when space is constrained

42-44: Verify the alignItems: 'flex-start' change doesn't introduce visual regressions.

The change from implicit center alignment to alignItems: 'flex-start' in the row style will affect the vertical positioning of child elements within the RoomItem rows. This may be intentional to improve alignment with dynamically scaled text, but please verify that the layout appears correct across different font size settings and doesn't introduce unexpected spacing or misalignment issues.


26-30: Hardcoded font sizes properly replaced with scaleFontSize.

The removal of fontSize: 17 from the title style and fontSize from tagText is correctly accompanied by dynamic font scaling in the corresponding components: Title.tsx uses fontSize: scaleFontSize(17) and Tag.tsx uses fontSize: scaleFontSize(13). The changes align with the PR objective and are properly implemented.


67-72: No action required. The style key rename from markdownText to lastMessageContainer is not a breaking change—neither style name is referenced anywhere in the codebase. The style definition exists but remains unused.

app/views/RoomsListView/index.tsx (3)

54-61: LGTM! Responsive layout integration is well-implemented.

The memoized extraData correctly includes responsive metrics (rowHeight, rowHeightCondensed, fontScale) to ensure the FlatList re-renders when font scaling changes. The dependencies are complete and the conditional logic properly handles search vs. normal mode.


73-73: LGTM! useEffect dependencies are now complete.

Including navigation and stopSearch in the dependencies array correctly follows React Hook rules, as both are used within the effect callback.


143-143: LGTM! Key includes fontScale to trigger re-renders.

Including fontScale in the keyExtractor ensures all list items re-render when font size changes, which is necessary for the font scaling feature to apply immediately.

app/stacks/types.ts (1)

202-202: LGTM! Navigation route type is correctly defined.

The FontSizePickerView route is properly added to ProfileStackParamList with no required parameters, consistent with other profile stack routes.

app/stacks/MasterDetailStack/types.ts (1)

190-190: LGTM! Modal route type is correctly defined.

The FontSizePickerView route is properly added to ModalStackParamList, ensuring the font size picker is accessible in master-detail navigation mode.

app/containers/AudioPlayer/PlaybackSpeed.tsx (1)

10-10: LGTM! Responsive font sizing correctly applied.

The integration of scaleFontSize follows the established pattern. The accessibility label improvement (removing the space before "x") makes it more natural to read.

Also applies to: 15-15, 26-26, 29-29

app/stacks/InsideStack.tsx (1)

40-40: LGTM! Navigation screen is properly registered.

The FontSizePickerView screen is correctly added to the ProfileStackNavigator, completing the navigation setup for the font size preference feature.

Also applies to: 177-177

app/views/SelectedUsersView/Header.tsx (1)

12-13: Looks good: scaleFontSize applied without altering layout logic
The scaleFontSize(12) override being last in the style array is the right precedence (keeps sharedStyles but forces responsive sizing).

Also applies to: 37-50

app/views/UserPreferencesView/index.tsx (1)

138-153: > Likely an incorrect or invalid review comment.

app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (2)

1-1: Verify React 19 compatibility for hooks and context usage.

The code uses React hooks (useState, useEffect, useCallback, useMemo, useContext) and context patterns. Since the library context indicates React 19.0.0 is in use, confirm that:

  1. The hook dependency arrays are complete and won't trigger warnings in React 19's stricter Strict Mode
  2. The cleanup pattern in useEffect (line 53) is compatible with React 19's double-mount behavior in development

The current implementation appears correct, but given React 19's changes to Strict Mode behavior, verify this in testing.

Also applies to: 40-40, 41-54, 57-57, 67-67, 69-81


67-67: Confirmed: scaleFontSize correctly excludes system fontScale for Text component usage.

The implementation on line 67 intentionally multiplies by fontSizeMultiplier (user preference) only and excludes systemFontScale. This is correct because React Native's Text component automatically applies system font scaling to fontSize styles. All 50+ usages across the codebase are in Text/TextInput component styles where this automatic scaling is applied, so no double-scaling occurs.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @app/containers/Button/index.tsx:
- Around line 85-93: The dynamic style object currently overrides any
fontSize/lineHeight in styleText because it comes after styleText in the
textStyle array; change the ordering so styleText is last (textStyle = [small ?
styles.smallText : styles.text, { color: ..., fontSize: ..., lineHeight: ... },
styleText]) so consumer overrides win, and update the fontSize/lineHeight logic
in that dynamic object to respect the fontSize prop and provide sensible
defaults (e.g., fontSize: scaleFontSize(small ? (fontSize ?? 12) : (fontSize ??
16)) and set lineHeight for both small and normal when not provided) by
checking/using fontSize and lineHeight values rather than hardcoding
12/undefined; modify the textStyle construction in the Button component
accordingly (symbols: textStyle, styleText, small, fontSize, lineHeight,
scaleFontSize).

In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 68-77: fontScaleLimited is computed but never used, so rowHeight
and rowHeightCondensed still use the unbounded fontScale; update the
calculations in useResponsiveLayout to use fontScaleLimited instead of fontScale
when computing rowHeight and rowHeightCondensed (references: fontSizeMultiplier,
fontScale, fontScaleLimited, rowHeight, rowHeightCondensed, FONT_SCALE_LIMIT) so
the FONT_SCALE_LIMIT is actually enforced, or if you prefer unbounded scaling
remove fontScaleLimited and all references to FONT_SCALE_LIMIT consistently.
🧹 Nitpick comments (3)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (3)

29-29: Consider exporting SMALL_FONT_THRESHOLD for consistency.

While currently only used internally, this threshold might be useful for other components that need to detect small font scenarios. Consider exporting it alongside the other constants for API completeness.

♻️ Proposed refactor
-const SMALL_FONT_THRESHOLD = 0.9;
+export const SMALL_FONT_THRESHOLD = 0.9;

41-49: Extract duplicate parsing logic into a helper function.

The font size parsing logic (getNumber fallback to getString with Number parsing) is duplicated in lines 41-49 and lines 54-61. Extract this into a reusable helper function to improve maintainability.

♻️ Proposed refactor

Add a helper function before the component:

+const parseFontSizePreference = (): number => {
+	const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
+	if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber)) {
+		return storedNumber;
+	}
+	const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
+	const parsed = storedString !== null ? Number(storedString) : undefined;
+	return !Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL;
+};
+
 const ResponsiveLayoutProvider = ({ children }: IResponsiveFontScaleProviderProps) => {
 	const { fontScale: systemFontScale, width, height } = useWindowDimensions();
-	const [customFontSize, setCustomFontSize] = useState(() => {
-		const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
-		if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber)) {
-			return storedNumber;
-		}
-		const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
-		const parsed = storedString !== null ? Number(storedString) : undefined;
-		return !Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL;
-	});
+	const [customFontSize, setCustomFontSize] = useState(parseFontSizePreference);

Then update the listener:

 	useEffect(() => {
 		const listener = initializeStorage.addOnValueChangedListener((changedKey: string) => {
 			if (changedKey === FONT_SIZE_PREFERENCES_KEY) {
-				const newValue = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
-				if (typeof newValue === 'number' && !Number.isNaN(newValue)) {
-					setCustomFontSize(newValue);
-					return;
-				}
-				const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
-				const parsed = storedString !== null ? Number(storedString) : undefined;
-				setCustomFontSize(!Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL);
+				setCustomFontSize(parseFontSizePreference());
 			}
 		});

101-112: Consider adding a development warning for missing provider.

The fallback logic prevents crashes when the hook is used outside the provider, which is good defensive programming. However, silently returning defaults might hide integration issues during development.

♻️ Proposed enhancement
 	if (!context || Object.keys(context).length === 0) {
+		if (__DEV__) {
+			console.warn('useResponsiveLayout must be used within ResponsiveLayoutProvider. Using default values.');
+		}
 		return {
 			fontScale: 1.0,
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 35d82e0 and ae0a351.

📒 Files selected for processing (4)
  • app/containers/Button/index.tsx
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
  • app/views/FontSizePickerView/index.tsx
  • app/views/UserPreferencesView/FontSizePicker.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/views/UserPreferencesView/FontSizePicker.tsx
  • app/views/FontSizePickerView/index.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/containers/Button/index.tsx
🧬 Code graph analysis (2)
app/containers/Button/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (98-115)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (6)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (3)

51-66: LGTM: Effect properly manages storage listener lifecycle.

The effect correctly sets up the storage listener on mount and cleans it up on unmount. The empty dependency array is appropriate since the listener should persist for the component's lifetime.


79-79: LGTM: Proper useCallback implementation.

The callback correctly memoizes the font scaling function with the appropriate dependency, ensuring stable references and optimal re-render behavior.


81-93: LGTM: Context value properly memoized.

The context value is correctly memoized with all relevant dependencies, ensuring consumers only re-render when necessary.

app/containers/Button/index.tsx (3)

43-45: LGTM: Font properties moved to dynamic styling.

Removing static fontSize and lineHeight from the style object allows the component to apply font size scaling dynamically, which is the correct approach for this feature.


66-69: LGTM: Clean hook integration.

The hook is properly integrated and the disabled text color computation correctly varies by button type.


90-91: Clarify fontSize prop behavior for small buttons and lineHeight for normal buttons.

Two potentially unexpected behaviors:

  1. Line 90: When small={true}, the fontSize prop is ignored and always defaults to 12 (scaled). This might surprise developers who pass a custom fontSize expecting it to apply.

  2. Line 91: lineHeight is only set for small buttons. Normal buttons get undefined, which relies on the default line height computation. This asymmetry might cause inconsistent text rendering.

Please verify:

  • Is ignoring the fontSize prop for small buttons intentional? Consider documenting this in the interface or applying: small ? scaleFontSize(12) : scaleFontSize(fontSize)
  • Should normal buttons also receive an explicit lineHeight for consistency?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 68-82: scaleFontSize currently multiplies by fontSizeMultiplier
only, ignoring system accessibility scaling; update the useCallback for
scaleFontSize to use fontScale (which includes systemFontScale *
fontSizeMultiplier) and update its dependency array to [fontScale] so scaled
text respects both system and custom font settings; locate the scaleFontSize
definition and replace the multiplier to use fontScale instead of
fontSizeMultiplier and adjust dependencies accordingly.
🧹 Nitpick comments (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)

41-49: Extract storage retrieval logic to eliminate duplication.

The font size retrieval logic (getNumber with fallback to getString parsing) is duplicated in lines 54-61. This creates maintenance burden and increases risk of inconsistency.

♻️ Proposed refactor to extract common logic
+const retrieveFontSizeFromStorage = (): number => {
+	const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
+	if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber)) {
+		return storedNumber;
+	}
+	const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
+	const parsed = storedString !== null ? Number(storedString) : undefined;
+	return !Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL;
+};
+
 const ResponsiveLayoutProvider = ({ children }: IResponsiveFontScaleProviderProps) => {
 	const { fontScale: systemFontScale, width, height } = useWindowDimensions();
-	const [customFontSize, setCustomFontSize] = useState(() => {
-		const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
-		if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber)) {
-			return storedNumber;
-		}
-		const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
-		const parsed = storedString !== null ? Number(storedString) : undefined;
-		return !Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL;
-	});
+	const [customFontSize, setCustomFontSize] = useState(retrieveFontSizeFromStorage);
 	
 	useEffect(() => {
 		const listener = initializeStorage.addOnValueChangedListener((changedKey: string) => {
 			if (changedKey === FONT_SIZE_PREFERENCES_KEY) {
-				const newValue = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
-				if (typeof newValue === 'number' && !Number.isNaN(newValue)) {
-					setCustomFontSize(newValue);
-					return;
-				}
-				const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
-				const parsed = storedString !== null ? Number(storedString) : undefined;
-				setCustomFontSize(!Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL);
+				setCustomFontSize(retrieveFontSizeFromStorage());
 			}
 		});
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ae0a351 and bb7ad01.

📒 Files selected for processing (2)
  • app/containers/Button/index.tsx
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/containers/Button/index.tsx
🧬 Code graph analysis (1)
app/containers/Button/index.tsx (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
  • useResponsiveLayout (101-118)
app/lib/constants/colors.ts (1)
  • colors (280-302)
🔇 Additional comments (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (2)

15-15: LGTM! Clean API extension for font scaling.

The additions are well-structured:

  • scaleFontSize function properly typed in the interface
  • FONT_SIZE_OPTIONS provides clear preset values
  • Small font threshold aligns with the SMALL option (0.9)
  • Alternative base row heights maintain layout proportions when scaling

Also applies to: 27-36


84-96: LGTM! Context memoization and fallback are correct.

  • Dependencies array properly includes scaleFontSize to trigger updates when the multiplier changes
  • Fallback provides safe defaults, including identity function for scaleFontSize
  • All interface properties are satisfied

Also applies to: 104-115

app/containers/Button/index.tsx (2)

8-8: LGTM! Clean integration of responsive font sizing.

The changes correctly:

  • Import the layout hook for font scaling
  • Remove static font sizing from smallText style (now handled dynamically)
  • Extract scaleFontSize for use in text styling
  • Compute disabled text color based on button type

Also applies to: 44-44, 66-66, 69-69


85-93: The edge case you identified is real (there's actual usage in ChangePassword.tsx with both props), but it's not problematic. The small prop is a preset variant by design—it always uses fixed sizing (fontSize: scaleFontSize(12), lineHeight: scaleFontSize(18)) and intentionally ignores any custom fontSize prop. This is working as intended.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 41-49: The font-size retrieval logic duplicated between the
useState initializer (customFontSize) and the storage listener should be
extracted into a single helper function (e.g., getStoredFontSize) that
encapsulates reading from userPreferences using FONT_SIZE_PREFERENCES_KEY,
parsing numeric/string values, validating with Number.isNaN, and falling back to
FONT_SIZE_OPTIONS.NORMAL; replace the inline logic in the useState initializer
and the storage listener callback with calls to this helper and call
setCustomFontSize(getStoredFontSize()) where appropriate so both initialization
and updates share the same, tested logic.
🧹 Nitpick comments (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (2)

68-69: Consider documenting the multiplicative font scaling behavior.

The code multiplies systemFontScale by fontSizeMultiplier (line 69), meaning these scales compound. For example, if a user has system accessibility set to 1.5× and selects "Large" (1.1), the result is 1.65× total scaling.

While this aligns with the commit message "respect system accessibility settings," this behavior might be unexpected. Consider adding a comment explaining this design decision.

📝 Suggested comment
+ // Compound system accessibility scale with user preference to respect both settings
+ // e.g., system 1.5× × custom 1.1× = 1.65× total
  const fontSizeMultiplier = typeof customFontSize === 'number' && !Number.isNaN(customFontSize) ? customFontSize : 1.0;
  const fontScale = systemFontScale * fontSizeMultiplier;

27-29: This row height logic is intentional and correct—no changes needed.

When fontScale drops below the SMALL_FONT_THRESHOLD (0.9), the code intentionally uses a larger base row height (82 vs 75) to ensure rows don't become cramped. For example, at fontScale 0.8, the small font base yields 65.6 units versus 60 with the normal base—a deliberate spacing buffer for accessibility. This design prevents UI crowding when users have reduced font scaling.

That said, a brief comment explaining this behavior would improve code clarity, since the naming alone could suggest the opposite intent.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bb7ad01 and f45f2f1.

📒 Files selected for processing (1)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (4)

1-16: LGTM: Interface and imports are well-structured.

The additions to the interface and imports properly support the new font scaling functionality.


84-99: LGTM: Context value is properly memoized.

The context value is correctly memoized with all necessary dependencies, and the provider implementation is sound.


104-115: LGTM: Fallback values provide safe defaults.

The hook correctly handles missing context by returning sensible default values, preventing runtime errors when used outside the provider.


51-66: Storage initialization is synchronous and ready before component mount—no race condition exists.

The userPreferences and initializeStorage objects are created synchronously at module load time (lines 61 and 165 in userPreferences.ts), not asynchronously. The MMKV instance is fully initialized when the module is imported, so both are available and safe to use when this component mounts. All methods (getString, getNumber, addOnValueChangedListener) are synchronous with proper error handling.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 38-46: getFontSizeFromStorage currently handles both string and
number formats; standardize font-size storage to a number across the app by
updating the writers (UserPreferencesView/index.tsx,
UserPreferencesView/FontSizePicker.tsx, and FontSizePickerView/index.tsx) to
persist FONT_SIZE_PREFERENCES_KEY as a numeric value via
userPreferences.setNumber, then simplify getFontSizeFromStorage to read only
userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY) and fall back to
FONT_SIZE_OPTIONS.NORMAL.
🧹 Nitpick comments (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (2)

38-46: Consider adding validation for font size values.

The getFontSizeFromStorage function reads from storage but doesn't validate that the value is within the expected range (0.9-1.2 based on FONT_SIZE_OPTIONS). Consider adding validation to constrain values:

const value = /* ...existing logic... */;
return Math.min(Math.max(value, 0.5), 2.0); // Reasonable min/max bounds

This prevents extreme scaling if invalid values are stored.


63-64: Simplify the font size multiplier fallback.

The validation on line 63 is redundant—getFontSizeFromStorage() already returns a valid number or 1.0. You can simplify:

-const fontSizeMultiplier = typeof customFontSize === 'number' && !Number.isNaN(customFontSize) ? customFontSize : 1.0;
+const fontSizeMultiplier = customFontSize;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f45f2f1 and c65aa96.

📒 Files selected for processing (1)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (6)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (6)

27-36: LGTM! Constants are well-defined.

The new base row height constants for small fonts and the FONT_SIZE_OPTIONS preset values are clearly defined and appropriately exported for use across the codebase.


77-77: LGTM! Correct useCallback implementation.

The scaleFontSize callback correctly depends on fontScale and will update whenever the font scale changes, ensuring consumers always get the current scaling factor.


79-91: LGTM! Proper memoization with complete dependencies.

The context value is correctly memoized with all dependencies listed, preventing unnecessary re-renders while ensuring updates propagate when values change.


99-110: LGTM! Proper fallback when context is unavailable.

The hook correctly detects when it's used outside a provider (empty context object) and returns sensible default values, including a no-op scaleFontSize function. This prevents crashes when the hook is used without the provider.


63-72: [rewritten review comment]
[classification tag]


53-61: Storage listener cleanup is correctly implemented.

The code properly handles listener cleanup: initializeStorage.addOnValueChangedListener() returns an object with a .remove() method (confirmed by the mock implementation and type annotations used elsewhere in the codebase like app/lib/methods/helpers/theme.ts). The cleanup function correctly unsubscribes by calling listener.remove(), preventing memory leaks and ensuring the listener doesn't fire after component unmount.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 72-84: The computed fontScale (fontScale = systemFontScale *
fontSizeMultiplier) is unbounded and used by scaleFontSize, allowing text to
exceed FONT_SCALE_LIMIT; change the logic so that the capped value is used for
text sizing: compute fontScaleLimited = Math.min(systemFontScale *
fontSizeMultiplier, FONT_SCALE_LIMIT) (or apply fontScaleLimited before any use)
and update scaleFontSize to reference fontScaleLimited instead of raw fontScale,
while keeping rowHeight and rowHeightCondensed calculations using the same
capped value.
🧹 Nitpick comments (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)

38-55: Consider optimizing storage writes during migration.

The type normalization logic is correct and handles the migration from string to numeric storage gracefully. However, lines 48 and 53 write to storage on every read when the value isn't already numeric. During migration, this could cause a storage write on every app launch or preference read.

Consider tracking whether normalization has occurred to avoid repeated writes, or accept this as acceptable migration overhead.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c65aa96 and 3b7e879.

📒 Files selected for processing (2)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
  • app/views/UserPreferencesView/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/views/UserPreferencesView/index.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (4)

1-16: LGTM! Interface and imports properly extended.

The new imports support storage-backed preferences and the scaleFontSize function signature correctly extends the context interface.


86-102: LGTM! Proper memoization prevents unnecessary re-renders.

The scaleFontSize callback and context value are correctly memoized with appropriate dependencies.


108-119: LGTM! Fallback defaults improve initialization robustness.

The default values prevent crashes when the hook is used before the provider mounts, with sensible identity/base values.


27-29: This behavior is intentional and appropriate for accessibility.

When fontScale < 0.9, using larger base row heights (82 vs 75, and 68 vs 60) prevents rows from becoming too compressed when users apply small font scales. For example, with fontScale = 0.8, this yields 65.6 vs 60 pixels—maintaining minimum visual space and touch targets. This is a valid accessibility pattern, not an error.

Consider adding test coverage for the small font scenario (fontScale < 0.9) to document this behavior.

- Use native number storage in useUserPreferences for numeric values
- Add findClosestOption to handle floating point precision in label lookup
- Apply fontScaleLimited to scaleFontSize to prevent unbounded scaling
- Fix current size label showing correctly in FontSizePickerView and UserPreferencesView
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 38-55: getFontSizeFromStorage currently accepts any numeric value
from storage; add bounds validation to reject or clamp invalid sizes (zero,
negative, or excessively large). After parsing the numeric value in
getFontSizeFromStorage, check it against allowed min/max (e.g.,
FONT_SIZE_OPTIONS.MIN and FONT_SIZE_OPTIONS.MAX or use
FONT_SIZE_OPTIONS.SMALL/NORMAL/LARGE constants) and if out of range, set and
return FONT_SIZE_OPTIONS.NORMAL (or clamp to the nearest allowed value); ensure
you still call userPreferences.setNumber(FONT_SIZE_PREFERENCES_KEY,
<validatedNumber>) to normalize stored type. Update only getFontSizeFromStorage
and any constants/enums for min/max values if needed; preserve existing behavior
of falling back to FONT_SIZE_OPTIONS.NORMAL when no valid value exists.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3b7e879 and e0de1f3.

📒 Files selected for processing (4)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
  • app/lib/methods/userPreferences.ts
  • app/views/FontSizePickerView/index.tsx
  • app/views/UserPreferencesView/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/views/UserPreferencesView/index.tsx
  • app/views/FontSizePickerView/index.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (8)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (6)

27-29: Verify the small-font base height logic.

When fontScale < 0.9 (small font), the code applies larger base row heights (BASE_ROW_HEIGHT_SMALL_FONT = 82 vs. BASE_ROW_HEIGHT = 75). This appears counterintuitive—why do smaller fonts require more vertical space?

If this ensures minimum touch targets per iOS (44pt) and Android (48dp) guidelines even with reduced text size, that's excellent accessible design. However, please confirm this is intentional and document the reasoning in a comment.

📝 Suggested documentation
+// When font scale is small, use larger base heights to maintain minimum
+// touch target sizes (iOS: 44pt, Android: 48dp) for accessibility.
 const SMALL_FONT_THRESHOLD = 0.9;

Also applies to: 74-76


59-73: Verify accessibility implications of compounding font scales with absolute cap.

Line 73 compounds systemFontScale * fontSizeMultiplier, then caps the result at FONT_SCALE_LIMIT = 1.3. This means a user with system accessibility settings at 1.5× who also selects EXTRA_LARGE (1.2×) would expect 1.8× but only receives 1.3×, potentially undermining their accessibility needs.

If the cap is necessary to prevent layout breakage, consider:

  • Documenting this tradeoff
  • Using dynamic layouts that accommodate larger scales
  • Respecting system accessibility settings by applying the cap only to the custom multiplier

Based on learnings, accessibility is important to this codebase (iOS VoiceOver support). Confirm whether limiting users' chosen accessibility settings aligns with your accessibility goals.


62-70: LGTM: Storage listener implementation.

The storage change listener correctly updates customFontSize state and properly cleans up on unmount.


86-87: LGTM: scaleFontSize implementation.

Using fontScaleLimited prevents unbounded scaling and avoids compound scaling issues. The useCallback ensures stable function identity.


89-101: LGTM: Context value memoization.

All dependencies are correctly listed in the useMemo dependency array, preventing unnecessary re-renders.


109-120: LGTM: Defensive fallback context.

Provides sensible defaults when the context is unavailable, including a no-op scaleFontSize. Good defensive programming.

app/lib/methods/userPreferences.ts (2)

64-102: LGTM: Numeric storage with migration.

The implementation correctly:

  • Prefers native numeric storage for number defaults (lines 71-88)
  • Migrates legacy string-stored numbers to numeric storage (lines 77-88)
  • Falls back to string storage for non-numeric defaults (lines 89-102)
  • Handles JSON parsing for complex types

The type-based branching assumes keys are consistently used with the same default type, which is a reasonable constraint for user preference keys.


104-121: LGTM: setValue handles both storage types correctly.

The logic appropriately:

  • Clears both storages for numeric types (supporting migration cleanup, lines 106-109)
  • Clears only string storage for non-numeric types (line 109)
  • Stores numbers in native numeric storage and cleans up legacy string storage (lines 110-115)
  • Stores strings and JSON-serialized objects in string storage (lines 116-120)

The asymmetry in clearing is correct: numeric types might have old string representations from pre-migration storage, while non-numeric types never use numeric storage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (2)

41-49: Extract storage reading logic to reduce duplication.

The logic for reading and normalizing the font size from storage (lines 41-49) is duplicated in the storage listener (lines 54-62). Extract this into a helper function to improve maintainability.

♻️ Proposed refactor
+const readFontSizeFromStorage = (): number => {
+	const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
+	if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber)) {
+		return storedNumber;
+	}
+	const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
+	const parsed = storedString !== null ? Number(storedString) : undefined;
+	return !Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL;
+};
+
 const ResponsiveLayoutProvider = ({ children }: IResponsiveFontScaleProviderProps) => {
 	const { fontScale: systemFontScale, width, height } = useWindowDimensions();
-	const [customFontSize, setCustomFontSize] = useState(() => {
-		const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
-		if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber)) {
-			return storedNumber;
-		}
-		const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
-		const parsed = storedString !== null ? Number(storedString) : undefined;
-		return !Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL;
-	});
+	const [customFontSize, setCustomFontSize] = useState(readFontSizeFromStorage);
 	
 	useEffect(() => {
 		const listener = initializeStorage.addOnValueChangedListener((changedKey: string) => {
 			if (changedKey === FONT_SIZE_PREFERENCES_KEY) {
-				const newValue = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
-				if (typeof newValue === 'number' && !Number.isNaN(newValue)) {
-					setCustomFontSize(newValue);
-					return;
-				}
-				const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
-				const parsed = storedString !== null ? Number(storedString) : undefined;
-				setCustomFontSize(!Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL);
+				setCustomFontSize(readFontSizeFromStorage());
 			}
 		});
 		
 		return () => listener.remove();
 	}, []);

51-66: Add error handling for storage operations.

The storage listener and initialization code don't handle potential errors from userPreferences operations. If storage access fails, it could crash the provider during initialization or when preferences change.

🛡️ Proposed error handling
 const [customFontSize, setCustomFontSize] = useState(() => {
+	try {
 		const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
 		if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber)) {
 			return storedNumber;
 		}
 		const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
 		const parsed = storedString !== null ? Number(storedString) : undefined;
 		return !Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL;
+	} catch (error) {
+		console.warn('Failed to read font size preference:', error);
+		return FONT_SIZE_OPTIONS.NORMAL;
+	}
 });
 
 useEffect(() => {
 	const listener = initializeStorage.addOnValueChangedListener((changedKey: string) => {
 		if (changedKey === FONT_SIZE_PREFERENCES_KEY) {
+			try {
 				const newValue = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
 				if (typeof newValue === 'number' && !Number.isNaN(newValue)) {
 					setCustomFontSize(newValue);
 					return;
 				}
 				const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
 				const parsed = storedString !== null ? Number(storedString) : undefined;
 				setCustomFontSize(!Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL);
+			} catch (error) {
+				console.warn('Failed to read font size preference:', error);
+				setCustomFontSize(FONT_SIZE_OPTIONS.NORMAL);
+			}
 		}
 	});
 	
 	return () => listener.remove();
 }, []);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e0de1f3 and 4defe20.

📒 Files selected for processing (3)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
  • app/views/FontSizePickerView/index.tsx
  • app/views/UserPreferencesView/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/views/UserPreferencesView/index.tsx
  • app/views/FontSizePickerView/index.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (6)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (6)

15-15: LGTM: scaleFontSize addition to context.

The scaleFontSize function is a well-designed addition to the responsive layout context, enabling components to scale font sizes consistently based on user preferences.


27-29: Verify small font row heights are larger than normal.

The base row heights for small fonts (82, 68) are larger than normal row heights (75, 60). This seems counterintuitive—typically smaller fonts would allow for more compact layouts. Please confirm this is intentional (perhaps to maintain touch target sizes) and consider adding a comment explaining the rationale.


68-69: Verify font scale multiplication behavior with users.

The font scale calculation multiplies the system font scale by the user's font size preference (line 69). This means the two scales compound rather than the user preference overriding the system scale. For example, if a user has system font scale of 1.5 and selects "Large" (1.1), the result is 1.65x scaling.

Confirm this is the intended UX—users might expect their preference to replace rather than multiply the system setting.


82-96: LGTM: Well-structured memoization.

The scaleFontSize callback is properly memoized with useCallback, and the context value is correctly memoized with all dependencies. This ensures optimal performance and prevents unnecessary re-renders.


104-116: Defensive fallback is good practice.

The empty context check provides a safe fallback when the hook is used outside the provider. While the provider should always wrap consuming components, this defensive programming prevents potential crashes during development or refactoring.


29-29: SMALL_FONT_THRESHOLD is only used internally within this file and does not need to be exported.

The constant is defined and used only once more on line 70 within the same file. The value 0.9 that is used for font size decisions elsewhere in the codebase is already exported as part of FONT_SIZE_OPTIONS.SMALL. No external imports of SMALL_FONT_THRESHOLD exist.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 41-66: The font-size parsing logic duplicated in the useState
initializer and in the initializeStorage listener should be moved into a small
helper function (e.g., parseStoredFontSize) so both places call it; implement a
function that reads userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY) and
falls back to parsing userPreferences.getString(FONT_SIZE_PREFERENCES_KEY)
returning a valid number or FONT_SIZE_OPTIONS.NORMAL, then replace the inline
logic in the useState initialiser and inside the
initializeStorage.addOnValueChangedListener callback to call parseStoredFontSize
and pass its result to setCustomFontSize.
🧹 Nitpick comments (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (4)

27-36: Consider exporting SMALL_FONT_THRESHOLD for consistency.

Lines 27-28 export the new base row height constants, but line 29's SMALL_FONT_THRESHOLD is not exported. For consistency and potential reuse, consider exporting it:

-const SMALL_FONT_THRESHOLD = 0.9;
+export const SMALL_FONT_THRESHOLD = 0.9;

68-68: Redundant validation of customFontSize.

Since customFontSize is validated in both the initial state (lines 41-49) and the listener (lines 51-66), the validation at line 68 is redundant:

-	const fontSizeMultiplier = typeof customFontSize === 'number' && !Number.isNaN(customFontSize) ? customFontSize : 1.0;
+	const fontSizeMultiplier = customFontSize;

Alternatively, if you prefer defensive programming, this is acceptable but consider whether the state guarantees are sufficient.


102-113: Consider using FONT_SIZE_OPTIONS.NORMAL for consistency.

The fallback object uses hardcoded 1.0 for fontScale and fontScaleLimited. For consistency with the rest of the codebase, consider using FONT_SIZE_OPTIONS.NORMAL:

 	if (!context || Object.keys(context).length === 0) {
 		return {
-			fontScale: 1.0,
+			fontScale: FONT_SIZE_OPTIONS.NORMAL,
 			width: 0,
 			height: 0,
 			isLargeFontScale: false,
-			fontScaleLimited: 1.0,
+			fontScaleLimited: FONT_SIZE_OPTIONS.NORMAL,
 			rowHeight: BASE_ROW_HEIGHT,
 			rowHeightCondensed: BASE_ROW_HEIGHT_CONDENSED,
 			scaleFontSize: (size: number) => size
 		};
 	}

41-66: Add error handling for storage operations.

The storage operations (lines 42-48, 54-61) lack error handling. If userPreferences methods throw or initializeStorage.addOnValueChangedListener fails, the component will crash.

Consider wrapping storage operations in try-catch blocks:

🛡️ Add defensive error handling
 const [customFontSize, setCustomFontSize] = useState(() => {
+	try {
 		const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
 		if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber)) {
 			return storedNumber;
 		}
 		const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
 		const parsed = storedString !== null ? Number(storedString) : undefined;
 		return !Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL;
+	} catch (error) {
+		console.warn('Failed to read font size from storage:', error);
+		return FONT_SIZE_OPTIONS.NORMAL;
+	}
 });
 
 useEffect(() => {
+	try {
 		const listener = initializeStorage.addOnValueChangedListener((changedKey: string) => {
 			if (changedKey === FONT_SIZE_PREFERENCES_KEY) {
+				try {
 					const newValue = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
 					if (typeof newValue === 'number' && !Number.isNaN(newValue)) {
 						setCustomFontSize(newValue);
 						return;
 					}
 					const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
 					const parsed = storedString !== null ? Number(storedString) : undefined;
 					setCustomFontSize(!Number.isNaN(parsed ?? NaN) ? (parsed as number) : FONT_SIZE_OPTIONS.NORMAL);
+				} catch (error) {
+					console.warn('Failed to update font size from storage:', error);
+				}
 			}
 		});
 		
 		return () => listener.remove();
+	} catch (error) {
+		console.warn('Failed to initialize font size listener:', error);
+	}
 }, []);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4defe20 and d37bba9.

📒 Files selected for processing (1)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (6)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (6)

1-5: LGTM!

The additional React hooks and storage utilities are appropriately imported for the font size customization feature.


15-15: LGTM!

The scaleFontSize method signature correctly describes the font scaling utility.


70-78: LGTM!

The small-font detection and row height calculations correctly adjust base heights for better layout at small font scales and apply appropriate clamping.


80-80: LGTM!

The scaleFontSize callback is correctly memoized with the appropriate dependency.


82-94: LGTM!

The context value is properly memoized with all dependencies included.


69-69: The multiplicative font scaling behavior (combining systemFontScale from device accessibility with fontSizeMultiplier from app preferences) is intentional. The design keeps these sources separate—device setting vs. user preference—and combines them to allow both to apply. For example, if the user has system accessibility set to 1.5× and selects app preference LARGE (1.1×), the result of 1.65× correctly reflects both settings. The implementation includes appropriate safeguards: a cap at 1.3× to prevent excessive scaling and consistent application across all layout calculations.

@deepak0x deepak0x requested a review from diegolmello January 11, 2026 11:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (4)

29-29: Consider exporting SMALL_FONT_THRESHOLD for consistency.

The constant is used internally to determine when small-font layout adjustments apply. Exporting it would allow other components to reference the same threshold if they need font-scale-aware behavior.

📤 Proposed export
-const SMALL_FONT_THRESHOLD = 0.9;
+export const SMALL_FONT_THRESHOLD = 0.9;

63-63: Remove redundant validation.

Since getFontSizeFromStorage() already ensures a valid number is returned (with FONT_SIZE_OPTIONS.NORMAL as fallback), the NaN and type checks here are redundant.

♻️ Proposed simplification
-	const fontSizeMultiplier = typeof customFontSize === 'number' && !Number.isNaN(customFontSize) ? customFontSize : 1.0;
+	const fontSizeMultiplier = customFontSize;

65-71: Add comments to explain counterintuitive row height logic.

The code uses larger base row heights when fontScale < 0.9, which seems counterintuitive. This appears intentional—to prevent rows from becoming too cramped when fonts are very small—but lacks explanation. Future maintainers might misinterpret this as a bug.

💬 Proposed clarifying comments
+	// When fontScale is very small (< 0.9), use larger base heights to maintain minimum
+	// row spacing and prevent overly cramped layouts. The larger base partially compensates
+	// for the small scale factor (e.g., 82 * 0.85 ≈ 70, closer to normal 75 * 1.0).
 	const isSmallFont = fontScale < SMALL_FONT_THRESHOLD;
 	const baseRowHeight = isSmallFont ? BASE_ROW_HEIGHT_SMALL_FONT : BASE_ROW_HEIGHT;
 	const baseRowHeightCondensed = isSmallFont ? BASE_ROW_HEIGHT_CONDENSED_SMALL_FONT : BASE_ROW_HEIGHT_CONDENSED;
 	const scaledRowHeight = baseRowHeight * fontScale;
 	const scaledRowHeightCondensed = baseRowHeightCondensed * fontScale;
+	// Clamp to prevent excessive row heights when fontScale exceeds the limit
 	const rowHeight = Math.min(scaledRowHeight, baseRowHeight * FONT_SCALE_LIMIT);
 	const rowHeightCondensed = Math.min(scaledRowHeightCondensed, baseRowHeightCondensed * FONT_SCALE_LIMIT);

75-75: Consider whether scaleFontSize should apply bounds.

The function multiplies by fontScale without limits, while row heights use clamping via FONT_SCALE_LIMIT. This could produce extreme scaled values (e.g., fontScale = 2.0 → 200px from 100px input).

Verify whether unbounded scaling is intentional—allowing consumers to decide limits—or if scaleFontSize should use fontScaleLimited for consistency with row height calculations.

♻️ Alternative: Apply bounds in scaleFontSize

If bounding is desired:

-	const scaleFontSize = useCallback((size: number): number => size * fontScale, [fontScale]);
+	const scaleFontSize = useCallback((size: number): number => size * fontScaleLimited, [fontScaleLimited]);

Update dependencies in the context value memoization accordingly if you make this change.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d37bba9 and 9b3ce1d.

📒 Files selected for processing (1)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (3)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (3)

1-5: LGTM!

The new imports are properly used throughout the implementation for state management, memoization, and storage persistence.


97-108: LGTM!

The fallback provides safe defaults that match the interface shape, preventing crashes when the hook is called outside the provider context. The identity function for scaleFontSize is a sensible no-op default.


38-46: The dual-type handling is necessary due to storage serialization inconsistency.

The getFontSizeFromStorage function handles both number and string types because useUserPreferences internally serializes non-string types to JSON before storing them via MMKV. Components writing via useUserPreferences<number>() store JSON-serialized numbers, while the UserPreferences.getNumber() method expects native MMKV numbers. The fallback to getString() and manual parsing correctly handles both formats.

Note: UserPreferencesView incorrectly uses useUserPreferences<string>() for this numeric preference; it should use useUserPreferences<number>() for consistency, though this component only reads the value, so it doesn't cause write conflicts.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx:
- Around line 38-49: getFontSizeFromStorage currently only checks for >0 and
finiteness, allowing unreasonably large values; introduce a MAX_FONT_SIZE
constant (e.g., 48 or a value consistent with FONT_SIZE_OPTIONS) and validate
that both storedNumber and parsed are <= MAX_FONT_SIZE (or clamp them to
MAX_FONT_SIZE) before returning; update the checks in getFontSizeFromStorage to
enforce this upper bound and fall back to FONT_SIZE_OPTIONS.NORMAL if the value
is out of range, referencing FONT_SIZE_PREFERENCES_KEY and FONT_SIZE_OPTIONS in
the change.
- Around line 56-64: The effect that registers
initializeStorage.addOnValueChangedListener and calls setCustomFontSize is
unsafe for concurrent React because the listener closes over render state and
can cause tearing; refactor to use useSyncExternalStore: subscribe by returning
the listener remover that calls the provided callback only when changedKey ===
FONT_SIZE_PREFERENCES_KEY, provide getFontSizeFromStorage as the snapshot and
serverSnapshot selectors, and replace the local state/setCustomFontSize usage
with the value returned from useSyncExternalStore; if useSyncExternalStore isn’t
feasible, keep a ref of the latest font size (updated in an effect) and wrap
setCustomFontSize calls in startTransition inside the listener to avoid stale
closures and make updates non-blocking.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9b3ce1d and 7eb016a.

📒 Files selected for processing (1)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)

100-111: The fallback returns width: 0 and height: 0 when the context is unavailable, but this is an appropriate safety guard for an edge case that should never occur in production. The ResponsiveLayoutProvider is required at the application root and enforces the context throughout the component tree. When the hook is used normally, width and height come directly from React Native's useWindowDimensions(), which always returns actual device dimensions—never zero. The actual usage of these values (e.g., const portrait = height > width in RoomHeader) is safe with zero values and incurs no risk of division-by-zero errors. The jest setup deliberately omits width and height from the mock, confirming they are not critical to core functionality. No changes needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (2)

38-50: Consider aligning MAX_FONT_SIZE with FONT_SIZE_OPTIONS or documenting the discrepancy.

MAX_FONT_SIZE (5.0) is significantly higher than FONT_SIZE_OPTIONS.EXTRA_LARGE (1.2). While this provides a defensive upper bound against corrupted storage values, it allows persisted values outside the UI's selectable range (0.9–1.2). If a user somehow stores 3.0, it would be accepted as valid.

Consider either:

  1. Lowering MAX_FONT_SIZE closer to the actual max option (e.g., 1.5 with some buffer), or
  2. Adding a comment explaining the defensive reasoning.

Also, the as number cast on line 47 is redundant since parsed is already validated as a number.

Suggested improvement
 const getFontSizeFromStorage = (): number => {
-	const MAX_FONT_SIZE = 5.0;
+	// Defensive upper bound - actual UI options max at 1.2 (EXTRA_LARGE)
+	const MAX_FONT_SIZE = 1.5;
 	const storedNumber = userPreferences.getNumber(FONT_SIZE_PREFERENCES_KEY);
 	if (typeof storedNumber === 'number' && !Number.isNaN(storedNumber) && Number.isFinite(storedNumber) && storedNumber > 0 && storedNumber <= MAX_FONT_SIZE) {
 		return storedNumber;
 	}
 	const storedString = userPreferences.getString(FONT_SIZE_PREFERENCES_KEY);
 	const parsed = storedString !== null ? Number(storedString) : undefined;
 	if (parsed !== undefined && !Number.isNaN(parsed) && Number.isFinite(parsed) && parsed > 0 && parsed <= MAX_FONT_SIZE) {
-		return parsed as number;
+		return parsed;
 	}
 	return FONT_SIZE_OPTIONS.NORMAL;
 };

103-114: Consider memoizing the fallback object to avoid referential instability.

When the context is unavailable, a new fallback object (including a new scaleFontSize function) is created on every render. This could cause unnecessary re-renders in consumers that rely on referential equality.

This is only an issue if components use useResponsiveLayout outside the provider, which should be rare. If this edge case is expected to occur, consider extracting the fallback to a module-level constant.

Optional: Extract fallback to module scope
+const DEFAULT_LAYOUT_CONTEXT: IResponsiveLayoutContextData = {
+	fontScale: 1.0,
+	width: 0,
+	height: 0,
+	isLargeFontScale: false,
+	fontScaleLimited: 1.0,
+	rowHeight: BASE_ROW_HEIGHT,
+	rowHeightCondensed: BASE_ROW_HEIGHT_CONDENSED,
+	scaleFontSize: (size: number) => size
+};
+
 export const useResponsiveLayout = () => {
 	const context = useContext(ResponsiveLayoutContext);
 
 	if (!context || Object.keys(context).length === 0) {
-		return {
-			fontScale: 1.0,
-			width: 0,
-			height: 0,
-			isLargeFontScale: false,
-			fontScaleLimited: 1.0,
-			rowHeight: BASE_ROW_HEIGHT,
-			rowHeightCondensed: BASE_ROW_HEIGHT_CONDENSED,
-			scaleFontSize: (size: number) => size
-		};
+		return DEFAULT_LAYOUT_CONTEXT;
 	}
 
 	return context;
 };
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7eb016a and 6827e90.

📒 Files selected for processing (1)
  • app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx
🧬 Code graph analysis (1)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (1)
app/lib/constants/keys.ts (1)
  • FONT_SIZE_PREFERENCES_KEY (23-23)
🔇 Additional comments (4)
app/lib/hooks/useResponsiveLayout/useResponsiveLayout.tsx (4)

29-29: SMALL_FONT_THRESHOLD is not exported.

The AI summary indicates this constant should be exposed, but it's declared without export. If it's needed by other components (e.g., for conditional styling based on small font detection), consider exporting it. If it's intentionally internal-only, this is fine as-is.


81-81: scaleFontSize uses unbounded fontScale — verify this is intentional.

scaleFontSize multiplies by the uncapped fontScale (which could be systemFontScale * fontSizeMultiplier, potentially reaching 2.0+ for users with accessibility settings). Meanwhile, fontScaleLimited caps at 1.3 for row heights.

If this is intentional for accessibility compliance (allowing text to scale fully), this is correct. If you want to cap scaled font sizes similar to row heights, consider using fontScaleLimited instead.


52-98: Well-structured implementation using modern React patterns.

Good use of:

  • useSyncExternalStore for subscribing to storage changes — this is the correct React 18+/19 pattern for external stores.
  • useCallback for scaleFontSize to maintain stable reference.
  • useMemo for contextValue to prevent unnecessary consumer re-renders.

The row height clamping logic with FONT_SCALE_LIMIT appropriately prevents layout overflow while still allowing font accessibility scaling.


56-67: The code correctly uses the MMKV API. The initializeStorage.addOnValueChangedListener() method from react-native-mmkv v3.3.3 returns an object with a .remove() method, and the cleanup function at line 65 properly invokes it. The useSyncExternalStore subscription pattern is implemented correctly.

@deepak0x
Copy link
Author

@diegolmello you can check this
all reported issues fixed...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants