@livx.cc/appwrap 0.61.13 → 0.61.14
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/package.json +1 -1
- package/runtime/app/main-page.ts +55 -18
- package/runtime/app/main-page.xml +0 -5
package/package.json
CHANGED
package/runtime/app/main-page.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Application, AndroidApplication, Button, EventData,
|
|
1
|
+
import { Application, AndroidApplication, Button, EventData, LoadEventData, Page, StackLayout, WebView, isAndroid, isIOS, knownFolders, path } from '@nativescript/core';
|
|
2
2
|
import { bridge } from './shell/bridge';
|
|
3
3
|
import { effectiveServerUrl } from './shell/server-url';
|
|
4
4
|
import { registerHandlers } from './shell/handlers';
|
|
@@ -180,32 +180,25 @@ export function onPageLoaded(args: EventData): void {
|
|
|
180
180
|
* NOTE: web-process termination has no NS seam in a prod build (the didTerminate forwarder lives on
|
|
181
181
|
* the debug-only DevCertNavDelegate) — out of scope here.
|
|
182
182
|
*/
|
|
183
|
-
/** iOS LaunchScreen.Center is stamped at 220pt; Android's splash canvas is 288dp (cli.ts
|
|
184
|
-
* ANDROID_SPLASH_DP). The overlay draws the SAME image at the SAME size so the hand-off from the OS
|
|
185
|
-
* launch screen is invisible. */
|
|
186
|
-
const SPLASH_LOGO_SIZE = isIOS ? 220 : 288;
|
|
187
|
-
|
|
188
183
|
/**
|
|
189
184
|
* `splashHold`: keep the launch splash up until the PAGE has painted. Without it the OS splash drops
|
|
190
185
|
* the moment this native page draws, and a loader:'server' WebView shows its blank canvas for the
|
|
191
186
|
* whole index.html + bundle download. The page ends the hold with `kit.invoke('ui.splash.hide')`;
|
|
192
187
|
* a safety timeout and the load-failure view end it too, so a page that never calls it (or an old
|
|
193
188
|
* web build behind a new shell) costs at most `timeoutMs`. Returns the idempotent hide.
|
|
189
|
+
*
|
|
190
|
+
* PIXEL-IDENTICAL BY CONSTRUCTION: the overlay IS the OS launch splash, re-instantiated natively and
|
|
191
|
+
* pinned full-bleed over the whole window — iOS instantiates LaunchScreen.storyboard itself, Android
|
|
192
|
+
* paints the window-background drawable (splash_screen.xml). An NS-layout copy sat inside the safe
|
|
193
|
+
* area, so its logo landed lower than the storyboard's and visibly jumped at the hand-off.
|
|
194
194
|
*/
|
|
195
195
|
function wireSplashHold(page: Page): (why: string) => void {
|
|
196
196
|
const noop = () => {};
|
|
197
|
-
const
|
|
198
|
-
if (!
|
|
197
|
+
const remove = SHELL_CONFIG.splash?.hold ? mountNativeSplash(page) : null;
|
|
198
|
+
if (!remove) {
|
|
199
199
|
bridge.register('ui.splash.hide', noop); // a page may always call it; nothing is held here
|
|
200
200
|
return noop;
|
|
201
201
|
}
|
|
202
|
-
const logo = page.getViewById<Image>('splashLogo');
|
|
203
|
-
if (SHELL_CONFIG.splash.logo && logo) {
|
|
204
|
-
logo.src = isIOS ? 'res://LaunchScreen.Center' : 'res://splash_logo';
|
|
205
|
-
logo.width = SPLASH_LOGO_SIZE;
|
|
206
|
-
logo.height = SPLASH_LOGO_SIZE;
|
|
207
|
-
}
|
|
208
|
-
overlay.visibility = 'visible';
|
|
209
202
|
const shownAt = Date.now();
|
|
210
203
|
let done = false;
|
|
211
204
|
const hide = (why: string): void => {
|
|
@@ -213,15 +206,43 @@ function wireSplashHold(page: Page): (why: string) => void {
|
|
|
213
206
|
done = true;
|
|
214
207
|
clearTimeout(timer);
|
|
215
208
|
appwrapNativeLog(`[native:splash] hide (${why}) after ${Date.now() - shownAt}ms`);
|
|
216
|
-
|
|
217
|
-
.catch(() => { /* view torn down mid-fade — collapsing below is all that matters */ })
|
|
218
|
-
.then(() => { overlay.visibility = 'collapse'; });
|
|
209
|
+
remove();
|
|
219
210
|
};
|
|
220
211
|
const timer = setTimeout(() => hide('timeout'), SHELL_CONFIG.splash.timeoutMs);
|
|
221
212
|
bridge.register('ui.splash.hide', () => { hide('page'); });
|
|
222
213
|
return hide;
|
|
223
214
|
}
|
|
224
215
|
|
|
216
|
+
/** Put the OS launch splash back on screen as a native full-window view; returns its fade-out+remove. */
|
|
217
|
+
function mountNativeSplash(page: Page): (() => void) | null {
|
|
218
|
+
const FADE_MS = 180;
|
|
219
|
+
try {
|
|
220
|
+
if (isIOS) {
|
|
221
|
+
const host = (page.viewController as UIViewController | undefined)?.view;
|
|
222
|
+
const vc = UIStoryboard.storyboardWithNameBundle('LaunchScreen', null).instantiateInitialViewController();
|
|
223
|
+
if (!host || !vc) return null;
|
|
224
|
+
const v = vc.view;
|
|
225
|
+
v.frame = UIScreen.mainScreen.bounds;
|
|
226
|
+
v.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
|
|
227
|
+
host.addSubview(v);
|
|
228
|
+
return () => UIView.animateWithDurationAnimationsCompletion(FADE_MS / 1000, () => { v.alpha = 0; }, () => v.removeFromSuperview());
|
|
229
|
+
}
|
|
230
|
+
const act = (Application.android.foregroundActivity || Application.android.startActivity) as android.app.Activity | null;
|
|
231
|
+
const decor = act?.getWindow()?.getDecorView() as android.view.ViewGroup | undefined;
|
|
232
|
+
if (!act || !decor) return null;
|
|
233
|
+
const v = new android.view.View(act);
|
|
234
|
+
const resId = act.getResources().getIdentifier('splash_screen', 'drawable', act.getPackageName());
|
|
235
|
+
if (resId) v.setBackground(act.getResources().getDrawable(resId, act.getTheme()));
|
|
236
|
+
else v.setBackgroundColor(android.graphics.Color.parseColor(SHELL_CONFIG.backgroundColor));
|
|
237
|
+
v.setClickable(true); // swallow touches while the page boots, like the OS splash
|
|
238
|
+
decor.addView(v, new android.view.ViewGroup.LayoutParams(-1, -1));
|
|
239
|
+
return () => v.animate().alpha(0).setDuration(FADE_MS).withEndAction(new java.lang.Runnable({ run: () => decor.removeView(v) })).start();
|
|
240
|
+
} catch (e) {
|
|
241
|
+
appwrapNativeLog(`[native:splash] overlay unavailable: ${e}`);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
225
246
|
function wireLoadFallback(page: Page, webView: CustomWebView, hideSplash: (why: string) => void = () => {}): void {
|
|
226
247
|
if (SHELL_CONFIG.loader !== 'server') return; // bundled loaders can't fail on network
|
|
227
248
|
const fallback = page.getViewById<StackLayout>('loadFallback');
|
|
@@ -230,6 +251,7 @@ function wireLoadFallback(page: Page, webView: CustomWebView, hideSplash: (why:
|
|
|
230
251
|
|
|
231
252
|
let retryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
232
253
|
let backoffMs = 3000;
|
|
254
|
+
let triedCache = false; // iOS: one cache-only replay per failure run (see below)
|
|
233
255
|
const clearTimer = () => { if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; } };
|
|
234
256
|
const retry = () => {
|
|
235
257
|
clearTimer();
|
|
@@ -245,6 +267,7 @@ function wireLoadFallback(page: Page, webView: CustomWebView, hideSplash: (why:
|
|
|
245
267
|
const err = String(args.error ?? '');
|
|
246
268
|
if (!err) {
|
|
247
269
|
// successful load → hide + reset (idempotent; covers the auto-retry that finally lands)
|
|
270
|
+
triedCache = false;
|
|
248
271
|
clearTimer();
|
|
249
272
|
backoffMs = 3000;
|
|
250
273
|
if (fallback.visibility !== 'collapse') {
|
|
@@ -254,6 +277,20 @@ function wireLoadFallback(page: Page, webView: CustomWebView, hideSplash: (why:
|
|
|
254
277
|
return;
|
|
255
278
|
}
|
|
256
279
|
if (/cancel/i.test(err)) return; // NSURLErrorCancelled: navigation superseded, not a failure
|
|
280
|
+
// iOS OFFLINE LAUNCH: WKWebView gets no service worker without app-bound domains, but its HTTP
|
|
281
|
+
// cache already holds the last index.html (no-cache = revalidate, still stored) and the immutable
|
|
282
|
+
// hashed assets. Before showing the error, replay the launch URL cache-only; online launches never
|
|
283
|
+
// take this path, so they still revalidate and get the newest deploy (OTA).
|
|
284
|
+
if (isIOS && !triedCache) {
|
|
285
|
+
triedCache = true;
|
|
286
|
+
const wk = webView.ios as WKWebView | null;
|
|
287
|
+
if (wk) {
|
|
288
|
+
appwrapNativeLog(`[native:fallback] load failed (${err}) — replaying from the HTTP cache`);
|
|
289
|
+
wk.loadRequest(NSURLRequest.requestWithURLCachePolicyTimeoutInterval(
|
|
290
|
+
NSURL.URLWithString(effectiveServerUrl()), NSURLRequestCachePolicy.ReturnCacheDataDontLoad, 10));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
257
294
|
appwrapNativeLog(`[native:fallback] load failed: ${err}`);
|
|
258
295
|
hideSplash('load-failed');
|
|
259
296
|
fallback.visibility = 'visible';
|
|
@@ -6,11 +6,6 @@
|
|
|
6
6
|
backgroundColor="{{ backgroundColor }}">
|
|
7
7
|
<GridLayout rows="*">
|
|
8
8
|
<shell:CustomWebView id="webview" row="0" />
|
|
9
|
-
<!-- splashHold: the launch splash (bg + splashIcon) held over the WebView until the page reports
|
|
10
|
-
its first paint (`ui.splash.hide`) — see wireSplashHold in main-page.ts. -->
|
|
11
|
-
<GridLayout id="splashOverlay" row="0" visibility="collapse" backgroundColor="{{ backgroundColor }}" iosOverflowSafeArea="true">
|
|
12
|
-
<Image id="splashLogo" stretch="aspectFit" horizontalAlignment="center" verticalAlignment="center" />
|
|
13
|
-
</GridLayout>
|
|
14
9
|
<!-- loader:'server' load-failure fallback (App Review 2.1a): shown by main-page.ts when the
|
|
15
10
|
server navigation fails (offline / server down) so the reviewer never sees a white screen. -->
|
|
16
11
|
<StackLayout id="loadFallback" row="0" visibility="collapse" backgroundColor="#ffffff" verticalAlignment="center" padding="32">
|