Repository navigation
Add liquid glass bottom tab bar to iOS, Android and mWeb #101339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sumo-slonik
wants to merge
76
commits into
Expensify:main
Choose a base branch
from
software-mansion-labs:feat/liquid-glass-tab-bar
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,155
−292
Open
Changes from all commits
Commits
Show all changes
76 commits
Select commit
Hold shift + click to select a range
d7c11c8
feat: liquid glass tab bar owned by the tab navigator
sumo-slonik e4b2f69
Merge branch 'main' into feat/liquid-glass-tab-bar
sumo-slonik 2c15f8a
feat: native bottom tabs on iOS and Android, floating capsule on mWeb
sumo-slonik 39d637e
fix: floating tab bar inset on the two remaining tab roots
sumo-slonik 08e28cd
chore: bump react-navigation for the native tabs contract
sumo-slonik f330dda
fix: reveal the tab bar when the pop starts, not when it commits
sumo-slonik 6e868e3
fix: tint native tab bar icons and mount tab screens eagerly
sumo-slonik 8760e96
fix: keep each native tab's badge color with that tab on iOS 26
sumo-slonik 5eab7d4
revert: drop the iOS 26 tab bar color patch, measurements show it has…
sumo-slonik 4c2aa31
fix: draw tab status dots into the icons and fade the floating button…
sumo-slonik 0c8fac9
fix: let UIKit animate hiding the native tab bar instead of blinking it
sumo-slonik 9d243e9
fix: match the floating button fade to the system tab bar animation
sumo-slonik fc40c0f
Merge branch 'main' into feat/liquid-glass-tab-bar
sumo-slonik 022c63a
fix: drop the beta gate the merge left in the inner insights component
sumo-slonik b3dd3d5
fix: draw the Android tab bar like the iOS one
sumo-slonik e09d3fd
fix: restore tab content after an opaque push
sumo-slonik 67db700
fix: show skeleton rows while Search loads
sumo-slonik a3b1482
feat: floating tab bar on Android, painted labels on iOS
sumo-slonik 8ce0b8f
Merge branch 'main' into feat/liquid-glass-tab-bar
sumo-slonik 713268a
fix: remove dead tab bar footer slot, android patches, style duplicates
sumo-slonik d2f8b24
Merge remote-tracking branch 'upstream/main' into feat/liquid-glass-t…
sumo-slonik 0415ee9
fix: fold tab bar surface style into the container, correct stale com…
sumo-slonik 877e3e7
feat: scale down floating tab bar icons and blur what the bar covers
sumo-slonik 69a666e
feat: blur the focused tab scene under the Android tab bar
sumo-slonik 7adbdd0
feat: native Material tab bar on Android, flat floating bar on mobile…
sumo-slonik fcb90fe
Merge upstream/main into feat/liquid-glass-tab-bar
sumo-slonik 4f891cc
refactor: share native tab navigator code, lay out the mWeb bar via b…
sumo-slonik aab4316
fix: start tab navigation spans from native tab bar presses
sumo-slonik 2782a22
fix: larger iOS native tab icons, softer Android tab pill
sumo-slonik 44c3fb4
feat: Insights tab on the native bar with the beta, Account avatar on…
sumo-slonik c81bbef
clearing up pr
sumo-slonik 8e215cb
clearing up pr
sumo-slonik 8d5dfff
fix: restore Search skeleton rows dropped in the PR cleanup
sumo-slonik 355459f
Merge remote-tracking branch 'upstream/main' into feat/liquid-glass-t…
sumo-slonik 1ac76e6
fix: tint Android tab icons in release builds, even pill inset on mWeb
sumo-slonik 4f32e80
fix: keep the avatar on the selected Android Account tab
sumo-slonik c2c6539
fix: run iOS tab root content under the glass tab bar, inset list end…
sumo-slonik 0becb2a
fix: keep Android tab root content above the Material tab bar
sumo-slonik f4b79ce
feat: Android tab bar on appBG with a soft top shadow instead of a to…
sumo-slonik 75f34ea
feat: float the mWeb tab bar over tab root content, inset list ends p…
sumo-slonik db655b4
fix: draw iOS tab glyphs as Skia vector paths so resized icons stay s…
sumo-slonik 124600e
fix: bold the selected Android tab label and tighten label letter spa…
sumo-slonik 92918cf
perf: freeze background native tabs, which have no freezeOnBlur
sumo-slonik a30432a
perf: give tab bars the Inbox status alone, so LHN updates do not re-…
sumo-slonik 46fe6f7
perf: draw native tab icons synchronously from vector glyphs on both …
sumo-slonik d8ab0f4
refactor: one tab root scroll props hook, platform tab bar insets wit…
sumo-slonik 5f852c0
refactor: one rule for the tab the native bar has no item for
sumo-slonik c46d048
chore: merge the two Android tab label patches for react-native-scree…
sumo-slonik 901bff3
refactor: fade the Android tab bar shadow and the floating buttons in…
sumo-slonik 29c2652
revert: move the Search skeleton rows change to its own branch, fix/s…
sumo-slonik 4d63259
chore: relative import for the tab without a bar item rule
sumo-slonik 4a3e8d1
perf: no icon draws for the tab without a bar item, gesture effect re…
sumo-slonik 35304d7
refactor: one tab root scroll props call in the Insights dashboard
sumo-slonik 743cc0e
chore: drop the unused src hunk from the bottom-tabs indicator color …
sumo-slonik 4ba41ea
docs: note when the tab bar hide and indicator color patches can go
sumo-slonik 4d060b2
fix: name the iOS native tabs for VoiceOver, note in patch docs which…
sumo-slonik 43a71c0
revert: stop freezing background native tabs, the native bar shows a …
sumo-slonik aa861e2
fix: show Search skeleton rows on the first visit to a background mou…
sumo-slonik f873587
fix: derive the natively selected tab from the history, so a beta cha…
sumo-slonik 2a9acc3
fix: replace instead of push when leaving Insights without the beta o…
sumo-slonik 3855aca
fix: native tab taps run the JS tab button rules, Inbox opens at its …
sumo-slonik 433cc00
fix: still background Spend skeleton, no tab bar animation before it …
sumo-slonik 0eb4f28
chore: shared tab root scroll props type, constraint-only comments, p…
sumo-slonik ac4fd10
fix: Android selected tab no longer sits higher than the others, tab …
sumo-slonik 9aefaea
fix: navigating to the split sidebar already on top is a handled no-o…
sumo-slonik cd5a62b
fix: Android tab dots drawn into the icon keep their own color, nativ…
sumo-slonik 0f50568
Merge remote-tracking branch 'upstream/main' into feat/liquid-glass-t…
sumo-slonik 71ffd6e
chore: no double safe area under the mWeb Inbox list, one owner for t…
sumo-slonik 9b01141
fix: native Workspaces and first Spend taps follow the JS tab buttons…
sumo-slonik 61a832a
fix: mock native tab icons under the Skia jest mock, platform hook ty…
sumo-slonik 412eefe
refactor: one listener for the native Spend and Workspaces taps, plat…
sumo-slonik 4c9148e
fix: native bar switches to Inbox, Spend and Workspaces itself when t…
sumo-slonik 434ec5a
fix: iOS tab labels fall back for non-Latin scripts and truncate to t…
sumo-slonik 1b99159
refactor: review fixes, narrow overlay split out, tab options from sc…
sumo-slonik b627741
chore: bump bottom-tabs to 7.18.16 and native to 7.3.16, the newest w…
sumo-slonik c1aabf4
fix: iOS offline indicator sits above the native tab bar on every tab…
sumo-slonik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # `react-native-screens` patches | ||
|
|
||
| ### [react-native-screens+4.28.0+001+animate-hiding-the-native-tab-bar.patch](react-native-screens+4.28.0+001+animate-hiding-the-native-tab-bar.patch) | ||
|
|
||
| - Reason: The App hides the native tab bar on screens that are not a tab root, which React Navigation forwards to RNScreens as `tabBarHidden` (it derives the flag from `tabBarStyle.display === 'none'`). RNScreens calls UIKit's `setTabBarHidden:animated:` with `animated:NO`, so the bar blinks in and out instead of travelling with the screen that hid it. The patch animates the change once the bar is in a window, which lets UIKit run its own show/hide animation without animating the bar out on the first render. The pre-iOS 18 branch is left alone: it assigns `tabBar.hidden` directly and has no animated counterpart. **This patch can be removed on the bump to react-native-screens 4.29.0**, the first release with the upstream fix linked below. | ||
| - Upstream PR/issue: fixed upstream by [software-mansion/react-native-screens#4632](https://github.com/software-mansion/react-native-screens/pull/4632), which animates by default behind a new `ios.tabBarHiddenAnimationEnabled` prop, and [#4673](https://github.com/software-mansion/react-native-screens/pull/4673), which skips the animation on the first render. Both are merged after the 4.28.0 release and labeled for the v4 backport. | ||
| - E/App issue: [#101169](https://github.com/Expensify/App/issues/101169) | ||
| - PR introducing patch: [#101339](https://github.com/Expensify/App/pull/101339) | ||
|
|
||
| ### [react-native-screens+4.28.0+002+android-tab-label-typography.patch](react-native-screens+4.28.0+002+android-tab-label-typography.patch) | ||
|
|
||
| - Reason: Two changes to the Android tab labels, both in `TabsAppearanceApplicator`, which React Navigation has no option for. | ||
| - Bold selected label. The Android tab bar takes one font weight for every label, while the design marks the selected tab with a bold label, the same way the JS side bar does. Material's `BottomNavigationView` draws each item with two labels, a small one shown while unselected and a large one shown while selected. `updateFontStyles` sets the large label's typeface with `Typeface.BOLD`, the way Material applies its own bold, so Android picks the bold face of the same font family. Material's `setItemTextAppearanceActiveBoldEnabled` is not used for this: it re-applies Material's 12sp text size to the large label alone and recomputes the selected item's offset from the difference between the two labels' sizes, so every selected tab would sit higher than the others. | ||
| - Letter spacing. `updateFontStyles` replaces the typeface and size of Material's tab labels but keeps the rest of Material 3's `LabelMedium` text appearance, which tracks letters 0.5sp apart. With Expensify Neue the labels read too spread out next to the rest of the app, so the patch sets the letter spacing of both labels to 0. | ||
| - Upstream PR/issue: not reported, because both changes are the App's typography (a bold selected label and its letter spacing) rather than a library defect, and RNScreens has no option for either. | ||
| - E/App issue: [#101169](https://github.com/Expensify/App/issues/101169) | ||
| - PR introducing patch: [#101339](https://github.com/Expensify/App/pull/101339) | ||
|
|
||
| ### [react-native-screens+4.28.0+003+no-android-tab-icon-tint.patch](react-native-screens+4.28.0+003+no-android-tab-icon-tint.patch) | ||
|
|
||
| - Reason: The Android account tab shows the user's avatar. `TabsAppearanceApplicator` assigns `bottomNavigationView.itemIconTintList` unconditionally, and a `ColorStateList` tint is `SRC_IN`, so it flattens the avatar to a solid silhouette in the tint color. React Navigation's `tinted: false` only reaches iOS, since `getPlatformIcon` in `@react-navigation/bottom-tabs` always hands Android a plain `imageSource`. The patch drops the icon tint list so Android draws every icon bitmap as supplied. The App recolors the glyphs off-screen in Skia for both selection states, so they keep the design's colors. Label colors are untouched and keep coming from `tabBarItemTitleFontColor`. | ||
| - Upstream PR/issue: not reported, because dropping the tint for every icon fits only a bar whose icons all arrive pre-colored, as the App's do. The disabled and focused icon colors go with it, which the App does not use. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, can't we add an option in |
||
| - E/App issue: [#101169](https://github.com/Expensify/App/issues/101169) | ||
| - PR introducing patch: [#101339](https://github.com/Expensify/App/pull/101339) | ||
14 changes: 14 additions & 0 deletions
14
...ct-native-screens/react-native-screens+4.28.0+001+animate-hiding-the-native-tab-bar.patch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| diff --git a/node_modules/react-native-screens/ios/tabs/host/RNSTabsHostComponentView.mm b/node_modules/react-native-screens/ios/tabs/host/RNSTabsHostComponentView.mm | ||
| --- a/node_modules/react-native-screens/ios/tabs/host/RNSTabsHostComponentView.mm | ||
| +++ b/node_modules/react-native-screens/ios/tabs/host/RNSTabsHostComponentView.mm | ||
| @@ -244,7 +244,9 @@ | ||
| _tabBarHidden = newComponentProps.tabBarHidden; | ||
| #if RNS_IPHONE_OS_VERSION_AVAILABLE(18_0) | ||
| if (@available(iOS 18.0, *)) { | ||
| - [_controller setTabBarHidden:_tabBarHidden animated:NO]; | ||
| + // Animated so the bar slides out with the screen that hid it instead of blinking away, except before the bar | ||
| + // is on screen, where the first render would animate it out. | ||
| + [_controller setTabBarHidden:_tabBarHidden animated:[[_controller tabBar] window] != nil]; | ||
| } else | ||
| #endif // RNS_IPHONE_OS_VERSION_AVAILABLE(18_0) | ||
| { |
32 changes: 32 additions & 0 deletions
32
...s/react-native-screens/react-native-screens+4.28.0+002+android-tab-label-typography.patch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/appearance/TabsAppearanceApplicator.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/appearance/TabsAppearanceApplicator.kt | ||
| index 10cd91b..6f059bd 100644 | ||
| --- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/appearance/TabsAppearanceApplicator.kt | ||
| +++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/appearance/TabsAppearanceApplicator.kt | ||
| @@ -3,6 +3,7 @@ package com.swmansion.rnscreens.gamma.tabs.appearance | ||
| import android.annotation.SuppressLint | ||
| import android.content.Context | ||
| import android.content.res.ColorStateList | ||
| +import android.graphics.Typeface | ||
| import android.graphics.drawable.StateListDrawable | ||
| import android.util.TypedValue | ||
| import android.view.MenuItem | ||
| @@ -161,13 +162,18 @@ internal class TabsAppearanceApplicator( | ||
| tabBarAppearance?.tabBarItemTitleLargeLabelFontSize?.takeIf { it > 0 }?.let { PixelUtil.toPixelFromSP(it) } | ||
| ?: context.resources.getDimension(R.dimen.design_bottom_navigation_text_size) | ||
|
|
||
| + // Both labels drop the 0.5sp letter tracking of Material's text appearance, which reads wider than the app's labels. | ||
| // Inactive | ||
| smallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallFontSize) | ||
| smallLabel.typeface = fontFamily | ||
| + smallLabel.letterSpacing = 0f | ||
|
|
||
| // Active | ||
| + // Bold is set here rather than with `setItemTextAppearanceActiveBoldEnabled`, which re-applies Material's text size | ||
| + // to this label alone, so Material lifts the selected item by the size difference between the two labels. | ||
| largeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, largeFontSize) | ||
| - largeLabel.typeface = fontFamily | ||
| + largeLabel.setTypeface(fontFamily, Typeface.BOLD) | ||
| + largeLabel.letterSpacing = 0f | ||
| } | ||
| } | ||
|
|
33 changes: 33 additions & 0 deletions
33
patches/react-native-screens/react-native-screens+4.28.0+003+no-android-tab-icon-tint.patch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/appearance/TabsAppearanceApplicator.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/appearance/TabsAppearanceApplicator.kt | ||
| index 9238416..d7d2024 100644 | ||
| --- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/appearance/TabsAppearanceApplicator.kt | ||
| +++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/appearance/TabsAppearanceApplicator.kt | ||
| @@ -64,24 +64,10 @@ internal class TabsAppearanceApplicator( | ||
| bottomNavigationView.itemTextColor = ColorStateList(states, fontColors) | ||
|
|
||
| // Icon color | ||
| - val iconDisabledColor = | ||
| - tabBarAppearance?.disabled?.tabBarItemIconColor | ||
| - ?: resolveColorAttr(context, R.attr.colorOnSurfaceVariant) | ||
| - | ||
| - val iconFocusedColor = | ||
| - tabBarAppearance?.focused?.tabBarItemIconColor | ||
| - ?: resolveColorAttr(context, R.attr.colorOnSurfaceVariant) | ||
| - | ||
| - val iconSelectedColor = | ||
| - tabBarAppearance?.selected?.tabBarItemIconColor | ||
| - ?: resolveColorAttr(context, R.attr.colorOnSecondaryContainer) | ||
| - | ||
| - val iconNormalColor = | ||
| - tabBarAppearance?.normal?.tabBarItemIconColor | ||
| - ?: resolveColorAttr(context, R.attr.colorOnSurfaceVariant) | ||
| - | ||
| - val iconColors = intArrayOf(iconDisabledColor, iconSelectedColor, iconFocusedColor, iconNormalColor) | ||
| - bottomNavigationView.itemIconTintList = ColorStateList(states, iconColors) | ||
| + // Every tab icon arrives as a bitmap that already carries its own colors: the recolored glyph and the user's | ||
| + // avatar on the account tab. A tint list paints over all of it with a single color, so no tint is applied and | ||
| + // the bitmap is drawn as supplied, which is how `tinted: false` already behaves on iOS. | ||
| + bottomNavigationView.itemIconTintList = null | ||
|
|
||
| // LabelVisibilityMode | ||
| // From docs: can be one of LABEL_VISIBILITY_AUTO, LABEL_VISIBILITY_SELECTED, LABEL_VISIBILITY_LABELED, or LABEL_VISIBILITY_UNLABELED |
File renamed without changes.
13 changes: 13 additions & 0 deletions
13
...igation/@react-navigation+bottom-tabs+7.18.16+002+active-indicator-color-precedence.patch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| diff --git a/node_modules/@react-navigation/bottom-tabs/lib/module/unstable/NativeBottomTabView.native.js b/node_modules/@react-navigation/bottom-tabs/lib/module/unstable/NativeBottomTabView.native.js | ||
| index 1183c51..303635c 100644 | ||
| --- a/node_modules/@react-navigation/bottom-tabs/lib/module/unstable/NativeBottomTabView.native.js | ||
| +++ b/node_modules/@react-navigation/bottom-tabs/lib/module/unstable/NativeBottomTabView.native.js | ||
| @@ -160,7 +160,7 @@ export function NativeBottomTabView({ | ||
| ios: PlatformColor('label'), | ||
| default: colors.text | ||
| }); | ||
| - const activeIndicatorColor = currentOptions?.tabBarActiveIndicatorColor ?? typeof activeTintColor === 'string' ? Color(activeTintColor)?.alpha(0.1).string() : undefined; | ||
| + const activeIndicatorColor = currentOptions?.tabBarActiveIndicatorColor ?? (typeof activeTintColor === 'string' ? Color(activeTintColor)?.alpha(0.1).string() : undefined); | ||
| const onTransitionStart = ({ | ||
| closing, | ||
| route |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could add such options in an upstream PR? If not we'll have to support this patch forever