@capgo/capacitor-native-navigation 8.4.5 → 8.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -33
- package/android/src/main/java/app/capgo/nativenavigation/NativeNavigationPlugin.java +306 -64
- package/android/src/main/java/app/capgo/nativenavigation/TabbarChromeSupport.java +13 -0
- package/dist/docs.json +2 -2
- package/dist/esm/definitions.d.ts +6 -2
- package/dist/esm/definitions.js.map +1 -1
- package/docs/android-floating-tabbar-after-300.png +0 -0
- package/docs/android-floating-tabbar-after-full.png +0 -0
- package/docs/android-floating-tabbar-before-300.png +0 -0
- package/docs/pr54-android-floating-reported-280.png +0 -0
- package/docs/pr54-android-floating-reported.png +0 -0
- package/ios/Sources/NativeNavigationPlugin/NativeNavigationPlugin.swift +109 -36
- package/ios/Tests/NativeNavigationPluginTests/NativeNavigationTests.swift +24 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<a href="https://capgo.app/?ref=plugin_native_navigation"><img src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-native-navigation" alt="Capgo - Instant updates for Capacitor" /></a>
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
6
|
-
<p><b>Capgo</b>:
|
|
6
|
+
<p><b>Capgo</b>: push fixes to your Capacitor users in minutes, build signed iOS and Android apps without a Mac, and roll back in one click.</p>
|
|
7
7
|
<h2><a href="https://capgo.app/register/?ref=plugin_native_navigation">➡️ Get started for free</a></h2>
|
|
8
8
|
<p>14-day unlimited free trial. No credit card required</p>
|
|
9
9
|
<p><a href="https://capgo.app/consulting/?ref=plugin_native_navigation">Missing a feature? We'll build the plugin for you 💪</a></p>
|
|
@@ -159,7 +159,7 @@ await NativeNavigation.setTabbar({
|
|
|
159
159
|
|
|
160
160
|
## Android Liquid Glass
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
On Android 12+, floating tabbars use `liquidGlass` automatically when `setTabbar` omits `glass` (pass `glass: { effect: 'none' }` for a flat fill). The live blur uses a platform `RenderEffect` behind the bar shape; older Android versions keep a translucent tint without live blur. Tune `colors.background` alpha and `glass.surfaceAlpha` (default `0.62`) together.
|
|
163
163
|
|
|
164
164
|
```typescript
|
|
165
165
|
await NativeNavigation.configure({
|
|
@@ -650,17 +650,17 @@ Global plugin configuration.
|
|
|
650
650
|
|
|
651
651
|
Native bar colors. Use CSS-style hex strings (`#RRGGBB` or `#AARRGGBB`).
|
|
652
652
|
|
|
653
|
-
| Prop | Type | Description
|
|
654
|
-
| --------------------- | -------------------- |
|
|
655
|
-
| **`dynamic`** | <code>boolean</code> | When `true`, Android 12+ derives unspecified bar colors from Material You system palettes. Explicit color fields still win.
|
|
656
|
-
| **`tint`** | <code>string</code> | Tint color for active buttons/items.
|
|
657
|
-
| **`inactiveTint`** | <code>string</code> | Color for inactive tab items. Ignored on iOS 26+ unless `experimentalBakedTintColors` is enabled.
|
|
658
|
-
| **`background`** | <code>string</code> | Optional background tint. Ignored on iOS 26+ so UIKit can preserve the system Liquid Glass navigation appearance.
|
|
659
|
-
| **`foreground`** | <code>string</code> | Title and label text color where the native platform supports it.
|
|
660
|
-
| **`badgeBackground`** | <code>string</code> | Badge background color for native tab badges.
|
|
661
|
-
| **`badgeText`** | <code>string</code> | Badge text color for native tab badges.
|
|
662
|
-
| **`indicator`** | <code>string</code> | Active tab indicator color on Android.
|
|
663
|
-
| **`ripple`** | <code>string</code> | Tab press ripple color on Android.
|
|
653
|
+
| Prop | Type | Description |
|
|
654
|
+
| --------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
655
|
+
| **`dynamic`** | <code>boolean</code> | When `true`, Android 12+ derives unspecified bar colors from Material You system palettes. Explicit color fields still win. |
|
|
656
|
+
| **`tint`** | <code>string</code> | Tint color for active buttons/items. |
|
|
657
|
+
| **`inactiveTint`** | <code>string</code> | Color for inactive tab items. Ignored on iOS 26+ unless `experimentalBakedTintColors` is enabled. |
|
|
658
|
+
| **`background`** | <code>string</code> | Optional background tint. Ignored on iOS 26+ so UIKit can preserve the system Liquid Glass navigation appearance. On Android, alpha is honored. When `glass.effect` is not `liquidGlass`, floating tabbars still apply `glass.surfaceAlpha` (from `configure` / `setTabbar`) to the fill tint. |
|
|
659
|
+
| **`foreground`** | <code>string</code> | Title and label text color where the native platform supports it. |
|
|
660
|
+
| **`badgeBackground`** | <code>string</code> | Badge background color for native tab badges. |
|
|
661
|
+
| **`badgeText`** | <code>string</code> | Badge text color for native tab badges. |
|
|
662
|
+
| **`indicator`** | <code>string</code> | Active tab indicator color on Android. |
|
|
663
|
+
| **`ripple`** | <code>string</code> | Tab press ripple color on Android. |
|
|
664
664
|
|
|
665
665
|
|
|
666
666
|
#### NativeNavigationGlassOptions
|
|
@@ -736,26 +736,26 @@ because icons are rendered by native UI.
|
|
|
736
736
|
|
|
737
737
|
Native tabbar state.
|
|
738
738
|
|
|
739
|
-
| Prop | Type | Description
|
|
740
|
-
| ------------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
741
|
-
| **`hidden`** | <code>boolean</code> | Hide the native tabbar.
|
|
742
|
-
| **`tabs`** | <code>NativeNavigationTab[]</code> | Tab definitions.
|
|
743
|
-
| **`selectedId`** | <code>string</code> | Currently selected tab id.
|
|
744
|
-
| **`labels`** | <code>boolean</code> | Show text labels. Defaults to `true`.
|
|
745
|
-
| **`labelVisibilityMode`** | <code><a href="#nativenavigationtablabelvisibilitymode">NativeNavigationTabLabelVisibilityMode</a></code> | Native label visibility mode. Overrides `labels` when provided.
|
|
746
|
-
| **`icons`** | <code>boolean</code> | Show icons. Defaults to `true`.
|
|
747
|
-
| **`colors`** | <code><a href="#nativenavigationcolors">NativeNavigationColors</a></code> | Tabbar color hints.
|
|
748
|
-
| **`blurEffect`** | <code><a href="#nativenavigationblureffect">NativeNavigationBlurEffect</a></code> | iOS blur/material effect for the tabbar background when glass is not available.
|
|
749
|
-
| **`glass`** | <code><a href="#nativenavigationglassoptions">NativeNavigationGlassOptions</a></code> | Optional glass background behavior. Overrides `configure({ glass })` for this tabbar update.
|
|
750
|
-
| **`experimentalBakedTintColors`** | <code>boolean</code> | Opt into the iOS 26 Liquid Glass tint workaround that renders active and inactive tab items into baked images. This can affect badge positioning and icon sizing, so it is disabled by default.
|
|
751
|
-
| **`disableTransparentOnScrollEdge`** | <code>boolean</code> | Keep the iOS scroll-edge tabbar appearance from becoming transparent. Mirrors Expo Router native tabs' `disableTransparentOnScrollEdge` option. Defaults to `false`.
|
|
752
|
-
| **`disableIndicator`** | <code>boolean</code> | Disable the Android active tab indicator.
|
|
753
|
-
| **`indicatorColor`** | <code>string</code> | Active tab indicator color on Android. `colors.indicator` is also supported.
|
|
754
|
-
| **`rippleColor`** | <code>string</code> | Tab press ripple color on Android. `colors.ripple` is also supported.
|
|
755
|
-
| **`badgeBackgroundColor`** | <code>string</code> | Badge background color. `colors.badgeBackground` is also supported.
|
|
756
|
-
| **`badgeTextColor`** | <code>string</code> | Badge text color. `colors.badgeText` is also supported.
|
|
757
|
-
| **`style`** | <code><a href="#nativenavigationtabbarstyle">NativeNavigationTabbarStyle</a></code> | Optional native tabbar layout and shape customization.
|
|
758
|
-
| **`animated`** | <code>boolean</code> | Animate native tabbar changes.
|
|
739
|
+
| Prop | Type | Description |
|
|
740
|
+
| ------------------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
741
|
+
| **`hidden`** | <code>boolean</code> | Hide the native tabbar. |
|
|
742
|
+
| **`tabs`** | <code>NativeNavigationTab[]</code> | Tab definitions. |
|
|
743
|
+
| **`selectedId`** | <code>string</code> | Currently selected tab id. |
|
|
744
|
+
| **`labels`** | <code>boolean</code> | Show text labels. Defaults to `true`. |
|
|
745
|
+
| **`labelVisibilityMode`** | <code><a href="#nativenavigationtablabelvisibilitymode">NativeNavigationTabLabelVisibilityMode</a></code> | Native label visibility mode. Overrides `labels` when provided. |
|
|
746
|
+
| **`icons`** | <code>boolean</code> | Show icons. Defaults to `true`. |
|
|
747
|
+
| **`colors`** | <code><a href="#nativenavigationcolors">NativeNavigationColors</a></code> | Tabbar color hints. |
|
|
748
|
+
| **`blurEffect`** | <code><a href="#nativenavigationblureffect">NativeNavigationBlurEffect</a></code> | iOS blur/material effect for the tabbar background when glass is not available. |
|
|
749
|
+
| **`glass`** | <code><a href="#nativenavigationglassoptions">NativeNavigationGlassOptions</a></code> | Optional glass background behavior. Overrides `configure({ glass })` for this tabbar update. On Android 12+, floating tabbars default to `liquidGlass` (live blur plus tint) when this field is omitted. Pass `effect: 'none'` for a flat translucent fill without blur. |
|
|
750
|
+
| **`experimentalBakedTintColors`** | <code>boolean</code> | Opt into the iOS 26 Liquid Glass tint workaround that renders active and inactive tab items into baked images. This can affect badge positioning and icon sizing, so it is disabled by default. |
|
|
751
|
+
| **`disableTransparentOnScrollEdge`** | <code>boolean</code> | Keep the iOS scroll-edge tabbar appearance from becoming transparent. Mirrors Expo Router native tabs' `disableTransparentOnScrollEdge` option. Defaults to `false`. |
|
|
752
|
+
| **`disableIndicator`** | <code>boolean</code> | Disable the Android active tab indicator. |
|
|
753
|
+
| **`indicatorColor`** | <code>string</code> | Active tab indicator color on Android. `colors.indicator` is also supported. |
|
|
754
|
+
| **`rippleColor`** | <code>string</code> | Tab press ripple color on Android. `colors.ripple` is also supported. |
|
|
755
|
+
| **`badgeBackgroundColor`** | <code>string</code> | Badge background color. `colors.badgeBackground` is also supported. |
|
|
756
|
+
| **`badgeTextColor`** | <code>string</code> | Badge text color. `colors.badgeText` is also supported. |
|
|
757
|
+
| **`style`** | <code><a href="#nativenavigationtabbarstyle">NativeNavigationTabbarStyle</a></code> | Optional native tabbar layout and shape customization. |
|
|
758
|
+
| **`animated`** | <code>boolean</code> | Animate native tabbar changes. |
|
|
759
759
|
|
|
760
760
|
|
|
761
761
|
#### NativeNavigationTab
|
|
@@ -36,6 +36,7 @@ import androidx.annotation.RequiresApi;
|
|
|
36
36
|
import androidx.appcompat.content.res.AppCompatResources;
|
|
37
37
|
import androidx.appcompat.widget.Toolbar;
|
|
38
38
|
import androidx.core.graphics.PathParser;
|
|
39
|
+
import androidx.core.widget.TextViewCompat;
|
|
39
40
|
import com.getcapacitor.JSArray;
|
|
40
41
|
import com.getcapacitor.JSObject;
|
|
41
42
|
import com.getcapacitor.Plugin;
|
|
@@ -61,6 +62,11 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
61
62
|
|
|
62
63
|
private static final int DEFAULT_NAVBAR_DP = 56;
|
|
63
64
|
private static final int DEFAULT_TABBAR_DP = 64;
|
|
65
|
+
private static final int TAB_LABEL_TEXT_SP = 12;
|
|
66
|
+
private static final int TAB_CONTENT_PADDING_DP = 4;
|
|
67
|
+
private static final int INDICATOR_HORIZONTAL_PADDING_DP = 12;
|
|
68
|
+
private static final int INDICATOR_VERTICAL_PADDING_DP = 6;
|
|
69
|
+
private static final int INDICATOR_SLOT_INSET_DP = 2;
|
|
64
70
|
private static final int DEFAULT_TRANSITION_MS = 350;
|
|
65
71
|
private static final int MENU_ITEM_BASE = 10_000;
|
|
66
72
|
private static final int DEFAULT_TABBAR_BACKGROUND_COLOR = Color.WHITE;
|
|
@@ -81,6 +87,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
81
87
|
private boolean tabbarVisible = false;
|
|
82
88
|
private String contentInsetMode = "css";
|
|
83
89
|
private GlassOptions defaultGlassOptions = GlassOptions.defaults();
|
|
90
|
+
private boolean configureSpecifiedGlassEffect = false;
|
|
84
91
|
private GlassOptions navbarGlassOptions = GlassOptions.defaults();
|
|
85
92
|
private GlassOptions tabbarGlassOptions = GlassOptions.defaults();
|
|
86
93
|
private JSObject navbarGlassConfig;
|
|
@@ -105,6 +112,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
105
112
|
private final Map<Integer, String> menuActionPlacements = new HashMap<>();
|
|
106
113
|
private final List<NativeTabItem> tabItems = new ArrayList<>();
|
|
107
114
|
private int selectedTabIndex = 0;
|
|
115
|
+
private boolean tabbarChromeHideAnimating = false;
|
|
108
116
|
private boolean tabbarDisableIndicator = false;
|
|
109
117
|
private Integer tabbarIndicatorColor = null;
|
|
110
118
|
|
|
@@ -124,9 +132,11 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
124
132
|
runOnUiThread(() -> {
|
|
125
133
|
enabled = call.getBoolean("enabled", true);
|
|
126
134
|
contentInsetMode = call.getString("contentInsetMode", contentInsetMode);
|
|
127
|
-
|
|
135
|
+
JSObject configureGlass = call.getObject("glass", null);
|
|
136
|
+
configureSpecifiedGlassEffect = configureGlass != null && configureGlass.has("effect");
|
|
137
|
+
defaultGlassOptions = GlassOptions.from(configureGlass, defaultGlassOptions);
|
|
128
138
|
navbarGlassOptions = GlassOptions.from(navbarGlassConfig, defaultGlassOptions);
|
|
129
|
-
tabbarGlassOptions =
|
|
139
|
+
tabbarGlassOptions = resolveTabbarGlassOptions(call);
|
|
130
140
|
Double duration = call.getDouble("animationDuration");
|
|
131
141
|
if (duration != null) {
|
|
132
142
|
defaultTransitionMs = Math.max(0, duration.intValue());
|
|
@@ -218,17 +228,10 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
218
228
|
}
|
|
219
229
|
|
|
220
230
|
boolean hidden = call.getBoolean("hidden", false);
|
|
231
|
+
boolean animated = call.getBoolean("animated", false);
|
|
221
232
|
tabbarVisible = !hidden;
|
|
222
233
|
if (hidden) {
|
|
223
|
-
|
|
224
|
-
tabbar.setVisibility(View.GONE);
|
|
225
|
-
}
|
|
226
|
-
if (tabbarContainer != null) {
|
|
227
|
-
tabbarContainer.setVisibility(View.GONE);
|
|
228
|
-
}
|
|
229
|
-
if (tabbarBackdrop != null) {
|
|
230
|
-
tabbarBackdrop.setVisibility(View.GONE);
|
|
231
|
-
}
|
|
234
|
+
setTabbarChromeVisible(false, animated);
|
|
232
235
|
updateInsetsAndNotify();
|
|
233
236
|
call.resolve(insetsResult());
|
|
234
237
|
return;
|
|
@@ -246,7 +249,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
246
249
|
String selectedId = call.getString("selectedId", null);
|
|
247
250
|
JSObject colors = call.getObject("colors", new JSObject());
|
|
248
251
|
tabbarGlassConfig = call.getObject("glass", null);
|
|
249
|
-
tabbarGlassOptions = GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
|
|
250
252
|
badgeBackgroundColor = colorOption(call, colors, "badgeBackgroundColor", "badgeBackground", Color.rgb(255, 59, 48));
|
|
251
253
|
badgeTextColor = colorOption(call, colors, "badgeTextColor", "badgeText", Color.WHITE);
|
|
252
254
|
tabbarDisableIndicator = call.getBoolean("disableIndicator", false);
|
|
@@ -276,6 +278,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
276
278
|
|
|
277
279
|
applyTabbarColors(call, colors);
|
|
278
280
|
tabbarStyle = makeTabbarStyle(call.getObject("style", new JSObject()));
|
|
281
|
+
tabbarGlassOptions = resolveTabbarGlassOptions(call);
|
|
279
282
|
|
|
280
283
|
// Keep at most one detached trailing action for floating bars.
|
|
281
284
|
// Curve bars ignore role so tab order / center selection stay stable.
|
|
@@ -317,14 +320,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
317
320
|
|
|
318
321
|
applyTabbarBackground(centerTabIndex());
|
|
319
322
|
renderTabbarItems(labelVisibilityMode, icons);
|
|
320
|
-
if (tabbarContainer != null) {
|
|
321
|
-
tabbarContainer.setVisibility(View.VISIBLE);
|
|
322
|
-
}
|
|
323
|
-
if (tabbarBackdrop != null) {
|
|
324
|
-
tabbarBackdrop.setVisibility(View.VISIBLE);
|
|
325
|
-
}
|
|
326
|
-
nativeTabbar.setVisibility(View.VISIBLE);
|
|
327
323
|
layoutChrome();
|
|
324
|
+
setTabbarChromeVisible(true, animated);
|
|
328
325
|
updateInsetsAndNotify();
|
|
329
326
|
call.resolve(insetsResult());
|
|
330
327
|
});
|
|
@@ -762,6 +759,10 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
762
759
|
tabbarContainer.setClipChildren(false);
|
|
763
760
|
tabbarContainer.setClipToPadding(false);
|
|
764
761
|
tabbarContainer.setElevation(dp(12));
|
|
762
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
763
|
+
tabbarContainer.setOutlineAmbientShadowColor(0x33000000);
|
|
764
|
+
tabbarContainer.setOutlineSpotShadowColor(0x66000000);
|
|
765
|
+
}
|
|
765
766
|
|
|
766
767
|
tabbarGlassBackdrop = new GlassBackdropView(getContext());
|
|
767
768
|
tabbarGlassSurface = new View(getContext());
|
|
@@ -843,7 +844,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
843
844
|
}
|
|
844
845
|
|
|
845
846
|
private FrameLayout makeTabButton(NativeTabItem item, boolean selected, boolean showLabel, boolean icons, boolean center) {
|
|
846
|
-
|
|
847
|
+
TabButton button = new TabButton(getContext());
|
|
847
848
|
button.setClipChildren(false);
|
|
848
849
|
button.setClipToPadding(false);
|
|
849
850
|
button.setForeground(selectableItemBackground());
|
|
@@ -857,25 +858,33 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
857
858
|
button.addView(centerFill, new FrameLayout.LayoutParams(centerFillDiameter, centerFillDiameter, Gravity.CENTER));
|
|
858
859
|
}
|
|
859
860
|
|
|
861
|
+
Drawable currentIcon = selected && item.selectedIcon != null ? item.selectedIcon : item.icon;
|
|
862
|
+
boolean labelVisible = center ? showLabel && (currentIcon == null || !icons) : showLabel;
|
|
863
|
+
boolean iconVisible = icons && currentIcon != null;
|
|
864
|
+
// Measured with the bold selected style for every tab so the slots don't shift when the selection changes.
|
|
865
|
+
int contentWidth = indicatorContentWidth(item.title, labelVisible, iconVisible);
|
|
866
|
+
button.preferredWidth = contentWidth + dp(INDICATOR_HORIZONTAL_PADDING_DP + INDICATOR_SLOT_INSET_DP) * 2;
|
|
867
|
+
|
|
860
868
|
if (!center && selected && !tabbarDisableIndicator) {
|
|
861
869
|
GradientDrawable selectedBackground = new GradientDrawable();
|
|
862
|
-
selectedBackground.setShape(GradientDrawable.
|
|
870
|
+
selectedBackground.setShape(GradientDrawable.RECTANGLE);
|
|
863
871
|
selectedBackground.setColor(TabbarChromeSupport.resolveSelectedIndicatorColor(tabbarIndicatorColor, tintColor));
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
button.addView(
|
|
872
|
+
TabIndicatorView indicator = new TabIndicatorView(getContext(), contentWidth, indicatorContentHeight(labelVisible, iconVisible));
|
|
873
|
+
indicator.setBackground(selectedBackground);
|
|
874
|
+
button.addView(
|
|
875
|
+
indicator,
|
|
876
|
+
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)
|
|
877
|
+
);
|
|
867
878
|
}
|
|
868
879
|
|
|
869
880
|
LinearLayout content = new LinearLayout(getContext());
|
|
870
881
|
content.setOrientation(LinearLayout.VERTICAL);
|
|
871
882
|
content.setGravity(Gravity.CENTER);
|
|
872
|
-
content.setPadding(dp(
|
|
883
|
+
content.setPadding(dp(TAB_CONTENT_PADDING_DP), dp(TAB_CONTENT_PADDING_DP), dp(TAB_CONTENT_PADDING_DP), dp(TAB_CONTENT_PADDING_DP));
|
|
873
884
|
button.addView(content, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
874
885
|
|
|
875
|
-
Drawable currentIcon = selected && item.selectedIcon != null ? item.selectedIcon : item.icon;
|
|
876
|
-
boolean labelVisible = center ? showLabel && (currentIcon == null || !icons) : showLabel;
|
|
877
886
|
int itemColor = center ? tabbarStyle.centerButtonIconColor : (selected ? tintColor : inactiveTintColor);
|
|
878
|
-
if (
|
|
887
|
+
if (iconVisible) {
|
|
879
888
|
Drawable icon = currentIcon.mutate();
|
|
880
889
|
icon.setTint(itemColor);
|
|
881
890
|
ImageView image = new ImageView(getContext());
|
|
@@ -891,10 +900,13 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
891
900
|
TextView label = new TextView(getContext());
|
|
892
901
|
label.setText(item.title);
|
|
893
902
|
label.setTextColor(itemColor);
|
|
894
|
-
label.setTextSize(
|
|
903
|
+
label.setTextSize(TAB_LABEL_TEXT_SP);
|
|
895
904
|
label.setTypeface(Typeface.DEFAULT, selected ? Typeface.BOLD : Typeface.NORMAL);
|
|
896
905
|
label.setGravity(Gravity.CENTER);
|
|
897
|
-
|
|
906
|
+
// maxLines rather than singleLine: autosize is ignored on a single-line (horizontally scrolling) TextView.
|
|
907
|
+
label.setMaxLines(1);
|
|
908
|
+
// Shrink long titles to fit the tab instead of cutting them off.
|
|
909
|
+
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(label, 9, TAB_LABEL_TEXT_SP, 1, TypedValue.COMPLEX_UNIT_SP);
|
|
898
910
|
content.addView(label, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(18)));
|
|
899
911
|
}
|
|
900
912
|
|
|
@@ -924,6 +936,75 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
924
936
|
return button;
|
|
925
937
|
}
|
|
926
938
|
|
|
939
|
+
/** Width of the selected item's icon or label (whichever is wider), before the indicator padding. */
|
|
940
|
+
private int indicatorContentWidth(String title, boolean labelVisible, boolean iconVisible) {
|
|
941
|
+
int width = iconVisible ? dp(24) : 0;
|
|
942
|
+
if (labelVisible && title != null) {
|
|
943
|
+
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
944
|
+
paint.setTypeface(Typeface.DEFAULT_BOLD);
|
|
945
|
+
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, TAB_LABEL_TEXT_SP, getContext().getResources().getDisplayMetrics()));
|
|
946
|
+
width = Math.max(width, (int) Math.ceil(paint.measureText(title)));
|
|
947
|
+
}
|
|
948
|
+
return width;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
private int indicatorContentHeight(boolean labelVisible, boolean iconVisible) {
|
|
952
|
+
int height = 0;
|
|
953
|
+
if (iconVisible) {
|
|
954
|
+
height += dp(24);
|
|
955
|
+
}
|
|
956
|
+
if (labelVisible) {
|
|
957
|
+
height += dp(18) + (iconVisible ? dp(2) : 0);
|
|
958
|
+
}
|
|
959
|
+
return height;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/** Tab item that reports how wide it wants to be, so the tabbar can size slots to their content. */
|
|
963
|
+
private static final class TabButton extends FrameLayout {
|
|
964
|
+
|
|
965
|
+
int preferredWidth = 0;
|
|
966
|
+
|
|
967
|
+
TabButton(Context context) {
|
|
968
|
+
super(context);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/** Selected-tab capsule that wraps the item's icon and label with padding, kept inside its tab slot. */
|
|
973
|
+
private static final class TabIndicatorView extends View {
|
|
974
|
+
|
|
975
|
+
private final int contentWidth;
|
|
976
|
+
private final int contentHeight;
|
|
977
|
+
|
|
978
|
+
TabIndicatorView(Context context, int contentWidth, int contentHeight) {
|
|
979
|
+
super(context);
|
|
980
|
+
this.contentWidth = contentWidth;
|
|
981
|
+
this.contentHeight = contentHeight;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
@Override
|
|
985
|
+
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
986
|
+
int slotWidth = MeasureSpec.getSize(widthMeasureSpec);
|
|
987
|
+
int slotHeight = MeasureSpec.getSize(heightMeasureSpec);
|
|
988
|
+
int height = contentHeight + dp(INDICATOR_VERTICAL_PADDING_DP) * 2;
|
|
989
|
+
if (slotHeight > 0) {
|
|
990
|
+
height = Math.min(height, slotHeight);
|
|
991
|
+
}
|
|
992
|
+
int width = Math.max(height, contentWidth + dp(INDICATOR_HORIZONTAL_PADDING_DP) * 2);
|
|
993
|
+
if (slotWidth > 0) {
|
|
994
|
+
width = Math.min(width, slotWidth - dp(INDICATOR_SLOT_INSET_DP) * 2);
|
|
995
|
+
}
|
|
996
|
+
setMeasuredDimension(width, height);
|
|
997
|
+
Drawable background = getBackground();
|
|
998
|
+
if (background instanceof GradientDrawable) {
|
|
999
|
+
((GradientDrawable) background).setCornerRadius(height / 2f);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
private int dp(int value) {
|
|
1004
|
+
return Math.round(value * getResources().getDisplayMetrics().density);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
927
1008
|
private Drawable iconFrom(JSONObject descriptor) {
|
|
928
1009
|
if (descriptor == null) {
|
|
929
1010
|
return null;
|
|
@@ -1218,11 +1299,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1218
1299
|
return;
|
|
1219
1300
|
}
|
|
1220
1301
|
|
|
1221
|
-
|
|
1222
|
-
int childWidth = width / count;
|
|
1223
|
-
for (int index = 0; index < getChildCount(); index++) {
|
|
1224
|
-
measureChildExact(getChildAt(index), childWidth, height);
|
|
1225
|
-
}
|
|
1302
|
+
measureRange(0, getChildCount(), width, height);
|
|
1226
1303
|
setMeasuredDimension(width, height);
|
|
1227
1304
|
}
|
|
1228
1305
|
|
|
@@ -1285,28 +1362,56 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1285
1362
|
}
|
|
1286
1363
|
|
|
1287
1364
|
private void measureRange(int start, int end, int width, int height) {
|
|
1288
|
-
int
|
|
1289
|
-
if (count == 0) {
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
int childWidth = Math.max(0, width / count);
|
|
1365
|
+
int[] widths = slotWidths(start, end, width);
|
|
1293
1366
|
for (int index = start; index < end; index++) {
|
|
1294
|
-
measureChildExact(getChildAt(index),
|
|
1367
|
+
measureChildExact(getChildAt(index), widths[index - start], height);
|
|
1295
1368
|
}
|
|
1296
1369
|
}
|
|
1297
1370
|
|
|
1298
1371
|
private void layoutRange(int start, int end, int left, int top, int width, int height) {
|
|
1299
|
-
int
|
|
1300
|
-
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
int childWidth = Math.max(0, width / count);
|
|
1372
|
+
int[] widths = slotWidths(start, end, width);
|
|
1373
|
+
int childLeft = left;
|
|
1304
1374
|
for (int index = start; index < end; index++) {
|
|
1305
|
-
int
|
|
1375
|
+
int childWidth = widths[index - start];
|
|
1306
1376
|
getChildAt(index).layout(childLeft, top, childLeft + childWidth, top + height);
|
|
1377
|
+
childLeft += childWidth;
|
|
1307
1378
|
}
|
|
1308
1379
|
}
|
|
1309
1380
|
|
|
1381
|
+
/**
|
|
1382
|
+
* Each tab gets the width its content needs; leftover space is shared equally so the gaps stay even. When the
|
|
1383
|
+
* bar is too narrow, slots shrink in proportion and long labels autosize down.
|
|
1384
|
+
*/
|
|
1385
|
+
private int[] slotWidths(int start, int end, int width) {
|
|
1386
|
+
int count = Math.max(0, end - start);
|
|
1387
|
+
int[] widths = new int[count];
|
|
1388
|
+
if (count == 0) {
|
|
1389
|
+
return widths;
|
|
1390
|
+
}
|
|
1391
|
+
int total = 0;
|
|
1392
|
+
for (int index = 0; index < count; index++) {
|
|
1393
|
+
View child = getChildAt(start + index);
|
|
1394
|
+
int preferred = child instanceof TabButton ? ((TabButton) child).preferredWidth : 0;
|
|
1395
|
+
widths[index] = Math.max(preferred, 0);
|
|
1396
|
+
total += widths[index];
|
|
1397
|
+
}
|
|
1398
|
+
if (total <= 0) {
|
|
1399
|
+
for (int index = 0; index < count; index++) {
|
|
1400
|
+
widths[index] = width / count;
|
|
1401
|
+
}
|
|
1402
|
+
} else if (total <= width) {
|
|
1403
|
+
int extra = (width - total) / count;
|
|
1404
|
+
for (int index = 0; index < count; index++) {
|
|
1405
|
+
widths[index] += extra;
|
|
1406
|
+
}
|
|
1407
|
+
} else {
|
|
1408
|
+
for (int index = 0; index < count; index++) {
|
|
1409
|
+
widths[index] = (int) ((long) widths[index] * width / total);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return widths;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1310
1415
|
private void measureChildExact(View child, int width, int height) {
|
|
1311
1416
|
child.measure(
|
|
1312
1417
|
MeasureSpec.makeMeasureSpec(Math.max(0, width), MeasureSpec.EXACTLY),
|
|
@@ -1650,11 +1755,14 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1650
1755
|
if (tabbarContainer != null) {
|
|
1651
1756
|
tabbarContainer.setBackgroundColor(Color.TRANSPARENT);
|
|
1652
1757
|
}
|
|
1653
|
-
|
|
1758
|
+
if (tabbarGlassSurface != null) {
|
|
1759
|
+
tabbarGlassSurface.setBackground(chromeBackgroundDrawable(drawColor, 0f));
|
|
1760
|
+
tabbarGlassSurface.setVisibility(View.VISIBLE);
|
|
1761
|
+
}
|
|
1654
1762
|
if (tabbarGlassBackdrop != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
1655
1763
|
View webView = getBridge() == null ? null : getBridge().getWebView();
|
|
1656
1764
|
tabbarGlassBackdrop.setClipPathProvider((width, height) -> tabbar.backgroundPath(width, height));
|
|
1657
|
-
tabbarGlassBackdrop.configure(webView, dp(resolvedGlassOptions.blurRadiusDp),
|
|
1765
|
+
tabbarGlassBackdrop.configure(webView, dp(resolvedGlassOptions.blurRadiusDp), Color.TRANSPARENT);
|
|
1658
1766
|
tabbarGlassBackdrop.setVisibility(View.VISIBLE);
|
|
1659
1767
|
} else if (tabbarGlassBackdrop != null) {
|
|
1660
1768
|
tabbarGlassBackdrop.setClipPathProvider((width, height) -> tabbar.backgroundPath(width, height));
|
|
@@ -1671,15 +1779,36 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1671
1779
|
}
|
|
1672
1780
|
}
|
|
1673
1781
|
|
|
1674
|
-
|
|
1782
|
+
boolean glassBlurActive =
|
|
1783
|
+
resolvedGlassOptions.isLiquidGlass() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
|
|
1784
|
+
int tabbarFillColor = glassBlurActive ? Color.TRANSPARENT : drawColor;
|
|
1785
|
+
tabbar.setTabbarStyle(tabbarStyle, tabbarFillColor, centerIndex);
|
|
1675
1786
|
applyTabbarContainerOutline();
|
|
1676
1787
|
}
|
|
1677
1788
|
|
|
1789
|
+
private GlassOptions resolveTabbarGlassOptions(PluginCall call) {
|
|
1790
|
+
if (tabbarGlassConfig != null) {
|
|
1791
|
+
return GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
|
|
1792
|
+
}
|
|
1793
|
+
boolean apiAtLeastS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
|
|
1794
|
+
if (
|
|
1795
|
+
TabbarChromeSupport.shouldEnableDefaultLiquidGlass(apiAtLeastS, tabbarStyle.isCurve(), false)
|
|
1796
|
+
&& (!configureSpecifiedGlassEffect || defaultGlassOptions.isLiquidGlass())
|
|
1797
|
+
) {
|
|
1798
|
+
return GlassOptions.from(new JSObject().put("effect", "liquidGlass"), defaultGlassOptions);
|
|
1799
|
+
}
|
|
1800
|
+
return GlassOptions.from(null, defaultGlassOptions);
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1678
1803
|
private int resolvedTabbarSurfaceColor() {
|
|
1679
1804
|
GlassOptions resolvedGlassOptions = tabbarGlassOptions == null ? GlassOptions.defaults() : tabbarGlassOptions;
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1805
|
+
if (resolvedGlassOptions.isLiquidGlass()) {
|
|
1806
|
+
return glassSurfaceColor(tabbarBackgroundColor, resolvedGlassOptions);
|
|
1807
|
+
}
|
|
1808
|
+
if (!tabbarStyle.isCurve()) {
|
|
1809
|
+
return glassSurfaceColor(tabbarBackgroundColor, resolvedGlassOptions);
|
|
1810
|
+
}
|
|
1811
|
+
return tabbarBackgroundColor;
|
|
1683
1812
|
}
|
|
1684
1813
|
|
|
1685
1814
|
private void reapplyVisibleChromeBackgrounds() {
|
|
@@ -1834,7 +1963,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1834
1963
|
int bottom = navigationBarInset();
|
|
1835
1964
|
int navbarHeight = navbarVisible ? status + dp(DEFAULT_NAVBAR_DP) : 0;
|
|
1836
1965
|
int tabbarHeight = dp(tabbarStyle.totalHeight());
|
|
1837
|
-
|
|
1966
|
+
boolean tabbarLayoutVisible = tabbarVisible || tabbarChromeHideAnimating;
|
|
1967
|
+
int tabbarBottomMargin = tabbarLayoutVisible ? bottom + dp(tabbarStyle.bottomGap) : bottom;
|
|
1838
1968
|
|
|
1839
1969
|
if (navbarContainer != null) {
|
|
1840
1970
|
FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(
|
|
@@ -1857,7 +1987,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1857
1987
|
if (tabbarBackdrop != null) {
|
|
1858
1988
|
// Only the edge-to-edge curve bar extends its surface under the gesture area. A floating
|
|
1859
1989
|
// tabbar must leave the page visible below it, or it sits on a solid band.
|
|
1860
|
-
int backdropHeight =
|
|
1990
|
+
int backdropHeight = tabbarLayoutVisible && tabbarStyle.isCurve() ? bottom + dp(tabbarStyle.bottomGap) : 0;
|
|
1861
1991
|
FrameLayout.LayoutParams backdropParams = new FrameLayout.LayoutParams(
|
|
1862
1992
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1863
1993
|
backdropHeight,
|
|
@@ -1865,7 +1995,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1865
1995
|
);
|
|
1866
1996
|
tabbarBackdrop.setLayoutParams(backdropParams);
|
|
1867
1997
|
tabbarBackdrop.setBackgroundColor(resolvedTabbarSurfaceColor());
|
|
1868
|
-
tabbarBackdrop.setVisibility(
|
|
1998
|
+
tabbarBackdrop.setVisibility(tabbarLayoutVisible && backdropHeight > 0 ? View.VISIBLE : View.GONE);
|
|
1869
1999
|
}
|
|
1870
2000
|
if (tabbarContainer != null) {
|
|
1871
2001
|
int rootWidth = root.getWidth() > 0 ? root.getWidth() : Resources.getSystem().getDisplayMetrics().widthPixels;
|
|
@@ -1907,6 +2037,97 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1907
2037
|
bringChromeToFront();
|
|
1908
2038
|
}
|
|
1909
2039
|
|
|
2040
|
+
private void setTabbarChromeVisible(boolean visible, boolean animated) {
|
|
2041
|
+
if (!animated) {
|
|
2042
|
+
tabbarChromeHideAnimating = false;
|
|
2043
|
+
int visibility = visible ? View.VISIBLE : View.GONE;
|
|
2044
|
+
if (tabbar != null) {
|
|
2045
|
+
tabbar.setVisibility(visibility);
|
|
2046
|
+
}
|
|
2047
|
+
if (tabbarContainer != null) {
|
|
2048
|
+
tabbarContainer.animate().cancel();
|
|
2049
|
+
tabbarContainer.setTranslationY(0f);
|
|
2050
|
+
tabbarContainer.setAlpha(1f);
|
|
2051
|
+
tabbarContainer.setVisibility(visibility);
|
|
2052
|
+
}
|
|
2053
|
+
if (tabbarBackdrop != null) {
|
|
2054
|
+
tabbarBackdrop.animate().cancel();
|
|
2055
|
+
tabbarBackdrop.setAlpha(1f);
|
|
2056
|
+
tabbarBackdrop.setVisibility(visible && tabbarVisible ? View.VISIBLE : View.GONE);
|
|
2057
|
+
}
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
if (tabbarContainer == null) {
|
|
2062
|
+
setTabbarChromeVisible(visible, false);
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
float slideDistance = tabbarSlideDistance();
|
|
2067
|
+
tabbarContainer.animate().cancel();
|
|
2068
|
+
if (tabbarBackdrop != null) {
|
|
2069
|
+
tabbarBackdrop.animate().cancel();
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
if (!visible) {
|
|
2073
|
+
tabbarChromeHideAnimating = true;
|
|
2074
|
+
tabbarContainer
|
|
2075
|
+
.animate()
|
|
2076
|
+
.translationY(slideDistance)
|
|
2077
|
+
.alpha(0f)
|
|
2078
|
+
.setDuration(TabbarChromeSupport.TABBAR_VISIBILITY_ANIMATION_MS)
|
|
2079
|
+
.withEndAction(() -> {
|
|
2080
|
+
tabbarChromeHideAnimating = false;
|
|
2081
|
+
if (tabbar != null) {
|
|
2082
|
+
tabbar.setVisibility(View.GONE);
|
|
2083
|
+
}
|
|
2084
|
+
tabbarContainer.setVisibility(View.GONE);
|
|
2085
|
+
if (tabbarBackdrop != null) {
|
|
2086
|
+
tabbarBackdrop.setVisibility(View.GONE);
|
|
2087
|
+
}
|
|
2088
|
+
})
|
|
2089
|
+
.start();
|
|
2090
|
+
if (tabbarBackdrop != null) {
|
|
2091
|
+
tabbarBackdrop.animate().alpha(0f).setDuration(TabbarChromeSupport.TABBAR_VISIBILITY_ANIMATION_MS).start();
|
|
2092
|
+
}
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
tabbarChromeHideAnimating = false;
|
|
2097
|
+
tabbarContainer.setVisibility(View.VISIBLE);
|
|
2098
|
+
tabbarContainer.setAlpha(0f);
|
|
2099
|
+
tabbarContainer.setTranslationY(slideDistance);
|
|
2100
|
+
if (tabbarBackdrop != null) {
|
|
2101
|
+
tabbarBackdrop.setVisibility(View.VISIBLE);
|
|
2102
|
+
tabbarBackdrop.setAlpha(0f);
|
|
2103
|
+
}
|
|
2104
|
+
if (tabbar != null) {
|
|
2105
|
+
tabbar.setVisibility(View.VISIBLE);
|
|
2106
|
+
}
|
|
2107
|
+
tabbarContainer
|
|
2108
|
+
.animate()
|
|
2109
|
+
.translationY(0f)
|
|
2110
|
+
.alpha(1f)
|
|
2111
|
+
.setDuration(TabbarChromeSupport.TABBAR_VISIBILITY_ANIMATION_MS)
|
|
2112
|
+
.start();
|
|
2113
|
+
if (tabbarBackdrop != null) {
|
|
2114
|
+
tabbarBackdrop.animate().alpha(1f).setDuration(TabbarChromeSupport.TABBAR_VISIBILITY_ANIMATION_MS).start();
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
private float tabbarSlideDistance() {
|
|
2119
|
+
if (tabbarContainer == null) {
|
|
2120
|
+
return dp(tabbarStyle.totalHeight() + tabbarStyle.bottomGap);
|
|
2121
|
+
}
|
|
2122
|
+
ViewGroup.LayoutParams params = tabbarContainer.getLayoutParams();
|
|
2123
|
+
int bottomMargin = params instanceof ViewGroup.MarginLayoutParams ? ((ViewGroup.MarginLayoutParams) params).bottomMargin : 0;
|
|
2124
|
+
return TabbarChromeSupport.tabbarSlideDistancePx(
|
|
2125
|
+
tabbarContainer.getHeight(),
|
|
2126
|
+
bottomMargin,
|
|
2127
|
+
dp(tabbarStyle.totalHeight() + tabbarStyle.bottomGap)
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
1910
2131
|
private void bringChromeToFront() {
|
|
1911
2132
|
if (navbarContainer != null) {
|
|
1912
2133
|
navbarContainer.bringToFront();
|
|
@@ -2079,26 +2300,37 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
2079
2300
|
return Math.max(0, rawNavigationBarInset() - contentRootBottomOffset());
|
|
2080
2301
|
}
|
|
2081
2302
|
|
|
2303
|
+
// API 30+ reads the currently visible bars so a hidden navigation bar (immersive mode) gives the space back.
|
|
2082
2304
|
private int rawStatusBarInset() {
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
if (
|
|
2086
|
-
return insets.
|
|
2305
|
+
WindowInsets insets = rootWindowInsets();
|
|
2306
|
+
if (insets != null) {
|
|
2307
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
2308
|
+
return insets.getInsets(WindowInsets.Type.statusBars() | WindowInsets.Type.displayCutout()).top;
|
|
2087
2309
|
}
|
|
2310
|
+
return insets.getStableInsetTop();
|
|
2088
2311
|
}
|
|
2089
2312
|
return systemDimension("status_bar_height");
|
|
2090
2313
|
}
|
|
2091
2314
|
|
|
2092
2315
|
private int rawNavigationBarInset() {
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
if (
|
|
2096
|
-
return insets.
|
|
2316
|
+
WindowInsets insets = rootWindowInsets();
|
|
2317
|
+
if (insets != null) {
|
|
2318
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
2319
|
+
return insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
|
|
2097
2320
|
}
|
|
2321
|
+
return insets.getStableInsetBottom();
|
|
2098
2322
|
}
|
|
2099
2323
|
return systemDimension("navigation_bar_height");
|
|
2100
2324
|
}
|
|
2101
2325
|
|
|
2326
|
+
private WindowInsets rootWindowInsets() {
|
|
2327
|
+
Activity activity = getActivity();
|
|
2328
|
+
if (activity == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
2329
|
+
return null;
|
|
2330
|
+
}
|
|
2331
|
+
return activity.getWindow().getDecorView().getRootWindowInsets();
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2102
2334
|
private int contentRootTopOffset() {
|
|
2103
2335
|
FrameLayout root = contentRoot();
|
|
2104
2336
|
if (root == null || !root.isLaidOut()) {
|
|
@@ -2136,6 +2368,16 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
2136
2368
|
view.post(this::updateInsetsAndNotify);
|
|
2137
2369
|
}
|
|
2138
2370
|
});
|
|
2371
|
+
// Showing or hiding a system bar changes the insets without necessarily moving the content root
|
|
2372
|
+
// (edge-to-edge apps). A zero-size probe hears every inset dispatch without replacing anyone's listener.
|
|
2373
|
+
View insetsProbe = new View(getContext());
|
|
2374
|
+
insetsProbe.setOnApplyWindowInsetsListener((view, insets) -> {
|
|
2375
|
+
if (navbarVisible || tabbarVisible) {
|
|
2376
|
+
view.post(this::updateInsetsAndNotify);
|
|
2377
|
+
}
|
|
2378
|
+
return insets;
|
|
2379
|
+
});
|
|
2380
|
+
root.addView(insetsProbe, new FrameLayout.LayoutParams(0, 0));
|
|
2139
2381
|
}
|
|
2140
2382
|
|
|
2141
2383
|
private int systemDimension(String name) {
|
|
@@ -6,6 +6,8 @@ import android.os.Build;
|
|
|
6
6
|
|
|
7
7
|
public final class TabbarChromeSupport {
|
|
8
8
|
|
|
9
|
+
public static final int TABBAR_VISIBILITY_ANIMATION_MS = 200;
|
|
10
|
+
|
|
9
11
|
public enum TabbarOutlineMode {
|
|
10
12
|
FLOATING_SHAPE,
|
|
11
13
|
CURVE_DEFAULT
|
|
@@ -13,6 +15,13 @@ public final class TabbarChromeSupport {
|
|
|
13
15
|
|
|
14
16
|
private TabbarChromeSupport() {}
|
|
15
17
|
|
|
18
|
+
public static float tabbarSlideDistancePx(int containerHeight, int bottomMargin, int fallbackDistance) {
|
|
19
|
+
if (containerHeight > 0) {
|
|
20
|
+
return (float) containerHeight + (float) bottomMargin;
|
|
21
|
+
}
|
|
22
|
+
return (float) fallbackDistance;
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
public static int resolveSelectedIndicatorColor(Integer explicitColor, int tintColor) {
|
|
17
26
|
if (explicitColor != null) {
|
|
18
27
|
return explicitColor;
|
|
@@ -28,6 +37,10 @@ public final class TabbarChromeSupport {
|
|
|
28
37
|
return mode == TabbarOutlineMode.FLOATING_SHAPE;
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
public static boolean shouldEnableDefaultLiquidGlass(boolean apiAtLeastS, boolean isCurve, boolean glassSpecified) {
|
|
41
|
+
return !glassSpecified && !isCurve && apiAtLeastS;
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
public static int floatingCapsuleWidth(int totalWidth, int barHeightPx, int trailingGapPx, boolean hasDetachedTrailing) {
|
|
32
45
|
if (!hasDetachedTrailing) {
|
|
33
46
|
return totalWidth;
|
package/dist/docs.json
CHANGED
|
@@ -419,7 +419,7 @@
|
|
|
419
419
|
{
|
|
420
420
|
"name": "background",
|
|
421
421
|
"tags": [],
|
|
422
|
-
"docs": "Optional background tint. Ignored on iOS 26+ so UIKit can preserve the\nsystem Liquid Glass navigation appearance.",
|
|
422
|
+
"docs": "Optional background tint. Ignored on iOS 26+ so UIKit can preserve the\nsystem Liquid Glass navigation appearance. On Android, alpha is honored.\nWhen `glass.effect` is not `liquidGlass`, floating tabbars still apply\n`glass.surfaceAlpha` (from `configure` / `setTabbar`) to the fill tint.",
|
|
423
423
|
"complexTypes": [],
|
|
424
424
|
"type": "string | undefined"
|
|
425
425
|
},
|
|
@@ -791,7 +791,7 @@
|
|
|
791
791
|
{
|
|
792
792
|
"name": "glass",
|
|
793
793
|
"tags": [],
|
|
794
|
-
"docs": "Optional glass background behavior. Overrides `configure({ glass })` for\nthis tabbar update.",
|
|
794
|
+
"docs": "Optional glass background behavior. Overrides `configure({ glass })` for\nthis tabbar update. On Android 12+, floating tabbars default to\n`liquidGlass` (live blur plus tint) when this field is omitted. Pass\n`effect: 'none'` for a flat translucent fill without blur.",
|
|
795
795
|
"complexTypes": [
|
|
796
796
|
"NativeNavigationGlassOptions"
|
|
797
797
|
],
|
|
@@ -150,7 +150,9 @@ export interface NativeNavigationColors {
|
|
|
150
150
|
inactiveTint?: string;
|
|
151
151
|
/**
|
|
152
152
|
* Optional background tint. Ignored on iOS 26+ so UIKit can preserve the
|
|
153
|
-
* system Liquid Glass navigation appearance.
|
|
153
|
+
* system Liquid Glass navigation appearance. On Android, alpha is honored.
|
|
154
|
+
* When `glass.effect` is not `liquidGlass`, floating tabbars still apply
|
|
155
|
+
* `glass.surfaceAlpha` (from `configure` / `setTabbar`) to the fill tint.
|
|
154
156
|
*/
|
|
155
157
|
background?: string;
|
|
156
158
|
/**
|
|
@@ -439,7 +441,9 @@ export interface NativeNavigationTabbarOptions {
|
|
|
439
441
|
blurEffect?: NativeNavigationBlurEffect;
|
|
440
442
|
/**
|
|
441
443
|
* Optional glass background behavior. Overrides `configure({ glass })` for
|
|
442
|
-
* this tabbar update.
|
|
444
|
+
* this tabbar update. On Android 12+, floating tabbars default to
|
|
445
|
+
* `liquidGlass` (live blur plus tint) when this field is omitted. Pass
|
|
446
|
+
* `effect: 'none'` for a flat translucent fill without blur.
|
|
443
447
|
*/
|
|
444
448
|
glass?: NativeNavigationGlassOptions;
|
|
445
449
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Platform rendering preference for the native bars.\n */\nexport type NativeNavigationPlatformStyle = 'auto' | 'ios' | 'android';\n\n/**\n * How the plugin exposes native bar sizes to web content.\n */\nexport type NativeNavigationContentInsetMode = 'css' | 'none';\n\n/**\n * Navigation animation direction.\n */\nexport type NativeNavigationTransitionDirection = 'forward' | 'back' | 'root' | 'tab' | 'zoom' | 'none';\n\n/**\n * Native material/blur effect preference.\n */\nexport type NativeNavigationBlurEffect =\n | 'none'\n | 'systemDefault'\n | 'extraLight'\n | 'light'\n | 'dark'\n | 'regular'\n | 'prominent'\n | 'systemUltraThinMaterial'\n | 'systemThinMaterial'\n | 'systemMaterial'\n | 'systemThickMaterial'\n | 'systemChromeMaterial'\n | 'systemUltraThinMaterialLight'\n | 'systemThinMaterialLight'\n | 'systemMaterialLight'\n | 'systemThickMaterialLight'\n | 'systemChromeMaterialLight'\n | 'systemUltraThinMaterialDark'\n | 'systemThinMaterialDark'\n | 'systemMaterialDark'\n | 'systemThickMaterialDark'\n | 'systemChromeMaterialDark';\n\n/**\n * Native glass background rendering preference.\n */\nexport type NativeNavigationGlassEffect = 'none' | 'liquidGlass';\n\n/**\n * Native glass background configuration.\n */\nexport interface NativeNavigationGlassOptions {\n /**\n * `liquidGlass` enables the Android 12+ live blurred WebView backdrop for\n * native bars. Android 11 and older keep a translucent surface fallback. iOS\n * uses the platform-owned Liquid Glass behavior.\n */\n effect?: NativeNavigationGlassEffect;\n\n /**\n * Android blur radius in native dp for `liquidGlass`. Defaults to `18`.\n */\n blurRadius?: number;\n\n /**\n * Alpha multiplier for the tint surface over the glass backdrop. Defaults to\n * `0.62`.\n */\n surfaceAlpha?: number;\n}\n\n/**\n * Native tab label visibility behavior.\n */\nexport type NativeNavigationTabLabelVisibilityMode = 'auto' | 'selected' | 'labeled' | 'unlabeled';\n\n/**\n * A rectangle in WebView viewport coordinates, expressed in native points/dp.\n */\nexport interface NativeNavigationRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Native tabbar background shape.\n */\nexport type NativeNavigationTabbarShape = 'floating' | 'curve';\n\n/**\n * Native tab role for Liquid Glass tab bars.\n *\n * `search` (and `prominent` when the OS supports it) renders as a detached\n * trailing circular action beside the floating tab capsule — the Apple News /\n * Photos pattern. Only one detached trailing role is used; if multiple tabs\n * set `search` or `prominent`, the last one wins. Curve-shaped bars ignore\n * `role` and keep using the included center action instead.\n */\nexport type NativeNavigationTabRole = 'normal' | 'search' | 'prominent';\n\n/**\n * A serializable icon descriptor. Framework nodes are intentionally not accepted\n * because icons are rendered by native UI.\n */\nexport interface NativeNavigationIcon {\n /**\n * Cross-platform asset path or URL fallback.\n */\n src?: string;\n\n /**\n * Cross-platform inline SVG markup. The native renderers support common icon\n * shapes such as path, line, polyline, polygon, circle, and rect. SVG icons\n * are rendered as template images by default so native tint colors still\n * apply.\n */\n svg?: string;\n\n /**\n * Preferred rendered icon width in native points/dp. Defaults to `24`.\n */\n width?: number;\n\n /**\n * Preferred rendered icon height in native points/dp. Defaults to `24`.\n */\n height?: number;\n\n /**\n * When `true`, native tint colors are applied to the rendered SVG/image.\n * Defaults to `true`.\n */\n template?: boolean;\n\n /**\n * iOS-specific SF Symbol, bundled image name, or inline SVG.\n */\n ios?: {\n /**\n * SF Symbol name, for example `house.fill`.\n */\n sfSymbol?: string;\n\n /**\n * Bundled image name from the app asset catalog.\n */\n image?: string;\n\n /**\n * iOS-specific inline SVG markup.\n */\n svg?: string;\n };\n\n /**\n * Android-specific drawable resource, asset name, or inline SVG.\n */\n android?: {\n /**\n * Drawable resource name without the `R.drawable.` prefix.\n */\n resource?: string;\n\n /**\n * Bundled image asset name.\n */\n image?: string;\n\n /**\n * Android-specific inline SVG markup.\n */\n svg?: string;\n };\n}\n\n/**\n * Native bar colors. Use CSS-style hex strings (`#RRGGBB` or `#AARRGGBB`).\n */\nexport interface NativeNavigationColors {\n /**\n * When `true`, Android 12+ derives unspecified bar colors from Material You\n * system palettes. Explicit color fields still win.\n */\n dynamic?: boolean;\n\n /**\n * Tint color for active buttons/items.\n */\n tint?: string;\n\n /**\n * Color for inactive tab items. Ignored on iOS 26+ unless\n * `experimentalBakedTintColors` is enabled.\n */\n inactiveTint?: string;\n\n /**\n * Optional background tint. Ignored on iOS 26+ so UIKit can preserve the\n * system Liquid Glass navigation appearance.\n */\n background?: string;\n\n /**\n * Title and label text color where the native platform supports it.\n */\n foreground?: string;\n\n /**\n * Badge background color for native tab badges.\n */\n badgeBackground?: string;\n\n /**\n * Badge text color for native tab badges.\n */\n badgeText?: string;\n\n /**\n * Active tab indicator color on Android.\n */\n indicator?: string;\n\n /**\n * Tab press ripple color on Android.\n */\n ripple?: string;\n}\n\n/**\n * Global plugin configuration.\n */\nexport interface NativeNavigationConfigureOptions {\n /**\n * Enables or disables the native chrome host.\n */\n enabled?: boolean;\n\n /**\n * Native style preference. `auto` uses the current platform.\n */\n platformStyle?: NativeNavigationPlatformStyle;\n\n /**\n * When `css`, the plugin writes CSS variables on `document.documentElement`.\n */\n contentInsetMode?: NativeNavigationContentInsetMode;\n\n /**\n * Default native transition duration in milliseconds.\n */\n animationDuration?: number;\n\n /**\n * Shared color hints for native bars.\n */\n colors?: NativeNavigationColors;\n\n /**\n * Shared glass background defaults for native bars.\n */\n glass?: NativeNavigationGlassOptions;\n}\n\n/**\n * A button shown in the native navbar.\n */\nexport interface NativeNavigationBarButton {\n /**\n * Stable id returned in `navbarItemTap`.\n */\n id: string;\n\n /**\n * Visible text label.\n */\n title?: string;\n\n /**\n * Native icon descriptor.\n */\n icon?: NativeNavigationIcon;\n\n /**\n * Whether the action is enabled. Defaults to `true`.\n */\n enabled?: boolean;\n}\n\n/**\n * Native back button configuration.\n */\nexport interface NativeNavigationBackButton {\n /**\n * Show the native back affordance.\n */\n visible?: boolean;\n\n /**\n * Optional back title.\n */\n title?: string;\n}\n\n/**\n * Native navbar state.\n */\nexport interface NativeNavigationNavbarOptions {\n /**\n * Hide the native navbar.\n */\n hidden?: boolean;\n\n /**\n * Main title.\n */\n title?: string;\n\n /**\n * Secondary title where supported by the platform.\n */\n subtitle?: string;\n\n /**\n * Prefer a large iOS title style.\n */\n large?: boolean;\n\n /**\n * Prefer transparent/scroll-edge style.\n */\n transparent?: boolean;\n\n /**\n * iOS blur/material effect for the navbar background when glass is not\n * available. Defaults to `systemChromeMaterial` for transparent bars.\n */\n blurEffect?: NativeNavigationBlurEffect;\n\n /**\n * Optional glass background behavior. Overrides `configure({ glass })` for\n * this navbar update.\n */\n glass?: NativeNavigationGlassOptions;\n\n /**\n * Back button state.\n */\n backButton?: NativeNavigationBackButton;\n\n /**\n * Left-side action buttons.\n */\n leftItems?: NativeNavigationBarButton[];\n\n /**\n * Right-side action buttons.\n */\n rightItems?: NativeNavigationBarButton[];\n\n /**\n * Navbar color hints.\n */\n colors?: NativeNavigationColors;\n\n /**\n * Animate native navbar changes.\n */\n animated?: boolean;\n}\n\n/**\n * A native tab item.\n */\nexport interface NativeNavigationTab {\n /**\n * Stable tab id returned in `tabSelect`.\n */\n id: string;\n\n /**\n * Visible tab label.\n */\n title?: string;\n\n /**\n * Native icon descriptor.\n */\n icon?: NativeNavigationIcon;\n\n /**\n * Optional selected-state icon.\n */\n selectedIcon?: NativeNavigationIcon;\n\n /**\n * Optional badge. Numeric badges are supported on both platforms; text badge\n * support depends on platform capabilities.\n */\n badge?: string | number;\n\n /**\n * Whether the tab is enabled. Defaults to `true`.\n */\n enabled?: boolean;\n\n /**\n * Hide the tab item from the native tabbar. When the hidden tab is selected,\n * native platform constraints may keep it visible until another tab is\n * selected.\n */\n hidden?: boolean;\n\n /**\n * Optional tab role. On floating tabbars, `search` (or `prominent` when\n * available) becomes a detached trailing circular action beside the capsule\n * — including the iOS 26+ system Liquid Glass tab bar, Android floating\n * layout, and the custom floating capsule path. Only one detached trailing\n * role is used; if multiple tabs set `search` or `prominent`, the last one\n * wins. Curve-shaped bars ignore `role` and keep using the included center\n * action. Defaults to `normal`.\n */\n role?: NativeNavigationTabRole;\n}\n\n/**\n * Native tabbar layout and background shape options.\n */\nexport interface NativeNavigationTabbarStyle {\n /**\n * `floating` keeps the capsule tabbar. On iOS 26+ this uses the\n * system-owned Liquid Glass `UITabBarController` unless a custom capsule\n * path is required; earlier iOS and the custom capsule path use\n * `UIGlassEffect` on iOS 26+ (blur material fallback otherwise). `curve`\n * draws a full-width bar with an included center action.\n */\n shape?: NativeNavigationTabbarShape;\n\n /**\n * Bar height in native points/dp, not including the system bottom inset.\n * Curve defaults to `49` on iOS (83pt once the home indicator is added) and\n * `80` on Android (Material navigation bar). Floating defaults to `64`.\n * Pass `height` to override either platform.\n */\n height?: number;\n\n /**\n * Horizontal margin in native points/dp. Defaults to `24` for `floating` and\n * `0` for `curve`.\n */\n horizontalMargin?: number;\n\n /**\n * Maximum tabbar width in native points/dp. Defaults to `430` for `floating`;\n * `curve` uses the available width unless this is set.\n */\n maxWidth?: number;\n\n /**\n * Bottom gap above the platform safe area in native points/dp. Defaults to\n * `10` for `floating` and `0` for `curve`.\n */\n bottomGap?: number;\n\n /**\n * Background corner radius in native points/dp. Defaults to a capsule radius\n * for `floating` and `0` for `curve`.\n */\n cornerRadius?: number;\n\n /**\n * Tab id promoted into the included center button for `curve`. Defaults to\n * the middle tab.\n */\n centerItemId?: string;\n\n /**\n * Included center button diameter in native points/dp. Defaults to `56`.\n */\n centerButtonDiameter?: number;\n\n /**\n * Distance from the top of the center button to the top edge of the bar in\n * native points/dp. Defaults to half of `centerButtonDiameter`.\n */\n centerButtonLift?: number;\n\n /**\n * Included center button color. Defaults to the active tint color.\n */\n centerButtonColor?: string;\n\n /**\n * Included center button icon color. Defaults to white.\n */\n centerButtonIconColor?: string;\n}\n\n/**\n * Native tabbar state.\n */\nexport interface NativeNavigationTabbarOptions {\n /**\n * Hide the native tabbar.\n */\n hidden?: boolean;\n\n /**\n * Tab definitions.\n */\n tabs?: NativeNavigationTab[];\n\n /**\n * Currently selected tab id.\n */\n selectedId?: string;\n\n /**\n * Show text labels. Defaults to `true`.\n */\n labels?: boolean;\n\n /**\n * Native label visibility mode. Overrides `labels` when provided.\n */\n labelVisibilityMode?: NativeNavigationTabLabelVisibilityMode;\n\n /**\n * Show icons. Defaults to `true`.\n */\n icons?: boolean;\n\n /**\n * Tabbar color hints.\n */\n colors?: NativeNavigationColors;\n\n /**\n * iOS blur/material effect for the tabbar background when glass is not\n * available.\n */\n blurEffect?: NativeNavigationBlurEffect;\n\n /**\n * Optional glass background behavior. Overrides `configure({ glass })` for\n * this tabbar update.\n */\n glass?: NativeNavigationGlassOptions;\n\n /**\n * Opt into the iOS 26 Liquid Glass tint workaround that renders active and\n * inactive tab items into baked images. This can affect badge positioning\n * and icon sizing, so it is disabled by default.\n */\n experimentalBakedTintColors?: boolean;\n\n /**\n * Keep the iOS scroll-edge tabbar appearance from becoming transparent.\n * Mirrors Expo Router native tabs' `disableTransparentOnScrollEdge` option.\n * Defaults to `false`.\n */\n disableTransparentOnScrollEdge?: boolean;\n\n /**\n * Disable the Android active tab indicator.\n */\n disableIndicator?: boolean;\n\n /**\n * Active tab indicator color on Android. `colors.indicator` is also\n * supported.\n */\n indicatorColor?: string;\n\n /**\n * Tab press ripple color on Android. `colors.ripple` is also supported.\n */\n rippleColor?: string;\n\n /**\n * Badge background color. `colors.badgeBackground` is also supported.\n */\n badgeBackgroundColor?: string;\n\n /**\n * Badge text color. `colors.badgeText` is also supported.\n */\n badgeTextColor?: string;\n\n /**\n * Optional native tabbar layout and shape customization.\n */\n style?: NativeNavigationTabbarStyle;\n\n /**\n * Animate native tabbar changes.\n */\n animated?: boolean;\n}\n\n/**\n * Insets exposed to web content.\n */\nexport interface NativeNavigationInsets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n navbarHeight: number;\n tabbarHeight: number;\n}\n\n/**\n * Returned by methods that may change safe content bounds.\n */\nexport interface NativeNavigationInsetsResult {\n insets: NativeNavigationInsets;\n}\n\n/**\n * Begin a native transition transaction before JS changes route content.\n */\nexport interface NativeNavigationBeginTransitionOptions {\n id?: string;\n direction?: NativeNavigationTransitionDirection;\n duration?: number;\n /**\n * Source rectangle for `zoom` transitions. Use viewport coordinates such as\n * those returned by `Element.getBoundingClientRect()`.\n */\n sourceRect?: NativeNavigationRect;\n /**\n * Destination rectangle for shared-element-style `zoom` transitions.\n */\n targetRect?: NativeNavigationRect;\n /**\n * Corner radius used while animating a `zoom` transition.\n */\n cornerRadius?: number;\n}\n\n/**\n * Finish a native transition transaction after JS has changed route content.\n */\nexport interface NativeNavigationFinishTransitionOptions {\n id?: string;\n direction?: NativeNavigationTransitionDirection;\n duration?: number;\n /**\n * Source rectangle for `zoom` transitions when no active source was recorded.\n */\n sourceRect?: NativeNavigationRect;\n /**\n * Destination rectangle for shared-element-style `zoom` transitions.\n */\n targetRect?: NativeNavigationRect;\n /**\n * Corner radius used while animating a `zoom` transition.\n */\n cornerRadius?: number;\n}\n\n/**\n * Native transition result.\n */\nexport interface NativeNavigationTransitionResult {\n id: string;\n direction: NativeNavigationTransitionDirection;\n duration: number;\n}\n\n/**\n * Plugin version payload.\n */\nexport interface PluginVersionResult {\n /**\n * Version identifier returned by the platform implementation.\n */\n version: string;\n}\n\nexport interface NativeNavigationBackEvent {\n source: 'navbar';\n}\n\nexport interface NativeNavigationBarItemTapEvent {\n id: string;\n title?: string;\n placement: 'left' | 'right';\n}\n\nexport interface NativeNavigationTabSelectEvent {\n id: string;\n index: number;\n title?: string;\n}\n\nexport interface NativeNavigationSafeAreaChangedEvent {\n insets: NativeNavigationInsets;\n}\n\nexport interface NativeNavigationTransitionEvent {\n id: string;\n direction: NativeNavigationTransitionDirection;\n duration: number;\n}\n\n/**\n * Framework-agnostic native navigation chrome API.\n */\nexport interface NativeNavigationPlugin {\n /**\n * Configure the native chrome host and content inset behavior.\n */\n configure(options?: NativeNavigationConfigureOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Render or update the native navbar.\n */\n setNavbar(options: NativeNavigationNavbarOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Render or update the native tabbar.\n */\n setTabbar(options: NativeNavigationTabbarOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Capture the current WebView and prepare a native transition.\n */\n beginTransition(options?: NativeNavigationBeginTransitionOptions): Promise<NativeNavigationTransitionResult>;\n\n /**\n * Animate from the captured WebView snapshot to the current live WebView.\n */\n finishTransition(options?: NativeNavigationFinishTransitionOptions): Promise<NativeNavigationTransitionResult>;\n\n /**\n * Returns the platform implementation version marker.\n */\n getPluginVersion(): Promise<PluginVersionResult>;\n\n addListener(\n eventName: 'navbarBack',\n listenerFunc: (event: NativeNavigationBackEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'navbarItemTap',\n listenerFunc: (event: NativeNavigationBarItemTapEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'tabSelect',\n listenerFunc: (event: NativeNavigationTabSelectEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'safeAreaChanged',\n listenerFunc: (event: NativeNavigationSafeAreaChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'transitionStart',\n listenerFunc: (event: NativeNavigationTransitionEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'transitionEnd',\n listenerFunc: (event: NativeNavigationTransitionEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Platform rendering preference for the native bars.\n */\nexport type NativeNavigationPlatformStyle = 'auto' | 'ios' | 'android';\n\n/**\n * How the plugin exposes native bar sizes to web content.\n */\nexport type NativeNavigationContentInsetMode = 'css' | 'none';\n\n/**\n * Navigation animation direction.\n */\nexport type NativeNavigationTransitionDirection = 'forward' | 'back' | 'root' | 'tab' | 'zoom' | 'none';\n\n/**\n * Native material/blur effect preference.\n */\nexport type NativeNavigationBlurEffect =\n | 'none'\n | 'systemDefault'\n | 'extraLight'\n | 'light'\n | 'dark'\n | 'regular'\n | 'prominent'\n | 'systemUltraThinMaterial'\n | 'systemThinMaterial'\n | 'systemMaterial'\n | 'systemThickMaterial'\n | 'systemChromeMaterial'\n | 'systemUltraThinMaterialLight'\n | 'systemThinMaterialLight'\n | 'systemMaterialLight'\n | 'systemThickMaterialLight'\n | 'systemChromeMaterialLight'\n | 'systemUltraThinMaterialDark'\n | 'systemThinMaterialDark'\n | 'systemMaterialDark'\n | 'systemThickMaterialDark'\n | 'systemChromeMaterialDark';\n\n/**\n * Native glass background rendering preference.\n */\nexport type NativeNavigationGlassEffect = 'none' | 'liquidGlass';\n\n/**\n * Native glass background configuration.\n */\nexport interface NativeNavigationGlassOptions {\n /**\n * `liquidGlass` enables the Android 12+ live blurred WebView backdrop for\n * native bars. Android 11 and older keep a translucent surface fallback. iOS\n * uses the platform-owned Liquid Glass behavior.\n */\n effect?: NativeNavigationGlassEffect;\n\n /**\n * Android blur radius in native dp for `liquidGlass`. Defaults to `18`.\n */\n blurRadius?: number;\n\n /**\n * Alpha multiplier for the tint surface over the glass backdrop. Defaults to\n * `0.62`.\n */\n surfaceAlpha?: number;\n}\n\n/**\n * Native tab label visibility behavior.\n */\nexport type NativeNavigationTabLabelVisibilityMode = 'auto' | 'selected' | 'labeled' | 'unlabeled';\n\n/**\n * A rectangle in WebView viewport coordinates, expressed in native points/dp.\n */\nexport interface NativeNavigationRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Native tabbar background shape.\n */\nexport type NativeNavigationTabbarShape = 'floating' | 'curve';\n\n/**\n * Native tab role for Liquid Glass tab bars.\n *\n * `search` (and `prominent` when the OS supports it) renders as a detached\n * trailing circular action beside the floating tab capsule — the Apple News /\n * Photos pattern. Only one detached trailing role is used; if multiple tabs\n * set `search` or `prominent`, the last one wins. Curve-shaped bars ignore\n * `role` and keep using the included center action instead.\n */\nexport type NativeNavigationTabRole = 'normal' | 'search' | 'prominent';\n\n/**\n * A serializable icon descriptor. Framework nodes are intentionally not accepted\n * because icons are rendered by native UI.\n */\nexport interface NativeNavigationIcon {\n /**\n * Cross-platform asset path or URL fallback.\n */\n src?: string;\n\n /**\n * Cross-platform inline SVG markup. The native renderers support common icon\n * shapes such as path, line, polyline, polygon, circle, and rect. SVG icons\n * are rendered as template images by default so native tint colors still\n * apply.\n */\n svg?: string;\n\n /**\n * Preferred rendered icon width in native points/dp. Defaults to `24`.\n */\n width?: number;\n\n /**\n * Preferred rendered icon height in native points/dp. Defaults to `24`.\n */\n height?: number;\n\n /**\n * When `true`, native tint colors are applied to the rendered SVG/image.\n * Defaults to `true`.\n */\n template?: boolean;\n\n /**\n * iOS-specific SF Symbol, bundled image name, or inline SVG.\n */\n ios?: {\n /**\n * SF Symbol name, for example `house.fill`.\n */\n sfSymbol?: string;\n\n /**\n * Bundled image name from the app asset catalog.\n */\n image?: string;\n\n /**\n * iOS-specific inline SVG markup.\n */\n svg?: string;\n };\n\n /**\n * Android-specific drawable resource, asset name, or inline SVG.\n */\n android?: {\n /**\n * Drawable resource name without the `R.drawable.` prefix.\n */\n resource?: string;\n\n /**\n * Bundled image asset name.\n */\n image?: string;\n\n /**\n * Android-specific inline SVG markup.\n */\n svg?: string;\n };\n}\n\n/**\n * Native bar colors. Use CSS-style hex strings (`#RRGGBB` or `#AARRGGBB`).\n */\nexport interface NativeNavigationColors {\n /**\n * When `true`, Android 12+ derives unspecified bar colors from Material You\n * system palettes. Explicit color fields still win.\n */\n dynamic?: boolean;\n\n /**\n * Tint color for active buttons/items.\n */\n tint?: string;\n\n /**\n * Color for inactive tab items. Ignored on iOS 26+ unless\n * `experimentalBakedTintColors` is enabled.\n */\n inactiveTint?: string;\n\n /**\n * Optional background tint. Ignored on iOS 26+ so UIKit can preserve the\n * system Liquid Glass navigation appearance. On Android, alpha is honored.\n * When `glass.effect` is not `liquidGlass`, floating tabbars still apply\n * `glass.surfaceAlpha` (from `configure` / `setTabbar`) to the fill tint.\n */\n background?: string;\n\n /**\n * Title and label text color where the native platform supports it.\n */\n foreground?: string;\n\n /**\n * Badge background color for native tab badges.\n */\n badgeBackground?: string;\n\n /**\n * Badge text color for native tab badges.\n */\n badgeText?: string;\n\n /**\n * Active tab indicator color on Android.\n */\n indicator?: string;\n\n /**\n * Tab press ripple color on Android.\n */\n ripple?: string;\n}\n\n/**\n * Global plugin configuration.\n */\nexport interface NativeNavigationConfigureOptions {\n /**\n * Enables or disables the native chrome host.\n */\n enabled?: boolean;\n\n /**\n * Native style preference. `auto` uses the current platform.\n */\n platformStyle?: NativeNavigationPlatformStyle;\n\n /**\n * When `css`, the plugin writes CSS variables on `document.documentElement`.\n */\n contentInsetMode?: NativeNavigationContentInsetMode;\n\n /**\n * Default native transition duration in milliseconds.\n */\n animationDuration?: number;\n\n /**\n * Shared color hints for native bars.\n */\n colors?: NativeNavigationColors;\n\n /**\n * Shared glass background defaults for native bars.\n */\n glass?: NativeNavigationGlassOptions;\n}\n\n/**\n * A button shown in the native navbar.\n */\nexport interface NativeNavigationBarButton {\n /**\n * Stable id returned in `navbarItemTap`.\n */\n id: string;\n\n /**\n * Visible text label.\n */\n title?: string;\n\n /**\n * Native icon descriptor.\n */\n icon?: NativeNavigationIcon;\n\n /**\n * Whether the action is enabled. Defaults to `true`.\n */\n enabled?: boolean;\n}\n\n/**\n * Native back button configuration.\n */\nexport interface NativeNavigationBackButton {\n /**\n * Show the native back affordance.\n */\n visible?: boolean;\n\n /**\n * Optional back title.\n */\n title?: string;\n}\n\n/**\n * Native navbar state.\n */\nexport interface NativeNavigationNavbarOptions {\n /**\n * Hide the native navbar.\n */\n hidden?: boolean;\n\n /**\n * Main title.\n */\n title?: string;\n\n /**\n * Secondary title where supported by the platform.\n */\n subtitle?: string;\n\n /**\n * Prefer a large iOS title style.\n */\n large?: boolean;\n\n /**\n * Prefer transparent/scroll-edge style.\n */\n transparent?: boolean;\n\n /**\n * iOS blur/material effect for the navbar background when glass is not\n * available. Defaults to `systemChromeMaterial` for transparent bars.\n */\n blurEffect?: NativeNavigationBlurEffect;\n\n /**\n * Optional glass background behavior. Overrides `configure({ glass })` for\n * this navbar update.\n */\n glass?: NativeNavigationGlassOptions;\n\n /**\n * Back button state.\n */\n backButton?: NativeNavigationBackButton;\n\n /**\n * Left-side action buttons.\n */\n leftItems?: NativeNavigationBarButton[];\n\n /**\n * Right-side action buttons.\n */\n rightItems?: NativeNavigationBarButton[];\n\n /**\n * Navbar color hints.\n */\n colors?: NativeNavigationColors;\n\n /**\n * Animate native navbar changes.\n */\n animated?: boolean;\n}\n\n/**\n * A native tab item.\n */\nexport interface NativeNavigationTab {\n /**\n * Stable tab id returned in `tabSelect`.\n */\n id: string;\n\n /**\n * Visible tab label.\n */\n title?: string;\n\n /**\n * Native icon descriptor.\n */\n icon?: NativeNavigationIcon;\n\n /**\n * Optional selected-state icon.\n */\n selectedIcon?: NativeNavigationIcon;\n\n /**\n * Optional badge. Numeric badges are supported on both platforms; text badge\n * support depends on platform capabilities.\n */\n badge?: string | number;\n\n /**\n * Whether the tab is enabled. Defaults to `true`.\n */\n enabled?: boolean;\n\n /**\n * Hide the tab item from the native tabbar. When the hidden tab is selected,\n * native platform constraints may keep it visible until another tab is\n * selected.\n */\n hidden?: boolean;\n\n /**\n * Optional tab role. On floating tabbars, `search` (or `prominent` when\n * available) becomes a detached trailing circular action beside the capsule\n * — including the iOS 26+ system Liquid Glass tab bar, Android floating\n * layout, and the custom floating capsule path. Only one detached trailing\n * role is used; if multiple tabs set `search` or `prominent`, the last one\n * wins. Curve-shaped bars ignore `role` and keep using the included center\n * action. Defaults to `normal`.\n */\n role?: NativeNavigationTabRole;\n}\n\n/**\n * Native tabbar layout and background shape options.\n */\nexport interface NativeNavigationTabbarStyle {\n /**\n * `floating` keeps the capsule tabbar. On iOS 26+ this uses the\n * system-owned Liquid Glass `UITabBarController` unless a custom capsule\n * path is required; earlier iOS and the custom capsule path use\n * `UIGlassEffect` on iOS 26+ (blur material fallback otherwise). `curve`\n * draws a full-width bar with an included center action.\n */\n shape?: NativeNavigationTabbarShape;\n\n /**\n * Bar height in native points/dp, not including the system bottom inset.\n * Curve defaults to `49` on iOS (83pt once the home indicator is added) and\n * `80` on Android (Material navigation bar). Floating defaults to `64`.\n * Pass `height` to override either platform.\n */\n height?: number;\n\n /**\n * Horizontal margin in native points/dp. Defaults to `24` for `floating` and\n * `0` for `curve`.\n */\n horizontalMargin?: number;\n\n /**\n * Maximum tabbar width in native points/dp. Defaults to `430` for `floating`;\n * `curve` uses the available width unless this is set.\n */\n maxWidth?: number;\n\n /**\n * Bottom gap above the platform safe area in native points/dp. Defaults to\n * `10` for `floating` and `0` for `curve`.\n */\n bottomGap?: number;\n\n /**\n * Background corner radius in native points/dp. Defaults to a capsule radius\n * for `floating` and `0` for `curve`.\n */\n cornerRadius?: number;\n\n /**\n * Tab id promoted into the included center button for `curve`. Defaults to\n * the middle tab.\n */\n centerItemId?: string;\n\n /**\n * Included center button diameter in native points/dp. Defaults to `56`.\n */\n centerButtonDiameter?: number;\n\n /**\n * Distance from the top of the center button to the top edge of the bar in\n * native points/dp. Defaults to half of `centerButtonDiameter`.\n */\n centerButtonLift?: number;\n\n /**\n * Included center button color. Defaults to the active tint color.\n */\n centerButtonColor?: string;\n\n /**\n * Included center button icon color. Defaults to white.\n */\n centerButtonIconColor?: string;\n}\n\n/**\n * Native tabbar state.\n */\nexport interface NativeNavigationTabbarOptions {\n /**\n * Hide the native tabbar.\n */\n hidden?: boolean;\n\n /**\n * Tab definitions.\n */\n tabs?: NativeNavigationTab[];\n\n /**\n * Currently selected tab id.\n */\n selectedId?: string;\n\n /**\n * Show text labels. Defaults to `true`.\n */\n labels?: boolean;\n\n /**\n * Native label visibility mode. Overrides `labels` when provided.\n */\n labelVisibilityMode?: NativeNavigationTabLabelVisibilityMode;\n\n /**\n * Show icons. Defaults to `true`.\n */\n icons?: boolean;\n\n /**\n * Tabbar color hints.\n */\n colors?: NativeNavigationColors;\n\n /**\n * iOS blur/material effect for the tabbar background when glass is not\n * available.\n */\n blurEffect?: NativeNavigationBlurEffect;\n\n /**\n * Optional glass background behavior. Overrides `configure({ glass })` for\n * this tabbar update. On Android 12+, floating tabbars default to\n * `liquidGlass` (live blur plus tint) when this field is omitted. Pass\n * `effect: 'none'` for a flat translucent fill without blur.\n */\n glass?: NativeNavigationGlassOptions;\n\n /**\n * Opt into the iOS 26 Liquid Glass tint workaround that renders active and\n * inactive tab items into baked images. This can affect badge positioning\n * and icon sizing, so it is disabled by default.\n */\n experimentalBakedTintColors?: boolean;\n\n /**\n * Keep the iOS scroll-edge tabbar appearance from becoming transparent.\n * Mirrors Expo Router native tabs' `disableTransparentOnScrollEdge` option.\n * Defaults to `false`.\n */\n disableTransparentOnScrollEdge?: boolean;\n\n /**\n * Disable the Android active tab indicator.\n */\n disableIndicator?: boolean;\n\n /**\n * Active tab indicator color on Android. `colors.indicator` is also\n * supported.\n */\n indicatorColor?: string;\n\n /**\n * Tab press ripple color on Android. `colors.ripple` is also supported.\n */\n rippleColor?: string;\n\n /**\n * Badge background color. `colors.badgeBackground` is also supported.\n */\n badgeBackgroundColor?: string;\n\n /**\n * Badge text color. `colors.badgeText` is also supported.\n */\n badgeTextColor?: string;\n\n /**\n * Optional native tabbar layout and shape customization.\n */\n style?: NativeNavigationTabbarStyle;\n\n /**\n * Animate native tabbar changes.\n */\n animated?: boolean;\n}\n\n/**\n * Insets exposed to web content.\n */\nexport interface NativeNavigationInsets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n navbarHeight: number;\n tabbarHeight: number;\n}\n\n/**\n * Returned by methods that may change safe content bounds.\n */\nexport interface NativeNavigationInsetsResult {\n insets: NativeNavigationInsets;\n}\n\n/**\n * Begin a native transition transaction before JS changes route content.\n */\nexport interface NativeNavigationBeginTransitionOptions {\n id?: string;\n direction?: NativeNavigationTransitionDirection;\n duration?: number;\n /**\n * Source rectangle for `zoom` transitions. Use viewport coordinates such as\n * those returned by `Element.getBoundingClientRect()`.\n */\n sourceRect?: NativeNavigationRect;\n /**\n * Destination rectangle for shared-element-style `zoom` transitions.\n */\n targetRect?: NativeNavigationRect;\n /**\n * Corner radius used while animating a `zoom` transition.\n */\n cornerRadius?: number;\n}\n\n/**\n * Finish a native transition transaction after JS has changed route content.\n */\nexport interface NativeNavigationFinishTransitionOptions {\n id?: string;\n direction?: NativeNavigationTransitionDirection;\n duration?: number;\n /**\n * Source rectangle for `zoom` transitions when no active source was recorded.\n */\n sourceRect?: NativeNavigationRect;\n /**\n * Destination rectangle for shared-element-style `zoom` transitions.\n */\n targetRect?: NativeNavigationRect;\n /**\n * Corner radius used while animating a `zoom` transition.\n */\n cornerRadius?: number;\n}\n\n/**\n * Native transition result.\n */\nexport interface NativeNavigationTransitionResult {\n id: string;\n direction: NativeNavigationTransitionDirection;\n duration: number;\n}\n\n/**\n * Plugin version payload.\n */\nexport interface PluginVersionResult {\n /**\n * Version identifier returned by the platform implementation.\n */\n version: string;\n}\n\nexport interface NativeNavigationBackEvent {\n source: 'navbar';\n}\n\nexport interface NativeNavigationBarItemTapEvent {\n id: string;\n title?: string;\n placement: 'left' | 'right';\n}\n\nexport interface NativeNavigationTabSelectEvent {\n id: string;\n index: number;\n title?: string;\n}\n\nexport interface NativeNavigationSafeAreaChangedEvent {\n insets: NativeNavigationInsets;\n}\n\nexport interface NativeNavigationTransitionEvent {\n id: string;\n direction: NativeNavigationTransitionDirection;\n duration: number;\n}\n\n/**\n * Framework-agnostic native navigation chrome API.\n */\nexport interface NativeNavigationPlugin {\n /**\n * Configure the native chrome host and content inset behavior.\n */\n configure(options?: NativeNavigationConfigureOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Render or update the native navbar.\n */\n setNavbar(options: NativeNavigationNavbarOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Render or update the native tabbar.\n */\n setTabbar(options: NativeNavigationTabbarOptions): Promise<NativeNavigationInsetsResult>;\n\n /**\n * Capture the current WebView and prepare a native transition.\n */\n beginTransition(options?: NativeNavigationBeginTransitionOptions): Promise<NativeNavigationTransitionResult>;\n\n /**\n * Animate from the captured WebView snapshot to the current live WebView.\n */\n finishTransition(options?: NativeNavigationFinishTransitionOptions): Promise<NativeNavigationTransitionResult>;\n\n /**\n * Returns the platform implementation version marker.\n */\n getPluginVersion(): Promise<PluginVersionResult>;\n\n addListener(\n eventName: 'navbarBack',\n listenerFunc: (event: NativeNavigationBackEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'navbarItemTap',\n listenerFunc: (event: NativeNavigationBarItemTapEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'tabSelect',\n listenerFunc: (event: NativeNavigationTabSelectEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'safeAreaChanged',\n listenerFunc: (event: NativeNavigationSafeAreaChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'transitionStart',\n listenerFunc: (event: NativeNavigationTransitionEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n addListener(\n eventName: 'transitionEnd',\n listenerFunc: (event: NativeNavigationTransitionEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import Foundation
|
|
4
4
|
import Capacitor
|
|
5
|
-
import ObjectiveC
|
|
6
5
|
import UIKit
|
|
7
6
|
|
|
8
7
|
private struct NativeNavigationTransitionContext {
|
|
@@ -58,6 +57,9 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
58
57
|
private var tabbarHeight: CGFloat = NativeNavigationTabbarStyleConfig().totalHeight
|
|
59
58
|
private var navbarVisible = false
|
|
60
59
|
private var tabbarVisible = false
|
|
60
|
+
private var tabbarSoftHidden = false
|
|
61
|
+
private var tabbarSkipImmediateSystemReveal = false
|
|
62
|
+
private var floatingTabBarWantsHidden = false
|
|
61
63
|
private var contentInsetMode = "css"
|
|
62
64
|
private var isEnabled = true
|
|
63
65
|
private var defaultTransitionDuration: TimeInterval = 0.35
|
|
@@ -203,15 +205,36 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
let hidden = call.getBool("hidden", false)
|
|
208
|
+
let animated = call.getBool("animated", false)
|
|
206
209
|
self.tabbarVisible = !hidden
|
|
207
210
|
|
|
208
211
|
guard !hidden else {
|
|
209
|
-
self.hideTabBarChrome()
|
|
212
|
+
self.hideTabBarChrome(animated: animated)
|
|
210
213
|
self.updateInsetsAndNotify()
|
|
211
214
|
call.resolve(self.insetsResult())
|
|
212
215
|
return
|
|
213
216
|
}
|
|
214
217
|
|
|
218
|
+
if self.tabbarSoftHidden {
|
|
219
|
+
self.tabbarSoftHidden = false
|
|
220
|
+
if animated {
|
|
221
|
+
if nativeNavigationPrefersAnimatedSystemTabbarVisibility(
|
|
222
|
+
animated: true,
|
|
223
|
+
usesSystemLiquidGlass: self.usesSystemLiquidGlass,
|
|
224
|
+
shape: self.tabbarStyle.shape == .curve ? "curve" : "floating"
|
|
225
|
+
) {
|
|
226
|
+
self.tabbarSkipImmediateSystemReveal = true
|
|
227
|
+
self.setSystemTabBarHidden(false, animated: true, syncSubviewState: false)
|
|
228
|
+
} else {
|
|
229
|
+
self.setFloatingTabBarVisibility(hidden: false, animated: true)
|
|
230
|
+
}
|
|
231
|
+
} else if self.usesSystemLiquidGlass && self.tabbarStyle.shape != .curve {
|
|
232
|
+
self.setSystemTabBarHidden(false, animated: false, syncSubviewState: true)
|
|
233
|
+
} else {
|
|
234
|
+
self.setFloatingTabBarVisibility(hidden: false, animated: false)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
215
238
|
self.tabbarStyle = self.makeTabbarStyle(from: call)
|
|
216
239
|
self.tabbarHeight = self.tabbarStyle.totalHeight
|
|
217
240
|
|
|
@@ -241,6 +264,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
241
264
|
self.applyTabBarAppearance(tabBar: tabBar, options: call)
|
|
242
265
|
if items.isEmpty {
|
|
243
266
|
self.tabbarVisible = false
|
|
267
|
+
self.tabbarSkipImmediateSystemReveal = false
|
|
244
268
|
self.hideSystemTabBarChromeCompletely()
|
|
245
269
|
} else {
|
|
246
270
|
self.showTabBarChrome(tabBar)
|
|
@@ -904,18 +928,22 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
904
928
|
}
|
|
905
929
|
}
|
|
906
930
|
|
|
907
|
-
private func setSystemTabBarHidden(_ hidden: Bool) {
|
|
931
|
+
private func setSystemTabBarHidden(_ hidden: Bool, animated: Bool = false, syncSubviewState: Bool = true) {
|
|
908
932
|
guard let tabBarController = tabBarController else {
|
|
909
933
|
return
|
|
910
934
|
}
|
|
911
935
|
|
|
912
936
|
let tabBar = tabBarController.tabBar
|
|
913
937
|
if #available(iOS 18.0, *) {
|
|
914
|
-
tabBarController.setTabBarHidden(hidden, animated:
|
|
938
|
+
tabBarController.setTabBarHidden(hidden, animated: animated)
|
|
915
939
|
} else {
|
|
916
940
|
tabBar.isHidden = hidden
|
|
917
941
|
}
|
|
918
942
|
|
|
943
|
+
guard syncSubviewState else {
|
|
944
|
+
return
|
|
945
|
+
}
|
|
946
|
+
|
|
919
947
|
if hidden {
|
|
920
948
|
setTabBarBackgroundSubviewsHidden(true, on: tabBar)
|
|
921
949
|
} else {
|
|
@@ -943,7 +971,24 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
943
971
|
floatingTabBar?.isHidden = true
|
|
944
972
|
}
|
|
945
973
|
|
|
946
|
-
private func hideTabBarChrome() {
|
|
974
|
+
private func hideTabBarChrome(animated: Bool = false) {
|
|
975
|
+
if nativeNavigationPrefersAnimatedSystemTabbarVisibility(
|
|
976
|
+
animated: animated,
|
|
977
|
+
usesSystemLiquidGlass: usesSystemLiquidGlass,
|
|
978
|
+
shape: tabbarStyle.shape == .curve ? "curve" : "floating"
|
|
979
|
+
) {
|
|
980
|
+
tabbarSoftHidden = true
|
|
981
|
+
setSystemTabBarHidden(true, animated: true, syncSubviewState: false)
|
|
982
|
+
return
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if animated {
|
|
986
|
+
tabbarSoftHidden = true
|
|
987
|
+
setFloatingTabBarVisibility(hidden: true, animated: true)
|
|
988
|
+
return
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
tabbarSoftHidden = false
|
|
947
992
|
if usesSystemLiquidGlass && tabbarStyle.shape != .curve {
|
|
948
993
|
hideSystemTabBarChromeCompletely()
|
|
949
994
|
} else {
|
|
@@ -953,12 +998,51 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
953
998
|
}
|
|
954
999
|
}
|
|
955
1000
|
|
|
1001
|
+
private func setFloatingTabBarVisibility(hidden: Bool, animated: Bool) {
|
|
1002
|
+
floatingTabBarWantsHidden = hidden
|
|
1003
|
+
let targets = [tabContainer, floatingTabBar].compactMap { $0 }
|
|
1004
|
+
guard !targets.isEmpty else {
|
|
1005
|
+
return
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if !animated {
|
|
1009
|
+
targets.forEach { view in
|
|
1010
|
+
view.isHidden = hidden
|
|
1011
|
+
view.alpha = hidden ? 0 : 1
|
|
1012
|
+
}
|
|
1013
|
+
return
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if hidden {
|
|
1017
|
+
UIView.animate(withDuration: 0.2, animations: {
|
|
1018
|
+
targets.forEach { $0.alpha = 0 }
|
|
1019
|
+
}, completion: { finished in
|
|
1020
|
+
guard finished, self.floatingTabBarWantsHidden else {
|
|
1021
|
+
return
|
|
1022
|
+
}
|
|
1023
|
+
targets.forEach { $0.isHidden = true }
|
|
1024
|
+
})
|
|
1025
|
+
return
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
targets.forEach { view in
|
|
1029
|
+
view.isHidden = false
|
|
1030
|
+
view.alpha = 0
|
|
1031
|
+
}
|
|
1032
|
+
UIView.animate(withDuration: 0.2) {
|
|
1033
|
+
targets.forEach { $0.alpha = 1 }
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
956
1037
|
private func showTabBarChrome(_ tabBar: UITabBar) {
|
|
957
1038
|
tabContainer?.isHidden = true
|
|
958
1039
|
floatingTabBar?.isHidden = true
|
|
959
1040
|
tabBarController?.view.isHidden = false
|
|
960
1041
|
if usesSystemLiquidGlass && tabbarStyle.shape != .curve {
|
|
961
|
-
|
|
1042
|
+
if !tabbarSkipImmediateSystemReveal {
|
|
1043
|
+
setSystemTabBarHidden(false)
|
|
1044
|
+
}
|
|
1045
|
+
tabbarSkipImmediateSystemReveal = false
|
|
962
1046
|
liftWebViewOverlaysAboveSystemTabs()
|
|
963
1047
|
hostWebViewInSelectedSystemTab()
|
|
964
1048
|
} else {
|
|
@@ -1967,44 +2051,25 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
1967
2051
|
}
|
|
1968
2052
|
|
|
1969
2053
|
private func liquidGlassEffect() -> UIVisualEffect? {
|
|
1970
|
-
guard usesSystemLiquidGlass
|
|
1971
|
-
let effectClass = NSClassFromString("UIGlassEffect") else {
|
|
2054
|
+
guard usesSystemLiquidGlass else {
|
|
1972
2055
|
return nil
|
|
1973
2056
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
typealias EffectWithStyle = @convention(c) (AnyClass, Selector, Int) -> AnyObject?
|
|
1978
|
-
let factory = unsafeBitCast(method_getImplementation(method), to: EffectWithStyle.self)
|
|
1979
|
-
if let effect = factory(effectClass, styleSelector, 0) as? UIVisualEffect {
|
|
1980
|
-
return effect
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
if let objectType = effectClass as? NSObject.Type {
|
|
1985
|
-
return objectType.init() as? UIVisualEffect
|
|
2057
|
+
#if compiler(>=6.2)
|
|
2058
|
+
if #available(iOS 26.0, *) {
|
|
2059
|
+
return UIGlassEffect(style: .regular)
|
|
1986
2060
|
}
|
|
1987
|
-
|
|
2061
|
+
#endif
|
|
1988
2062
|
return nil
|
|
1989
2063
|
}
|
|
1990
2064
|
|
|
1991
2065
|
private func configureGlassBarButtonItem(_ item: UIBarButtonItem, id: String) {
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
// button Liquid Glass grouping APIs when the runtime exposes them.
|
|
1998
|
-
let object = item as NSObject
|
|
1999
|
-
if object.responds(to: NSSelectorFromString("setIdentifier:")) {
|
|
2000
|
-
object.setValue(id, forKey: "identifier")
|
|
2001
|
-
}
|
|
2002
|
-
if object.responds(to: NSSelectorFromString("setSharesBackground:")) {
|
|
2003
|
-
object.setValue(true, forKey: "sharesBackground")
|
|
2004
|
-
}
|
|
2005
|
-
if object.responds(to: NSSelectorFromString("setHidesSharedBackground:")) {
|
|
2006
|
-
object.setValue(false, forKey: "hidesSharedBackground")
|
|
2066
|
+
#if compiler(>=6.2)
|
|
2067
|
+
if #available(iOS 26.0, *) {
|
|
2068
|
+
item.identifier = id
|
|
2069
|
+
item.sharesBackground = true
|
|
2070
|
+
item.hidesSharedBackground = false
|
|
2007
2071
|
}
|
|
2072
|
+
#endif
|
|
2008
2073
|
}
|
|
2009
2074
|
|
|
2010
2075
|
private func currentInsets() -> [String: Any] {
|
|
@@ -2780,6 +2845,14 @@ func nativeNavigationUsesStationaryTransitionCrossfade(direction: String) -> Boo
|
|
|
2780
2845
|
direction == "tab" || direction == "root" || direction == "none"
|
|
2781
2846
|
}
|
|
2782
2847
|
|
|
2848
|
+
func nativeNavigationPrefersAnimatedSystemTabbarVisibility(
|
|
2849
|
+
animated: Bool,
|
|
2850
|
+
usesSystemLiquidGlass: Bool,
|
|
2851
|
+
shape: String
|
|
2852
|
+
) -> Bool {
|
|
2853
|
+
animated && usesSystemLiquidGlass && shape != "curve"
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2783
2856
|
private func nativeNavigationNeedsTransitionSurface(_ color: UIColor?) -> Bool {
|
|
2784
2857
|
guard let color else {
|
|
2785
2858
|
return true
|
|
@@ -97,4 +97,28 @@ class NativeNavigationTests: XCTestCase {
|
|
|
97
97
|
XCTAssertFalse(nativeNavigationUsesStationaryTransitionCrossfade(direction: "forward"))
|
|
98
98
|
XCTAssertFalse(nativeNavigationUsesStationaryTransitionCrossfade(direction: "back"))
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
func testAnimatedSystemTabbarVisibilityUsesSoftHide() {
|
|
102
|
+
XCTAssertTrue(
|
|
103
|
+
nativeNavigationPrefersAnimatedSystemTabbarVisibility(
|
|
104
|
+
animated: true,
|
|
105
|
+
usesSystemLiquidGlass: true,
|
|
106
|
+
shape: "floating"
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
XCTAssertFalse(
|
|
110
|
+
nativeNavigationPrefersAnimatedSystemTabbarVisibility(
|
|
111
|
+
animated: false,
|
|
112
|
+
usesSystemLiquidGlass: true,
|
|
113
|
+
shape: "floating"
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
XCTAssertFalse(
|
|
117
|
+
nativeNavigationPrefersAnimatedSystemTabbarVisibility(
|
|
118
|
+
animated: true,
|
|
119
|
+
usesSystemLiquidGlass: true,
|
|
120
|
+
shape: "curve"
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
}
|
|
100
124
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-native-navigation",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.0",
|
|
4
4
|
"description": "Capacitor plugin for native navbar, tabbar, safe-area handling, and WebView snapshot transitions.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|