@capgo/capacitor-native-navigation 8.1.0 → 8.2.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.
@@ -1,7 +1,7 @@
1
1
  package app.capgo.nativenavigation;
2
2
 
3
3
  import android.app.Activity;
4
- import android.content.res.ColorStateList;
4
+ import android.content.Context;
5
5
  import android.content.res.Configuration;
6
6
  import android.content.res.Resources;
7
7
  import android.graphics.Bitmap;
@@ -12,26 +12,25 @@ import android.graphics.Paint;
12
12
  import android.graphics.Path;
13
13
  import android.graphics.Rect;
14
14
  import android.graphics.RectF;
15
+ import android.graphics.Typeface;
15
16
  import android.graphics.drawable.BitmapDrawable;
16
17
  import android.graphics.drawable.ColorDrawable;
17
18
  import android.graphics.drawable.Drawable;
18
19
  import android.graphics.drawable.GradientDrawable;
19
- import android.graphics.drawable.StateListDrawable;
20
20
  import android.os.Build;
21
- import android.view.ContextThemeWrapper;
21
+ import android.util.TypedValue;
22
22
  import android.view.Gravity;
23
23
  import android.view.Menu;
24
24
  import android.view.MenuItem;
25
25
  import android.view.View;
26
26
  import android.view.ViewGroup;
27
27
  import android.view.ViewOutlineProvider;
28
- import android.view.ViewTreeObserver;
29
28
  import android.view.Window;
30
29
  import android.view.WindowInsets;
31
30
  import android.widget.FrameLayout;
32
31
  import android.widget.ImageView;
32
+ import android.widget.LinearLayout;
33
33
  import android.widget.TextView;
34
- import androidx.annotation.RequiresApi;
35
34
  import androidx.appcompat.content.res.AppCompatResources;
36
35
  import androidx.appcompat.widget.Toolbar;
37
36
  import androidx.core.graphics.PathParser;
@@ -41,9 +40,6 @@ import com.getcapacitor.Plugin;
41
40
  import com.getcapacitor.PluginCall;
42
41
  import com.getcapacitor.PluginMethod;
43
42
  import com.getcapacitor.annotation.CapacitorPlugin;
44
- import com.google.android.material.badge.BadgeDrawable;
45
- import com.google.android.material.bottomnavigation.BottomNavigationView;
46
- import com.google.android.material.navigation.NavigationBarView;
47
43
  import java.io.StringReader;
48
44
  import java.net.URLDecoder;
49
45
  import java.util.ArrayDeque;
