@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.
- package/README.md +27 -0
- package/android/src/main/java/app/capgo/nativenavigation/NativeNavigationPlugin.java +511 -577
- package/dist/docs.json +112 -0
- package/dist/esm/definitions.d.ts +65 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +1 -0
- package/dist/esm/web.js +16 -3
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +16 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +16 -3
- package/dist/plugin.js.map +1 -1
- package/docs/demo-curved-tabbar.png +0 -0
- package/docs/native-screenshots/android-curved-tabbar.png +0 -0
- package/ios/Sources/NativeNavigationPlugin/NativeNavigationPlugin.swift +661 -41
- package/ios/Tests/NativeNavigationPluginTests/NativeNavigationTests.swift +8 -0
- package/package.json +4 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package app.capgo.nativenavigation;
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
|
-
import android.content.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
119
|
-
private
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
228
|
+
selectedTabIndex = index;
|
|
302
229
|
}
|
|
303
230
|
}
|
|
304
231
|
|
|
305
|
-
if (
|
|
306
|
-
|
|
232
|
+
if (!tabItems.isEmpty() && (selectedTabIndex < 0 || selectedTabIndex >= tabItems.size())) {
|
|
233
|
+
selectedTabIndex = 0;
|
|
307
234
|
}
|
|
308
235
|
|
|
309
|
-
applyTabbarColors(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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(
|
|
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
|
|
731
|
-
if (
|
|
732
|
-
return
|
|
631
|
+
private void renderTabbarItems(String labelVisibilityMode, boolean icons) {
|
|
632
|
+
if (tabbar == null) {
|
|
633
|
+
return;
|
|
733
634
|
}
|
|
734
|
-
|
|
735
|
-
|
|
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
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
if ("selected".equals(mode)) {
|
|
668
|
+
return selected;
|
|
739
669
|
}
|
|
740
|
-
return
|
|
670
|
+
return true;
|
|
741
671
|
}
|
|
742
672
|
|
|
743
|
-
private
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1097
|
-
|
|
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
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
int
|
|
1110
|
-
int
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
int
|
|
1114
|
-
int
|
|
1115
|
-
int
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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
|
|
1142
|
-
if (
|
|
1143
|
-
|
|
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
|
|
1192
|
-
if (
|
|
1193
|
-
|
|
1194
|
-
backdrop.setVisibility(View.GONE);
|
|
1367
|
+
private int centerTabIndex() {
|
|
1368
|
+
if (!tabbarStyle.isCurve() || tabItems.isEmpty()) {
|
|
1369
|
+
return -1;
|
|
1195
1370
|
}
|
|
1196
|
-
if (
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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 =
|
|
1372
|
-
int tabbarBottomMargin = tabbarVisible ?
|
|
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
|
|
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,
|
|
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
|
|
1475
|
-
int
|
|
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",
|
|
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
|
-
|
|
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) {
|