Repository navigation
refactor(Android, Tabs): bit-flag container invalidation flags, re-apply only invalidated tab bar parts - #5011
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
5a1d5e0 to
8ba2f20
Compare
ac9dc4e to
8bce222
Compare
8ba2f20 to
92b11d3
Compare
0fa6b66 to
9d8597d
Compare
92b11d3 to
5e2f94f
Compare
9d8597d to
a5d4810
Compare
5e2f94f to
181baba
Compare
f4283d0 to
4ff4e0f
Compare
… invalidation flags TabsContainerInvalidationFlags was a class of four mutable booleans. It is now a value class over bit flags, like StackHeaderInvalidationFlags and TabsItemInvalidationFlags, in its own file. TabsContainer keeps the pending flags in a private var, raises them with invalidate(flags), which also requests an update from the ContainerUpdateScheduler, or with recordInvalidation(flags) where no request is made (the constructor and invalidations raised during an update), and clears what each update phase applies with takeInvalidationFlags(mask). No behaviour change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3v2mWCBgU7pKxJjUc3sMB
…ab bar Any bar-wide change (tab selection, tabBarHidden, an icon size, the selected tab's appearance, the colour scheme) ran the same whole-bar pass: icon box, visibility, shared appearance, badges and accessibility of every menu item, and fonts. The single NAVIGATION_MENU_APPEARANCE flag is split into the parts that pass applied, and TabsContainer.updateTabBarIfNeeded applies each one only when its flag is set, like StackHeaderCoordinatorLayout.processUpdate: - TAB_BAR_VISIBILITY: tabBarHidden only toggles visibility. - ICON_BOX: icon / active indicator size changes and tab set changes re-resolve the shared icon box; the changed menu item applies its own icon and indicator. - SELECTED_APPEARANCE: a selection change or an appearance change of the selected tab compares the selected appearance with the one the bar shows and re-applies only the parts that differ: SHARED_APPEARANCE (bar colours, label visibility mode, ripple, active indicator), BADGE_APPEARANCE (badge colours of every item) and TITLE_FONTS. Switching between tabs that look the same applies nothing bar-wide. - The colour scheme re-applies only THEME (shared appearance and badge colours), the parts with theme colour defaults. Accessibility is no longer re-applied to every menu item on each bar pass: Material replaces item views only when the menu is rebuilt, and a rebuild resets the menu item states (fully dirty). A rebuild also invalidates the fonts of the new item views. Adding or removing a tab no longer invalidates everything, only the menu structure and the icon box. Size and appearance changes raised while the container is detached are now recorded and flushed on attach, instead of relying on the attach-time colour scheme callback re-applying the whole bar. Nothing is applied before the first tab is selected or while the menu is out of date; the flags stay pending. Every container invalidation from outside an update now goes through invalidate(), which requests the update. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3v2mWCBgU7pKxJjUc3sMB
…le resizes them Material masks an item's ripple with the item's active indicator drawable, so laying out the ripple sets that drawable's bounds to the icon container. The active indicator view resets its background's bounds only when its own size changes. With an icon box larger than a tab's active indicator, the indicator was then redrawn as tall as the icon box. The whole-bar pass hid this by re-creating the indicator drawables on every selection and appearance update. Since the tab bar re-applies only the parts that changed, the stale bounds showed on mount and after any relayout of the bar. TabsBottomNavigationView now restores each indicator's bounds before every draw and redraws the indicator when they had drifted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3v2mWCBgU7pKxJjUc3sMB
826ccbd to
fc108a1
Compare
|
Android e2e on Two attempts on the same commit failed in two different places:
So every Android e2e file has passed on No fix exists for either problem yet. For the timeout, I'm proposing this patch rather than adding it here, since it's CI config. Green runs already take 55 to 59 minutes (for example this run on --- a/.github/workflows/android-e2e-test-fabric.yml
+++ b/.github/workflows/android-e2e-test-fabric.yml
@@ jobs:
test:
runs-on: ubuntu-latest
- timeout-minutes: 60
+ timeout-minutes: 75The launch disconnect needs its own investigation. The uploaded artifact holds only screenshots, so the app's log isn't available from CI. I've used my one re-run, so I won't trigger another. Generated by Claude Code |
Requested by Kacper · project thread
Description
RFC-1823 A6 follow-up. Two steps, one commit each, plus a fix that the device run surfaced.
TabsContainerInvalidationFlagswas a class of four mutable booleans. It is now a@JvmInlinevalue class over bit flags, the same shape asStackHeaderInvalidationFlagsandTabsItemInvalidationFlags. No behaviour change.tabBarHidden, an icon size, the selected tab's appearance, the colour scheme) ran the same whole-bar pass: icon box, visibility, shared appearance, badges and accessibility of every menu item, then fonts. The single appearance flag is now split into the parts that pass applied, andTabsContainer.updateTabBarIfNeededapplies each part only when its flag is set, likeStackHeaderCoordinatorLayout.processUpdate.Closes https://github.com/software-mansion/react-native-screens-labs/issues/1976.
Stacked on #4976 at
4ff4e0f(rebased ontomainafter #4975 was merged as95bb9d2), including itsContainerUpdateSchedulercommits. This PR changes what an update applies; #4976 decides when it runs.Changes
Step 1 (
3cd2f4d)container/TabsContainerInvalidationFlags.kt:NONE,SELECTED_TAB,NAVIGATION_MENU_STRUCTURE,NAVIGATION_MENU_APPEARANCE,MENU_ITEMS,ALL, plusor,and,without,containsAnyandisEmpty.andandwithoutare new compared with the other two classes: the container clears flags per update phase.TabsContainerkeeps the flags in aprivate var(it wasinternal val, with no users outside the class).invalidate(flags)records them and requests an update from the scheduler;recordInvalidation(flags)only records, for the constructor and for invalidations raised by an update that applies them in the same pass.takeInvalidationFlags(mask)clears what each phase applies.Step 2 (
aac9216)tabBarItemIconSize/ active indicator size of any tabtabBarHiddenTAB_BAR_VISIBILITY,ICON_BOX,SELECTED_APPEARANCE,SHARED_APPEARANCE,BADGE_APPEARANCE,TITLE_FONTS; groupsTHEME,BAR_WIDE,TAB_BAR.SELECTED_APPEARANCEcompares the selected tab's appearance withappliedAppearance(what the bar shows) throughhasSameSharedAppearanceAs/hasSameBadgeAppearanceAs/hasSameTitleFontAs(TabsAppearanceModel.kt), which splitTabsAppearance's fields by where they are applied. Icon size and indicator size stay per tab.TabsAppearanceCoordinator.updateBarAppearanceis split intoupdateIconBox,updateTabBarVisibilityandupdateSharedAppearance; the menu items read the applied icon box (iconBoxDp).TabsAppearanceApplicator.updateSharedAppearanceno longer sets visibility (applyTabBarVisibility).updateTabBarIfNeededlooks items up withbottomNavigationView.requireMenuItem(fragment.menuItemId).getMenuItemForItemis removed: its only caller (updateMenuItem) is folded into the tab bar pass. The comments ononItemInvalidatedandTabsMenuItemState.consumePendingFlagsnow name the tab bar update instead of the whole-bar one.NavigationBarMenuView.buildMenuView, reached only from ourmenu.clear()/menu.add()), and our rebuild resets the menu item states to fully dirty. The content description lives on theMenuItem, whichinitialize()re-reads.selectedTab, and a colour-scheme pass between adding a tab and the menu rebuild threw on the missingMenuItem.)invalidate(), so it requests an update;onItemInvalidatedno longer requests one at its end.Fix: active indicator bounds (
fc108a1)NavigationBarItemView.refreshItemBackground), so laying out the ripple sets that drawable's bounds to the icon container. The indicator view resets its background's bounds only when its own size changes.setItemActiveIndicatorColor). Step 2 stopped doing that, so the stale bounds showed on mount and after any relayout.TabsBottomNavigationViewnow restores each indicator's bounds before every draw (a pre-draw listener while attached) and redraws the indicator view when they had drifted. The listener runs on every frame of the window: onefindViewByIdand a bounds comparison per menu item.Left out (suggestions)
updateNavigationMenuStructureclears and re-adds every menu item, and Material rebuilds all item views on eachadd(n + 1 rebuilds), dropping every item's state. Diffing the menu and suspending presenter updates would need restrictedMenuBuilderAPIs; tab sets rarely change at runtime.ColorSchemeCoordinator.setup, whose callback then re-applies the theme parts under the resolved theme: two passes for those parts. Setting up the colour scheme first would make it one, but changes when nested containers resolve their scheme. The callback also still applies synchronously inside a batch.SHARED_APPEARANCEre-applies colours, label mode, ripple and indicator together. Material already skips an unchanged label visibility mode, and the colour setters are cheap.refreshItemBackgroundrebuilds it.tabBarItemRippleColor,updateSharedAppearanceresolvesR.attr.itemRippleColoras a theme attribute. It is aBottomNavigationViewstyleable attribute, so it falls back to transparent and the default tab bar has no visible touch ripple (logcat: "Failed to resolve color attribute. Falling back to 0."). Same onmain; worth its own fix.Test plan
ktlint1.3.0 (the versionspotless.gradlepins) onandroid/src/**/*.ktandnode scripts/check-android-layers.js: pass (cloud, onfc108a1).claude/project-thread-4pvvbj.826ccbd(the same three commits before perf(Android, Tabs): apply tab configuration changes at the end of the React transaction #4976 moved ontomain;git range-diffshows them unchanged, and the tree differs only bymain's e2e test changes from refactor(test): make example e2e tests work on iOS 27 #4801): Test Android build on Fabric, Lint and Test Android e2e - new architecture (run) passed.fc108a1: build and lint passed. Android e2e (run) is red in places this diff doesn't reach: an app-launch disconnect in the RTL layout-direction suite, then on re-runTest658(no Tabs) and the job's 60-minute cap. Every file passed in one of the two attempts. Details in this comment.8ba2f20(step 2 before the rebases; it differs from step 2 now only by the base's menu item lookup, visibility and comment changes): Detox 69 passed, 1 IME-insets timing flake (passed 5/5 twice alone). The pill regression was found and bisected here: base and step 1 fine, step 2 not.826ccbd: Detox (12 Android tabs files, plus bottomTabs and nativeBottomTabs) 70 passed, 0 failed, 28 skipped (iOS-only describes); logcat crash buffer empty.On a device (
826ccbd):test-tabs-appearance-defined-by-selected-tab: colours (sampled from the framebuffer), label mode, badge colours and fonts follow the selected tab on every switch, by tap and programmatically; 18 rapid mixed switches end in the right state.test-tabs-item-icon-tint-and-size: drawn pill equals the indicator view at about 50 points (on mount, every tab switch, runtime icon size 32 / 56 / default, indicator 96x64 / auto, JS picker open / close, background / foreground, light / dark). Sizes: Sized 71x52, Multicolor 70x38, Indicator 56x36, Image 64x32, Controls 64x32. With a 64dp icon box every tab keeps its own pill and the bar height grows and returns.test-tabs-general-appearance-android:tabBarHiddentoggles; testIDs and content descriptions are stable across tab switches and all label modes. The explicit-colour ripple on the Indicator tab is a pill exactly over the indicator, selected or not.TabsHostscheme, 20 states; badge colours follow the theme unless set explicitly.Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01P3v2mWCBgU7pKxJjUc3sMB