@@ -62,36 +58,15 @@ import org.xmlpull.v1.XmlPullParserFactory;
62
58
  public class NativeNavigationPlugin extends Plugin {
63
59
 
64
60
  private static final int DEFAULT_NAVBAR_DP = 56;
65
- // Android uses Material bottom navigation metrics; web/iOS keep their own platform-native tab bar heights.
66
- private static final int DEFAULT_TABBAR_DP = 80;
67
- private static final int LIQUID_GLASS_TABBAR_DP = 64;
68
- private static final int LIQUID_GLASS_TABBAR_MAX_WIDTH_DP = 300;
69
- private static final int LIQUID_GLASS_TABBAR_SIDE_MARGIN_DP = 30;
70
- private static final int LIQUID_GLASS_TABBAR_CONTENT_TOP_PADDING_DP = 8;
71
- private static final int LIQUID_GLASS_TABBAR_CONTENT_TRANSLATION_Y_DP = 6;
72
- private static final int LIQUID_GLASS_TABBAR_LABEL_TRANSLATION_Y_DP = 3;
73
- private static final int LIQUID_GLASS_TABBAR_BADGE_VERTICAL_OFFSET_DP = 2;
74
- private static final int TABBAR_ICON_DP = 24;
75
- private static final int TABBAR_ITEM_VERTICAL_PADDING_DP = 8;
76
- private static final int LIQUID_GLASS_TABBAR_ITEM_VERTICAL_PADDING_DP = 4;
77
- private static final int TABBAR_INDICATOR_DP = 32;
78
- private static final int LIQUID_GLASS_TABBAR_INDICATOR_DP = 28;
79
- private static final int TABBAR_INDICATOR_LABEL_PADDING_DP = 4;
80
- private static final int LIQUID_GLASS_TABBAR_INDICATOR_LABEL_PADDING_DP = 6;
81
- private static final int TABBAR_FLOATING_BOTTOM_GAP_DP = 10;
82
- private static final int LIQUID_GLASS_TABBAR_FLOATING_BOTTOM_GAP_DP = 24;
61
+ private static final int DEFAULT_TABBAR_DP = 64;
83
62
  private static final int DEFAULT_TRANSITION_MS = 350;
84
63
  private static final int MENU_ITEM_BASE = 10_000;
85
64
 
86
65
  private final NativeNavigation implementation = new NativeNavigation();
87
66
  private FrameLayout navbarContainer;
88
67
  private FrameLayout tabbarContainer;
89
- private GlassBackdropView navbarGlassBackdrop;
90
- private View navbarGlassSurface;
91
- private GlassBackdropView tabbarGlassBackdrop;
92
- private View tabbarGlassSurface;
93
68
  private Toolbar toolbar;
94
- private BottomNavigationView tabbar;
69
+ private NativeTabbarLayout tabbar;
95
70
  private ImageView transitionSnapshot;
96
71
  private boolean enabled = true;
97
72
  private boolean navbarVisible = false;
@@ -101,22 +76,21 @@ public class NativeNavigationPlugin extends Plugin {
101
76
  private int activeTransitionMs = DEFAULT_TRANSITION_MS;
102
77
  private int tintColor = Color.rgb(0, 122, 255);
103
78
  private int inactiveTintColor = Color.rgb(120, 126, 137);
104
- private GlassOptions defaultGlassOptions = GlassOptions.defaults();
105
- private GlassOptions navbarGlassOptions = GlassOptions.defaults();
106
- private GlassOptions tabbarGlassOptions = GlassOptions.defaults();
107
- private JSObject navbarGlassConfig;
108
- private JSObject tabbarGlassConfig;
109
- private int navbarBackgroundColor = Color.argb(225, 255, 255, 255);
110
79
  private int tabbarBackgroundColor = Color.argb(235, 255, 255, 255);
80
+ private int badgeBackgroundColor = Color.rgb(255, 59, 48);
81
+ private int badgeTextColor = Color.WHITE;
82
+ private TabbarStyle tabbarStyle = TabbarStyle.defaults(Color.rgb(0, 122, 255));
111
83
  private String activeTransitionId;
112
84
  private String activeTransitionDirection = "forward";
113
85
  private RectF activeZoomSourceFrame;
114
86
  private float activeZoomCornerRadius = 0f;
87
+ private Drawable activeTransitionRootBackground;
88
+ private boolean activeTransitionRootBackgroundCaptured = false;
115
89
  private final Map<Integer, String> menuActionIds = new HashMap<>();
116
90
  private final Map<Integer, String> menuActionTitles = new HashMap<>();
117
91
  private final Map<Integer, String> menuActionPlacements = new HashMap<>();
118
- private final Map<Integer, String> tabIds = new HashMap<>();
119
- private final Map<Integer, String> tabTitles = new HashMap<>();
92
+ private final List<NativeTabItem> tabItems = new ArrayList<>();
93
+ private int selectedTabIndex = 0;
120
94
 
121
95
  @Override
122
96
  public void load() {
@@ -131,9 +105,6 @@ public class NativeNavigationPlugin extends Plugin {
131
105
  runOnUiThread(() -> {
132
106
  enabled = call.getBoolean("enabled", true);
133
107
  contentInsetMode = call.getString("contentInsetMode", contentInsetMode);
134
- defaultGlassOptions = GlassOptions.from(call.getObject("glass", null), defaultGlassOptions);
135
- navbarGlassOptions = GlassOptions.from(navbarGlassConfig, defaultGlassOptions);
136
- tabbarGlassOptions = GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
137
108
  Double duration = call.getDouble("animationDuration");
138
109
  if (duration != null) {
139
110
  defaultTransitionMs = Math.max(0, duration.intValue());
@@ -149,12 +120,6 @@ public class NativeNavigationPlugin extends Plugin {
149
120
  tabbarContainer.setVisibility(View.GONE);
150
121
  }
151
122
  }
152
- if (enabled) {
153
- reapplyVisibleChromeBackgrounds();
154
- if (tabbar != null) {
155
- applyTabbarMetrics(tabbar);
156
- }
157
- }
158
123
  updateInsetsAndNotify();
159
124
  call.resolve(insetsResult());
160
125
  });
@@ -202,8 +167,6 @@ public class NativeNavigationPlugin extends Plugin {
202
167
 
203
168
  addToolbarItems(nativeToolbar, call.getArray("rightItems", new JSArray()), "right");
204
169
  JSObject colors = call.getObject("colors", new JSObject());
205
- navbarGlassConfig = call.getObject("glass", null);
206
- navbarGlassOptions = GlassOptions.from(navbarGlassConfig, defaultGlassOptions);
207
170
  applyToolbarColors(nativeToolbar, colors);
208
171
  navbarContainer.setVisibility(View.VISIBLE);
209
172
  layoutChrome();
@@ -236,77 +199,44 @@ public class NativeNavigationPlugin extends Plugin {
236
199
  return;
237
200
  }
238
201
 
239
- BottomNavigationView nativeTabbar = ensureTabbar();
240
- for (Integer existingItemId : new ArrayList<>(tabIds.keySet())) {
241
- nativeTabbar.removeBadge(existingItemId);
242
- }
243
- nativeTabbar.getMenu().clear();
244
- tabIds.clear();
245
- tabTitles.clear();
202
+ NativeTabbarLayout nativeTabbar = ensureTabbar();
203
+ nativeTabbar.removeAllViews();
204
+ tabItems.clear();
205
+ selectedTabIndex = -1;
246
206
 
247
207
  boolean labels = call.getBoolean("labels", true);
248
208
  boolean icons = call.getBoolean("icons", true);
249
209
  String labelVisibilityMode = call.getString("labelVisibilityMode", labels ? "labeled" : "unlabeled");
250
- nativeTabbar.setLabelVisibilityMode(labelVisibilityMode(labelVisibilityMode));
251
-
252
210
  JSONArray tabs = call.getArray("tabs", new JSArray());
253
211
  String selectedId = call.getString("selectedId", null);
254
212
  JSObject colors = call.getObject("colors", new JSObject());
255
- tabbarGlassConfig = call.getObject("glass", null);
256
- tabbarGlassOptions = GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
257
- Integer badgeBackground = colorOption(call, colors, "badgeBackgroundColor", "badgeBackground", null);
258
- Integer badgeText = colorOption(call, colors, "badgeTextColor", "badgeText", null);
213
+ badgeBackgroundColor = colorOption(call, colors, "badgeBackgroundColor", "badgeBackground", Color.rgb(255, 59, 48));
214
+ badgeTextColor = colorOption(call, colors, "badgeTextColor", "badgeText", Color.WHITE);
215
+
259
216
  for (int index = 0; index < tabs.length(); index++) {
260
217
  JSONObject tab = tabs.optJSONObject(index);
261
218
  if (tab == null) {
262
219
  continue;
263
220
  }
264
- int itemId = MENU_ITEM_BASE + index;
265
221
  String id = tab.optString("id", "tab-" + index);
266
222
  String title = tab.optString("title", "");
267
- MenuItem item = nativeTabbar.getMenu().add(Menu.NONE, itemId, index, labelVisibilityMode.equals("unlabeled") ? "" : title);
268
- item.setEnabled(tab.optBoolean("enabled", true));
269
- Drawable icon = icons ? tabIconFrom(tab) : new ColorDrawable(Color.TRANSPARENT);
270
- if (icon != null) {
271
- item.setIcon(icon);
272
- }
273
- if (tab.has("badge")) {
274
- nativeTabbar.removeBadge(itemId);
275
- BadgeDrawable badge = nativeTabbar.getOrCreateBadge(itemId);
276
- if (isLiquidGlassTabbar()) {
277
- badge.setVerticalOffset(dp(LIQUID_GLASS_TABBAR_BADGE_VERTICAL_OFFSET_DP));
278
- }
279
- if (badgeBackground != null) {
280
- badge.setBackgroundColor(badgeBackground);
281
- }
282
- if (badgeText != null) {
283
- badge.setBadgeTextColor(badgeText);
284
- }
285
- Object badgeValue = tab.opt("badge");
286
- if (badgeValue instanceof Number) {
287
- badge.setNumber(((Number) badgeValue).intValue());
288
- } else {
289
- try {
290
- badge.setNumber(Integer.parseInt(String.valueOf(badgeValue)));
291
- } catch (NumberFormatException ignored) {
292
- badge.setVisible(true);
293
- }
294
- }
295
- } else {
296
- nativeTabbar.removeBadge(itemId);
297
- }
298
- tabIds.put(itemId, id);
299
- tabTitles.put(itemId, title);
223
+ Drawable icon = icons ? iconFrom(tab.optJSONObject("icon")) : new ColorDrawable(Color.TRANSPARENT);
224
+ Drawable selectedIcon = icons ? iconFrom(tab.optJSONObject("selectedIcon")) : null;
225
+ String badge = tab.has("badge") ? String.valueOf(tab.opt("badge")) : null;
226
+ tabItems.add(new NativeTabItem(id, title, icon, selectedIcon, badge, tab.optBoolean("enabled", true)));
300
227
  if (id.equals(selectedId)) {
301
- nativeTabbar.setSelectedItemId(itemId);
228
+ selectedTabIndex = index;
302
229
  }
303
230
  }
304
231
 
305
- if (nativeTabbar.getSelectedItemId() == 0 && nativeTabbar.getMenu().size() > 0) {
306
- nativeTabbar.setSelectedItemId(nativeTabbar.getMenu().getItem(0).getItemId());
232
+ if (!tabItems.isEmpty() && (selectedTabIndex < 0 || selectedTabIndex >= tabItems.size())) {
233
+ selectedTabIndex = 0;
307
234
  }
308
235
 
309
- applyTabbarColors(nativeTabbar, call, colors);
236
+ applyTabbarColors(call, colors);
237
+ tabbarStyle = makeTabbarStyle(call.getObject("style", new JSObject()));
238
+ nativeTabbar.setTabbarStyle(tabbarStyle, tabbarBackgroundColor, centerTabIndex());
239
+ renderTabbarItems(labelVisibilityMode, icons);
310
240
  if (tabbarContainer != null) {
311
241
  tabbarContainer.setVisibility(View.VISIBLE);
312
242
  }
@@ -338,8 +268,11 @@ public class NativeNavigationPlugin extends Plugin {
338
268
 
339
269
  if (transitionSnapshot != null) {
340
270
  root.removeView(transitionSnapshot);
271
+ restoreTransitionRootBackground();
341
272
  }
342
273
 
274
+ int transitionSurface = transitionSurfaceColor(webView);
275
+ prepareTransitionRootBackground(root, transitionSurface);
343
276
  Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(), webView.getHeight(), Bitmap.Config.ARGB_8888);
344
277
  webView.draw(new Canvas(bitmap));
345
278
  if (zoomSourceRect != null) {
@@ -348,6 +281,7 @@ public class NativeNavigationPlugin extends Plugin {
348
281
  }
349
282
  transitionSnapshot = new ImageView(getContext());
350
283
  transitionSnapshot.setImageBitmap(bitmap);
284
+ transitionSnapshot.setBackgroundColor(transitionSurface);
351
285
  transitionSnapshot.setScaleType(ImageView.ScaleType.FIT_XY);
352
286
  FrameLayout.LayoutParams params =
353
287
  activeZoomSourceFrame == null
@@ -415,8 +349,9 @@ public class NativeNavigationPlugin extends Plugin {
415
349
  snapshotEndTranslation = -width * 0.3f;
416
350
  }
417
351
 
352
+ boolean stationaryTransition = isStationaryTransition(direction);
418
353
  webView.setTranslationX(startTranslation);
419
- webView.setAlpha("none".equals(direction) ? 1f : 0.01f);
354
+ webView.setAlpha(stationaryTransition ? 1f : 0.01f);
420
355
  View snapshot = transitionSnapshot;
421
356
  JSObject event = transitionEvent(transitionId, direction, durationMs);
422
357
  Runnable finish = () -> {
@@ -424,6 +359,7 @@ public class NativeNavigationPlugin extends Plugin {
424
359
  if (root != null && transitionSnapshot != null) {
425
360
  root.removeView(transitionSnapshot);
426
361
  }
362
+ restoreTransitionRootBackground();
427
363
  transitionSnapshot = null;
428
364
  activeTransitionId = null;
429
365
  activeZoomSourceFrame = null;
@@ -443,7 +379,7 @@ public class NativeNavigationPlugin extends Plugin {
443
379
  snapshot
444
380
  .animate()
445
381
  .translationX(snapshotEndTranslation)
446
- .alpha("none".equals(direction) ? 0f : 0.75f)
382
+ .alpha(stationaryTransition ? 0f : 0.75f)
447
383
  .setDuration(durationMs)
448
384
  .withEndAction(finish)
449
385
  .start();
@@ -480,6 +416,7 @@ public class NativeNavigationPlugin extends Plugin {
480
416
  if (root != null && transitionSnapshot != null) {
481
417
  root.removeView(transitionSnapshot);
482
418
  }
419
+ restoreTransitionRootBackground();
483
420
  transitionSnapshot = null;
484
421
  activeTransitionId = null;
485
422
  activeZoomSourceFrame = null;
@@ -638,10 +575,6 @@ public class NativeNavigationPlugin extends Plugin {
638
575
  FrameLayout root = contentRoot();
639
576
  navbarContainer = new FrameLayout(getContext());
640
577
  navbarContainer.setElevation(dp(8));
641
- navbarGlassBackdrop = new GlassBackdropView(getContext());
642
- navbarGlassSurface = new View(getContext());
643
- navbarGlassBackdrop.setVisibility(View.GONE);
644
- navbarGlassSurface.setVisibility(View.GONE);
645
578
  toolbar = new Toolbar(getContext());
646
579
  toolbar.setPopupTheme(androidx.appcompat.R.style.ThemeOverlay_AppCompat_Light);
647
580
  toolbar.setOnMenuItemClickListener((item) -> {
@@ -654,8 +587,6 @@ public class NativeNavigationPlugin extends Plugin {
654
587
  return true;
655
588
  });
656
589
 
657
- navbarContainer.addView(navbarGlassBackdrop);
658
- navbarContainer.addView(navbarGlassSurface);
659
590
  navbarContainer.addView(toolbar);
660
591
  if (root != null) {
661
592
  root.addView(navbarContainer);
@@ -668,90 +599,157 @@ public class NativeNavigationPlugin extends Plugin {
668
599
  return toolbar;
669
600
  }
670
601
 
671
- private BottomNavigationView ensureTabbar() {
602
+ private NativeTabbarLayout ensureTabbar() {
672
603
  if (tabbar != null) {
673
604
  return tabbar;
674
605
  }
675
606
  FrameLayout root = contentRoot();
676
607
  tabbarContainer = new FrameLayout(getContext());
677
- tabbarContainer.setElevation(dp(12));
678
608
  tabbarContainer.setClipChildren(false);
679
609
  tabbarContainer.setClipToPadding(false);
680
- tabbarGlassBackdrop = new GlassBackdropView(getContext());
681
- tabbarGlassSurface = new View(getContext());
682
- tabbarGlassBackdrop.setVisibility(View.GONE);
683
- tabbarGlassSurface.setVisibility(View.GONE);
684
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
685
- ViewOutlineProvider tabbarPillOutlineProvider = new ViewOutlineProvider() {
686
- @Override
687
- public void getOutline(View view, Outline outline) {
688
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), view.getHeight() / 2f);
689
- }
690
- };
691
- tabbarContainer.setClipToOutline(false);
692
- tabbarContainer.setOutlineProvider(tabbarPillOutlineProvider);
693
- tabbarGlassBackdrop.setClipToOutline(true);
694
- tabbarGlassBackdrop.setOutlineProvider(tabbarPillOutlineProvider);
695
- tabbarGlassSurface.setClipToOutline(true);
696
- tabbarGlassSurface.setOutlineProvider(tabbarPillOutlineProvider);
697
- }
698
- tabbar = new BottomNavigationView(
699
- new ContextThemeWrapper(getContext(), com.google.android.material.R.style.Theme_MaterialComponents_DayNight)
700
- );
701
- tabbar.setElevation(0);
610
+ tabbarContainer.setElevation(dp(12));
611
+
612
+ tabbar = new NativeTabbarLayout(getContext());
702
613
  tabbar.setClipChildren(false);
703
614
  tabbar.setClipToPadding(false);
704
- tabbar.setItemIconSize(dp(TABBAR_ICON_DP));
705
- applyTabbarMetrics(tabbar);
706
615
  tabbar.setBackgroundColor(Color.TRANSPARENT);
707
- tabbar.setOnItemSelectedListener((item) -> {
708
- int itemId = item.getItemId();
709
- if (!tabIds.containsKey(itemId)) {
710
- return false;
711
- }
712
- JSObject event = new JSObject();
713
- event.put("id", tabIds.get(itemId));
714
- event.put("index", itemId - MENU_ITEM_BASE);
715
- event.put("title", tabTitles.get(itemId));
716
- notifyListeners("tabSelect", event);
717
- return true;
718
- });
719
- tabbarContainer.addView(tabbarGlassBackdrop);
720
- tabbarContainer.addView(tabbarGlassSurface);
721
- tabbarContainer.addView(tabbar);
616
+ tabbarContainer.addView(
617
+ tabbar,
618
+ new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
619
+ );
722
620
  if (root != null) {
723
621
  root.addView(tabbarContainer);
724
622
  } else {
725
- getActivity().addContentView(tabbarContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, tabbarHeight()));
623
+ getActivity().addContentView(
624
+ tabbarContainer,
625
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(DEFAULT_TABBAR_DP))
626
+ );
726
627
  }
727
628
  return tabbar;
728
629
  }
729
630
 
730
- private int labelVisibilityMode(String mode) {
731
- if ("auto".equals(mode)) {
732
- return NavigationBarView.LABEL_VISIBILITY_AUTO;
631
+ private void renderTabbarItems(String labelVisibilityMode, boolean icons) {
632
+ if (tabbar == null) {
633
+ return;
733
634
  }
734
- if ("selected".equals(mode)) {
735
- return NavigationBarView.LABEL_VISIBILITY_SELECTED;
635
+ tabbar.removeAllViews();
636
+ int centerIndex = centerTabIndex();
637
+ tabbar.setTabbarStyle(tabbarStyle, tabbarBackgroundColor, centerIndex);
638
+ for (int index = 0; index < tabItems.size(); index++) {
639
+ final int itemIndex = index;
640
+ NativeTabItem item = tabItems.get(index);
641
+ boolean selected = itemIndex == selectedTabIndex;
642
+ boolean showLabel = shouldShowTabLabel(labelVisibilityMode, selected);
643
+ FrameLayout button = makeTabButton(item, selected, showLabel, icons, itemIndex == centerIndex);
644
+ button.setSelected(selected);
645
+ button.setEnabled(item.enabled);
646
+ button.setAlpha(item.enabled ? 1f : 0.38f);
647
+ button.setOnClickListener((view) -> {
648
+ if (!item.enabled) {
649
+ return;
650
+ }
651
+ selectedTabIndex = itemIndex;
652
+ renderTabbarItems(labelVisibilityMode, icons);
653
+ JSObject event = new JSObject();
654
+ event.put("id", item.id);
655
+ event.put("index", itemIndex);
656
+ event.put("title", item.title);
657
+ notifyListeners("tabSelect", event);
658
+ });
659
+ tabbar.addView(button, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
736
660
  }
661
+ }
662
+
663
+ private boolean shouldShowTabLabel(String mode, boolean selected) {
737
664
  if ("unlabeled".equals(mode)) {
738
- return NavigationBarView.LABEL_VISIBILITY_UNLABELED;
665
+ return false;
666
+ }
667
+ if ("selected".equals(mode)) {
668
+ return selected;
739
669
  }
740
- return NavigationBarView.LABEL_VISIBILITY_LABELED;
670
+ return true;
741
671
  }
742
672
 
743
- private Drawable tabIconFrom(JSONObject tab) {
744
- Drawable icon = iconFrom(tab.optJSONObject("icon"));
745
- Drawable selectedIcon = iconFrom(tab.optJSONObject("selectedIcon"));
746
- if (selectedIcon == null) {
747
- return icon;
748
- }
749
- StateListDrawable stateList = new StateListDrawable();
750
- stateList.addState(new int[] { android.R.attr.state_checked }, selectedIcon);
751
- if (icon != null) {
752
- stateList.addState(new int[] {}, icon);
673
+ private FrameLayout makeTabButton(NativeTabItem item, boolean selected, boolean showLabel, boolean icons, boolean center) {
674
+ FrameLayout button = new FrameLayout(getContext());
675
+ button.setClipChildren(false);
676
+ button.setClipToPadding(false);
677
+ button.setForeground(selectableItemBackground());
678
+ if (center) {
679
+ GradientDrawable centerBackground = new GradientDrawable();
680
+ centerBackground.setShape(GradientDrawable.OVAL);
681
+ centerBackground.setColor(tabbarStyle.centerButtonColor);
682
+ button.setBackground(centerBackground);
683
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
684
+ button.setElevation(dp(10));
685
+ }
686
+ }
687
+
688
+ if (!center && selected) {
689
+ GradientDrawable selectedBackground = new GradientDrawable();
690
+ selectedBackground.setShape(GradientDrawable.OVAL);
691
+ selectedBackground.setColor(withAlpha(tintColor, 34));
692
+ View selectedCircle = new View(getContext());
693
+ selectedCircle.setBackground(selectedBackground);
694
+ button.addView(selectedCircle, new FrameLayout.LayoutParams(dp(58), dp(58), Gravity.CENTER));
695
+ }
696
+
697
+ LinearLayout content = new LinearLayout(getContext());
698
+ content.setOrientation(LinearLayout.VERTICAL);
699
+ content.setGravity(Gravity.CENTER);
700
+ content.setPadding(dp(4), dp(4), dp(4), dp(4));
701
+ button.addView(content, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
702
+
703
+ Drawable currentIcon = selected && item.selectedIcon != null ? item.selectedIcon : item.icon;
704
+ boolean labelVisible = center ? showLabel && (currentIcon == null || !icons) : showLabel;
705
+ int itemColor = center ? tabbarStyle.centerButtonIconColor : (selected ? tintColor : inactiveTintColor);
706
+ if (icons && currentIcon != null) {
707
+ Drawable icon = currentIcon.mutate();
708
+ icon.setTint(itemColor);
709
+ ImageView image = new ImageView(getContext());
710
+ image.setImageDrawable(icon);
711
+ image.setColorFilter(itemColor);
712
+ int imageSize = center ? dp(32) : dp(24);
713
+ LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams(imageSize, imageSize);
714
+ imageParams.bottomMargin = labelVisible ? dp(2) : 0;
715
+ content.addView(image, imageParams);
716
+ }
717
+
718
+ if (labelVisible) {
719
+ TextView label = new TextView(getContext());
720
+ label.setText(item.title);
721
+ label.setTextColor(itemColor);
722
+ label.setTextSize(12);
723
+ label.setTypeface(Typeface.DEFAULT, selected ? Typeface.BOLD : Typeface.NORMAL);
724
+ label.setGravity(Gravity.CENTER);
725
+ label.setSingleLine(true);
726
+ content.addView(label, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(18)));
727
+ }
728
+
729
+ if (item.badge != null && !item.badge.isEmpty() && !"0".equals(item.badge)) {
730
+ TextView badge = new TextView(getContext());
731
+ badge.setText(item.badge);
732
+ badge.setTextColor(badgeTextColor);
733
+ badge.setTextSize(11);
734
+ badge.setTypeface(Typeface.DEFAULT_BOLD);
735
+ badge.setGravity(Gravity.CENTER);
736
+ GradientDrawable badgeBackground = new GradientDrawable();
737
+ badgeBackground.setColor(badgeBackgroundColor);
738
+ badgeBackground.setCornerRadius(dp(9));
739
+ badge.setBackground(badgeBackground);
740
+ int badgeWidth = Math.max(dp(18), item.badge.length() * dp(7) + dp(10));
741
+ FrameLayout.LayoutParams badgeParams = new FrameLayout.LayoutParams(
742
+ badgeWidth,
743
+ dp(18),
744
+ Gravity.TOP | Gravity.CENTER_HORIZONTAL
745
+ );
746
+ badgeParams.topMargin = center ? 0 : dp(4);
747
+ badge.setTranslationX(center ? dp(18) : dp(16));
748
+ button.addView(badge, badgeParams);
753
749
  }
754
- return stateList;
750
+
751
+ button.setContentDescription(item.title);
752
+ return button;
755
753
  }
756
754
 
757
755
  private Drawable iconFrom(JSONObject descriptor) {
@@ -904,6 +902,235 @@ public class NativeNavigationPlugin extends Plugin {
904
902
  }
905
903
  }
906
904
 
905
+ private static final class NativeTabItem {
906
+
907
+ final String id;
908
+ final String title;
909
+ final Drawable icon;
910
+ final Drawable selectedIcon;
911
+ final String badge;
912
+ final boolean enabled;
913
+
914
+ NativeTabItem(String id, String title, Drawable icon, Drawable selectedIcon, String badge, boolean enabled) {
915
+ this.id = id;
916
+ this.title = title;
917
+ this.icon = icon;
918
+ this.selectedIcon = selectedIcon;
919
+ this.badge = badge;
920
+ this.enabled = enabled;
921
+ }
922
+ }
923
+
924
+ private static final class TabbarStyle {
925
+
926
+ final String shape;
927
+ final int height;
928
+ final int horizontalMargin;
929
+ final int maxWidth;
930
+ final int bottomGap;
931
+ final int cornerRadius;
932
+ final String centerItemId;
933
+ final int centerButtonDiameter;
934
+ final int centerButtonLift;
935
+ final int centerButtonColor;
936
+ final int centerButtonIconColor;
937
+
938
+ TabbarStyle(
939
+ String shape,
940
+ int height,
941
+ int horizontalMargin,
942
+ int maxWidth,
943
+ int bottomGap,
944
+ int cornerRadius,
945
+ String centerItemId,
946
+ int centerButtonDiameter,
947
+ int centerButtonLift,
948
+ int centerButtonColor,
949
+ int centerButtonIconColor
950
+ ) {
951
+ this.shape = shape;
952
+ this.height = height;
953
+ this.horizontalMargin = horizontalMargin;
954
+ this.maxWidth = maxWidth;
955
+ this.bottomGap = bottomGap;
956
+ this.cornerRadius = cornerRadius;
957
+ this.centerItemId = centerItemId;
958
+ this.centerButtonDiameter = centerButtonDiameter;
959
+ this.centerButtonLift = centerButtonLift;
960
+ this.centerButtonColor = centerButtonColor;
961
+ this.centerButtonIconColor = centerButtonIconColor;
962
+ }
963
+
964
+ static TabbarStyle defaults(int tintColor) {
965
+ return new TabbarStyle("floating", 64, 24, 430, 10, 32, null, 76, 38, tintColor, Color.WHITE);
966
+ }
967
+
968
+ boolean isCurve() {
969
+ return "curve".equals(shape);
970
+ }
971
+
972
+ int barTop() {
973
+ return isCurve() ? centerButtonLift : 0;
974
+ }
975
+
976
+ int totalHeight() {
977
+ return height + barTop();
978
+ }
979
+ }
980
+
981
+ private static final class NativeTabbarLayout extends FrameLayout {
982
+
983
+ private final Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
984
+ private TabbarStyle style = TabbarStyle.defaults(Color.rgb(0, 122, 255));
985
+ private int backgroundColor = Color.argb(235, 255, 255, 255);
986
+ private int centerIndex = -1;
987
+
988
+ NativeTabbarLayout(Context context) {
989
+ super(context);
990
+ setWillNotDraw(false);
991
+ }
992
+
993
+ void setTabbarStyle(TabbarStyle style, int backgroundColor, int centerIndex) {
994
+ this.style = style;
995
+ this.backgroundColor = backgroundColor;
996
+ this.centerIndex = centerIndex;
997
+ requestLayout();
998
+ invalidate();
999
+ }
1000
+
1001
+ @Override
1002
+ protected void onDraw(Canvas canvas) {
1003
+ super.onDraw(canvas);
1004
+ backgroundPaint.setColor(backgroundColor);
1005
+ canvas.drawPath(backgroundPath(getWidth(), getHeight()), backgroundPaint);
1006
+ }
1007
+
1008
+ @Override
1009
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1010
+ int width = MeasureSpec.getSize(widthMeasureSpec);
1011
+ int height = MeasureSpec.getSize(heightMeasureSpec);
1012
+ if (hasCenterButton()) {
1013
+ int barHeight = dp(style.height);
1014
+ int barTop = dp(style.barTop());
1015
+ int centerGap = Math.min(dp(style.centerButtonDiameter + 22), Math.round(width * 0.46f));
1016
+ int leftWidth = Math.max(0, Math.round(width / 2f - centerGap / 2f));
1017
+ int rightX = Math.min(width, Math.round(width / 2f + centerGap / 2f));
1018
+ measureRange(0, centerIndex, leftWidth, barHeight);
1019
+ measureChildExact(getChildAt(centerIndex), dp(style.centerButtonDiameter), dp(style.centerButtonDiameter));
1020
+ measureRange(centerIndex + 1, getChildCount(), width - rightX, barHeight);
1021
+ setMeasuredDimension(width, Math.max(height, barTop + barHeight));
1022
+ return;
1023
+ }
1024
+
1025
+ int count = Math.max(getChildCount(), 1);
1026
+ int childWidth = width / count;
1027
+ for (int index = 0; index < getChildCount(); index++) {
1028
+ measureChildExact(getChildAt(index), childWidth, height);
1029
+ }
1030
+ setMeasuredDimension(width, height);
1031
+ }
1032
+
1033
+ @Override
1034
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1035
+ int width = right - left;
1036
+ int height = bottom - top;
1037
+ if (hasCenterButton()) {
1038
+ int barTop = dp(style.barTop());
1039
+ int barHeight = dp(style.height);
1040
+ int buttonDiameter = dp(style.centerButtonDiameter);
1041
+ int centerGap = Math.min(dp(style.centerButtonDiameter + 22), Math.round(width * 0.46f));
1042
+ int leftWidth = Math.max(0, Math.round(width / 2f - centerGap / 2f));
1043
+ int rightX = Math.min(width, Math.round(width / 2f + centerGap / 2f));
1044
+ View center = getChildAt(centerIndex);
1045
+ center.layout((width - buttonDiameter) / 2, 0, (width + buttonDiameter) / 2, buttonDiameter);
1046
+ layoutRange(0, centerIndex, 0, barTop, leftWidth, barHeight);
1047
+ layoutRange(centerIndex + 1, getChildCount(), rightX, barTop, width - rightX, barHeight);
1048
+ return;
1049
+ }
1050
+
1051
+ layoutRange(0, getChildCount(), 0, 0, width, height);
1052
+ }
1053
+
1054
+ private boolean hasCenterButton() {
1055
+ return style.isCurve() && centerIndex >= 0 && centerIndex < getChildCount();
1056
+ }
1057
+
1058
+ private void measureRange(int start, int end, int width, int height) {
1059
+ int count = Math.max(0, end - start);
1060
+ if (count == 0) {
1061
+ return;
1062
+ }
1063
+ int childWidth = Math.max(0, width / count);
1064
+ for (int index = start; index < end; index++) {
1065
+ measureChildExact(getChildAt(index), childWidth, height);
1066
+ }
1067
+ }
1068
+
1069
+ private void layoutRange(int start, int end, int left, int top, int width, int height) {
1070
+ int count = Math.max(0, end - start);
1071
+ if (count == 0) {
1072
+ return;
1073
+ }
1074
+ int childWidth = Math.max(0, width / count);
1075
+ for (int index = start; index < end; index++) {
1076
+ int childLeft = left + (index - start) * childWidth;
1077
+ getChildAt(index).layout(childLeft, top, childLeft + childWidth, top + height);
1078
+ }
1079
+ }
1080
+
1081
+ private void measureChildExact(View child, int width, int height) {
1082
+ child.measure(
1083
+ MeasureSpec.makeMeasureSpec(Math.max(0, width), MeasureSpec.EXACTLY),
1084
+ MeasureSpec.makeMeasureSpec(Math.max(0, height), MeasureSpec.EXACTLY)
1085
+ );
1086
+ }
1087
+
1088
+ private Path backgroundPath(int width, int height) {
1089
+ Path path = new Path();
1090
+ if (!style.isCurve()) {
1091
+ float radius = dp(style.cornerRadius);
1092
+ path.addRoundRect(new RectF(0, 0, width, height), radius, radius, Path.Direction.CW);
1093
+ return path;
1094
+ }
1095
+ float barTop = dp(style.barTop());
1096
+ float barHeight = dp(style.height);
1097
+ float cornerRadius = Math.min(dp(style.cornerRadius), barHeight / 2f);
1098
+ float centerX = width / 2f;
1099
+ float notchRadius = dp(style.centerButtonDiameter) / 2f + dp(7);
1100
+ float notchDepth = Math.min(barHeight * 0.78f, notchRadius);
1101
+ float leftShoulder = Math.max(cornerRadius, centerX - notchRadius);
1102
+ float rightShoulder = Math.min(width - cornerRadius, centerX + notchRadius);
1103
+ float control = notchDepth * 0.55228475f;
1104
+ RectF barRect = new RectF(0, barTop, width, barTop + barHeight);
1105
+
1106
+ path.moveTo(barRect.left + cornerRadius, barRect.top);
1107
+ path.lineTo(leftShoulder, barRect.top);
1108
+ path.cubicTo(
1109
+ leftShoulder,
1110
+ barRect.top + control,
1111
+ centerX - control,
1112
+ barRect.top + notchDepth,
1113
+ centerX,
1114
+ barRect.top + notchDepth
1115
+ );
1116
+ path.cubicTo(centerX + control, barRect.top + notchDepth, rightShoulder, barRect.top + control, rightShoulder, barRect.top);
1117
+ path.lineTo(barRect.right - cornerRadius, barRect.top);
1118
+ path.quadTo(barRect.right, barRect.top, barRect.right, barRect.top + cornerRadius);
1119
+ path.lineTo(barRect.right, barRect.bottom - cornerRadius);
1120
+ path.quadTo(barRect.right, barRect.bottom, barRect.right - cornerRadius, barRect.bottom);
1121
+ path.lineTo(barRect.left + cornerRadius, barRect.bottom);
1122
+ path.quadTo(barRect.left, barRect.bottom, barRect.left, barRect.bottom - cornerRadius);
1123
+ path.lineTo(barRect.left, barRect.top + cornerRadius);
1124
+ path.quadTo(barRect.left, barRect.top, barRect.left + cornerRadius, barRect.top);
1125
+ path.close();
1126
+ return path;
1127
+ }
1128
+
1129
+ private int dp(int value) {
1130
+ return Math.round(value * getResources().getDisplayMetrics().density);
1131
+ }
1132
+ }
1133
+
907
1134
  private static final class SvgIconRenderer {
908
1135
 
909
1136
  private static final Pattern NUMBER_PATTERN = Pattern.compile("[-+]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][-+]?\\d+)?");
@@ -1082,7 +1309,6 @@ public class NativeNavigationPlugin extends Plugin {
1082
1309
  : Color.rgb(20, 24, 32);
1083
1310
  int tint = parseColor(colors.getString("tint", null), tintFallback);
1084
1311
  int background = parseColor(colors.getString("background", null), backgroundFallback);
1085
- navbarBackgroundColor = background;
1086
1312
  int foreground = parseColor(colors.getString("foreground", null), foregroundFallback);
1087
1313
  tintColor = tint;
1088
1314
  nativeToolbar.setTitleTextColor(foreground);
@@ -1093,8 +1319,9 @@ public class NativeNavigationPlugin extends Plugin {
1093
1319
  tintedIcon.setTint(tint);
1094
1320
  nativeToolbar.setNavigationIcon(tintedIcon);
1095
1321
  }
1096
- nativeToolbar.setBackgroundColor(Color.TRANSPARENT);
1097
- applyChromeBackground(navbarContainer, navbarGlassBackdrop, navbarGlassSurface, background, navbarGlassOptions, 0f);
1322
+ if (navbarContainer != null) {
1323
+ navbarContainer.setBackgroundColor(background);
1324
+ }
1098
1325
  for (int index = 0; index < nativeToolbar.getMenu().size(); index++) {
1099
1326
  Drawable icon = nativeToolbar.getMenu().getItem(index).getIcon();
1100
1327
  if (icon != null) {
@@ -1103,113 +1330,67 @@ public class NativeNavigationPlugin extends Plugin {
1103
1330
  }
1104
1331
  }
1105
1332
 
1106
- private void applyTabbarColors(BottomNavigationView nativeTabbar, PluginCall call, JSObject colors) {
1107
- boolean dynamic = Boolean.TRUE.equals(colors.getBool("dynamic"));
1108
- int tintFallback = dynamic ? dynamicColor("system_accent1_600", tintColor) : tintColor;
1109
- int inactiveFallback = dynamic ? dynamicColor("system_neutral2_600", inactiveTintColor) : inactiveTintColor;
1110
- int backgroundFallback = dynamic
1111
- ? withAlpha(dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", Color.WHITE), 245)
1112
- : Color.argb(235, 255, 255, 255);
1113
- int tint = parseColor(colors.getString("tint", null), tintFallback);
1114
- int inactiveTint = parseColor(colors.getString("inactiveTint", null), inactiveFallback);
1115
- int background = parseColor(colors.getString("background", null), backgroundFallback);
1116
- tabbarBackgroundColor = background;
1117
- tintColor = tint;
1118
- inactiveTintColor = inactiveTint;
1119
- int[][] states = new int[][] { new int[] { android.R.attr.state_checked }, new int[] {} };
1120
- int[] colorState = new int[] { tint, inactiveTint };
1121
- ColorStateList colorStateList = new ColorStateList(states, colorState);
1122
- nativeTabbar.setItemIconTintList(colorStateList);
1123
- nativeTabbar.setItemTextColor(colorStateList);
1124
- nativeTabbar.setItemActiveIndicatorEnabled(!call.getBoolean("disableIndicator", false));
1125
- Integer indicator = colorOption(call, colors, "indicatorColor", "indicator", null);
1126
- nativeTabbar.setItemActiveIndicatorColor(indicator == null ? null : ColorStateList.valueOf(indicator));
1127
- Integer ripple = colorOption(call, colors, "rippleColor", "ripple", null);
1128
- nativeTabbar.setItemRippleColor(ripple == null ? null : ColorStateList.valueOf(ripple));
1129
- nativeTabbar.setBackgroundColor(Color.TRANSPARENT);
1130
- applyTabbarMetrics(nativeTabbar);
1131
- applyChromeBackground(
1132
- tabbarContainer,
1133
- tabbarGlassBackdrop,
1134
- tabbarGlassSurface,
1135
- background,
1136
- tabbarGlassOptions,
1137
- tabbarCornerRadius()
1333
+ private TabbarStyle makeTabbarStyle(JSObject rawStyle) {
1334
+ String requestedShape = rawStyle.getString("shape", "floating");
1335
+ boolean curve = "curve".equalsIgnoreCase(requestedShape);
1336
+ int centerButtonDiameter = Math.max(styleDimension(rawStyle, "centerButtonDiameter", 76), 44);
1337
+ int height = Math.max(styleDimension(rawStyle, "height", curve ? 76 : DEFAULT_TABBAR_DP), 44);
1338
+ int centerButtonLift = Math.max(styleDimension(rawStyle, "centerButtonLift", centerButtonDiameter / 2), 0);
1339
+ int bottomGap = Math.max(styleDimension(rawStyle, "bottomGap", curve ? 0 : 10), 0);
1340
+ int horizontalMargin = Math.max(styleDimension(rawStyle, "horizontalMargin", curve ? 0 : 24), 0);
1341
+ int maxWidth = Math.max(styleDimension(rawStyle, "maxWidth", curve ? 0 : 430), 0);
1342
+ int cornerRadius = Math.max(styleDimension(rawStyle, "cornerRadius", curve ? 24 : height / 2), 0);
1343
+ int centerButtonColor = parseColor(rawStyle.getString("centerButtonColor", null), tintColor);
1344
+ int centerButtonIconColor = parseColor(rawStyle.getString("centerButtonIconColor", null), Color.WHITE);
1345
+ return new TabbarStyle(
1346
+ curve ? "curve" : "floating",
1347
+ height,
1348
+ horizontalMargin,
1349
+ maxWidth,
1350
+ bottomGap,
1351
+ cornerRadius,
1352
+ rawStyle.getString("centerItemId", null),
1353
+ centerButtonDiameter,
1354
+ centerButtonLift,
1355
+ centerButtonColor,
1356
+ centerButtonIconColor
1138
1357
  );
1139
1358
  }
1140
1359
 
1141
- private void reapplyVisibleChromeBackgrounds() {
1142
- if (navbarContainer != null && navbarContainer.getVisibility() == View.VISIBLE) {
1143
- applyChromeBackground(navbarContainer, navbarGlassBackdrop, navbarGlassSurface, navbarBackgroundColor, navbarGlassOptions, 0f);
1144
- }
1145
- if (tabbarContainer != null && tabbarContainer.getVisibility() == View.VISIBLE) {
1146
- applyChromeBackground(
1147
- tabbarContainer,
1148
- tabbarGlassBackdrop,
1149
- tabbarGlassSurface,
1150
- tabbarBackgroundColor,
1151
- tabbarGlassOptions,
1152
- tabbarCornerRadius()
1153
- );
1154
- }
1155
- }
1156
-
1157
- private void applyChromeBackground(
1158
- ViewGroup container,
1159
- GlassBackdropView backdrop,
1160
- View surface,
1161
- int background,
1162
- GlassOptions glassOptions,
1163
- float cornerRadius
1164
- ) {
1165
- if (container == null) {
1166
- return;
1167
- }
1168
- GlassOptions resolvedGlassOptions = glassOptions == null ? GlassOptions.defaults() : glassOptions;
1169
- if (!resolvedGlassOptions.isLiquidGlass()) {
1170
- hideGlassBackground(backdrop, surface);
1171
- container.setBackground(chromeBackgroundDrawable(background, cornerRadius));
1172
- return;
1173
- }
1174
-
1175
- container.setBackgroundColor(Color.TRANSPARENT);
1176
- int surfaceColor = glassSurfaceColor(background, resolvedGlassOptions);
1177
- if (surface != null) {
1178
- surface.setBackground(chromeBackgroundDrawable(surfaceColor, cornerRadius));
1179
- surface.setVisibility(View.VISIBLE);
1180
- }
1181
- if (backdrop != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
1182
- View webView = getBridge() == null ? null : getBridge().getWebView();
1183
- backdrop.configure(webView, dp(resolvedGlassOptions.blurRadiusDp), surfaceColor);
1184
- backdrop.setVisibility(View.VISIBLE);
1185
- } else if (backdrop != null) {
1186
- backdrop.clearEffect();
1187
- backdrop.setVisibility(View.GONE);
1360
+ private int styleDimension(JSObject rawStyle, String key, int fallback) {
1361
+ if (rawStyle == null || !rawStyle.has(key)) {
1362
+ return fallback;
1188
1363
  }
1364
+ return (int) Math.round(rawStyle.optDouble(key, fallback));
1189
1365
  }
1190
1366
 
1191
- private void hideGlassBackground(GlassBackdropView backdrop, View surface) {
1192
- if (backdrop != null) {
1193
- backdrop.clearEffect();
1194
- backdrop.setVisibility(View.GONE);
1367
+ private int centerTabIndex() {
1368
+ if (!tabbarStyle.isCurve() || tabItems.isEmpty()) {
1369
+ return -1;
1195
1370
  }
1196
- if (surface != null) {
1197
- surface.setBackground(null);
1198
- surface.setVisibility(View.GONE);
1371
+ if (tabbarStyle.centerItemId != null) {
1372
+ for (int index = 0; index < tabItems.size(); index++) {
1373
+ if (tabbarStyle.centerItemId.equals(tabItems.get(index).id)) {
1374
+ return index;
1375
+ }
1376
+ }
1199
1377
  }
1378
+ return tabItems.size() / 2;
1200
1379
  }
1201
1380
 
1202
- private Drawable chromeBackgroundDrawable(int color, float cornerRadius) {
1203
- GradientDrawable drawable = new GradientDrawable();
1204
- drawable.setColor(color);
1205
- if (cornerRadius > 0f) {
1206
- drawable.setCornerRadius(cornerRadius);
1381
+ private void applyTabbarColors(PluginCall call, JSObject colors) {
1382
+ boolean dynamic = Boolean.TRUE.equals(colors.getBool("dynamic"));
1383
+ int tintFallback = dynamic ? dynamicColor("system_accent1_600", tintColor) : tintColor;
1384
+ int inactiveFallback = dynamic ? dynamicColor("system_neutral2_600", inactiveTintColor) : inactiveTintColor;
1385
+ int backgroundFallback = dynamic
1386
+ ? withAlpha(dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", Color.WHITE), 245)
1387
+ : Color.argb(235, 255, 255, 255);
1388
+ tintColor = parseColor(colors.getString("tint", null), tintFallback);
1389
+ inactiveTintColor = parseColor(colors.getString("inactiveTint", null), inactiveFallback);
1390
+ tabbarBackgroundColor = parseColor(colors.getString("background", null), backgroundFallback);
1391
+ if (tabbar != null) {
1392
+ tabbar.setTabbarStyle(tabbarStyle, tabbarBackgroundColor, centerTabIndex());
1207
1393
  }
1208
- return drawable;
1209
- }
1210
-
1211
- private int glassSurfaceColor(int background, GlassOptions glassOptions) {
1212
- return withAlpha(background, Math.round(Color.alpha(background) * (float) glassOptions.surfaceAlpha));
1213
1394
  }
1214
1395
 
1215
1396
  private int parseColor(String value, int fallback) {
@@ -1270,96 +1451,17 @@ public class NativeNavigationPlugin extends Plugin {
1270
1451
  return Color.argb(Math.max(0, Math.min(255, alpha)), Color.red(color), Color.green(color), Color.blue(color));
1271
1452
  }
1272
1453
 
1454
+ private Drawable selectableItemBackground() {
1455
+ TypedValue outValue = new TypedValue();
1456
+ getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
1457
+ return AppCompatResources.getDrawable(getContext(), outValue.resourceId);
1458
+ }
1459
+
1273
1460
  private boolean isNightMode() {
1274
1461
  int mode = getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
1275
1462
  return mode == Configuration.UI_MODE_NIGHT_YES;
1276
1463
  }
1277
1464
 
1278
- private void fillContainer(View view) {
1279
- if (view != null) {
1280
- view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
1281
- }
1282
- }
1283
-
1284
- private boolean isLiquidGlassTabbar() {
1285
- return tabbarGlassOptions != null && tabbarGlassOptions.isLiquidGlass();
1286
- }
1287
-
1288
- private int tabbarHeight() {
1289
- return dp(isLiquidGlassTabbar() ? LIQUID_GLASS_TABBAR_DP : DEFAULT_TABBAR_DP);
1290
- }
1291
-
1292
- private float tabbarCornerRadius() {
1293
- return tabbarHeight() / 2f;
1294
- }
1295
-
1296
- private int tabbarWidth(int rootWidth) {
1297
- if (isLiquidGlassTabbar()) {
1298
- return Math.min(Math.max(0, rootWidth - dp(LIQUID_GLASS_TABBAR_SIDE_MARGIN_DP * 2)), dp(LIQUID_GLASS_TABBAR_MAX_WIDTH_DP));
1299
- }
1300
- return Math.min(Math.max(0, rootWidth - dp(48)), dp(420));
1301
- }
1302
-
1303
- private int floatingTabbarBottomMargin(int navigationInset) {
1304
- if (isLiquidGlassTabbar()) {
1305
- return Math.max(dp(LIQUID_GLASS_TABBAR_FLOATING_BOTTOM_GAP_DP), navigationInset);
1306
- }
1307
- return navigationInset + dp(TABBAR_FLOATING_BOTTOM_GAP_DP);
1308
- }
1309
-
1310
- private void applyTabbarMetrics(BottomNavigationView nativeTabbar) {
1311
- boolean liquidGlass = isLiquidGlassTabbar();
1312
- nativeTabbar.setMinimumHeight(tabbarHeight());
1313
- nativeTabbar.setPadding(0, liquidGlass ? dp(LIQUID_GLASS_TABBAR_CONTENT_TOP_PADDING_DP) : 0, 0, 0);
1314
- nativeTabbar.setItemPaddingTop(dp(liquidGlass ? LIQUID_GLASS_TABBAR_ITEM_VERTICAL_PADDING_DP : TABBAR_ITEM_VERTICAL_PADDING_DP));
1315
- nativeTabbar.setItemPaddingBottom(dp(liquidGlass ? LIQUID_GLASS_TABBAR_ITEM_VERTICAL_PADDING_DP : TABBAR_ITEM_VERTICAL_PADDING_DP));
1316
- nativeTabbar.setItemActiveIndicatorHeight(dp(liquidGlass ? LIQUID_GLASS_TABBAR_INDICATOR_DP : TABBAR_INDICATOR_DP));
1317
- nativeTabbar.setActiveIndicatorLabelPadding(
1318
- dp(liquidGlass ? LIQUID_GLASS_TABBAR_INDICATOR_LABEL_PADDING_DP : TABBAR_INDICATOR_LABEL_PADDING_DP)
1319
- );
1320
- alignTabbarMenu(nativeTabbar, liquidGlass);
1321
- nativeTabbar.post(() -> alignTabbarMenu(nativeTabbar, isLiquidGlassTabbar()));
1322
- }
1323
-
1324
- private void alignTabbarMenu(BottomNavigationView nativeTabbar, boolean liquidGlass) {
1325
- nativeTabbar.setClipChildren(false);
1326
- nativeTabbar.setClipToPadding(false);
1327
- if (nativeTabbar.getChildCount() == 0) {
1328
- return;
1329
- }
1330
- View menuView = nativeTabbar.getChildAt(0);
1331
- ViewGroup.LayoutParams currentParams = menuView.getLayoutParams();
1332
- FrameLayout.LayoutParams menuParams =
1333
- currentParams instanceof FrameLayout.LayoutParams
1334
- ? (FrameLayout.LayoutParams) currentParams
1335
- : new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
1336
- menuParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
1337
- menuParams.height = liquidGlass ? ViewGroup.LayoutParams.MATCH_PARENT : ViewGroup.LayoutParams.WRAP_CONTENT;
1338
- menuParams.gravity = liquidGlass ? Gravity.CENTER : Gravity.TOP;
1339
- menuView.setLayoutParams(menuParams);
1340
- menuView.setTranslationY(liquidGlass ? dp(LIQUID_GLASS_TABBAR_CONTENT_TRANSLATION_Y_DP) : 0f);
1341
- if (menuView instanceof ViewGroup) {
1342
- ViewGroup menuGroup = (ViewGroup) menuView;
1343
- menuGroup.setClipChildren(false);
1344
- menuGroup.setClipToPadding(false);
1345
- translateTabbarLabels(menuGroup, liquidGlass ? dp(LIQUID_GLASS_TABBAR_LABEL_TRANSLATION_Y_DP) : 0f);
1346
- }
1347
- }
1348
-
1349
- private void translateTabbarLabels(View view, float translationY) {
1350
- if (view instanceof TextView) {
1351
- view.setTranslationY(translationY);
1352
- return;
1353
- }
1354
- if (!(view instanceof ViewGroup)) {
1355
- return;
1356
- }
1357
- ViewGroup group = (ViewGroup) view;
1358
- for (int index = 0; index < group.getChildCount(); index++) {
1359
- translateTabbarLabels(group.getChildAt(index), translationY);
1360
- }
1361
- }
1362
-
1363
1465
  private void layoutChrome() {
1364
1466
  FrameLayout root = contentRoot();
1365
1467
  if (root == null) {
@@ -1368,8 +1470,8 @@ public class NativeNavigationPlugin extends Plugin {
1368
1470
  int status = statusBarInset();
1369
1471
  int bottom = navigationBarInset();
1370
1472
  int navbarHeight = navbarVisible ? status + dp(DEFAULT_NAVBAR_DP) : 0;
1371
- int tabbarHeight = tabbarHeight();
1372
- int tabbarBottomMargin = tabbarVisible ? floatingTabbarBottomMargin(bottom) : bottom;
1473
+ int tabbarHeight = dp(tabbarStyle.totalHeight());
1474
+ int tabbarBottomMargin = tabbarVisible ? bottom + dp(tabbarStyle.bottomGap) : bottom;
1373
1475
 
1374
1476
  if (navbarContainer != null) {
1375
1477
  FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(
@@ -1378,8 +1480,6 @@ public class NativeNavigationPlugin extends Plugin {
1378
1480
  Gravity.TOP
1379
1481
  );
1380
1482
  navbarContainer.setLayoutParams(containerParams);
1381
- fillContainer(navbarGlassBackdrop);
1382
- fillContainer(navbarGlassSurface);
1383
1483
  FrameLayout.LayoutParams toolbarParams = new FrameLayout.LayoutParams(
1384
1484
  ViewGroup.LayoutParams.MATCH_PARENT,
1385
1485
  dp(DEFAULT_NAVBAR_DP),
@@ -1391,7 +1491,9 @@ public class NativeNavigationPlugin extends Plugin {
1391
1491
 
1392
1492
  if (tabbarContainer != null) {
1393
1493
  int rootWidth = root.getWidth() > 0 ? root.getWidth() : Resources.getSystem().getDisplayMetrics().widthPixels;
1394
- int tabbarWidth = tabbarWidth(rootWidth);
1494
+ int availableWidth = Math.max(0, rootWidth - dp(tabbarStyle.horizontalMargin) * 2);
1495
+ int maxWidth = tabbarStyle.maxWidth > 0 ? dp(tabbarStyle.maxWidth) : availableWidth;
1496
+ int tabbarWidth = Math.min(availableWidth, maxWidth);
1395
1497
  FrameLayout.LayoutParams tabbarContainerParams = new FrameLayout.LayoutParams(
1396
1498
  tabbarWidth,
1397
1499
  tabbarHeight,
@@ -1399,23 +1501,15 @@ public class NativeNavigationPlugin extends Plugin {
1399
1501
  );
1400
1502
  tabbarContainerParams.bottomMargin = tabbarBottomMargin;
1401
1503
  tabbarContainer.setLayoutParams(tabbarContainerParams);
1402
- fillContainer(tabbarGlassBackdrop);
1403
- fillContainer(tabbarGlassSurface);
1404
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1405
- tabbarContainer.invalidateOutline();
1406
- tabbarGlassBackdrop.invalidateOutline();
1407
- tabbarGlassSurface.invalidateOutline();
1408
- }
1409
1504
  }
1410
1505
 
1411
1506
  if (tabbar != null) {
1412
- applyTabbarMetrics(tabbar);
1413
1507
  FrameLayout.LayoutParams tabbarParams = new FrameLayout.LayoutParams(
1414
1508
  ViewGroup.LayoutParams.MATCH_PARENT,
1415
1509
  ViewGroup.LayoutParams.MATCH_PARENT
1416
1510
  );
1417
1511
  tabbar.setLayoutParams(tabbarParams);
1418
- tabbar.setPadding(0, isLiquidGlassTabbar() ? dp(LIQUID_GLASS_TABBAR_CONTENT_TOP_PADDING_DP) : 0, 0, 0);
1512
+ tabbar.setPadding(0, 0, 0, 0);
1419
1513
  }
1420
1514
 
1421
1515
  bringChromeToFront();
@@ -1471,15 +1565,14 @@ public class NativeNavigationPlugin extends Plugin {
1471
1565
  }
1472
1566
 
1473
1567
  private JSObject currentInsets() {
1474
- int navbarHeight = navbarVisible ? statusBarInset() + dp(DEFAULT_NAVBAR_DP) : 0;
1475
- int top = navbarVisible ? navbarHeight : 0;
1476
- int bottom = tabbarVisible ? floatingTabbarBottomMargin(navigationBarInset()) + tabbarHeight() : 0;
1568
+ int top = navbarVisible ? statusBarInset() + dp(DEFAULT_NAVBAR_DP) : 0;
1569
+ int bottom = tabbarVisible ? navigationBarInset() + dp(tabbarStyle.totalHeight()) + dp(tabbarStyle.bottomGap) : 0;
1477
1570
  JSObject insets = new JSObject();
1478
1571
  insets.put("top", top);
1479
1572
  insets.put("right", 0);
1480
1573
  insets.put("bottom", bottom);
1481
1574
  insets.put("left", 0);
1482
- insets.put("navbarHeight", navbarHeight);
1575
+ insets.put("navbarHeight", top);
1483
1576
  insets.put("tabbarHeight", bottom);
1484
1577
  return insets;
1485
1578
  }
@@ -1498,6 +1591,52 @@ public class NativeNavigationPlugin extends Plugin {
1498
1591
  return event;
1499
1592
  }
1500
1593
 
1594
+ private boolean isStationaryTransition(String direction) {
1595
+ return "tab".equals(direction) || "root".equals(direction) || "none".equals(direction);
1596
+ }
1597
+
1598
+ private void prepareTransitionRootBackground(FrameLayout root, int surfaceColor) {
1599
+ activeTransitionRootBackground = root.getBackground();
1600
+ activeTransitionRootBackgroundCaptured = true;
1601
+ if (needsTransitionSurface(activeTransitionRootBackground)) {
1602
+ root.setBackgroundColor(surfaceColor);
1603
+ }
1604
+ }
1605
+
1606
+ private void restoreTransitionRootBackground() {
1607
+ if (!activeTransitionRootBackgroundCaptured) {
1608
+ return;
1609
+ }
1610
+ FrameLayout root = contentRoot();
1611
+ if (root != null) {
1612
+ root.setBackground(activeTransitionRootBackground);
1613
+ }
1614
+ activeTransitionRootBackground = null;
1615
+ activeTransitionRootBackgroundCaptured = false;
1616
+ }
1617
+
1618
+ private boolean needsTransitionSurface(Drawable background) {
1619
+ if (background == null) {
1620
+ return true;
1621
+ }
1622
+ if (background instanceof ColorDrawable) {
1623
+ return Color.alpha(((ColorDrawable) background).getColor()) < 255;
1624
+ }
1625
+ return false;
1626
+ }
1627
+
1628
+ private int transitionSurfaceColor(View webView) {
1629
+ Drawable background = webView.getBackground();
1630
+ if (background instanceof ColorDrawable) {
1631
+ int color = ((ColorDrawable) background).getColor();
1632
+ if (Color.alpha(color) > 0) {
1633
+ return withAlpha(color, 255);
1634
+ }
1635
+ }
1636
+ int fallback = isNightMode() ? Color.rgb(18, 18, 18) : Color.WHITE;
1637
+ return dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", fallback);
1638
+ }
1639
+
1501
1640
  private FrameLayout contentRoot() {
1502
1641
  Activity activity = getActivity();
1503
1642
  return activity == null ? null : activity.findViewById(android.R.id.content);
@@ -1534,10 +1673,7 @@ public class NativeNavigationPlugin extends Plugin {
1534
1673
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1535
1674
  WindowInsets insets = getActivity().getWindow().getDecorView().getRootWindowInsets();
1536
1675
  if (insets != null) {
1537
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
1538
- return insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
1539
- }
1540
- return insets.getSystemWindowInsetBottom();
1676
+ return insets.getStableInsetBottom();
1541
1677
  }
1542
1678
  }
1543
1679
  return systemDimension("navigation_bar_height");
@@ -1552,208 +1688,6 @@ public class NativeNavigationPlugin extends Plugin {
1552
1688
  return Math.round(value * getContext().getResources().getDisplayMetrics().density);
1553
1689
  }
1554
1690
 
1555
- private float dp(double value) {
1556
- return (float) (value * getContext().getResources().getDisplayMetrics().density);
1557
- }
1558
-
1559
- private static final class GlassOptions {
1560
-
1561
- private static final String EFFECT_NONE = "none";
1562
- private static final String EFFECT_LIQUID_GLASS = "liquidGlass";
1563
- private static final double DEFAULT_BLUR_RADIUS_DP = 18d;
1564
- private static final double DEFAULT_SURFACE_ALPHA = 0.62d;
1565
-
1566
- final String effect;
1567
- final double blurRadiusDp;
1568
- final double surfaceAlpha;
1569
-
1570
- GlassOptions(String effect, double blurRadiusDp, double surfaceAlpha) {
1571
- this.effect = effect;
1572
- this.blurRadiusDp = Math.max(0d, blurRadiusDp);
1573
- this.surfaceAlpha = Math.max(0d, Math.min(1d, surfaceAlpha));
1574
- }
1575
-
1576
- static GlassOptions defaults() {
1577
- return new GlassOptions(EFFECT_NONE, DEFAULT_BLUR_RADIUS_DP, DEFAULT_SURFACE_ALPHA);
1578
- }
1579
-
1580
- static GlassOptions from(JSObject raw, GlassOptions fallback) {
1581
- GlassOptions base = fallback == null ? defaults() : fallback;
1582
- if (raw == null) {
1583
- return base;
1584
- }
1585
-
1586
- String effect = raw.optString("effect", base.effect);
1587
- if (!EFFECT_NONE.equals(effect) && !EFFECT_LIQUID_GLASS.equals(effect)) {
1588
- effect = base.effect;
1589
- }
1590
- double blurRadiusDp = raw.has("blurRadius") ? raw.optDouble("blurRadius", base.blurRadiusDp) : base.blurRadiusDp;
1591
- double surfaceAlpha = raw.has("surfaceAlpha") ? raw.optDouble("surfaceAlpha", base.surfaceAlpha) : base.surfaceAlpha;
1592
- return new GlassOptions(effect, blurRadiusDp, surfaceAlpha);
1593
- }
1594
-
1595
- boolean isLiquidGlass() {
1596
- return EFFECT_LIQUID_GLASS.equals(effect);
1597
- }
1598
- }
1599
-
1600
- private static final class GlassBackdropView extends View {
1601
-
1602
- private static final long SOURCE_CONTENT_REFRESH_INTERVAL_MS = 250L;
1603
-
1604
- private final Paint fallbackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1605
- private final int[] sourceLocation = new int[2];
1606
- private final int[] viewLocation = new int[2];
1607
- private final ViewTreeObserver.OnScrollChangedListener sourceScrollListener = this::markDirty;
1608
- private final ViewTreeObserver.OnPreDrawListener sourcePreDrawListener = () -> {
1609
- markDirtyFromSourcePreDraw();
1610
- return true;
1611
- };
1612
- private final View.OnLayoutChangeListener sourceLayoutListener = (
1613
- view,
1614
- left,
1615
- top,
1616
- right,
1617
- bottom,
1618
- oldLeft,
1619
- oldTop,
1620
- oldRight,
1621
- oldBottom
1622
- ) -> markDirty();
1623
- private View source;
1624
- private int fallbackColor = Color.TRANSPARENT;
1625
- private boolean dirty;
1626
- private boolean redrawPending;
1627
- private long lastSourcePreDrawRefreshMs;
1628
-
1629
- GlassBackdropView(android.content.Context context) {
1630
- super(context);
1631
- setWillNotDraw(false);
1632
- }
1633
-
1634
- void configure(View source, float blurRadiusPx, int fallbackColor) {
1635
- this.fallbackColor = fallbackColor;
1636
- attachSource(source);
1637
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
1638
- Api31RenderEffects.setBlur(this, blurRadiusPx);
1639
- }
1640
- markDirty();
1641
- }
1642
-
1643
- void clearEffect() {
1644
- attachSource(null);
1645
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
1646
- Api31RenderEffects.clear(this);
1647
- }
1648
- markDirty();
1649
- }
1650
-
1651
- private void attachSource(View nextSource) {
1652
- if (source == nextSource) {
1653
- return;
1654
- }
1655
- if (source != null) {
1656
- source.removeOnLayoutChangeListener(sourceLayoutListener);
1657
- ViewTreeObserver observer = source.getViewTreeObserver();
1658
- if (observer.isAlive()) {
1659
- observer.removeOnScrollChangedListener(sourceScrollListener);
1660
- observer.removeOnPreDrawListener(sourcePreDrawListener);
1661
- }
1662
- }
1663
- source = nextSource;
1664
- if (source != null) {
1665
- source.addOnLayoutChangeListener(sourceLayoutListener);
1666
- ViewTreeObserver observer = source.getViewTreeObserver();
1667
- if (observer.isAlive()) {
1668
- observer.addOnScrollChangedListener(sourceScrollListener);
1669
- observer.addOnPreDrawListener(sourcePreDrawListener);
1670
- }
1671
- }
1672
- markDirty();
1673
- }
1674
-
1675
- private void markDirty() {
1676
- dirty = true;
1677
- scheduleRedrawIfVisible();
1678
- }
1679
-
1680
- private void markDirtyFromSourcePreDraw() {
1681
- dirty = true;
1682
- long now = System.currentTimeMillis();
1683
- if (now - lastSourcePreDrawRefreshMs < SOURCE_CONTENT_REFRESH_INTERVAL_MS) {
1684
- return;
1685
- }
1686
- lastSourcePreDrawRefreshMs = now;
1687
- scheduleRedrawIfVisible();
1688
- }
1689
-
1690
- private void scheduleRedrawIfVisible() {
1691
- if (redrawPending || getVisibility() != View.VISIBLE || !isShown()) {
1692
- return;
1693
- }
1694
- redrawPending = true;
1695
- postInvalidateOnAnimation();
1696
- }
1697
-
1698
- @Override
1699
- protected void onDraw(Canvas canvas) {
1700
- super.onDraw(canvas);
1701
- View currentSource = source;
1702
- if (currentSource == null || currentSource.getWidth() <= 0 || currentSource.getHeight() <= 0) {
1703
- fallbackPaint.setColor(fallbackColor);
1704
- canvas.drawRect(0, 0, getWidth(), getHeight(), fallbackPaint);
1705
- } else {
1706
- currentSource.getLocationOnScreen(sourceLocation);
1707
- getLocationOnScreen(viewLocation);
1708
- canvas.save();
1709
- canvas.translate(sourceLocation[0] - viewLocation[0], sourceLocation[1] - viewLocation[1]);
1710
- currentSource.draw(canvas);
1711
- canvas.restore();
1712
- }
1713
-
1714
- dirty = false;
1715
- redrawPending = false;
1716
- }
1717
-
1718
- @Override
1719
- protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
1720
- super.onSizeChanged(width, height, oldWidth, oldHeight);
1721
- if (width != oldWidth || height != oldHeight) {
1722
- markDirty();
1723
- }
1724
- }
1725
-
1726
- @Override
1727
- protected void onVisibilityChanged(View changedView, int visibility) {
1728
- super.onVisibilityChanged(changedView, visibility);
1729
- if (visibility != View.VISIBLE) {
1730
- redrawPending = false;
1731
- return;
1732
- }
1733
- if (dirty) {
1734
- scheduleRedrawIfVisible();
1735
- }
1736
- }
1737
- }
1738
-
1739
- @RequiresApi(Build.VERSION_CODES.S)
1740
- private static final class Api31RenderEffects {
1741
-
1742
- static void setBlur(View view, float blurRadiusPx) {
1743
- if (blurRadiusPx <= 0f) {
1744
- view.setRenderEffect(null);
1745
- return;
1746
- }
1747
- view.setRenderEffect(
1748
- android.graphics.RenderEffect.createBlurEffect(blurRadiusPx, blurRadiusPx, android.graphics.Shader.TileMode.CLAMP)
1749
- );
1750
- }
1751
-
1752
- static void clear(View view) {
1753
- view.setRenderEffect(null);
1754
- }
1755
- }
1756
-
1757
1691
  private void runOnUiThread(Runnable runnable) {
1758
1692
  Activity activity = getActivity();
1759
1693
  if (activity == null) {