@capgo/capacitor-native-navigation 8.0.9
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/CapgoNativeNavigation.podspec +17 -0
- package/LICENSE +373 -0
- package/Package.swift +28 -0
- package/README.md +858 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/app/capgo/nativenavigation/NativeNavigation.java +8 -0
- package/android/src/main/java/app/capgo/nativenavigation/NativeNavigationPlugin.java +1322 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +1369 -0
- package/dist/esm/components.d.ts +1 -0
- package/dist/esm/components.js +159 -0
- package/dist/esm/components.js.map +1 -0
- package/dist/esm/definitions.d.ts +470 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.js +40 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/plugin.d.ts +2 -0
- package/dist/esm/plugin.js +2 -0
- package/dist/esm/plugin.js.map +1 -0
- package/dist/esm/web.d.ts +17 -0
- package/dist/esm/web.js +90 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +310 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +313 -0
- package/dist/plugin.js.map +1 -0
- package/docs/demo-navigation.webp +0 -0
- package/docs/demo-options.webp +0 -0
- package/docs/demo-svg-icons.webp +0 -0
- package/ios/Sources/NativeNavigationPlugin/NativeNavigation.swift +7 -0
- package/ios/Sources/NativeNavigationPlugin/NativeNavigationPlugin.swift +1580 -0
- package/ios/Tests/NativeNavigationPluginTests/NativeNavigationTests.swift +19 -0
- package/package.json +91 -0
|
@@ -0,0 +1,1322 @@
|
|
|
1
|
+
package app.capgo.nativenavigation;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.res.ColorStateList;
|
|
5
|
+
import android.content.res.Configuration;
|
|
6
|
+
import android.content.res.Resources;
|
|
7
|
+
import android.graphics.Bitmap;
|
|
8
|
+
import android.graphics.Canvas;
|
|
9
|
+
import android.graphics.Color;
|
|
10
|
+
import android.graphics.Outline;
|
|
11
|
+
import android.graphics.Paint;
|
|
12
|
+
import android.graphics.Path;
|
|
13
|
+
import android.graphics.Rect;
|
|
14
|
+
import android.graphics.RectF;
|
|
15
|
+
import android.graphics.drawable.BitmapDrawable;
|
|
16
|
+
import android.graphics.drawable.ColorDrawable;
|
|
17
|
+
import android.graphics.drawable.Drawable;
|
|
18
|
+
import android.graphics.drawable.GradientDrawable;
|
|
19
|
+
import android.graphics.drawable.StateListDrawable;
|
|
20
|
+
import android.os.Build;
|
|
21
|
+
import android.view.Gravity;
|
|
22
|
+
import android.view.Menu;
|
|
23
|
+
import android.view.MenuItem;
|
|
24
|
+
import android.view.View;
|
|
25
|
+
import android.view.ViewGroup;
|
|
26
|
+
import android.view.ViewOutlineProvider;
|
|
27
|
+
import android.view.Window;
|
|
28
|
+
import android.view.WindowInsets;
|
|
29
|
+
import android.widget.FrameLayout;
|
|
30
|
+
import android.widget.ImageView;
|
|
31
|
+
import androidx.appcompat.content.res.AppCompatResources;
|
|
32
|
+
import androidx.appcompat.widget.Toolbar;
|
|
33
|
+
import androidx.core.graphics.PathParser;
|
|
34
|
+
import com.getcapacitor.JSArray;
|
|
35
|
+
import com.getcapacitor.JSObject;
|
|
36
|
+
import com.getcapacitor.Plugin;
|
|
37
|
+
import com.getcapacitor.PluginCall;
|
|
38
|
+
import com.getcapacitor.PluginMethod;
|
|
39
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
40
|
+
import com.google.android.material.badge.BadgeDrawable;
|
|
41
|
+
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|
42
|
+
import com.google.android.material.navigation.NavigationBarView;
|
|
43
|
+
import java.io.StringReader;
|
|
44
|
+
import java.net.URLDecoder;
|
|
45
|
+
import java.util.ArrayDeque;
|
|
46
|
+
import java.util.ArrayList;
|
|
47
|
+
import java.util.HashMap;
|
|
48
|
+
import java.util.List;
|
|
49
|
+
import java.util.Map;
|
|
50
|
+
import java.util.regex.Matcher;
|
|
51
|
+
import java.util.regex.Pattern;
|
|
52
|
+
import org.json.JSONArray;
|
|
53
|
+
import org.json.JSONObject;
|
|
54
|
+
import org.xmlpull.v1.XmlPullParser;
|
|
55
|
+
import org.xmlpull.v1.XmlPullParserFactory;
|
|
56
|
+
|
|
57
|
+
@CapacitorPlugin(name = "NativeNavigation")
|
|
58
|
+
public class NativeNavigationPlugin extends Plugin {
|
|
59
|
+
|
|
60
|
+
private static final int DEFAULT_NAVBAR_DP = 56;
|
|
61
|
+
private static final int DEFAULT_TABBAR_DP = 64;
|
|
62
|
+
private static final int DEFAULT_TRANSITION_MS = 350;
|
|
63
|
+
private static final int MENU_ITEM_BASE = 10_000;
|
|
64
|
+
|
|
65
|
+
private final NativeNavigation implementation = new NativeNavigation();
|
|
66
|
+
private FrameLayout navbarContainer;
|
|
67
|
+
private FrameLayout tabbarContainer;
|
|
68
|
+
private Toolbar toolbar;
|
|
69
|
+
private BottomNavigationView tabbar;
|
|
70
|
+
private ImageView transitionSnapshot;
|
|
71
|
+
private boolean enabled = true;
|
|
72
|
+
private boolean navbarVisible = false;
|
|
73
|
+
private boolean tabbarVisible = false;
|
|
74
|
+
private String contentInsetMode = "css";
|
|
75
|
+
private int defaultTransitionMs = DEFAULT_TRANSITION_MS;
|
|
76
|
+
private int activeTransitionMs = DEFAULT_TRANSITION_MS;
|
|
77
|
+
private int tintColor = Color.rgb(0, 122, 255);
|
|
78
|
+
private int inactiveTintColor = Color.rgb(120, 126, 137);
|
|
79
|
+
private String activeTransitionId;
|
|
80
|
+
private String activeTransitionDirection = "forward";
|
|
81
|
+
private RectF activeZoomSourceFrame;
|
|
82
|
+
private float activeZoomCornerRadius = 0f;
|
|
83
|
+
private final Map<Integer, String> menuActionIds = new HashMap<>();
|
|
84
|
+
private final Map<Integer, String> menuActionTitles = new HashMap<>();
|
|
85
|
+
private final Map<Integer, String> menuActionPlacements = new HashMap<>();
|
|
86
|
+
private final Map<Integer, String> tabIds = new HashMap<>();
|
|
87
|
+
private final Map<Integer, String> tabTitles = new HashMap<>();
|
|
88
|
+
|
|
89
|
+
@Override
|
|
90
|
+
public void load() {
|
|
91
|
+
Activity activity = getActivity();
|
|
92
|
+
if (activity != null) {
|
|
93
|
+
activity.runOnUiThread(this::enableEdgeToEdge);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@PluginMethod
|
|
98
|
+
public void configure(PluginCall call) {
|
|
99
|
+
runOnUiThread(() -> {
|
|
100
|
+
enabled = call.getBoolean("enabled", true);
|
|
101
|
+
contentInsetMode = call.getString("contentInsetMode", contentInsetMode);
|
|
102
|
+
Double duration = call.getDouble("animationDuration");
|
|
103
|
+
if (duration != null) {
|
|
104
|
+
defaultTransitionMs = Math.max(0, duration.intValue());
|
|
105
|
+
}
|
|
106
|
+
if (!enabled) {
|
|
107
|
+
if (navbarContainer != null) {
|
|
108
|
+
navbarContainer.setVisibility(View.GONE);
|
|
109
|
+
}
|
|
110
|
+
if (tabbar != null) {
|
|
111
|
+
tabbar.setVisibility(View.GONE);
|
|
112
|
+
}
|
|
113
|
+
if (tabbarContainer != null) {
|
|
114
|
+
tabbarContainer.setVisibility(View.GONE);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
updateInsetsAndNotify();
|
|
118
|
+
call.resolve(insetsResult());
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@PluginMethod
|
|
123
|
+
public void setNavbar(PluginCall call) {
|
|
124
|
+
runOnUiThread(() -> {
|
|
125
|
+
if (!enabled) {
|
|
126
|
+
navbarVisible = false;
|
|
127
|
+
updateInsetsAndNotify();
|
|
128
|
+
call.resolve(insetsResult());
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
boolean hidden = call.getBoolean("hidden", false);
|
|
133
|
+
navbarVisible = !hidden;
|
|
134
|
+
if (hidden) {
|
|
135
|
+
if (navbarContainer != null) {
|
|
136
|
+
navbarContainer.setVisibility(View.GONE);
|
|
137
|
+
}
|
|
138
|
+
updateInsetsAndNotify();
|
|
139
|
+
call.resolve(insetsResult());
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Toolbar nativeToolbar = ensureToolbar();
|
|
144
|
+
nativeToolbar.setTitle(call.getString("title", ""));
|
|
145
|
+
nativeToolbar.setSubtitle(call.getString("subtitle", null));
|
|
146
|
+
nativeToolbar.getMenu().clear();
|
|
147
|
+
menuActionIds.clear();
|
|
148
|
+
menuActionTitles.clear();
|
|
149
|
+
menuActionPlacements.clear();
|
|
150
|
+
|
|
151
|
+
JSObject backButton = call.getObject("backButton", null);
|
|
152
|
+
if (backButton != null && Boolean.TRUE.equals(backButton.getBool("visible"))) {
|
|
153
|
+
nativeToolbar.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
|
|
154
|
+
nativeToolbar.setNavigationContentDescription(backButton.getString("title", "Back"));
|
|
155
|
+
nativeToolbar.setNavigationOnClickListener((v) -> notifyListeners("navbarBack", new JSObject().put("source", "navbar")));
|
|
156
|
+
} else {
|
|
157
|
+
nativeToolbar.setNavigationIcon(null);
|
|
158
|
+
nativeToolbar.setNavigationOnClickListener(null);
|
|
159
|
+
addToolbarItems(nativeToolbar, call.getArray("leftItems", new JSArray()), "left");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
addToolbarItems(nativeToolbar, call.getArray("rightItems", new JSArray()), "right");
|
|
163
|
+
JSObject colors = call.getObject("colors", new JSObject());
|
|
164
|
+
applyToolbarColors(nativeToolbar, colors);
|
|
165
|
+
navbarContainer.setVisibility(View.VISIBLE);
|
|
166
|
+
layoutChrome();
|
|
167
|
+
updateInsetsAndNotify();
|
|
168
|
+
call.resolve(insetsResult());
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@PluginMethod
|
|
173
|
+
public void setTabbar(PluginCall call) {
|
|
174
|
+
runOnUiThread(() -> {
|
|
175
|
+
if (!enabled) {
|
|
176
|
+
tabbarVisible = false;
|
|
177
|
+
updateInsetsAndNotify();
|
|
178
|
+
call.resolve(insetsResult());
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
boolean hidden = call.getBoolean("hidden", false);
|
|
183
|
+
tabbarVisible = !hidden;
|
|
184
|
+
if (hidden) {
|
|
185
|
+
if (tabbar != null) {
|
|
186
|
+
tabbar.setVisibility(View.GONE);
|
|
187
|
+
}
|
|
188
|
+
if (tabbarContainer != null) {
|
|
189
|
+
tabbarContainer.setVisibility(View.GONE);
|
|
190
|
+
}
|
|
191
|
+
updateInsetsAndNotify();
|
|
192
|
+
call.resolve(insetsResult());
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
BottomNavigationView nativeTabbar = ensureTabbar();
|
|
197
|
+
for (Integer existingItemId : new ArrayList<>(tabIds.keySet())) {
|
|
198
|
+
nativeTabbar.removeBadge(existingItemId);
|
|
199
|
+
}
|
|
200
|
+
nativeTabbar.getMenu().clear();
|
|
201
|
+
tabIds.clear();
|
|
202
|
+
tabTitles.clear();
|
|
203
|
+
|
|
204
|
+
boolean labels = call.getBoolean("labels", true);
|
|
205
|
+
boolean icons = call.getBoolean("icons", true);
|
|
206
|
+
String labelVisibilityMode = call.getString("labelVisibilityMode", labels ? "labeled" : "unlabeled");
|
|
207
|
+
nativeTabbar.setLabelVisibilityMode(labelVisibilityMode(labelVisibilityMode));
|
|
208
|
+
|
|
209
|
+
JSONArray tabs = call.getArray("tabs", new JSArray());
|
|
210
|
+
String selectedId = call.getString("selectedId", null);
|
|
211
|
+
JSObject colors = call.getObject("colors", new JSObject());
|
|
212
|
+
Integer badgeBackground = colorOption(call, colors, "badgeBackgroundColor", "badgeBackground", null);
|
|
213
|
+
Integer badgeText = colorOption(call, colors, "badgeTextColor", "badgeText", null);
|
|
214
|
+
for (int index = 0; index < tabs.length(); index++) {
|
|
215
|
+
JSONObject tab = tabs.optJSONObject(index);
|
|
216
|
+
if (tab == null) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
int itemId = MENU_ITEM_BASE + index;
|
|
220
|
+
String id = tab.optString("id", "tab-" + index);
|
|
221
|
+
String title = tab.optString("title", "");
|
|
222
|
+
MenuItem item = nativeTabbar.getMenu().add(Menu.NONE, itemId, index, labelVisibilityMode.equals("unlabeled") ? "" : title);
|
|
223
|
+
item.setEnabled(tab.optBoolean("enabled", true));
|
|
224
|
+
Drawable icon = icons ? tabIconFrom(tab) : new ColorDrawable(Color.TRANSPARENT);
|
|
225
|
+
if (icon != null) {
|
|
226
|
+
item.setIcon(icon);
|
|
227
|
+
}
|
|
228
|
+
if (tab.has("badge")) {
|
|
229
|
+
nativeTabbar.removeBadge(itemId);
|
|
230
|
+
BadgeDrawable badge = nativeTabbar.getOrCreateBadge(itemId);
|
|
231
|
+
if (badgeBackground != null) {
|
|
232
|
+
badge.setBackgroundColor(badgeBackground);
|
|
233
|
+
}
|
|
234
|
+
if (badgeText != null) {
|
|
235
|
+
badge.setBadgeTextColor(badgeText);
|
|
236
|
+
}
|
|
237
|
+
Object badgeValue = tab.opt("badge");
|
|
238
|
+
if (badgeValue instanceof Number) {
|
|
239
|
+
badge.setNumber(((Number) badgeValue).intValue());
|
|
240
|
+
} else {
|
|
241
|
+
try {
|
|
242
|
+
badge.setNumber(Integer.parseInt(String.valueOf(badgeValue)));
|
|
243
|
+
} catch (NumberFormatException ignored) {
|
|
244
|
+
badge.setVisible(true);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
nativeTabbar.removeBadge(itemId);
|
|
249
|
+
}
|
|
250
|
+
tabIds.put(itemId, id);
|
|
251
|
+
tabTitles.put(itemId, title);
|
|
252
|
+
if (id.equals(selectedId)) {
|
|
253
|
+
nativeTabbar.setSelectedItemId(itemId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (nativeTabbar.getSelectedItemId() == 0 && nativeTabbar.getMenu().size() > 0) {
|
|
258
|
+
nativeTabbar.setSelectedItemId(nativeTabbar.getMenu().getItem(0).getItemId());
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
applyTabbarColors(nativeTabbar, call, colors);
|
|
262
|
+
if (tabbarContainer != null) {
|
|
263
|
+
tabbarContainer.setVisibility(View.VISIBLE);
|
|
264
|
+
}
|
|
265
|
+
nativeTabbar.setVisibility(View.VISIBLE);
|
|
266
|
+
layoutChrome();
|
|
267
|
+
updateInsetsAndNotify();
|
|
268
|
+
call.resolve(insetsResult());
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@PluginMethod
|
|
273
|
+
public void beginTransition(PluginCall call) {
|
|
274
|
+
runOnUiThread(() -> {
|
|
275
|
+
View webView = getBridge().getWebView();
|
|
276
|
+
FrameLayout root = contentRoot();
|
|
277
|
+
if (webView == null || root == null || webView.getWidth() <= 0 || webView.getHeight() <= 0) {
|
|
278
|
+
call.reject("WebView unavailable");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
activeTransitionId = call.getString("id", "transition-" + System.currentTimeMillis());
|
|
283
|
+
activeTransitionDirection = call.getString("direction", "forward");
|
|
284
|
+
Double duration = call.getDouble("duration");
|
|
285
|
+
activeTransitionMs = duration == null ? defaultTransitionMs : Math.max(0, duration.intValue());
|
|
286
|
+
RectF zoomSourceRect = "zoom".equals(activeTransitionDirection) ? transitionRect(call.getObject("sourceRect", null)) : null;
|
|
287
|
+
activeZoomSourceFrame = zoomSourceRect == null ? null : rootFrame(zoomSourceRect, webView);
|
|
288
|
+
Double cornerRadius = call.getDouble("cornerRadius");
|
|
289
|
+
activeZoomCornerRadius = cornerRadius == null ? 0f : cornerRadius.floatValue();
|
|
290
|
+
|
|
291
|
+
if (transitionSnapshot != null) {
|
|
292
|
+
root.removeView(transitionSnapshot);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(), webView.getHeight(), Bitmap.Config.ARGB_8888);
|
|
296
|
+
webView.draw(new Canvas(bitmap));
|
|
297
|
+
if (zoomSourceRect != null) {
|
|
298
|
+
Rect crop = bitmapCropRect(zoomSourceRect, bitmap);
|
|
299
|
+
bitmap = Bitmap.createBitmap(bitmap, crop.left, crop.top, crop.width(), crop.height());
|
|
300
|
+
}
|
|
301
|
+
transitionSnapshot = new ImageView(getContext());
|
|
302
|
+
transitionSnapshot.setImageBitmap(bitmap);
|
|
303
|
+
transitionSnapshot.setScaleType(ImageView.ScaleType.FIT_XY);
|
|
304
|
+
FrameLayout.LayoutParams params =
|
|
305
|
+
activeZoomSourceFrame == null
|
|
306
|
+
? new FrameLayout.LayoutParams(webView.getWidth(), webView.getHeight())
|
|
307
|
+
: new FrameLayout.LayoutParams(Math.round(activeZoomSourceFrame.width()), Math.round(activeZoomSourceFrame.height()));
|
|
308
|
+
params.leftMargin = activeZoomSourceFrame == null ? webView.getLeft() : Math.round(activeZoomSourceFrame.left);
|
|
309
|
+
params.topMargin = activeZoomSourceFrame == null ? webView.getTop() : Math.round(activeZoomSourceFrame.top);
|
|
310
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && activeZoomCornerRadius > 0) {
|
|
311
|
+
transitionSnapshot.setClipToOutline(true);
|
|
312
|
+
transitionSnapshot.setOutlineProvider(roundRectOutlineProvider(activeZoomCornerRadius));
|
|
313
|
+
}
|
|
314
|
+
root.addView(transitionSnapshot, params);
|
|
315
|
+
webView.setAlpha(0.01f);
|
|
316
|
+
bringChromeToFront();
|
|
317
|
+
|
|
318
|
+
JSObject event = transitionEvent(activeTransitionId, activeTransitionDirection, activeTransitionMs);
|
|
319
|
+
notifyListeners("transitionStart", event);
|
|
320
|
+
call.resolve(event);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@PluginMethod
|
|
325
|
+
public void finishTransition(PluginCall call) {
|
|
326
|
+
runOnUiThread(() -> {
|
|
327
|
+
View webView = getBridge().getWebView();
|
|
328
|
+
if (webView == null) {
|
|
329
|
+
call.reject("WebView unavailable");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
String transitionId = call.getString(
|
|
334
|
+
"id",
|
|
335
|
+
activeTransitionId == null ? "transition-" + System.currentTimeMillis() : activeTransitionId
|
|
336
|
+
);
|
|
337
|
+
String direction = call.getString("direction", activeTransitionDirection);
|
|
338
|
+
Double duration = call.getDouble("duration");
|
|
339
|
+
int durationMs = duration == null ? activeTransitionMs : Math.max(0, duration.intValue());
|
|
340
|
+
float width = webView.getWidth();
|
|
341
|
+
if ("zoom".equals(direction)) {
|
|
342
|
+
RectF sourceRect = transitionRect(call.getObject("sourceRect", null));
|
|
343
|
+
RectF targetRect = transitionRect(call.getObject("targetRect", null));
|
|
344
|
+
Double cornerRadius = call.getDouble("cornerRadius");
|
|
345
|
+
finishZoomTransition(
|
|
346
|
+
webView,
|
|
347
|
+
transitionSnapshot,
|
|
348
|
+
transitionId,
|
|
349
|
+
durationMs,
|
|
350
|
+
sourceRect == null ? null : rootFrame(sourceRect, webView),
|
|
351
|
+
targetRect == null ? null : rootFrame(targetRect, webView),
|
|
352
|
+
cornerRadius == null ? activeZoomCornerRadius : cornerRadius.floatValue(),
|
|
353
|
+
call
|
|
354
|
+
);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
float startTranslation;
|
|
358
|
+
float snapshotEndTranslation;
|
|
359
|
+
if ("back".equals(direction)) {
|
|
360
|
+
startTranslation = -width * 0.3f;
|
|
361
|
+
snapshotEndTranslation = width;
|
|
362
|
+
} else if ("tab".equals(direction) || "root".equals(direction) || "none".equals(direction)) {
|
|
363
|
+
startTranslation = 0;
|
|
364
|
+
snapshotEndTranslation = 0;
|
|
365
|
+
} else {
|
|
366
|
+
startTranslation = width;
|
|
367
|
+
snapshotEndTranslation = -width * 0.3f;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
webView.setTranslationX(startTranslation);
|
|
371
|
+
webView.setAlpha("none".equals(direction) ? 1f : 0.01f);
|
|
372
|
+
View snapshot = transitionSnapshot;
|
|
373
|
+
JSObject event = transitionEvent(transitionId, direction, durationMs);
|
|
374
|
+
Runnable finish = () -> {
|
|
375
|
+
FrameLayout root = contentRoot();
|
|
376
|
+
if (root != null && transitionSnapshot != null) {
|
|
377
|
+
root.removeView(transitionSnapshot);
|
|
378
|
+
}
|
|
379
|
+
transitionSnapshot = null;
|
|
380
|
+
activeTransitionId = null;
|
|
381
|
+
activeZoomSourceFrame = null;
|
|
382
|
+
webView.setTranslationX(0);
|
|
383
|
+
webView.setAlpha(1f);
|
|
384
|
+
notifyListeners("transitionEnd", event);
|
|
385
|
+
call.resolve(event);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
if (durationMs == 0) {
|
|
389
|
+
finish.run();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
webView.animate().translationX(0).alpha(1f).setDuration(durationMs).start();
|
|
394
|
+
if (snapshot != null) {
|
|
395
|
+
snapshot
|
|
396
|
+
.animate()
|
|
397
|
+
.translationX(snapshotEndTranslation)
|
|
398
|
+
.alpha("none".equals(direction) ? 0f : 0.75f)
|
|
399
|
+
.setDuration(durationMs)
|
|
400
|
+
.withEndAction(finish)
|
|
401
|
+
.start();
|
|
402
|
+
} else {
|
|
403
|
+
webView.postDelayed(finish, durationMs);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
@PluginMethod
|
|
409
|
+
public void getPluginVersion(PluginCall call) {
|
|
410
|
+
JSObject ret = new JSObject();
|
|
411
|
+
ret.put("version", implementation.getPluginVersion());
|
|
412
|
+
call.resolve(ret);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private void finishZoomTransition(
|
|
416
|
+
View webView,
|
|
417
|
+
View snapshot,
|
|
418
|
+
String transitionId,
|
|
419
|
+
int durationMs,
|
|
420
|
+
RectF sourceFrame,
|
|
421
|
+
RectF targetFrame,
|
|
422
|
+
float cornerRadius,
|
|
423
|
+
PluginCall call
|
|
424
|
+
) {
|
|
425
|
+
RectF startFrame = sourceFrame == null ? activeZoomSourceFrame : sourceFrame;
|
|
426
|
+
if (startFrame == null) {
|
|
427
|
+
startFrame = new RectF(webView.getLeft(), webView.getTop(), webView.getRight(), webView.getBottom());
|
|
428
|
+
}
|
|
429
|
+
JSObject event = transitionEvent(transitionId, "zoom", durationMs);
|
|
430
|
+
Runnable finish = () -> {
|
|
431
|
+
FrameLayout root = contentRoot();
|
|
432
|
+
if (root != null && transitionSnapshot != null) {
|
|
433
|
+
root.removeView(transitionSnapshot);
|
|
434
|
+
}
|
|
435
|
+
transitionSnapshot = null;
|
|
436
|
+
activeTransitionId = null;
|
|
437
|
+
activeZoomSourceFrame = null;
|
|
438
|
+
webView.setTranslationX(0);
|
|
439
|
+
webView.setTranslationY(0);
|
|
440
|
+
webView.setScaleX(1f);
|
|
441
|
+
webView.setScaleY(1f);
|
|
442
|
+
webView.setAlpha(1f);
|
|
443
|
+
notifyListeners("transitionEnd", event);
|
|
444
|
+
call.resolve(event);
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
if (durationMs == 0) {
|
|
448
|
+
finish.run();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (targetFrame != null && snapshot != null) {
|
|
453
|
+
webView.setAlpha(0.01f);
|
|
454
|
+
snapshot.setX(startFrame.left);
|
|
455
|
+
snapshot.setY(startFrame.top);
|
|
456
|
+
snapshot.setPivotX(0f);
|
|
457
|
+
snapshot.setPivotY(0f);
|
|
458
|
+
float scaleX = targetFrame.width() / Math.max(startFrame.width(), 1f);
|
|
459
|
+
float scaleY = targetFrame.height() / Math.max(startFrame.height(), 1f);
|
|
460
|
+
webView.animate().alpha(1f).setDuration(durationMs).start();
|
|
461
|
+
snapshot
|
|
462
|
+
.animate()
|
|
463
|
+
.x(targetFrame.left)
|
|
464
|
+
.y(targetFrame.top)
|
|
465
|
+
.scaleX(scaleX)
|
|
466
|
+
.scaleY(scaleY)
|
|
467
|
+
.alpha(0f)
|
|
468
|
+
.setDuration(durationMs)
|
|
469
|
+
.withEndAction(finish)
|
|
470
|
+
.start();
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
float fullWidth = Math.max(webView.getWidth(), 1f);
|
|
475
|
+
float fullHeight = Math.max(webView.getHeight(), 1f);
|
|
476
|
+
float fullCenterX = webView.getLeft() + fullWidth / 2f;
|
|
477
|
+
float fullCenterY = webView.getTop() + fullHeight / 2f;
|
|
478
|
+
webView.setPivotX(fullWidth / 2f);
|
|
479
|
+
webView.setPivotY(fullHeight / 2f);
|
|
480
|
+
webView.setTranslationX(startFrame.centerX() - fullCenterX);
|
|
481
|
+
webView.setTranslationY(startFrame.centerY() - fullCenterY);
|
|
482
|
+
webView.setScaleX(Math.max(startFrame.width() / fullWidth, 0.01f));
|
|
483
|
+
webView.setScaleY(Math.max(startFrame.height() / fullHeight, 0.01f));
|
|
484
|
+
webView.setAlpha(1f);
|
|
485
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && cornerRadius > 0) {
|
|
486
|
+
webView.setClipToOutline(true);
|
|
487
|
+
webView.setOutlineProvider(roundRectOutlineProvider(cornerRadius));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (snapshot != null) {
|
|
491
|
+
snapshot.setX(startFrame.left);
|
|
492
|
+
snapshot.setY(startFrame.top);
|
|
493
|
+
snapshot.setPivotX(0f);
|
|
494
|
+
snapshot.setPivotY(0f);
|
|
495
|
+
snapshot
|
|
496
|
+
.animate()
|
|
497
|
+
.x(webView.getLeft())
|
|
498
|
+
.y(webView.getTop())
|
|
499
|
+
.scaleX(fullWidth / Math.max(startFrame.width(), 1f))
|
|
500
|
+
.scaleY(fullHeight / Math.max(startFrame.height(), 1f))
|
|
501
|
+
.alpha(0f)
|
|
502
|
+
.setDuration(durationMs)
|
|
503
|
+
.start();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
webView
|
|
507
|
+
.animate()
|
|
508
|
+
.translationX(0)
|
|
509
|
+
.translationY(0)
|
|
510
|
+
.scaleX(1f)
|
|
511
|
+
.scaleY(1f)
|
|
512
|
+
.alpha(1f)
|
|
513
|
+
.setDuration(durationMs)
|
|
514
|
+
.withEndAction(() -> {
|
|
515
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
516
|
+
webView.setClipToOutline(false);
|
|
517
|
+
webView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
|
518
|
+
}
|
|
519
|
+
finish.run();
|
|
520
|
+
})
|
|
521
|
+
.start();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private void addToolbarItems(Toolbar nativeToolbar, JSONArray rawItems, String placement) {
|
|
525
|
+
for (int index = 0; index < rawItems.length(); index++) {
|
|
526
|
+
JSONObject rawItem = rawItems.optJSONObject(index);
|
|
527
|
+
if (rawItem == null) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
int itemId = MENU_ITEM_BASE + menuActionIds.size();
|
|
531
|
+
String id = rawItem.optString("id", "item-" + itemId);
|
|
532
|
+
String title = rawItem.optString("title", "");
|
|
533
|
+
MenuItem item = nativeToolbar.getMenu().add(Menu.NONE, itemId, index, title);
|
|
534
|
+
item.setEnabled(rawItem.optBoolean("enabled", true));
|
|
535
|
+
Drawable icon = iconFrom(rawItem.optJSONObject("icon"));
|
|
536
|
+
if (icon != null) {
|
|
537
|
+
item.setIcon(icon);
|
|
538
|
+
}
|
|
539
|
+
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
|
540
|
+
menuActionIds.put(itemId, id);
|
|
541
|
+
menuActionTitles.put(itemId, title);
|
|
542
|
+
menuActionPlacements.put(itemId, placement);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private RectF transitionRect(JSObject object) {
|
|
547
|
+
if (object == null) {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
double width = object.optDouble("width", 0);
|
|
551
|
+
double height = object.optDouble("height", 0);
|
|
552
|
+
if (width <= 0 || height <= 0) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
float x = (float) object.optDouble("x", 0);
|
|
556
|
+
float y = (float) object.optDouble("y", 0);
|
|
557
|
+
return new RectF(x, y, x + (float) width, y + (float) height);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
private RectF rootFrame(RectF viewportRect, View webView) {
|
|
561
|
+
return new RectF(
|
|
562
|
+
webView.getLeft() + viewportRect.left,
|
|
563
|
+
webView.getTop() + viewportRect.top,
|
|
564
|
+
webView.getLeft() + viewportRect.right,
|
|
565
|
+
webView.getTop() + viewportRect.bottom
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private Rect bitmapCropRect(RectF viewportRect, Bitmap bitmap) {
|
|
570
|
+
int left = Math.max(0, Math.min(bitmap.getWidth() - 1, Math.round(viewportRect.left)));
|
|
571
|
+
int top = Math.max(0, Math.min(bitmap.getHeight() - 1, Math.round(viewportRect.top)));
|
|
572
|
+
int right = Math.max(left + 1, Math.min(bitmap.getWidth(), Math.round(viewportRect.right)));
|
|
573
|
+
int bottom = Math.max(top + 1, Math.min(bitmap.getHeight(), Math.round(viewportRect.bottom)));
|
|
574
|
+
return new Rect(left, top, right, bottom);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
private ViewOutlineProvider roundRectOutlineProvider(float radius) {
|
|
578
|
+
return new ViewOutlineProvider() {
|
|
579
|
+
@Override
|
|
580
|
+
public void getOutline(View view, Outline outline) {
|
|
581
|
+
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private Toolbar ensureToolbar() {
|
|
587
|
+
if (toolbar != null) {
|
|
588
|
+
return toolbar;
|
|
589
|
+
}
|
|
590
|
+
FrameLayout root = contentRoot();
|
|
591
|
+
navbarContainer = new FrameLayout(getContext());
|
|
592
|
+
navbarContainer.setElevation(dp(8));
|
|
593
|
+
toolbar = new Toolbar(getContext());
|
|
594
|
+
toolbar.setPopupTheme(androidx.appcompat.R.style.ThemeOverlay_AppCompat_Light);
|
|
595
|
+
toolbar.setOnMenuItemClickListener((item) -> {
|
|
596
|
+
int itemId = item.getItemId();
|
|
597
|
+
JSObject event = new JSObject();
|
|
598
|
+
event.put("id", menuActionIds.get(itemId));
|
|
599
|
+
event.put("title", menuActionTitles.get(itemId));
|
|
600
|
+
event.put("placement", menuActionPlacements.get(itemId));
|
|
601
|
+
notifyListeners("navbarItemTap", event);
|
|
602
|
+
return true;
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
navbarContainer.addView(toolbar);
|
|
606
|
+
if (root != null) {
|
|
607
|
+
root.addView(navbarContainer);
|
|
608
|
+
} else {
|
|
609
|
+
getActivity().addContentView(
|
|
610
|
+
navbarContainer,
|
|
611
|
+
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(DEFAULT_NAVBAR_DP))
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
return toolbar;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private BottomNavigationView ensureTabbar() {
|
|
618
|
+
if (tabbar != null) {
|
|
619
|
+
return tabbar;
|
|
620
|
+
}
|
|
621
|
+
FrameLayout root = contentRoot();
|
|
622
|
+
tabbarContainer = new FrameLayout(getContext());
|
|
623
|
+
tabbarContainer.setElevation(dp(12));
|
|
624
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
625
|
+
tabbarContainer.setClipToOutline(true);
|
|
626
|
+
tabbarContainer.setOutlineProvider(
|
|
627
|
+
new ViewOutlineProvider() {
|
|
628
|
+
@Override
|
|
629
|
+
public void getOutline(View view, Outline outline) {
|
|
630
|
+
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), view.getHeight() / 2f);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
tabbar = new BottomNavigationView(getContext());
|
|
637
|
+
tabbar.setElevation(0);
|
|
638
|
+
tabbar.setBackgroundColor(Color.TRANSPARENT);
|
|
639
|
+
tabbar.setOnItemSelectedListener((item) -> {
|
|
640
|
+
int itemId = item.getItemId();
|
|
641
|
+
if (!tabIds.containsKey(itemId)) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
JSObject event = new JSObject();
|
|
645
|
+
event.put("id", tabIds.get(itemId));
|
|
646
|
+
event.put("index", itemId - MENU_ITEM_BASE);
|
|
647
|
+
event.put("title", tabTitles.get(itemId));
|
|
648
|
+
notifyListeners("tabSelect", event);
|
|
649
|
+
return true;
|
|
650
|
+
});
|
|
651
|
+
tabbarContainer.addView(tabbar);
|
|
652
|
+
if (root != null) {
|
|
653
|
+
root.addView(tabbarContainer);
|
|
654
|
+
} else {
|
|
655
|
+
getActivity().addContentView(
|
|
656
|
+
tabbarContainer,
|
|
657
|
+
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(DEFAULT_TABBAR_DP))
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
return tabbar;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private int labelVisibilityMode(String mode) {
|
|
664
|
+
if ("auto".equals(mode)) {
|
|
665
|
+
return NavigationBarView.LABEL_VISIBILITY_AUTO;
|
|
666
|
+
}
|
|
667
|
+
if ("selected".equals(mode)) {
|
|
668
|
+
return NavigationBarView.LABEL_VISIBILITY_SELECTED;
|
|
669
|
+
}
|
|
670
|
+
if ("unlabeled".equals(mode)) {
|
|
671
|
+
return NavigationBarView.LABEL_VISIBILITY_UNLABELED;
|
|
672
|
+
}
|
|
673
|
+
return NavigationBarView.LABEL_VISIBILITY_LABELED;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private Drawable tabIconFrom(JSONObject tab) {
|
|
677
|
+
Drawable icon = iconFrom(tab.optJSONObject("icon"));
|
|
678
|
+
Drawable selectedIcon = iconFrom(tab.optJSONObject("selectedIcon"));
|
|
679
|
+
if (selectedIcon == null) {
|
|
680
|
+
return icon;
|
|
681
|
+
}
|
|
682
|
+
StateListDrawable stateList = new StateListDrawable();
|
|
683
|
+
stateList.addState(new int[] { android.R.attr.state_checked }, selectedIcon);
|
|
684
|
+
if (icon != null) {
|
|
685
|
+
stateList.addState(new int[] {}, icon);
|
|
686
|
+
}
|
|
687
|
+
return stateList;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private Drawable iconFrom(JSONObject descriptor) {
|
|
691
|
+
if (descriptor == null) {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
String svg = svgFrom(descriptor);
|
|
695
|
+
if (svg != null && !svg.isEmpty()) {
|
|
696
|
+
return SvgIconRenderer.render(getContext().getResources(), svg, iconSizeDp(descriptor));
|
|
697
|
+
}
|
|
698
|
+
JSONObject android = descriptor.optJSONObject("android");
|
|
699
|
+
String resource = android == null ? null : android.optString("resource", null);
|
|
700
|
+
if (resource == null || resource.isEmpty()) {
|
|
701
|
+
resource = android == null ? null : android.optString("image", null);
|
|
702
|
+
}
|
|
703
|
+
if (resource == null || resource.isEmpty()) {
|
|
704
|
+
resource = descriptor.optString("src", null);
|
|
705
|
+
}
|
|
706
|
+
String inlineSvg = inlineSvgFrom(resource);
|
|
707
|
+
if (inlineSvg != null) {
|
|
708
|
+
return SvgIconRenderer.render(getContext().getResources(), inlineSvg, iconSizeDp(descriptor));
|
|
709
|
+
}
|
|
710
|
+
if (resource == null || resource.isEmpty()) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
int id = getContext().getResources().getIdentifier(resource, "drawable", getContext().getPackageName());
|
|
714
|
+
if (id == 0) {
|
|
715
|
+
id = getContext().getResources().getIdentifier(resource, "mipmap", getContext().getPackageName());
|
|
716
|
+
}
|
|
717
|
+
if (id == 0) {
|
|
718
|
+
id = getContext().getResources().getIdentifier(resource, "drawable", "android");
|
|
719
|
+
}
|
|
720
|
+
return id == 0 ? null : AppCompatResources.getDrawable(getContext(), id);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private String svgFrom(JSONObject descriptor) {
|
|
724
|
+
JSONObject android = descriptor.optJSONObject("android");
|
|
725
|
+
if (android != null) {
|
|
726
|
+
String svg = android.optString("svg", null);
|
|
727
|
+
if (svg != null && !svg.isEmpty()) {
|
|
728
|
+
return svg;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
String svg = descriptor.optString("svg", null);
|
|
732
|
+
if (svg != null && !svg.isEmpty()) {
|
|
733
|
+
return svg;
|
|
734
|
+
}
|
|
735
|
+
return inlineSvgFrom(descriptor.optString("src", null));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
private String inlineSvgFrom(String value) {
|
|
739
|
+
if (value == null) {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
String trimmed = value.trim();
|
|
743
|
+
if (trimmed.startsWith("<svg")) {
|
|
744
|
+
return trimmed;
|
|
745
|
+
}
|
|
746
|
+
String lower = trimmed.toLowerCase();
|
|
747
|
+
if (!lower.startsWith("data:image/svg+xml")) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
int comma = trimmed.indexOf(',');
|
|
751
|
+
if (comma < 0) {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
String meta = trimmed.substring(0, comma);
|
|
755
|
+
String payload = trimmed.substring(comma + 1);
|
|
756
|
+
try {
|
|
757
|
+
if (meta.contains(";base64")) {
|
|
758
|
+
byte[] decoded = android.util.Base64.decode(payload, android.util.Base64.DEFAULT);
|
|
759
|
+
return new String(decoded, "UTF-8");
|
|
760
|
+
}
|
|
761
|
+
return URLDecoder.decode(payload, "UTF-8");
|
|
762
|
+
} catch (Exception ignored) {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
private int iconSizeDp(JSONObject descriptor) {
|
|
768
|
+
double width = descriptor.optDouble("width", 24);
|
|
769
|
+
return (int) Math.max(1, Math.round(width));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
private static String attr(XmlPullParser parser, String name) {
|
|
773
|
+
return parser.getAttributeValue(null, name);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
private static Float length(String value) {
|
|
777
|
+
if (value == null || value.trim().isEmpty()) {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
Matcher matcher = SvgIconRenderer.NUMBER_PATTERN.matcher(value.trim());
|
|
781
|
+
return matcher.find() ? Float.parseFloat(matcher.group()) : null;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
private static final class SvgStyle {
|
|
785
|
+
|
|
786
|
+
boolean fill = true;
|
|
787
|
+
boolean stroke = false;
|
|
788
|
+
float strokeWidth = 2f;
|
|
789
|
+
Paint.Cap lineCap = Paint.Cap.BUTT;
|
|
790
|
+
Paint.Join lineJoin = Paint.Join.MITER;
|
|
791
|
+
int alpha = 255;
|
|
792
|
+
|
|
793
|
+
SvgStyle copy() {
|
|
794
|
+
SvgStyle copy = new SvgStyle();
|
|
795
|
+
copy.fill = fill;
|
|
796
|
+
copy.stroke = stroke;
|
|
797
|
+
copy.strokeWidth = strokeWidth;
|
|
798
|
+
copy.lineCap = lineCap;
|
|
799
|
+
copy.lineJoin = lineJoin;
|
|
800
|
+
copy.alpha = alpha;
|
|
801
|
+
return copy;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
void apply(XmlPullParser parser) {
|
|
805
|
+
String fillValue = attr(parser, "fill");
|
|
806
|
+
if (fillValue != null) {
|
|
807
|
+
fill = !"none".equalsIgnoreCase(fillValue);
|
|
808
|
+
}
|
|
809
|
+
String strokeValue = attr(parser, "stroke");
|
|
810
|
+
if (strokeValue != null) {
|
|
811
|
+
stroke = !"none".equalsIgnoreCase(strokeValue);
|
|
812
|
+
}
|
|
813
|
+
Float width = length(attr(parser, "stroke-width"));
|
|
814
|
+
if (width != null) {
|
|
815
|
+
strokeWidth = width;
|
|
816
|
+
}
|
|
817
|
+
Float opacity = length(attr(parser, "opacity"));
|
|
818
|
+
if (opacity != null) {
|
|
819
|
+
alpha = Math.max(0, Math.min(255, Math.round(opacity * 255)));
|
|
820
|
+
}
|
|
821
|
+
String cap = attr(parser, "stroke-linecap");
|
|
822
|
+
if ("round".equalsIgnoreCase(cap)) {
|
|
823
|
+
lineCap = Paint.Cap.ROUND;
|
|
824
|
+
} else if ("square".equalsIgnoreCase(cap)) {
|
|
825
|
+
lineCap = Paint.Cap.SQUARE;
|
|
826
|
+
} else if (cap != null) {
|
|
827
|
+
lineCap = Paint.Cap.BUTT;
|
|
828
|
+
}
|
|
829
|
+
String join = attr(parser, "stroke-linejoin");
|
|
830
|
+
if ("round".equalsIgnoreCase(join)) {
|
|
831
|
+
lineJoin = Paint.Join.ROUND;
|
|
832
|
+
} else if ("bevel".equalsIgnoreCase(join)) {
|
|
833
|
+
lineJoin = Paint.Join.BEVEL;
|
|
834
|
+
} else if (join != null) {
|
|
835
|
+
lineJoin = Paint.Join.MITER;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private static final class SvgIconRenderer {
|
|
841
|
+
|
|
842
|
+
private static final Pattern NUMBER_PATTERN = Pattern.compile("[-+]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][-+]?\\d+)?");
|
|
843
|
+
|
|
844
|
+
static Drawable render(Resources resources, String svg, int iconSizeDp) {
|
|
845
|
+
int sizePx = Math.max(1, Math.round(iconSizeDp * resources.getDisplayMetrics().density));
|
|
846
|
+
Bitmap bitmap = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888);
|
|
847
|
+
Canvas canvas = new Canvas(bitmap);
|
|
848
|
+
RectF viewBox = viewBox(svg, iconSizeDp);
|
|
849
|
+
canvas.scale(sizePx / Math.max(viewBox.width(), 1f), sizePx / Math.max(viewBox.height(), 1f));
|
|
850
|
+
canvas.translate(-viewBox.left, -viewBox.top);
|
|
851
|
+
|
|
852
|
+
try {
|
|
853
|
+
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
|
|
854
|
+
parser.setInput(new StringReader(svg));
|
|
855
|
+
ArrayDeque<SvgStyle> styles = new ArrayDeque<>();
|
|
856
|
+
styles.push(new SvgStyle());
|
|
857
|
+
int event = parser.getEventType();
|
|
858
|
+
while (event != XmlPullParser.END_DOCUMENT) {
|
|
859
|
+
if (event == XmlPullParser.START_TAG) {
|
|
860
|
+
SvgStyle style = styles.peek().copy();
|
|
861
|
+
style.apply(parser);
|
|
862
|
+
styles.push(style);
|
|
863
|
+
drawElement(canvas, parser, style);
|
|
864
|
+
} else if (event == XmlPullParser.END_TAG && styles.size() > 1) {
|
|
865
|
+
styles.pop();
|
|
866
|
+
}
|
|
867
|
+
event = parser.next();
|
|
868
|
+
}
|
|
869
|
+
} catch (Exception ignored) {}
|
|
870
|
+
|
|
871
|
+
BitmapDrawable drawable = new BitmapDrawable(resources, bitmap);
|
|
872
|
+
drawable.setBounds(0, 0, sizePx, sizePx);
|
|
873
|
+
return drawable;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private static void drawElement(Canvas canvas, XmlPullParser parser, SvgStyle style) {
|
|
877
|
+
String name = parser.getName().toLowerCase();
|
|
878
|
+
if ("path".equals(name)) {
|
|
879
|
+
Path path = path(attr(parser, "d"));
|
|
880
|
+
if (path != null) {
|
|
881
|
+
drawPath(canvas, path, style);
|
|
882
|
+
}
|
|
883
|
+
} else if ("line".equals(name)) {
|
|
884
|
+
Path path = new Path();
|
|
885
|
+
path.moveTo(value(attr(parser, "x1")), value(attr(parser, "y1")));
|
|
886
|
+
path.lineTo(value(attr(parser, "x2")), value(attr(parser, "y2")));
|
|
887
|
+
drawPath(canvas, path, style);
|
|
888
|
+
} else if ("polyline".equals(name) || "polygon".equals(name)) {
|
|
889
|
+
Path path = pointsPath(attr(parser, "points"), "polygon".equals(name));
|
|
890
|
+
if (path != null) {
|
|
891
|
+
drawPath(canvas, path, style);
|
|
892
|
+
}
|
|
893
|
+
} else if ("circle".equals(name)) {
|
|
894
|
+
float cx = value(attr(parser, "cx"));
|
|
895
|
+
float cy = value(attr(parser, "cy"));
|
|
896
|
+
float radius = value(attr(parser, "r"));
|
|
897
|
+
Path path = new Path();
|
|
898
|
+
path.addOval(new RectF(cx - radius, cy - radius, cx + radius, cy + radius), Path.Direction.CW);
|
|
899
|
+
drawPath(canvas, path, style);
|
|
900
|
+
} else if ("rect".equals(name)) {
|
|
901
|
+
float x = value(attr(parser, "x"));
|
|
902
|
+
float y = value(attr(parser, "y"));
|
|
903
|
+
float width = value(attr(parser, "width"));
|
|
904
|
+
float height = value(attr(parser, "height"));
|
|
905
|
+
float radius = Math.max(value(attr(parser, "rx")), value(attr(parser, "ry")));
|
|
906
|
+
Path path = new Path();
|
|
907
|
+
RectF rect = new RectF(x, y, x + width, y + height);
|
|
908
|
+
if (radius > 0) {
|
|
909
|
+
path.addRoundRect(rect, radius, radius, Path.Direction.CW);
|
|
910
|
+
} else {
|
|
911
|
+
path.addRect(rect, Path.Direction.CW);
|
|
912
|
+
}
|
|
913
|
+
drawPath(canvas, path, style);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
private static void drawPath(Canvas canvas, Path path, SvgStyle style) {
|
|
918
|
+
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
919
|
+
paint.setColor(Color.BLACK);
|
|
920
|
+
paint.setAlpha(style.alpha);
|
|
921
|
+
if (style.fill) {
|
|
922
|
+
paint.setStyle(Paint.Style.FILL);
|
|
923
|
+
canvas.drawPath(path, paint);
|
|
924
|
+
}
|
|
925
|
+
if (style.stroke) {
|
|
926
|
+
paint.setStyle(Paint.Style.STROKE);
|
|
927
|
+
paint.setStrokeWidth(style.strokeWidth);
|
|
928
|
+
paint.setStrokeCap(style.lineCap);
|
|
929
|
+
paint.setStrokeJoin(style.lineJoin);
|
|
930
|
+
canvas.drawPath(path, paint);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private static Path path(String data) {
|
|
935
|
+
if (data == null || data.isEmpty()) {
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
try {
|
|
939
|
+
return PathParser.createPathFromPathData(data);
|
|
940
|
+
} catch (RuntimeException ignored) {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
private static Path pointsPath(String value, boolean closed) {
|
|
946
|
+
List<Float> numbers = numbers(value);
|
|
947
|
+
if (numbers.size() < 2) {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
Path path = new Path();
|
|
951
|
+
path.moveTo(numbers.get(0), numbers.get(1));
|
|
952
|
+
for (int index = 2; index + 1 < numbers.size(); index += 2) {
|
|
953
|
+
path.lineTo(numbers.get(index), numbers.get(index + 1));
|
|
954
|
+
}
|
|
955
|
+
if (closed) {
|
|
956
|
+
path.close();
|
|
957
|
+
}
|
|
958
|
+
return path;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
private static RectF viewBox(String svg, int iconSizeDp) {
|
|
962
|
+
List<Float> viewBoxValues = numbers(attribute(svg, "viewBox"));
|
|
963
|
+
if (viewBoxValues.size() >= 4) {
|
|
964
|
+
return new RectF(
|
|
965
|
+
viewBoxValues.get(0),
|
|
966
|
+
viewBoxValues.get(1),
|
|
967
|
+
viewBoxValues.get(0) + viewBoxValues.get(2),
|
|
968
|
+
viewBoxValues.get(1) + viewBoxValues.get(3)
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
float width = value(attribute(svg, "width"));
|
|
972
|
+
float height = value(attribute(svg, "height"));
|
|
973
|
+
if (width <= 0 || height <= 0) {
|
|
974
|
+
width = iconSizeDp;
|
|
975
|
+
height = iconSizeDp;
|
|
976
|
+
}
|
|
977
|
+
return new RectF(0, 0, width, height);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private static String attribute(String svg, String name) {
|
|
981
|
+
if (svg == null) {
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
Pattern pattern = Pattern.compile(name + "\\s*=\\s*[\"']([^\"']+)[\"']", Pattern.CASE_INSENSITIVE);
|
|
985
|
+
Matcher matcher = pattern.matcher(svg);
|
|
986
|
+
return matcher.find() ? matcher.group(1) : null;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
private static float value(String value) {
|
|
990
|
+
Float parsed = length(value);
|
|
991
|
+
return parsed == null ? 0f : parsed;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
private static List<Float> numbers(String value) {
|
|
995
|
+
List<Float> numbers = new ArrayList<>();
|
|
996
|
+
if (value == null) {
|
|
997
|
+
return numbers;
|
|
998
|
+
}
|
|
999
|
+
Matcher matcher = NUMBER_PATTERN.matcher(value);
|
|
1000
|
+
while (matcher.find()) {
|
|
1001
|
+
numbers.add(Float.parseFloat(matcher.group()));
|
|
1002
|
+
}
|
|
1003
|
+
return numbers;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
private void applyToolbarColors(Toolbar nativeToolbar, JSObject colors) {
|
|
1008
|
+
boolean dynamic = Boolean.TRUE.equals(colors.getBool("dynamic"));
|
|
1009
|
+
int tintFallback = dynamic ? dynamicColor("system_accent1_600", tintColor) : tintColor;
|
|
1010
|
+
int backgroundFallback = dynamic
|
|
1011
|
+
? withAlpha(dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", Color.WHITE), 235)
|
|
1012
|
+
: Color.argb(225, 255, 255, 255);
|
|
1013
|
+
int foregroundFallback = dynamic
|
|
1014
|
+
? dynamicColor(isNightMode() ? "system_neutral1_50" : "system_neutral1_900", Color.rgb(20, 24, 32))
|
|
1015
|
+
: Color.rgb(20, 24, 32);
|
|
1016
|
+
int tint = parseColor(colors.getString("tint", null), tintFallback);
|
|
1017
|
+
int background = parseColor(colors.getString("background", null), backgroundFallback);
|
|
1018
|
+
int foreground = parseColor(colors.getString("foreground", null), foregroundFallback);
|
|
1019
|
+
tintColor = tint;
|
|
1020
|
+
nativeToolbar.setTitleTextColor(foreground);
|
|
1021
|
+
nativeToolbar.setSubtitleTextColor(withAlpha(foreground, 190));
|
|
1022
|
+
Drawable navigationIcon = nativeToolbar.getNavigationIcon();
|
|
1023
|
+
if (navigationIcon != null) {
|
|
1024
|
+
Drawable tintedIcon = navigationIcon.mutate();
|
|
1025
|
+
tintedIcon.setTint(tint);
|
|
1026
|
+
nativeToolbar.setNavigationIcon(tintedIcon);
|
|
1027
|
+
}
|
|
1028
|
+
if (navbarContainer != null) {
|
|
1029
|
+
navbarContainer.setBackgroundColor(background);
|
|
1030
|
+
}
|
|
1031
|
+
for (int index = 0; index < nativeToolbar.getMenu().size(); index++) {
|
|
1032
|
+
Drawable icon = nativeToolbar.getMenu().getItem(index).getIcon();
|
|
1033
|
+
if (icon != null) {
|
|
1034
|
+
icon.mutate().setTint(tint);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
private void applyTabbarColors(BottomNavigationView nativeTabbar, PluginCall call, JSObject colors) {
|
|
1040
|
+
boolean dynamic = Boolean.TRUE.equals(colors.getBool("dynamic"));
|
|
1041
|
+
int tintFallback = dynamic ? dynamicColor("system_accent1_600", tintColor) : tintColor;
|
|
1042
|
+
int inactiveFallback = dynamic ? dynamicColor("system_neutral2_600", inactiveTintColor) : inactiveTintColor;
|
|
1043
|
+
int backgroundFallback = dynamic
|
|
1044
|
+
? withAlpha(dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", Color.WHITE), 245)
|
|
1045
|
+
: Color.argb(235, 255, 255, 255);
|
|
1046
|
+
int tint = parseColor(colors.getString("tint", null), tintFallback);
|
|
1047
|
+
int inactiveTint = parseColor(colors.getString("inactiveTint", null), inactiveFallback);
|
|
1048
|
+
int background = parseColor(colors.getString("background", null), backgroundFallback);
|
|
1049
|
+
tintColor = tint;
|
|
1050
|
+
inactiveTintColor = inactiveTint;
|
|
1051
|
+
int[][] states = new int[][] { new int[] { android.R.attr.state_checked }, new int[] {} };
|
|
1052
|
+
int[] colorState = new int[] { tint, inactiveTint };
|
|
1053
|
+
ColorStateList colorStateList = new ColorStateList(states, colorState);
|
|
1054
|
+
nativeTabbar.setItemIconTintList(colorStateList);
|
|
1055
|
+
nativeTabbar.setItemTextColor(colorStateList);
|
|
1056
|
+
nativeTabbar.setItemActiveIndicatorEnabled(!call.getBoolean("disableIndicator", false));
|
|
1057
|
+
Integer indicator = colorOption(call, colors, "indicatorColor", "indicator", null);
|
|
1058
|
+
nativeTabbar.setItemActiveIndicatorColor(indicator == null ? null : ColorStateList.valueOf(indicator));
|
|
1059
|
+
Integer ripple = colorOption(call, colors, "rippleColor", "ripple", null);
|
|
1060
|
+
nativeTabbar.setItemRippleColor(ripple == null ? null : ColorStateList.valueOf(ripple));
|
|
1061
|
+
nativeTabbar.setBackgroundColor(Color.TRANSPARENT);
|
|
1062
|
+
if (tabbarContainer != null) {
|
|
1063
|
+
GradientDrawable capsule = new GradientDrawable();
|
|
1064
|
+
capsule.setColor(background);
|
|
1065
|
+
capsule.setCornerRadius(dp(DEFAULT_TABBAR_DP) / 2f);
|
|
1066
|
+
tabbarContainer.setBackground(capsule);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
private int parseColor(String value, int fallback) {
|
|
1071
|
+
if (value == null || value.isEmpty()) {
|
|
1072
|
+
return fallback;
|
|
1073
|
+
}
|
|
1074
|
+
if ("android:dynamicPrimary".equals(value) || "system:primary".equals(value)) {
|
|
1075
|
+
return dynamicColor("system_accent1_600", fallback);
|
|
1076
|
+
}
|
|
1077
|
+
if ("android:dynamicSurface".equals(value) || "system:surface".equals(value)) {
|
|
1078
|
+
return dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", fallback);
|
|
1079
|
+
}
|
|
1080
|
+
try {
|
|
1081
|
+
return Color.parseColor(value);
|
|
1082
|
+
} catch (IllegalArgumentException ignored) {
|
|
1083
|
+
return fallback;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private Integer parseColorOrNull(String value) {
|
|
1088
|
+
if (value == null || value.isEmpty()) {
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
if ("android:dynamicPrimary".equals(value) || "system:primary".equals(value)) {
|
|
1092
|
+
return dynamicColor("system_accent1_600", tintColor);
|
|
1093
|
+
}
|
|
1094
|
+
if ("android:dynamicSurface".equals(value) || "system:surface".equals(value)) {
|
|
1095
|
+
return dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", Color.WHITE);
|
|
1096
|
+
}
|
|
1097
|
+
try {
|
|
1098
|
+
return Color.parseColor(value);
|
|
1099
|
+
} catch (IllegalArgumentException ignored) {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private Integer colorOption(PluginCall call, JSObject colors, String directKey, String colorKey, Integer fallback) {
|
|
1105
|
+
Integer direct = parseColorOrNull(call.getString(directKey, null));
|
|
1106
|
+
if (direct != null) {
|
|
1107
|
+
return direct;
|
|
1108
|
+
}
|
|
1109
|
+
Integer nested = parseColorOrNull(colors.getString(colorKey, null));
|
|
1110
|
+
return nested == null ? fallback : nested;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
private int dynamicColor(String name, int fallback) {
|
|
1114
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
1115
|
+
return fallback;
|
|
1116
|
+
}
|
|
1117
|
+
int id = Resources.getSystem().getIdentifier(name, "color", "android");
|
|
1118
|
+
if (id == 0) {
|
|
1119
|
+
return fallback;
|
|
1120
|
+
}
|
|
1121
|
+
return getContext().getColor(id);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private int withAlpha(int color, int alpha) {
|
|
1125
|
+
return Color.argb(Math.max(0, Math.min(255, alpha)), Color.red(color), Color.green(color), Color.blue(color));
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
private boolean isNightMode() {
|
|
1129
|
+
int mode = getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
|
1130
|
+
return mode == Configuration.UI_MODE_NIGHT_YES;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
private void layoutChrome() {
|
|
1134
|
+
FrameLayout root = contentRoot();
|
|
1135
|
+
if (root == null) {
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
int status = statusBarInset();
|
|
1139
|
+
int bottom = navigationBarInset();
|
|
1140
|
+
int navbarHeight = navbarVisible ? status + dp(DEFAULT_NAVBAR_DP) : 0;
|
|
1141
|
+
int tabbarHeight = dp(DEFAULT_TABBAR_DP);
|
|
1142
|
+
int tabbarBottomMargin = tabbarVisible ? bottom + dp(10) : bottom;
|
|
1143
|
+
|
|
1144
|
+
if (navbarContainer != null) {
|
|
1145
|
+
FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(
|
|
1146
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1147
|
+
navbarHeight,
|
|
1148
|
+
Gravity.TOP
|
|
1149
|
+
);
|
|
1150
|
+
navbarContainer.setLayoutParams(containerParams);
|
|
1151
|
+
FrameLayout.LayoutParams toolbarParams = new FrameLayout.LayoutParams(
|
|
1152
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1153
|
+
dp(DEFAULT_NAVBAR_DP),
|
|
1154
|
+
Gravity.TOP
|
|
1155
|
+
);
|
|
1156
|
+
toolbarParams.topMargin = status;
|
|
1157
|
+
toolbar.setLayoutParams(toolbarParams);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (tabbarContainer != null) {
|
|
1161
|
+
int rootWidth = root.getWidth() > 0 ? root.getWidth() : Resources.getSystem().getDisplayMetrics().widthPixels;
|
|
1162
|
+
int tabbarWidth = Math.min(Math.max(0, rootWidth - dp(48)), dp(420));
|
|
1163
|
+
FrameLayout.LayoutParams tabbarContainerParams = new FrameLayout.LayoutParams(
|
|
1164
|
+
tabbarWidth,
|
|
1165
|
+
tabbarHeight,
|
|
1166
|
+
Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL
|
|
1167
|
+
);
|
|
1168
|
+
tabbarContainerParams.bottomMargin = tabbarBottomMargin;
|
|
1169
|
+
tabbarContainer.setLayoutParams(tabbarContainerParams);
|
|
1170
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
1171
|
+
tabbarContainer.invalidateOutline();
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (tabbar != null) {
|
|
1176
|
+
FrameLayout.LayoutParams tabbarParams = new FrameLayout.LayoutParams(
|
|
1177
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1178
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
1179
|
+
);
|
|
1180
|
+
tabbar.setLayoutParams(tabbarParams);
|
|
1181
|
+
tabbar.setPadding(0, 0, 0, 0);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
bringChromeToFront();
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
private void bringChromeToFront() {
|
|
1188
|
+
if (navbarContainer != null) {
|
|
1189
|
+
navbarContainer.bringToFront();
|
|
1190
|
+
}
|
|
1191
|
+
if (tabbar != null) {
|
|
1192
|
+
tabbar.bringToFront();
|
|
1193
|
+
}
|
|
1194
|
+
if (tabbarContainer != null) {
|
|
1195
|
+
tabbarContainer.bringToFront();
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
private void updateInsetsAndNotify() {
|
|
1200
|
+
layoutChrome();
|
|
1201
|
+
JSObject insets = currentInsets();
|
|
1202
|
+
JSObject event = new JSObject();
|
|
1203
|
+
event.put("insets", insets);
|
|
1204
|
+
notifyListeners("safeAreaChanged", event);
|
|
1205
|
+
if ("none".equals(contentInsetMode) || getBridge() == null || getBridge().getWebView() == null) {
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
String script =
|
|
1209
|
+
"(() => {" +
|
|
1210
|
+
"const root=document.documentElement;" +
|
|
1211
|
+
"root.style.setProperty('--cap-native-navigation-top','" +
|
|
1212
|
+
insets.getInteger("top", 0) +
|
|
1213
|
+
"px');" +
|
|
1214
|
+
"root.style.setProperty('--cap-native-navigation-right','" +
|
|
1215
|
+
insets.getInteger("right", 0) +
|
|
1216
|
+
"px');" +
|
|
1217
|
+
"root.style.setProperty('--cap-native-navigation-bottom','" +
|
|
1218
|
+
insets.getInteger("bottom", 0) +
|
|
1219
|
+
"px');" +
|
|
1220
|
+
"root.style.setProperty('--cap-native-navigation-left','" +
|
|
1221
|
+
insets.getInteger("left", 0) +
|
|
1222
|
+
"px');" +
|
|
1223
|
+
"root.style.setProperty('--cap-native-navbar-height','" +
|
|
1224
|
+
insets.getInteger("navbarHeight", 0) +
|
|
1225
|
+
"px');" +
|
|
1226
|
+
"root.style.setProperty('--cap-native-tabbar-height','" +
|
|
1227
|
+
insets.getInteger("tabbarHeight", 0) +
|
|
1228
|
+
"px');" +
|
|
1229
|
+
"window.dispatchEvent(new CustomEvent('capNativeNavigation:safeAreaChanged',{detail:{insets:" +
|
|
1230
|
+
insets.toString() +
|
|
1231
|
+
"}}));" +
|
|
1232
|
+
"})();";
|
|
1233
|
+
getBridge().getWebView().evaluateJavascript(script, null);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
private JSObject currentInsets() {
|
|
1237
|
+
int top = navbarVisible ? statusBarInset() + dp(DEFAULT_NAVBAR_DP) : 0;
|
|
1238
|
+
int bottom = tabbarVisible ? navigationBarInset() + dp(DEFAULT_TABBAR_DP) + dp(10) : 0;
|
|
1239
|
+
JSObject insets = new JSObject();
|
|
1240
|
+
insets.put("top", top);
|
|
1241
|
+
insets.put("right", 0);
|
|
1242
|
+
insets.put("bottom", bottom);
|
|
1243
|
+
insets.put("left", 0);
|
|
1244
|
+
insets.put("navbarHeight", top);
|
|
1245
|
+
insets.put("tabbarHeight", bottom);
|
|
1246
|
+
return insets;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
private JSObject insetsResult() {
|
|
1250
|
+
JSObject result = new JSObject();
|
|
1251
|
+
result.put("insets", currentInsets());
|
|
1252
|
+
return result;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
private JSObject transitionEvent(String id, String direction, int duration) {
|
|
1256
|
+
JSObject event = new JSObject();
|
|
1257
|
+
event.put("id", id);
|
|
1258
|
+
event.put("direction", direction);
|
|
1259
|
+
event.put("duration", duration);
|
|
1260
|
+
return event;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
private FrameLayout contentRoot() {
|
|
1264
|
+
Activity activity = getActivity();
|
|
1265
|
+
return activity == null ? null : activity.findViewById(android.R.id.content);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
private void enableEdgeToEdge() {
|
|
1269
|
+
Activity activity = getActivity();
|
|
1270
|
+
if (activity == null) {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
Window window = activity.getWindow();
|
|
1274
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
1275
|
+
window.setDecorFitsSystemWindows(false);
|
|
1276
|
+
} else {
|
|
1277
|
+
window
|
|
1278
|
+
.getDecorView()
|
|
1279
|
+
.setSystemUiVisibility(
|
|
1280
|
+
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
private int statusBarInset() {
|
|
1286
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1287
|
+
WindowInsets insets = getActivity().getWindow().getDecorView().getRootWindowInsets();
|
|
1288
|
+
if (insets != null) {
|
|
1289
|
+
return insets.getStableInsetTop();
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return systemDimension("status_bar_height");
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
private int navigationBarInset() {
|
|
1296
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1297
|
+
WindowInsets insets = getActivity().getWindow().getDecorView().getRootWindowInsets();
|
|
1298
|
+
if (insets != null) {
|
|
1299
|
+
return insets.getStableInsetBottom();
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return systemDimension("navigation_bar_height");
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
private int systemDimension(String name) {
|
|
1306
|
+
int id = getContext().getResources().getIdentifier(name, "dimen", "android");
|
|
1307
|
+
return id == 0 ? 0 : getContext().getResources().getDimensionPixelSize(id);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
private int dp(int value) {
|
|
1311
|
+
return Math.round(value * getContext().getResources().getDisplayMetrics().density);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
private void runOnUiThread(Runnable runnable) {
|
|
1315
|
+
Activity activity = getActivity();
|
|
1316
|
+
if (activity == null) {
|
|
1317
|
+
runnable.run();
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
activity.runOnUiThread(runnable);
|
|
1321
|
+
}
|
|
1322
|
+
}
|