@livx.cc/appwrap 0.26.0 → 0.28.1
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_Resources/Android/src/main/AndroidManifest.xml +3 -0
- package/runtime/app/app.ts +57 -39
- package/runtime/app/shell/android-helpers.ts +6 -6
- package/runtime/app/shell/background-bootstrap.generated.ts +8 -0
- package/runtime/app/shell/background-context.ts +20 -0
- package/runtime/app/shell/banner.ts +9 -4
- package/runtime/app/shell/bridge.ts +2 -0
- package/runtime/app/shell/capabilities.manifest.ts +22 -3
- package/runtime/app/shell/custom-webview.android.ts +50 -23
- package/runtime/app/shell/custom-webview.ios.ts +90 -78
- package/runtime/app/shell/devmenu.ts +2 -2
- package/runtime/app/shell/env.ts +1 -3
- package/runtime/app/shell/events.ts +25 -4
- package/runtime/app/shell/fcm-firebase.d.ts +2 -1
- package/runtime/app/shell/handlers-android.ts +16 -12
- package/runtime/app/shell/handlers-background.ts +290 -0
- package/runtime/app/shell/handlers-billing.ts +90 -82
- package/runtime/app/shell/handlers-extended.ts +141 -110
- package/runtime/app/shell/handlers-fs.ts +53 -41
- package/runtime/app/shell/handlers-health.ts +27 -19
- package/runtime/app/shell/handlers-media.ts +8 -14
- package/runtime/app/shell/handlers-oauth.ts +19 -19
- package/runtime/app/shell/handlers-parity.ts +118 -89
- package/runtime/app/shell/handlers-push.ts +5 -15
- package/runtime/app/shell/handlers-reviews.ts +5 -5
- package/runtime/app/shell/handlers-scanner.ts +36 -38
- package/runtime/app/shell/handlers-speech.ts +43 -49
- package/runtime/app/shell/handlers-system.ts +122 -1
- package/runtime/app/shell/handlers.ts +10 -3
- package/runtime/app/shell/ios-frameworks.d.ts +9 -44
- package/runtime/app/shell/ios-ui-delegate.ts +112 -108
- package/runtime/app/shell/native-log.ts +1 -3
- package/runtime/app/shell/status-bar.ts +7 -4
- package/runtime/app/shell/toast.ts +1 -1
- package/runtime/references.d.ts +21 -0
- package/runtime/tsconfig.json +1 -0
- package/src/cli.ts +74 -18
- package/src/config.ts +20 -0
- package/src/derive.ts +75 -0
|
@@ -2,23 +2,25 @@ import { Utils, isAndroid, isIOS } from '@nativescript/core';
|
|
|
2
2
|
import { bridge } from './bridge';
|
|
3
3
|
import { requestPermissions } from './android-helpers';
|
|
4
4
|
|
|
5
|
-
// AVFoundation / Speech globals (NS auto-links the frameworks via metadata when referenced).
|
|
6
|
-
declare const AVSpeechSynthesizer: any;
|
|
7
|
-
declare const AVSpeechUtterance: any;
|
|
8
|
-
declare const AVSpeechSynthesisVoice: any;
|
|
9
|
-
declare const AVAudioEngine: any;
|
|
10
|
-
declare const AVAudioSession: any;
|
|
11
|
-
declare const AVAudioSessionCategoryPlayAndRecord: any;
|
|
12
|
-
declare const AVAudioSessionModeMeasurement: any;
|
|
13
|
-
declare const SFSpeechRecognizer: any;
|
|
14
|
-
declare const SFSpeechAudioBufferRecognitionRequest: any;
|
|
15
|
-
declare const SFSpeechRecognizerAuthorizationStatusAuthorized: any;
|
|
16
|
-
declare const NSLocale: any;
|
|
17
|
-
declare const android: any;
|
|
18
|
-
declare const java: any;
|
|
19
|
-
|
|
20
5
|
const err = (code: string, message: string) => Object.assign(new Error(message), { code });
|
|
21
6
|
|
|
7
|
+
// iOS speech-synthesizer delegate — both finish & cancel settle the pending speak (the JS
|
|
8
|
+
// `speakPending` is captured via the `onSettle` instance field, assigned after `new()`).
|
|
9
|
+
@NativeClass()
|
|
10
|
+
class SpeakDelegate extends NSObject implements AVSpeechSynthesizerDelegate {
|
|
11
|
+
static ObjCProtocols = [AVSpeechSynthesizerDelegate];
|
|
12
|
+
onSettle?: () => void;
|
|
13
|
+
static new(): SpeakDelegate {
|
|
14
|
+
return <SpeakDelegate>super.new();
|
|
15
|
+
}
|
|
16
|
+
speechSynthesizerDidFinishSpeechUtterance(_s: AVSpeechSynthesizer, _u: AVSpeechUtterance): void {
|
|
17
|
+
this.onSettle?.();
|
|
18
|
+
}
|
|
19
|
+
speechSynthesizerDidCancelSpeechUtterance(_s: AVSpeechSynthesizer, _u: AVSpeechUtterance): void {
|
|
20
|
+
this.onSettle?.();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
22
24
|
/**
|
|
23
25
|
* Voice I/O. TTS: iOS AVSpeechSynthesizer / Android TextToSpeech — speak resolves on finish.
|
|
24
26
|
* STT: iOS SFSpeechRecognizer + AVAudioEngine mic tap / Android SpeechRecognizer — listen resolves
|
|
@@ -37,15 +39,15 @@ export function registerSpeechHandlers(): void {
|
|
|
37
39
|
function registerIos(): void {
|
|
38
40
|
// Strong refs while a session is live — ARC would otherwise free the synthesizer/engine/delegate
|
|
39
41
|
// the moment the JS locals fall out of scope (same hazard as handlers-scanner/oauth).
|
|
40
|
-
let synth:
|
|
41
|
-
let speakDelegate:
|
|
42
|
+
let synth: AVSpeechSynthesizer | null = null;
|
|
43
|
+
let speakDelegate: SpeakDelegate | null = null;
|
|
42
44
|
let speakPending: { resolve: () => void } | null = null;
|
|
43
45
|
|
|
44
|
-
let engine:
|
|
45
|
-
let recognizer:
|
|
46
|
-
let request:
|
|
47
|
-
let task:
|
|
48
|
-
let listenPending: { resolve: (v: string) => void; reject: (e:
|
|
46
|
+
let engine: AVAudioEngine | null = null;
|
|
47
|
+
let recognizer: SFSpeechRecognizer | null = null;
|
|
48
|
+
let request: SFSpeechAudioBufferRecognitionRequest | null = null;
|
|
49
|
+
let task: SFSpeechRecognitionTask | null = null;
|
|
50
|
+
let listenPending: { resolve: (v: string) => void; reject: (e: Error) => void } | null = null;
|
|
49
51
|
let best = '';
|
|
50
52
|
|
|
51
53
|
const teardownListen = () => {
|
|
@@ -71,18 +73,8 @@ function registerIos(): void {
|
|
|
71
73
|
try {
|
|
72
74
|
if (!synth) {
|
|
73
75
|
synth = AVSpeechSynthesizer.new();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
speechSynthesizerDidFinishSpeechUtterance(_s: any, _u: any) {
|
|
77
|
-
const p = speakPending; speakPending = null; p?.resolve();
|
|
78
|
-
},
|
|
79
|
-
speechSynthesizerDidCancelSpeechUtterance(_s: any, _u: any) {
|
|
80
|
-
const p = speakPending; speakPending = null; p?.resolve();
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
{ protocols: [] }
|
|
84
|
-
);
|
|
85
|
-
speakDelegate = DelegateClass.new();
|
|
76
|
+
speakDelegate = SpeakDelegate.new();
|
|
77
|
+
speakDelegate.onSettle = () => { const p = speakPending; speakPending = null; p?.resolve(); };
|
|
86
78
|
synth.delegate = speakDelegate;
|
|
87
79
|
}
|
|
88
80
|
// A new speak resolves any prior pending (its utterance is being superseded).
|
|
@@ -136,8 +128,8 @@ function registerIos(): void {
|
|
|
136
128
|
best = '';
|
|
137
129
|
listenPending = { resolve, reject };
|
|
138
130
|
|
|
139
|
-
SFSpeechRecognizer.requestAuthorization((status:
|
|
140
|
-
if (status !==
|
|
131
|
+
SFSpeechRecognizer.requestAuthorization((status: SFSpeechRecognizerAuthorizationStatus) => {
|
|
132
|
+
if (status !== SFSpeechRecognizerAuthorizationStatus.Authorized) {
|
|
141
133
|
const p = listenPending; listenPending = null;
|
|
142
134
|
p?.reject(err('DENIED', 'Speech recognition not authorized'));
|
|
143
135
|
return;
|
|
@@ -167,17 +159,18 @@ function registerIos(): void {
|
|
|
167
159
|
|
|
168
160
|
const session = AVAudioSession.sharedInstance();
|
|
169
161
|
session.setCategoryModeOptionsError(AVAudioSessionCategoryPlayAndRecord, AVAudioSessionModeMeasurement, 1 /* duckOthers */, null);
|
|
170
|
-
|
|
162
|
+
// 0 = no activation flags; not a named member of the options enum, so cast (preserve value).
|
|
163
|
+
session.setActiveWithOptionsError(true, 0 as AVAudioSessionSetActiveOptions, null);
|
|
171
164
|
|
|
172
165
|
engine = AVAudioEngine.new();
|
|
173
166
|
request = SFSpeechAudioBufferRecognitionRequest.new();
|
|
174
167
|
request.shouldReportPartialResults = !!params?.partial;
|
|
175
168
|
|
|
176
|
-
task = recognizer.recognitionTaskWithRequestResultHandler(request, (result:
|
|
169
|
+
task = recognizer.recognitionTaskWithRequestResultHandler(request, (result: SFSpeechRecognitionResult, error: NSError) => {
|
|
177
170
|
if (result) {
|
|
178
171
|
best = String(result.bestTranscription?.formattedString ?? best);
|
|
179
|
-
if (params?.partial && !result.
|
|
180
|
-
if (result.
|
|
172
|
+
if (params?.partial && !result.final) bridge.emit('speech.partial', { transcript: best });
|
|
173
|
+
if (result.final && listenPending) settleListen();
|
|
181
174
|
}
|
|
182
175
|
if (error && listenPending) {
|
|
183
176
|
const p = listenPending; listenPending = null;
|
|
@@ -188,7 +181,7 @@ function registerIos(): void {
|
|
|
188
181
|
|
|
189
182
|
const input = engine.inputNode;
|
|
190
183
|
const format = input.outputFormatForBus(0);
|
|
191
|
-
input.installTapOnBusBufferSizeFormatBlock(0, 1024, format, (buffer:
|
|
184
|
+
input.installTapOnBusBufferSizeFormatBlock(0, 1024, format, (buffer: AVAudioPCMBuffer, _when: AVAudioTime) => {
|
|
192
185
|
request?.appendAudioPCMBuffer(buffer);
|
|
193
186
|
});
|
|
194
187
|
engine.prepare();
|
|
@@ -206,11 +199,11 @@ function registerIos(): void {
|
|
|
206
199
|
|
|
207
200
|
function registerAndroid(): void {
|
|
208
201
|
// TextToSpeech + SpeechRecognizer are plain Java → direct interop, no kotlin flag, no gradle dep.
|
|
209
|
-
let tts:
|
|
202
|
+
let tts: android.speech.tts.TextToSpeech | null = null;
|
|
210
203
|
let ttsReady = false;
|
|
211
204
|
const ctx = () => Utils.android.getApplicationContext();
|
|
212
205
|
|
|
213
|
-
const ensureTts = (): Promise<
|
|
206
|
+
const ensureTts = (): Promise<android.speech.tts.TextToSpeech | null> =>
|
|
214
207
|
new Promise((resolve) => {
|
|
215
208
|
if (tts && ttsReady) { resolve(tts); return; }
|
|
216
209
|
tts = new android.speech.tts.TextToSpeech(
|
|
@@ -236,7 +229,8 @@ function registerAndroid(): void {
|
|
|
236
229
|
const id = 'u' + Date.now();
|
|
237
230
|
return new Promise<void>((resolve) => {
|
|
238
231
|
engine.setOnUtteranceProgressListener(
|
|
239
|
-
|
|
232
|
+
// NS interop: subclass the abstract Java class via object-literal ctor (untypeable — cast base).
|
|
233
|
+
new (android.speech.tts.UtteranceProgressListener as any)({
|
|
240
234
|
onStart() {},
|
|
241
235
|
onDone() { resolve(); },
|
|
242
236
|
onError() { resolve(); }, // resolve regardless — speak() promises completion, not success
|
|
@@ -264,8 +258,8 @@ function registerAndroid(): void {
|
|
|
264
258
|
});
|
|
265
259
|
|
|
266
260
|
// STT — SpeechRecognizer must be created + driven on the main (UI) thread.
|
|
267
|
-
let recognizer:
|
|
268
|
-
let listenPending: { resolve: (v: string) => void; reject: (e:
|
|
261
|
+
let recognizer: android.speech.SpeechRecognizer | null = null;
|
|
262
|
+
let listenPending: { resolve: (v: string) => void; reject: (e: Error) => void } | null = null;
|
|
269
263
|
let best = '';
|
|
270
264
|
|
|
271
265
|
const settle = () => {
|
|
@@ -295,7 +289,7 @@ function registerAndroid(): void {
|
|
|
295
289
|
return;
|
|
296
290
|
}
|
|
297
291
|
recognizer = android.speech.SpeechRecognizer.createSpeechRecognizer(ctx());
|
|
298
|
-
const pickText = (b:
|
|
292
|
+
const pickText = (b: android.os.Bundle): string => {
|
|
299
293
|
const arr = b?.getStringArrayList(android.speech.SpeechRecognizer.RESULTS_RECOGNITION);
|
|
300
294
|
return arr && arr.size() ? String(arr.get(0)) : '';
|
|
301
295
|
};
|
|
@@ -303,12 +297,12 @@ function registerAndroid(): void {
|
|
|
303
297
|
new android.speech.RecognitionListener({
|
|
304
298
|
onReadyForSpeech() {}, onBeginningOfSpeech() {}, onRmsChanged() {},
|
|
305
299
|
onBufferReceived() {}, onEndOfSpeech() {}, onEvent() {},
|
|
306
|
-
onPartialResults(b:
|
|
300
|
+
onPartialResults(b: android.os.Bundle) {
|
|
307
301
|
if (!params?.partial) return;
|
|
308
302
|
const t = pickText(b);
|
|
309
303
|
if (t) { best = t; bridge.emit('speech.partial', { transcript: t }); }
|
|
310
304
|
},
|
|
311
|
-
onResults(b:
|
|
305
|
+
onResults(b: android.os.Bundle) {
|
|
312
306
|
const t = pickText(b);
|
|
313
307
|
if (t) best = t;
|
|
314
308
|
if (listenPending) settle();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Application, ApplicationSettings, Color, Utils, isAndroid, isIOS } from '@nativescript/core';
|
|
2
2
|
import { bridge } from './bridge';
|
|
3
3
|
|
|
4
|
-
declare const
|
|
4
|
+
declare const androidx: any; // no NS types: androidx
|
|
5
5
|
|
|
6
6
|
const err = (code: string, message: string) => Object.assign(new Error(message), { code });
|
|
7
7
|
|
|
@@ -119,6 +119,70 @@ export function registerSystemHandlers(): void {
|
|
|
119
119
|
|
|
120
120
|
bridge.register('app.environment', () => appEnvironment());
|
|
121
121
|
|
|
122
|
+
// Probe whether the OS can open a URL. Custom schemes need declaring up-front (iOS
|
|
123
|
+
// LSApplicationQueriesSchemes via appwrap.json.queryUrlSchemes; Android <queries> via
|
|
124
|
+
// queryPackages) — common schemes (http/https/tel/mailto/sms) resolve without declaration.
|
|
125
|
+
bridge.register('app.canOpenUrl', ({ url }: { url: string }) => {
|
|
126
|
+
const target = String(url ?? '');
|
|
127
|
+
if (!target) return false;
|
|
128
|
+
if (isIOS) {
|
|
129
|
+
const nsUrl = NSURL.URLWithString(target);
|
|
130
|
+
return !!nsUrl && UIApplication.sharedApplication.canOpenURL(nsUrl);
|
|
131
|
+
}
|
|
132
|
+
if (isAndroid) {
|
|
133
|
+
const pm = Utils.android.getApplicationContext().getPackageManager();
|
|
134
|
+
const intent = new android.content.Intent(
|
|
135
|
+
android.content.Intent.ACTION_VIEW,
|
|
136
|
+
android.net.Uri.parse(target)
|
|
137
|
+
);
|
|
138
|
+
return pm.resolveActivity(intent, 0) != null;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Home-screen long-press quick actions. iOS: UIApplication.shortcutItems. Android: dynamic
|
|
144
|
+
// ShortcutManager shortcuts (API 25+; no-op below) whose launch intent carries appwrap_shortcut=<id>.
|
|
145
|
+
bridge.register('app.setShortcuts', ({ items }: { items: Array<{ id: string; title: string; subtitle?: string }> }) => {
|
|
146
|
+
const list = (items ?? []).filter((i) => i && i.id && i.title);
|
|
147
|
+
if (isIOS) {
|
|
148
|
+
Utils.dispatchToMainThread(() => {
|
|
149
|
+
const arr = NSMutableArray.alloc().init();
|
|
150
|
+
for (const i of list) {
|
|
151
|
+
const item = UIApplicationShortcutItem.alloc().initWithTypeLocalizedTitleLocalizedSubtitleIconUserInfo(
|
|
152
|
+
String(i.id), String(i.title), i.subtitle ? String(i.subtitle) : null, null, null
|
|
153
|
+
);
|
|
154
|
+
arr.addObject(item);
|
|
155
|
+
}
|
|
156
|
+
UIApplication.sharedApplication.shortcutItems = arr as any;
|
|
157
|
+
});
|
|
158
|
+
} else if (isAndroid) {
|
|
159
|
+
if (android.os.Build.VERSION.SDK_INT < 25) return; // ShortcutManager is API 25+
|
|
160
|
+
const ctx = Utils.android.getApplicationContext();
|
|
161
|
+
const sm = ctx.getSystemService(android.content.Context.SHORTCUT_SERVICE); // API 25+ string const
|
|
162
|
+
if (!sm) return;
|
|
163
|
+
const launchClass = (Application.android.foregroundActivity ?? Application.android.startActivity)?.getClass();
|
|
164
|
+
const shortcuts = new java.util.ArrayList();
|
|
165
|
+
for (const i of list) {
|
|
166
|
+
const intent = new android.content.Intent(android.content.Intent.ACTION_VIEW);
|
|
167
|
+
if (launchClass) intent.setClassName(ctx, launchClass.getName());
|
|
168
|
+
intent.putExtra('appwrap_shortcut', String(i.id));
|
|
169
|
+
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK | android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
170
|
+
const b = new android.content.pm.ShortcutInfo.Builder(ctx, String(i.id))
|
|
171
|
+
.setShortLabel(String(i.title))
|
|
172
|
+
.setLongLabel(String(i.subtitle ?? i.title))
|
|
173
|
+
.setIntent(intent);
|
|
174
|
+
shortcuts.add(b.build());
|
|
175
|
+
}
|
|
176
|
+
sm.setDynamicShortcuts(shortcuts);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Privacy screen — hide content in the app-switcher / block screenshots. iOS: cover the key window
|
|
181
|
+
// with a blur while inactive/backgrounded (wired via the app lifecycle below). Android: FLAG_SECURE.
|
|
182
|
+
bridge.register('screen.setPrivacy', ({ enabled }: { enabled: boolean }) => {
|
|
183
|
+
setPrivacyScreen(!!enabled);
|
|
184
|
+
});
|
|
185
|
+
|
|
122
186
|
bridge.register('browser.open', ({ url, toolbarColor }: { url: string; toolbarColor?: string }) => {
|
|
123
187
|
const target = String(url ?? '');
|
|
124
188
|
if (!target) throw err('NATIVE_ERROR', 'browser.open: empty url');
|
|
@@ -139,3 +203,60 @@ export function registerSystemHandlers(): void {
|
|
|
139
203
|
}
|
|
140
204
|
});
|
|
141
205
|
}
|
|
206
|
+
|
|
207
|
+
// ── privacy screen ────────────────────────────────────────────────────
|
|
208
|
+
// iOS keeps the enabled flag + lazily wires the lifecycle hooks once. The cover MUST go on at
|
|
209
|
+
// `willResignActive` — that fires BEFORE the app-switcher snapshot, whereas NS's suspendEvent
|
|
210
|
+
// (≈ didEnterBackground) fires AFTER it, so suspendEvent alone would leak the first snapshot. We
|
|
211
|
+
// observe the raw UIApplicationWillResignActiveNotification for the cover, removing it on
|
|
212
|
+
// becomeActive. Android flips FLAG_SECURE immediately (also blocks screenshots).
|
|
213
|
+
let privacyEnabled = false;
|
|
214
|
+
let privacyCover: UIView | null = null;
|
|
215
|
+
let privacyLifecycleWired = false;
|
|
216
|
+
|
|
217
|
+
function setPrivacyScreen(enabled: boolean): void {
|
|
218
|
+
privacyEnabled = enabled;
|
|
219
|
+
if (isIOS) {
|
|
220
|
+
wireIosPrivacyLifecycle();
|
|
221
|
+
if (!enabled) Utils.dispatchToMainThread(removeIosPrivacyCover); // disabling mid-foreground
|
|
222
|
+
} else if (isAndroid) {
|
|
223
|
+
Utils.dispatchToMainThread(() => {
|
|
224
|
+
const activity = Application.android.foregroundActivity ?? Application.android.startActivity;
|
|
225
|
+
const FLAG_SECURE = android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
|
226
|
+
const window = activity?.getWindow();
|
|
227
|
+
if (!window) return;
|
|
228
|
+
if (enabled) window.setFlags(FLAG_SECURE, FLAG_SECURE);
|
|
229
|
+
else window.clearFlags(FLAG_SECURE);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function wireIosPrivacyLifecycle(): void {
|
|
235
|
+
if (privacyLifecycleWired) return;
|
|
236
|
+
privacyLifecycleWired = true;
|
|
237
|
+
const center = NSNotificationCenter.defaultCenter;
|
|
238
|
+
// willResignActive → cover BEFORE the OS captures the app-switcher snapshot (the critical timing).
|
|
239
|
+
center.addObserverForNameObjectQueueUsingBlock(
|
|
240
|
+
UIApplicationWillResignActiveNotification, null, null, () => { if (privacyEnabled) addIosPrivacyCover(); }
|
|
241
|
+
);
|
|
242
|
+
// didBecomeActive → reveal again once the user returns.
|
|
243
|
+
center.addObserverForNameObjectQueueUsingBlock(
|
|
244
|
+
UIApplicationDidBecomeActiveNotification, null, null, () => removeIosPrivacyCover()
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function addIosPrivacyCover(): void {
|
|
249
|
+
const window = UIApplication.sharedApplication.keyWindow;
|
|
250
|
+
if (!window || privacyCover) return;
|
|
251
|
+
const effect = UIBlurEffect.effectWithStyle(UIBlurEffectStyle.SystemMaterial);
|
|
252
|
+
const blur = UIVisualEffectView.alloc().initWithEffect(effect);
|
|
253
|
+
blur.frame = window.bounds;
|
|
254
|
+
blur.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
|
|
255
|
+
window.addSubview(blur);
|
|
256
|
+
privacyCover = blur;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function removeIosPrivacyCover(): void {
|
|
260
|
+
privacyCover?.removeFromSuperview();
|
|
261
|
+
privacyCover = null;
|
|
262
|
+
}
|
|
@@ -7,6 +7,7 @@ import { showBanner, dismissBanner } from './banner';
|
|
|
7
7
|
import { setStatusBarStyle } from './status-bar';
|
|
8
8
|
import { buildCapabilityMap } from './capabilities.manifest';
|
|
9
9
|
import { ACTIVE_MODULE_NAMES } from './active-modules.generated';
|
|
10
|
+
import { consumePendingBackgroundTaskId } from './background-context';
|
|
10
11
|
|
|
11
12
|
/** Build identifier for the native shell bundle — bump per deploy to spot stale bundles. */
|
|
12
13
|
export const SHELL_BUILD = 'updates-devmenu-3';
|
|
@@ -27,12 +28,17 @@ export function registerHandlers(): void {
|
|
|
27
28
|
// kit degrades gracefully. Push is special — gated by per-platform build config, not the manifest.
|
|
28
29
|
const capabilities = buildCapabilityMap(new Set(ACTIVE_MODULE_NAMES), isIOS ? 'ios' : 'android') as Record<string, 'native' | 'none'>;
|
|
29
30
|
capabilities.push = (isIOS ? SHELL_CONFIG.pushIos : SHELL_CONFIG.pushAndroid) ? 'native' : 'none';
|
|
31
|
+
// Background launch: the headless runner (handlers-background) set the wake id before loading this
|
|
32
|
+
// (offscreen) WebView. Report it so `kit.backgroundTask` dispatches the registered handler. Consumed
|
|
33
|
+
// (read-once) so a later foreground handshake in the same process never re-reports a stale wake.
|
|
34
|
+
const backgroundTaskId = consumePendingBackgroundTaskId();
|
|
30
35
|
return {
|
|
31
36
|
protocol: 1,
|
|
32
37
|
platform: isIOS ? 'ios' : 'android',
|
|
33
38
|
app: { id: SHELL_CONFIG.appId, name: SHELL_CONFIG.name, version: SHELL_CONFIG.version, build: SHELL_BUILD, loader: SHELL_CONFIG.loader },
|
|
34
39
|
debug: { lastNotifTap: safeJson(ApplicationSettings.getString('kit:__notifTap', '')) },
|
|
35
40
|
capabilities,
|
|
41
|
+
...(backgroundTaskId ? { backgroundTaskId } : {}),
|
|
36
42
|
};
|
|
37
43
|
});
|
|
38
44
|
|
|
@@ -74,7 +80,7 @@ export function registerHandlers(): void {
|
|
|
74
80
|
const items = NSMutableArray.new();
|
|
75
81
|
if (content) items.addObject(content);
|
|
76
82
|
const controller = UIActivityViewController.alloc().initWithActivityItemsApplicationActivities(
|
|
77
|
-
items
|
|
83
|
+
items,
|
|
78
84
|
null
|
|
79
85
|
);
|
|
80
86
|
const rootVC = Utils.ios.getRootViewController();
|
|
@@ -102,6 +108,7 @@ export function registerHandlers(): void {
|
|
|
102
108
|
if (text) items.addObject(text);
|
|
103
109
|
const tmp = NSTemporaryDirectory();
|
|
104
110
|
(files ?? []).forEach((f, i) => {
|
|
111
|
+
// 0 = no decoding options; the enum has no zero member, so cast the raw bitmask value.
|
|
105
112
|
const data = NSData.alloc().initWithBase64EncodedStringOptions(f.base64, 0 as unknown as NSDataBase64DecodingOptions);
|
|
106
113
|
if (!data) return;
|
|
107
114
|
const filePath = tmp + i + '-' + (f.name || 'file'); // index-prefix: avoid same-name collisions in one call
|
|
@@ -109,7 +116,7 @@ export function registerHandlers(): void {
|
|
|
109
116
|
items.addObject(NSURL.fileURLWithPath(filePath));
|
|
110
117
|
});
|
|
111
118
|
const controller = UIActivityViewController.alloc().initWithActivityItemsApplicationActivities(
|
|
112
|
-
items
|
|
119
|
+
items,
|
|
113
120
|
null
|
|
114
121
|
);
|
|
115
122
|
const rootVC = Utils.ios.getRootViewController();
|
|
@@ -181,6 +188,6 @@ function safeJson(s: string): unknown {
|
|
|
181
188
|
|
|
182
189
|
function vibrateAndroid(ms: number): void {
|
|
183
190
|
const context = Utils.android.getApplicationContext();
|
|
184
|
-
const vibrator = context.getSystemService(android.content.Context.VIBRATOR_SERVICE);
|
|
191
|
+
const vibrator = context.getSystemService(android.content.Context.VIBRATOR_SERVICE) as android.os.Vibrator;
|
|
185
192
|
vibrator?.vibrate(ms);
|
|
186
193
|
}
|
|
@@ -1,44 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
declare const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
declare const CLAuthorizationStatus: any;
|
|
11
|
-
declare const CLLocationManagerDelegate: any;
|
|
12
|
-
declare const kCLLocationAccuracyHundredMeters: number;
|
|
13
|
-
declare type CLLocation = any;
|
|
14
|
-
declare type CLLocationManager = any;
|
|
15
|
-
declare const PHPickerConfiguration: any;
|
|
16
|
-
declare const PHPickerViewController: any;
|
|
17
|
-
declare const PHPickerViewControllerDelegate: any;
|
|
18
|
-
declare type PHPickerResult = any;
|
|
19
|
-
declare type PHPickerViewController = any;
|
|
20
|
-
// StoreKit / CoreMotion / ContactsUI / EventKit (parity handlers)
|
|
21
|
-
declare const SKStoreReviewController: any;
|
|
22
|
-
declare const CMMotionManager: any;
|
|
23
|
-
declare const CMPedometer: any;
|
|
24
|
-
// HealthKit (health/steps module)
|
|
25
|
-
declare const HKHealthStore: any;
|
|
26
|
-
declare const HKObjectType: any;
|
|
27
|
-
declare const HKQuery: any;
|
|
28
|
-
declare const HKStatisticsQuery: any;
|
|
29
|
-
declare const HKStatisticsOptions: any;
|
|
30
|
-
declare const HKUnit: any;
|
|
31
|
-
declare const CNContactPickerViewController: any;
|
|
32
|
-
declare const CNContactPickerDelegate: any;
|
|
33
|
-
// Contacts framework (contacts.getAll bulk read — CNContactStore, vs the ContactsUI picker above)
|
|
34
|
-
declare const CNContactStore: any;
|
|
35
|
-
declare const CNEntityType: any;
|
|
36
|
-
declare const CNContactFetchRequest: any;
|
|
37
|
-
declare const CNContactGivenNameKey: any;
|
|
38
|
-
declare const CNContactFamilyNameKey: any;
|
|
39
|
-
declare const CNContactPhoneNumbersKey: any;
|
|
40
|
-
declare const CNContactEmailAddressesKey: any;
|
|
41
|
-
declare const EKEventStore: any;
|
|
42
|
-
declare const EKEvent: any;
|
|
43
|
-
declare const EKSpan: any;
|
|
44
|
-
declare const EKEntityType: any;
|
|
1
|
+
// Neutralizer stub — intentionally EMPTY. Do NOT add `declare const X: any` here.
|
|
2
|
+
//
|
|
3
|
+
// Real iOS framework types come from the @nativescript/types-ios references in `references.d.ts`.
|
|
4
|
+
// This file exists ONLY so `appwrap sync` copies it over `native/app/shell/ios-frameworks.d.ts`,
|
|
5
|
+
// overwriting a stale PRE-`references.d.ts` generated relic of the same name. That relic declared
|
|
6
|
+
// every framework symbol as `declare const X: any` (a VALUE); once handlers annotate params with the
|
|
7
|
+
// real types (e.g. `mm: CMMotionManager`), the value-shadow makes the build throw TS2749 ("refers to
|
|
8
|
+
// a value, used as a type"). Shipping this empty stub self-heals any native/ on the next sync.
|
|
9
|
+
export {};
|