feat(Android, Stack v5): add title/subtitle font customization - #4415
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Android Stack v5 (Material 3) header support for customizing title/subtitle text appearance (color, size, family, weight, style) for both small headers and expanded/collapsed states in medium/large collapsing headers, wiring new JS props through Fabric to native applicators and providing a manual SFT scenario to validate behavior.
Changes:
- Exposes new
androidprops for title/subtitle appearance (small + expanded/collapsed variants) and stringifiesfontWeighton the JS side for the native setter contract. - Implements Android-side appearance application with a new
TITLE_APPEARANCEinvalidation flag, default text-appearance reset via theme attrs, and typeface transforms viaReactTypefaceUtils. - Adds utilities (
resolveStyleResAttr,View.spToPx) and a new single-feature-test scenario for manual verification.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/fabric/stack/StackHeaderConfigAndroidNativeComponent.ts | Adds new native props for title/subtitle appearance (small + expanded/collapsed variants). |
| src/components/stack/header/StackHeaderConfig.android.types.ts | Updates public TS types/docs to expose new Android header appearance props. |
| src/components/stack/header/StackHeaderConfig.android.tsx | Converts fontWeight values to strings before passing to native. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-header-title-appearance-android/scenario.md | Adds manual test scenario documentation for verifying appearance behavior. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-header-title-appearance-android/scenario-description.ts | Registers scenario metadata for the new SFT. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-header-title-appearance-android/index.tsx | Implements the interactive SFT UI to exercise all appearance props. |
| apps/src/tests/single-feature-tests/stack-v5/index.ts | Exports/registers the new scenario in the Stack v5 SFT group. |
| android/src/main/java/com/swmansion/rnscreens/utils/StyleUtils.kt | Adds helper to resolve theme style attributes to concrete style resource IDs. |
| android/src/main/java/com/swmansion/rnscreens/utils/DimensionUtils.kt | Adds View.spToPx for font-size conversion respecting view display metrics/font scale. |
| android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderCoordinatorLayout.kt | Hooks new TITLE_APPEARANCE invalidation into the update pipeline. |
| android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderApplicator.kt | Applies title/subtitle appearance (reset-to-default + overrides) for small and collapsing headers. |
| android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderInvalidationFlags.kt | Introduces TITLE_APPEARANCE flag and includes it in APPEARANCE. |
| android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigViewManager.kt | Adds setters for all new appearance props, parsing weight/style via ReactTypefaceUtils. |
| android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigurationProviding.kt | Extends config contract with appearance-related values and TypefaceTransform. |
| android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfig.kt | Stores new appearance props, builds TypefaceTransforms, and adds invalidatingProperty helper. |
| android/src/main/java/com/swmansion/rnscreens/stack/header/appbar/StackHeaderAppBarLayout.kt | Forces creation of stable title/subtitle TextView references for small header toolbars. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| toolbar.title = FORCE_CREATE_PLACEHOLDER | ||
| val title = toolbar.toolbarTextViews().single() | ||
| toolbar.subtitle = FORCE_CREATE_PLACEHOLDER | ||
| val subtitle = toolbar.toolbarTextViews().first { it !== title } |
There was a problem hiding this comment.
if we're clearing the text anyway, won't it work to have two distinct placeholders rather than these checks ?
| internal var titleFontWeight: Int by invalidatingProperty(ReactConstants.UNSET, StackHeaderInvalidationFlags.TITLE_APPEARANCE) | ||
| internal var titleFontStyle: Int by invalidatingProperty(ReactConstants.UNSET, StackHeaderInvalidationFlags.TITLE_APPEARANCE) | ||
| override val titleTypefaceTransform: TypefaceTransform? | ||
| get() = buildTypefaceTransform(titleFontFamily, titleFontWeight, titleFontStyle) |
There was a problem hiding this comment.
won't it allocate a new lambda object on every call?
| override var subtitleColor: Int? by invalidatingProperty(null, StackHeaderInvalidationFlags.TITLE_APPEARANCE) | ||
| internal set | ||
| override var subtitleFontSize: Float? by invalidatingProperty(null, StackHeaderInvalidationFlags.TITLE_APPEARANCE) | ||
| internal set | ||
| internal var subtitleFontFamily: String? by invalidatingProperty(null, StackHeaderInvalidationFlags.TITLE_APPEARANCE) | ||
| internal var subtitleFontWeight: Int by invalidatingProperty(ReactConstants.UNSET, StackHeaderInvalidationFlags.TITLE_APPEARANCE) | ||
| internal var subtitleFontStyle: Int by invalidatingProperty(ReactConstants.UNSET, StackHeaderInvalidationFlags.TITLE_APPEARANCE) | ||
| override val subtitleTypefaceTransform: TypefaceTransform? | ||
| get() = buildTypefaceTransform(subtitleFontFamily, subtitleFontWeight, subtitleFontStyle) |
There was a problem hiding this comment.
Seems to be the same set of props as for the title - shouldn't we consider some grouping inside a dedicated object and operate on e.g. title/subtitleAppearance objects on the native side?
|
|
||
| titleColor?: ColorValue | undefined; | ||
| titleFontFamily?: string | undefined; | ||
| titleFontSize?: CT.WithDefault<CT.Float, -1>; |
There was a problem hiding this comment.
nit: codegen handles that default properly, but in other places we're using CT.WithDefault<CT.Float, -1.0>;
Description
Adds support for customizing text appearance for title and subtitle in header.
Closes https://github.com/software-mansion/react-native-screens-labs/issues/909.
Closes https://github.com/software-mansion/react-native-screens-labs/issues/910.
Typeface resolution
In order to keep separation between React and native side, I decided to handle the font as follows:
Applicatorresets the font to Material's default.Applicatorasks theConfigurationProvidingto apply any changes to the default font viatitleTypefaceTransformfunction.HeaderConfigwhich implementsConfigurationProvidingusesReactTypefaceUtils.applyStylesto modify the font family, style and weight and returns modifiedTypefaceApplicatorapplies the returnedTypefaceChanges
smallheader:{title,subtitle}{Color,FontFamily,FontSize,FontWeight,FontStyle}medium/largeheader:{collapsed,Expanded}{Title,Subtitle}{Color,FontFamily,FontSize,FontWeight,FontStyle}TITLE_APPEARANCEinvalidation flag toStackHeaderConfigStyleUtils.resolveStyleResAttrto resolve default text appearanceView.spToPxextension that allows to convert SP to pixels with respect to view's display metricsinvalidatingPropertyutil toStackHeaderConfigfor props that invalidate a flag on diffBefore & after - visual documentation
small.mp4
large.mp4
Test plan
Run
test-stack-header-title-appearance-android.Checklist