feat(Android, Stack v5): toolbar menu item icon and icon tint color#4105
feat(Android, Stack v5): toolbar menu item icon and icon tint color#4105kligarski wants to merge 18 commits into
Conversation
92b5302 to
8105b22
Compare
There was a problem hiding this comment.
Pull request overview
Adds Android Stack v5 support for toolbar menu item icons (drawable resources or image sources) and per-state icon tinting, including correct “no change” vs “reset to default” semantics for view-command updates. This integrates new JS-to-native props, native icon/tint application in MaterialToolbar, and a Single Feature Test scenario to validate behavior.
Changes:
- Extend Stack v5 toolbar menu item API with
iconandiconTintColor*props, and plumb them through Fabric + view-command update paths. - Introduce native update/value wrappers (
StackHeaderToolbarUpdate) and icon resolution infrastructure (IconResolver, icon source maps) to support async image loading and diffing. - Add a new Android SFT scenario for toolbar menu icons/tints and wire it into the stack-v5 scenario group.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.ts | Adds native prop fields for menu item icon sources and processed tint colors. |
| src/components/tabs/screen/TabsScreen.android.tsx | Switches tabs icon parsing to use shared parseIconToNativeProps. |
| src/components/shared/index.ts | Adds shared parseIconToNativeProps helper (moved from tabs). |
| src/components/gamma/stack/header/StackHeaderConfig.android.types.ts | Extends public TS types/docs with menu item icon and iconTintColor* props. |
| src/components/gamma/stack/header/StackHeaderConfig.android.tsx | Converts toolbar menu item icon/tint props to native props; updates view-command option serialization. |
| apps/src/tests/single-feature-tests/tabs/test-tabs-item-title-ios/index.tsx | Formatting/line wrapping updates in an iOS SFT description component. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-toolbar-menu-icon-android/scenario.md | New manual test scenario documenting expected behavior for icon + tint props/commands. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-toolbar-menu-icon-android/scenario-descriptions.ts | Registers the new SFT scenario metadata. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-toolbar-menu-icon-android/index.tsx | Implements the interactive SFT screen for icon/tint prop + command flows. |
| apps/src/tests/single-feature-tests/stack-v5/index.ts | Adds the new scenario to the stack-v5 scenario group. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/toolbar/StackHeaderToolbarUpdate.kt | Adds reset-vs-set wrapper to distinguish “reset default” from “no change”. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/toolbar/StackHeaderToolbarMenuItemOptions.kt | Extends menu item option updates to include icon + per-state tint updates. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/toolbar/StackHeaderToolbarMenuItemIconSource.kt | Adds a compact icon-source model (drawable name / image uri). |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/toolbar/StackHeaderToolbarMenuItemDefaults.kt | Adds defaults for icon/tint-related fields. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/toolbar/StackHeaderToolbarMenuItemConfig.kt | Extends config model with resolved icon drawable and tint colors. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/toolbar/StackHeaderToolbarMenuCoordinator.kt | Applies icon + tint updates to toolbar MenuItems and resizes drawables to 24dp. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/StackHeaderCoordinator.kt | Updates coordinator call signature for menu item updates. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/config/StackHeaderConfigViewManager.kt | Parses new icon/tint props/options and stages icon sources for async resolution. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/config/StackHeaderConfig.kt | Adds icon resolution for back button + toolbar items and atomic command application. |
| android/src/main/java/com/swmansion/rnscreens/gamma/helpers/IconResolver.kt | Adds reusable icon resolver for drawable-name vs image-uri sources with diffing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (drawableIconResourceName == lastEmittedDrawableName && | ||
| imageIconUri == lastEmittedImageUri | ||
| ) { | ||
| onResult(IconResolution.Unchanged) | ||
| return | ||
| } | ||
| lastEmittedDrawableName = drawableIconResourceName | ||
| lastEmittedImageUri = imageIconUri | ||
| when { | ||
| drawableIconResourceName != null -> | ||
| onResult(IconResolution.Resolved(getSystemDrawableResource(context, drawableIconResourceName))) | ||
| imageIconUri != null -> | ||
| loadImage(context, imageIconUri) { drawable -> | ||
| if (imageIconUri == lastImageUri && lastDrawableName == null) { | ||
| onResult(IconResolution.Resolved(drawable)) | ||
| } | ||
| } | ||
| else -> onResult(IconResolution.Resolved(null)) |
There was a problem hiding this comment.
I don't think this is an issue now. Let me know if you think we should add it now.
There was a problem hiding this comment.
at least we should note that down for further work, but this PR contains many changes, and as the initial version, I'm okay with merging it in current shape
| return drawable | ||
| .toBitmap(width = targetWidthPx, height = targetHeightPx) | ||
| .toDrawable(toolbar.resources) |
There was a problem hiding this comment.
This doesn't resize drawable resources correctly. It will be fixed in follow-up PR with resizing & tint for back button.
| // Helpers for view commands: | ||
| // - not defined -> null (means "no update") | ||
| // - null -> default (means "reset to default value") | ||
| private fun ReadableMap.readColor( |
There was a problem hiding this comment.
wondering whether we shouldn't have a general helper file with all extensions rather than private parsers in the manager's source code (I did the same for TabsScreenManager) , unrelated to this PR, but do you think we should add a ticket to unify the approach or at least have a single file definitions for prop parsing?
| * @summary Specifies the icon for the menu item. | ||
| * | ||
| * Supported values: | ||
| * - `{ type: 'imageSource', imageSource }` |
There was a problem hiding this comment.
nit: shouldn't we mention what type imageSource is?
| import { Image, type ImageResolvedAssetSource } from 'react-native'; | ||
| import type { PlatformIconAndroid } from '../../types'; | ||
|
|
||
| export function parseIconToNativeProps(icon: PlatformIconAndroid | undefined): { |
There was a problem hiding this comment.
nit: parseAndroidIconToNativeProps
| if (drawableIconResourceName == lastEmittedDrawableName && | ||
| imageIconUri == lastEmittedImageUri | ||
| ) { | ||
| onResult(IconResolution.Unchanged) | ||
| return | ||
| } | ||
| lastEmittedDrawableName = drawableIconResourceName | ||
| lastEmittedImageUri = imageIconUri | ||
| when { | ||
| drawableIconResourceName != null -> | ||
| onResult(IconResolution.Resolved(getSystemDrawableResource(context, drawableIconResourceName))) | ||
| imageIconUri != null -> | ||
| loadImage(context, imageIconUri) { drawable -> | ||
| if (imageIconUri == lastImageUri && lastDrawableName == null) { | ||
| onResult(IconResolution.Resolved(drawable)) | ||
| } | ||
| } | ||
| else -> onResult(IconResolution.Resolved(null)) |
There was a problem hiding this comment.
at least we should note that down for further work, but this PR contains many changes, and as the initial version, I'm okay with merging it in current shape
Description
Adds support for icons and icon tinting for toolbar menu items in Stack v5 on Android.
Closes https://github.com/software-mansion/react-native-screens-labs/issues/1207.
Details
normalleads to transparent icon - that's platform limitation. If you specify any tint, we can't use original appearance of the icon. You must specifynormalif you want to apply any tint color.Ctrl+Tab- this will move the focus to the toolbar.Changes
parseIconToNativePropstocomponents/sharedparseColor/resolveAssetSource)StackHeaderToolbarUpdateto handle props wherenullis the default value and "no change" needs different representation from "reset to default"IconResolverto handle icon-related logic (diffing, handling async updates)Before & after - visual documentation
toolbar_menu_icons.mp4
Test plan
Run
test-stack-toolbar-menu-icon-android. Check for regressions intest-stack-subviews-androidandtest-stack-back-button-android.Checklist