@livx.cc/appwrap 0.39.4 → 0.39.8

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.
@@ -7,116 +7,128 @@ import { maskForLock, setIosOrientationMask } from './orientation';
7
7
 
8
8
  interface GeoResult { lat: number; lng: number; accuracy: number; }
9
9
 
10
- // CLLocationManager delegate. Closure-captured callbacks (resolve/reject/cleanup) are assigned as
11
- // instance fields after new() — same pattern as handlers-speech/oauth.
12
- @NativeClass()
13
- class GeoDelegate extends NSObject implements CLLocationManagerDelegate {
14
- static ObjCProtocols = [CLLocationManagerDelegate];
15
- onResult?: (r: GeoResult) => void;
16
- onError?: (e: Error) => void;
17
- onCleanup?: () => void;
18
- static new(): GeoDelegate {
19
- return <GeoDelegate>super.new();
20
- }
21
- locationManagerDidUpdateLocations(_m: CLLocationManager, locations: NSArray<CLLocation>): void {
22
- const loc = locations.lastObject;
23
- if (!loc) return;
24
- const result = {
25
- lat: loc.coordinate.latitude,
26
- lng: loc.coordinate.longitude,
27
- accuracy: loc.horizontalAccuracy,
28
- };
29
- this.onCleanup?.();
30
- this.onResult?.(result);
31
- }
32
- locationManagerDidFailWithError(_m: CLLocationManager, error: NSError): void {
33
- this.onCleanup?.();
34
- this.onError?.(Object.assign(new Error(error.localizedDescription), { code: 'DENIED' }));
35
- }
36
- locationManagerDidChangeAuthorization(m: CLLocationManager): void {
37
- const st = m.authorizationStatus;
38
- if (st === CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedWhenInUse ||
39
- st === CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways) {
40
- m.startUpdatingLocation();
41
- } else if (st === CLAuthorizationStatus.kCLAuthorizationStatusDenied) {
10
+ // iOS-only native delegates. Defined lazily inside an isIOS-gated factory (mirrors banner.ts) so the
11
+ // shared module can instantiate on Android — NSObject/CL*/PH*/UN* are iOS globals, and a top-level
12
+ // `extends NSObject` would evaluate at ES-module load and crash the Android shell (ReferenceError).
13
+ // any: module-level holders for runtime-built ObjC subclasses; each class BODY stays fully typed.
14
+ let GeoDelegate: any, PhotoPickerDelegate: any, ForegroundNotificationDelegate: any;
15
+ function ensureIosDelegates(): void {
16
+ if (!isIOS || GeoDelegate) return;
17
+
18
+ // CLLocationManager delegate. Closure-captured callbacks (resolve/reject/cleanup) are assigned as
19
+ // instance fields after new() — same pattern as handlers-speech/oauth.
20
+ @NativeClass()
21
+ class GeoDelegateImpl extends NSObject implements CLLocationManagerDelegate {
22
+ static ObjCProtocols = [CLLocationManagerDelegate];
23
+ onResult?: (r: GeoResult) => void;
24
+ onError?: (e: Error) => void;
25
+ onCleanup?: () => void;
26
+ static new(): GeoDelegateImpl {
27
+ return <GeoDelegateImpl>super.new();
28
+ }
29
+ locationManagerDidUpdateLocations(_m: CLLocationManager, locations: NSArray<CLLocation>): void {
30
+ const loc = locations.lastObject;
31
+ if (!loc) return;
32
+ const result = {
33
+ lat: loc.coordinate.latitude,
34
+ lng: loc.coordinate.longitude,
35
+ accuracy: loc.horizontalAccuracy,
36
+ };
42
37
  this.onCleanup?.();
43
- this.onError?.(Object.assign(new Error('location permission denied'), { code: 'DENIED' }));
38
+ this.onResult?.(result);
39
+ }
40
+ locationManagerDidFailWithError(_m: CLLocationManager, error: NSError): void {
41
+ this.onCleanup?.();
42
+ this.onError?.(Object.assign(new Error(error.localizedDescription), { code: 'DENIED' }));
43
+ }
44
+ locationManagerDidChangeAuthorization(m: CLLocationManager): void {
45
+ const st = m.authorizationStatus;
46
+ if (st === CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedWhenInUse ||
47
+ st === CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways) {
48
+ m.startUpdatingLocation();
49
+ } else if (st === CLAuthorizationStatus.kCLAuthorizationStatusDenied) {
50
+ this.onCleanup?.();
51
+ this.onError?.(Object.assign(new Error('location permission denied'), { code: 'DENIED' }));
52
+ }
44
53
  }
45
54
  }
46
- }
55
+ GeoDelegate = GeoDelegateImpl;
47
56
 
48
- // PHPickerViewController delegate. Closure-captured opts (dataUrl/maxSize) + resolve assigned post-new().
49
- @NativeClass()
50
- class PhotoPickerDelegate extends NSObject implements PHPickerViewControllerDelegate {
51
- static ObjCProtocols = [PHPickerViewControllerDelegate];
52
- wantDataUrl?: boolean;
53
- maxSize?: number;
54
- onResult?: (r: { picked: boolean; width?: number; height?: number; dataUrl?: string }) => void;
55
- static new(): PhotoPickerDelegate {
56
- return <PhotoPickerDelegate>super.new();
57
- }
58
- pickerDidFinishPicking(picker: PHPickerViewController, results: NSArray<PHPickerResult>): void {
59
- picker.dismissViewControllerAnimatedCompletion(true, null);
60
- // Recover the WebView after the picker dismisses (orphaned-window / renderer-throttle guard).
61
- bridge.getWebView()?.recoverAfterNativeSurface();
62
- const result = results.count > 0 ? results.objectAtIndex(0) : null;
63
- if (!result) return this.onResult?.({ picked: false });
64
- result.itemProvider.loadObjectOfClassCompletionHandler(UIImage.class(), (img: UIImage) => {
65
- if (!img) return this.onResult?.({ picked: true });
66
- const out: { picked: boolean; width?: number; height?: number; dataUrl?: string } =
67
- { picked: true, width: img.size.width, height: img.size.height };
68
- if (this.wantDataUrl) out.dataUrl = uiImageToDataUrl(img, this.maxSize ?? 1024);
69
- this.onResult?.(out);
70
- });
57
+ // PHPickerViewController delegate. Closure-captured opts (dataUrl/maxSize) + resolve assigned post-new().
58
+ @NativeClass()
59
+ class PhotoPickerDelegateImpl extends NSObject implements PHPickerViewControllerDelegate {
60
+ static ObjCProtocols = [PHPickerViewControllerDelegate];
61
+ wantDataUrl?: boolean;
62
+ maxSize?: number;
63
+ onResult?: (r: { picked: boolean; width?: number; height?: number; dataUrl?: string }) => void;
64
+ static new(): PhotoPickerDelegateImpl {
65
+ return <PhotoPickerDelegateImpl>super.new();
66
+ }
67
+ pickerDidFinishPicking(picker: PHPickerViewController, results: NSArray<PHPickerResult>): void {
68
+ picker.dismissViewControllerAnimatedCompletion(true, null);
69
+ // Recover the WebView after the picker dismisses (orphaned-window / renderer-throttle guard).
70
+ bridge.getWebView()?.recoverAfterNativeSurface();
71
+ const result = results.count > 0 ? results.objectAtIndex(0) : null;
72
+ if (!result) return this.onResult?.({ picked: false });
73
+ result.itemProvider.loadObjectOfClassCompletionHandler(UIImage.class(), (img: UIImage) => {
74
+ if (!img) return this.onResult?.({ picked: true });
75
+ const out: { picked: boolean; width?: number; height?: number; dataUrl?: string } =
76
+ { picked: true, width: img.size.width, height: img.size.height };
77
+ if (this.wantDataUrl) out.dataUrl = uiImageToDataUrl(img, this.maxSize ?? 1024);
78
+ this.onResult?.(out);
79
+ });
80
+ }
71
81
  }
72
- }
82
+ PhotoPickerDelegate = PhotoPickerDelegateImpl;
73
83
 
74
- // UNUserNotificationCenter delegate — foreground present + tap routing. Idempotent singleton (notifDelegate).
75
- @NativeClass()
76
- class ForegroundNotificationDelegate extends NSObject implements UNUserNotificationCenterDelegate {
77
- static ObjCProtocols = [UNUserNotificationCenterDelegate];
78
- static new(): ForegroundNotificationDelegate {
79
- return <ForegroundNotificationDelegate>super.new();
80
- }
81
- userNotificationCenterWillPresentNotificationWithCompletionHandler(
82
- _center: UNUserNotificationCenter,
83
- notification: UNNotification,
84
- completionHandler: (opts: UNNotificationPresentationOptions) => void
85
- ): void {
86
- // Foreground REMOTE push (push trigger) → surface to kit.push.onMessage.
87
- if (isRemotePush(notification)) onRemoteMessage(notification.request.content.userInfo, false);
88
- // Present banner + list + sound + badge even while foreground (min target iOS 16).
89
- completionHandler(
90
- UNNotificationPresentationOptions.Banner |
91
- UNNotificationPresentationOptions.List |
92
- UNNotificationPresentationOptions.Sound |
93
- UNNotificationPresentationOptions.Badge
94
- );
95
- }
96
- // Notification tapped → route its deep link through the same path as an external open.
97
- userNotificationCenterDidReceiveNotificationResponseWithCompletionHandler(
98
- _center: UNUserNotificationCenter,
99
- response: UNNotificationResponse,
100
- completionHandler: () => void
101
- ): void {
102
- // Tapped REMOTE push → kit.push.onTap (with payload). Local notifs keep the deep-link path below.
103
- if (isRemotePush(response.notification)) onRemoteMessage(response.notification.request.content.userInfo, true);
104
- // userInfo may come back as an NSDictionary or an auto-marshalled JS object.
105
- // any: dual-path payload (NSDictionary vs marshalled JS object) probed dynamically.
106
- const info: any = response.notification.request.content.userInfo;
107
- const url = info ? (typeof info.objectForKey === 'function' ? info.objectForKey('url') : info.url) : null;
108
- // Diagnostic breadcrumb (persists across the cold relaunch) — surfaced in the handshake's debug field.
109
- try {
110
- ApplicationSettings.setString(
111
- 'kit:__notifTap',
112
- JSON.stringify({ at: Date.now(), url: url ? String(url) : null, hadInfo: !!info })
84
+ // UNUserNotificationCenter delegate — foreground present + tap routing. Idempotent singleton (notifDelegate).
85
+ @NativeClass()
86
+ class ForegroundNotificationDelegateImpl extends NSObject implements UNUserNotificationCenterDelegate {
87
+ static ObjCProtocols = [UNUserNotificationCenterDelegate];
88
+ static new(): ForegroundNotificationDelegateImpl {
89
+ return <ForegroundNotificationDelegateImpl>super.new();
90
+ }
91
+ userNotificationCenterWillPresentNotificationWithCompletionHandler(
92
+ _center: UNUserNotificationCenter,
93
+ notification: UNNotification,
94
+ completionHandler: (opts: UNNotificationPresentationOptions) => void
95
+ ): void {
96
+ // Foreground REMOTE push (push trigger) → surface to kit.push.onMessage.
97
+ if (isRemotePush(notification)) onRemoteMessage(notification.request.content.userInfo, false);
98
+ // Present banner + list + sound + badge even while foreground (min target iOS 16).
99
+ completionHandler(
100
+ UNNotificationPresentationOptions.Banner |
101
+ UNNotificationPresentationOptions.List |
102
+ UNNotificationPresentationOptions.Sound |
103
+ UNNotificationPresentationOptions.Badge
113
104
  );
114
- } catch {
115
- /* diagnostic only */
116
105
  }
117
- if (url) onDeepLink(String(url));
118
- completionHandler();
106
+ // Notification tapped → route its deep link through the same path as an external open.
107
+ userNotificationCenterDidReceiveNotificationResponseWithCompletionHandler(
108
+ _center: UNUserNotificationCenter,
109
+ response: UNNotificationResponse,
110
+ completionHandler: () => void
111
+ ): void {
112
+ // Tapped REMOTE push → kit.push.onTap (with payload). Local notifs keep the deep-link path below.
113
+ if (isRemotePush(response.notification)) onRemoteMessage(response.notification.request.content.userInfo, true);
114
+ // userInfo may come back as an NSDictionary or an auto-marshalled JS object.
115
+ // any: dual-path payload (NSDictionary vs marshalled JS object) probed dynamically.
116
+ const info: any = response.notification.request.content.userInfo;
117
+ const url = info ? (typeof info.objectForKey === 'function' ? info.objectForKey('url') : info.url) : null;
118
+ // Diagnostic breadcrumb (persists across the cold relaunch) — surfaced in the handshake's debug field.
119
+ try {
120
+ ApplicationSettings.setString(
121
+ 'kit:__notifTap',
122
+ JSON.stringify({ at: Date.now(), url: url ? String(url) : null, hadInfo: !!info })
123
+ );
124
+ } catch {
125
+ /* diagnostic only */
126
+ }
127
+ if (url) onDeepLink(String(url));
128
+ completionHandler();
129
+ }
119
130
  }
131
+ ForegroundNotificationDelegate = ForegroundNotificationDelegateImpl;
120
132
  }
121
133
 
122
134
  /**
@@ -124,6 +136,7 @@ class ForegroundNotificationDelegate extends NSObject implements UNUserNotificat
124
136
  * Each handler is small and self-contained; heavy domains (push, files) come later.
125
137
  */
126
138
  export function registerExtendedHandlers(): void {
139
+ ensureIosDelegates(); // no-op on Android; defines the iOS delegate classes used by the handlers below
127
140
  // ── device ─────────────────────────────────────────────────────────
128
141
  bridge.register('device.info', () => {
129
142
  let battery: { level: number; charging: boolean } | undefined;
@@ -298,7 +311,7 @@ export function registerExtendedHandlers(): void {
298
311
  }, 15000);
299
312
 
300
313
  let manager: CLLocationManager | null = null;
301
- let delegate: GeoDelegate | null = null;
314
+ let delegate: any = null;
302
315
  const cleanup = () => {
303
316
  clearTimeout(timer);
304
317
  manager?.stopUpdatingLocation();
@@ -406,7 +419,7 @@ function applyIosOrientation(mask: number): void {
406
419
  });
407
420
  }
408
421
 
409
- let notifDelegate: ForegroundNotificationDelegate | null = null; // retained for the app's lifetime
422
+ let notifDelegate: any = null; // retained for the app's lifetime
410
423
  /**
411
424
  * Install the UNUserNotificationCenter delegate. MUST run before
412
425
  * `didFinishLaunching` returns, else a tap that COLD-LAUNCHES the app never
@@ -416,6 +429,7 @@ let notifDelegate: ForegroundNotificationDelegate | null = null; // retained for
416
429
  */
417
430
  export function installForegroundNotificationDelegate(): void {
418
431
  if (!isIOS || notifDelegate) return;
432
+ ensureIosDelegates();
419
433
  notifDelegate = ForegroundNotificationDelegate.new();
420
434
  UNUserNotificationCenter.currentNotificationCenter().delegate = notifDelegate;
421
435
  }
@@ -152,42 +152,52 @@ function iosStat(full: string, name: string): any {
152
152
 
153
153
  /**
154
154
  * Delegate for UIDocumentPickerViewController. Captures the resolve callback as an instance field
155
- * (the original closed over `resolve`); both selectors funnel into it.
155
+ * (the original closed over `resolve`); both selectors funnel into it. Built lazily INSIDE the
156
+ * iOS-only path (mirrors banner.ts): NSObject/UI* are iOS globals and this file is imported on Android
157
+ * too, so a top-level `@NativeClass extends NSObject` would crash at module load.
156
158
  */
157
- @NativeClass()
158
- class DocumentPickerDelegate extends NSObject implements UIDocumentPickerDelegate {
159
- static ObjCProtocols = [UIDocumentPickerDelegate];
160
- onPick!: (out: any[]) => void;
161
-
162
- static new(): DocumentPickerDelegate {
163
- return <DocumentPickerDelegate>super.new();
164
- }
159
+ // any: runtime-built ObjC subclass holder; the class BODY stays fully typed.
160
+ let DocumentPickerDelegate: any;
161
+ function documentPickerDelegateClass(): any {
162
+ if (!DocumentPickerDelegate) {
163
+ @NativeClass()
164
+ class DocumentPickerDelegateImpl extends NSObject implements UIDocumentPickerDelegate {
165
+ static ObjCProtocols = [UIDocumentPickerDelegate];
166
+ onPick!: (out: any[]) => void;
167
+
168
+ static new(): DocumentPickerDelegateImpl {
169
+ return <DocumentPickerDelegateImpl>super.new();
170
+ }
165
171
 
166
- documentPickerDidPickDocumentsAtURLs(_controller: UIDocumentPickerViewController, urls: NSArray<NSURL>): void {
167
- const out: any[] = [];
168
- for (let i = 0; i < urls.count; i++) {
169
- const url = urls.objectAtIndex(i);
170
- const scoped = url.startAccessingSecurityScopedResource?.();
171
- try {
172
- const data = NSData.dataWithContentsOfURL(url);
173
- if (data) {
174
- out.push({
175
- name: url.lastPathComponent,
176
- mimeType: mimeForUrlIos(url),
177
- size: data.length,
178
- base64: data.base64EncodedStringWithOptions(0 as unknown as NSDataBase64EncodingOptions),
179
- });
172
+ documentPickerDidPickDocumentsAtURLs(_controller: UIDocumentPickerViewController, urls: NSArray<NSURL>): void {
173
+ const out: any[] = [];
174
+ for (let i = 0; i < urls.count; i++) {
175
+ const url = urls.objectAtIndex(i);
176
+ const scoped = url.startAccessingSecurityScopedResource?.();
177
+ try {
178
+ const data = NSData.dataWithContentsOfURL(url);
179
+ if (data) {
180
+ out.push({
181
+ name: url.lastPathComponent,
182
+ mimeType: mimeForUrlIos(url),
183
+ size: data.length,
184
+ base64: data.base64EncodedStringWithOptions(0 as unknown as NSDataBase64EncodingOptions),
185
+ });
186
+ }
187
+ } finally {
188
+ if (scoped) url.stopAccessingSecurityScopedResource?.();
189
+ }
180
190
  }
181
- } finally {
182
- if (scoped) url.stopAccessingSecurityScopedResource?.();
191
+ this.onPick(out);
183
192
  }
184
- }
185
- this.onPick(out);
186
- }
187
193
 
188
- documentPickerWasCancelled(_controller: UIDocumentPickerViewController): void {
189
- this.onPick([]);
194
+ documentPickerWasCancelled(_controller: UIDocumentPickerViewController): void {
195
+ this.onPick([]);
196
+ }
197
+ }
198
+ DocumentPickerDelegate = DocumentPickerDelegateImpl;
190
199
  }
200
+ return DocumentPickerDelegate;
191
201
  }
192
202
 
193
203
  /**
@@ -211,7 +221,7 @@ function pickFileIos(types: string[] | undefined, multiple: boolean): Promise<an
211
221
  const picker = UIDocumentPickerViewController.alloc().initForOpeningContentTypes(utis as any);
212
222
  picker.allowsMultipleSelection = multiple;
213
223
 
214
- const delegate = DocumentPickerDelegate.new();
224
+ const delegate = documentPickerDelegateClass().new();
215
225
  delegate.onPick = resolve;
216
226
  (picker as any)._appwrapDelegate = delegate; // interop: retain delegate past the present call (no typed slot)
217
227
  picker.delegate = delegate;
@@ -11,18 +11,28 @@ const err = (code: string, message: string) => Object.assign(new Error(message),
11
11
  let activeSession: ASWebAuthenticationSession | null = null;
12
12
  let activeProvider: ASWebAuthenticationPresentationContextProviding | null = null;
13
13
 
14
- @NativeClass()
15
- class OAuthPresentationContextProvider extends NSObject implements ASWebAuthenticationPresentationContextProviding {
16
- static ObjCProtocols = [ASWebAuthenticationPresentationContextProviding];
17
- static new(): OAuthPresentationContextProvider {
18
- return <OAuthPresentationContextProvider>super.new();
19
- }
20
- presentationAnchorForWebAuthenticationSession(_session: ASWebAuthenticationSession): UIWindow {
21
- return (
22
- Utils.ios.getRootViewController()?.view?.window ??
23
- UIApplication.sharedApplication.keyWindow
24
- );
14
+ // Built lazily INSIDE the iOS-only path (mirrors banner.ts): NSObject/AS* are iOS globals and this file
15
+ // is imported on Android too, so a top-level `@NativeClass extends NSObject` would crash at module load.
16
+ // any: runtime-built ObjC subclass holder; the class BODY stays fully typed.
17
+ let OAuthPresentationContextProvider: any;
18
+ function oauthContextProviderClass(): any {
19
+ if (!OAuthPresentationContextProvider) {
20
+ @NativeClass()
21
+ class OAuthPresentationContextProviderImpl extends NSObject implements ASWebAuthenticationPresentationContextProviding {
22
+ static ObjCProtocols = [ASWebAuthenticationPresentationContextProviding];
23
+ static new(): OAuthPresentationContextProviderImpl {
24
+ return <OAuthPresentationContextProviderImpl>super.new();
25
+ }
26
+ presentationAnchorForWebAuthenticationSession(_session: ASWebAuthenticationSession): UIWindow {
27
+ return (
28
+ Utils.ios.getRootViewController()?.view?.window ??
29
+ UIApplication.sharedApplication.keyWindow
30
+ );
31
+ }
32
+ }
33
+ OAuthPresentationContextProvider = OAuthPresentationContextProviderImpl;
25
34
  }
35
+ return OAuthPresentationContextProvider;
26
36
  }
27
37
 
28
38
  /**
@@ -64,7 +74,7 @@ export function registerOAuthHandlers(): void {
64
74
  }
65
75
  );
66
76
 
67
- activeProvider = OAuthPresentationContextProvider.new();
77
+ activeProvider = oauthContextProviderClass().new();
68
78
  session.presentationContextProvider = activeProvider;
69
79
  session.prefersEphemeralWebBrowserSession = !!ephemeral;
70
80
  activeSession = session;
@@ -20,77 +20,89 @@ function readContact(contact: CNContact): { name: string; phones: string[]; emai
20
20
  };
21
21
  }
22
22
 
23
- // CLLocationManager delegate for the persistent geo.watch stream. Callbacks are wired via instance
24
- // fields so the closure-captured behavior of the old (NSObject).extend(...) is preserved.
25
- @NativeClass()
26
- class GeoWatchDelegate extends NSObject implements CLLocationManagerDelegate {
27
- static ObjCProtocols = [CLLocationManagerDelegate];
28
- static new(): GeoWatchDelegate {
29
- return <GeoWatchDelegate>super.new();
30
- }
31
- locationManagerDidUpdateLocations(_m: CLLocationManager, locations: NSArray<CLLocation>): void {
32
- const loc = locations.lastObject;
33
- if (!loc) return;
34
- bridge.emit('geo.position', {
35
- lat: loc.coordinate.latitude,
36
- lng: loc.coordinate.longitude,
37
- accuracy: loc.horizontalAccuracy,
38
- });
39
- }
40
- locationManagerDidFailWithError(_m: CLLocationManager, error: NSError): void {
41
- console.warn('AppWrap: geo.watch error', error.localizedDescription);
42
- }
43
- locationManagerDidChangeAuthorization(m: CLLocationManager): void {
44
- const st = m.authorizationStatus;
45
- if (st === CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedWhenInUse ||
46
- st === CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways) {
47
- m.startUpdatingLocation();
23
+ // iOS-only native delegates. Defined lazily inside an isIOS-gated factory (mirrors banner.ts) so the
24
+ // shared module can instantiate on Android — NSObject/CL*/CN*/UIImagePicker* are iOS globals, and a
25
+ // top-level `extends NSObject` would evaluate at ES-module load and crash the Android shell.
26
+ // any: module-level holders for runtime-built ObjC subclasses; each class BODY stays fully typed.
27
+ let GeoWatchDelegate: any, ContactPickerDelegate: any, CameraCaptureDelegate: any;
28
+ function ensureIosDelegates(): void {
29
+ if (!isIOS || GeoWatchDelegate) return;
30
+
31
+ // CLLocationManager delegate for the persistent geo.watch stream. Callbacks are wired via instance
32
+ // fields so the closure-captured behavior of the old (NSObject).extend(...) is preserved.
33
+ @NativeClass()
34
+ class GeoWatchDelegateImpl extends NSObject implements CLLocationManagerDelegate {
35
+ static ObjCProtocols = [CLLocationManagerDelegate];
36
+ static new(): GeoWatchDelegateImpl {
37
+ return <GeoWatchDelegateImpl>super.new();
38
+ }
39
+ locationManagerDidUpdateLocations(_m: CLLocationManager, locations: NSArray<CLLocation>): void {
40
+ const loc = locations.lastObject;
41
+ if (!loc) return;
42
+ bridge.emit('geo.position', {
43
+ lat: loc.coordinate.latitude,
44
+ lng: loc.coordinate.longitude,
45
+ accuracy: loc.horizontalAccuracy,
46
+ });
47
+ }
48
+ locationManagerDidFailWithError(_m: CLLocationManager, error: NSError): void {
49
+ console.warn('AppWrap: geo.watch error', error.localizedDescription);
50
+ }
51
+ locationManagerDidChangeAuthorization(m: CLLocationManager): void {
52
+ const st = m.authorizationStatus;
53
+ if (st === CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedWhenInUse ||
54
+ st === CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways) {
55
+ m.startUpdatingLocation();
56
+ }
48
57
  }
49
58
  }
50
- }
59
+ GeoWatchDelegate = GeoWatchDelegateImpl;
51
60
 
52
- // CNContactPickerViewController delegate. The picker resolution callbacks are set as instance fields
53
- // after construction (replacing the old closure-captured `resolve`).
54
- @NativeClass()
55
- class ContactPickerDelegate extends NSObject implements CNContactPickerDelegate {
56
- static ObjCProtocols = [CNContactPickerDelegate];
57
- static new(): ContactPickerDelegate {
58
- return <ContactPickerDelegate>super.new();
59
- }
60
- onSelect?: (contact: CNContact) => void;
61
- onCancel?: (picker: CNContactPickerViewController) => void;
62
- contactPickerDidSelectContact(_picker: CNContactPickerViewController, contact: CNContact): void {
63
- this.onSelect?.(contact);
64
- }
65
- contactPickerDidCancel(picker: CNContactPickerViewController): void {
66
- this.onCancel?.(picker);
61
+ // CNContactPickerViewController delegate. The picker resolution callbacks are set as instance fields
62
+ // after construction (replacing the old closure-captured `resolve`).
63
+ @NativeClass()
64
+ class ContactPickerDelegateImpl extends NSObject implements CNContactPickerDelegate {
65
+ static ObjCProtocols = [CNContactPickerDelegate];
66
+ static new(): ContactPickerDelegateImpl {
67
+ return <ContactPickerDelegateImpl>super.new();
68
+ }
69
+ onSelect?: (contact: CNContact) => void;
70
+ onCancel?: (picker: CNContactPickerViewController) => void;
71
+ contactPickerDidSelectContact(_picker: CNContactPickerViewController, contact: CNContact): void {
72
+ this.onSelect?.(contact);
73
+ }
74
+ contactPickerDidCancel(picker: CNContactPickerViewController): void {
75
+ this.onCancel?.(picker);
76
+ }
67
77
  }
68
- }
78
+ ContactPickerDelegate = ContactPickerDelegateImpl;
69
79
 
70
- // UIImagePickerController delegate (camera.capture). Result/options are set as instance fields after
71
- // construction, replacing the old closure-captured `resolve`/`dataUrl`/`maxSize`.
72
- @NativeClass()
73
- class CameraCaptureDelegate extends NSObject implements UIImagePickerControllerDelegate, UINavigationControllerDelegate {
74
- static ObjCProtocols = [UIImagePickerControllerDelegate, UINavigationControllerDelegate];
75
- static new(): CameraCaptureDelegate {
76
- return <CameraCaptureDelegate>super.new();
77
- }
78
- wantDataUrl = false;
79
- maxSize = 1024;
80
- onResult?: (out: { picked: boolean; width?: number; height?: number; dataUrl?: string }) => void;
81
- imagePickerControllerDidFinishPickingMediaWithInfo(picker: UIImagePickerController, info: NSDictionary<string, any>): void {
82
- picker.dismissViewControllerAnimatedCompletion(true, null);
83
- const img = info.objectForKey(UIImagePickerControllerOriginalImage) as UIImage | null;
84
- if (!img) return this.onResult?.({ picked: true });
85
- const out: { picked: boolean; width?: number; height?: number; dataUrl?: string } =
86
- { picked: true, width: img.size.width, height: img.size.height };
87
- if (this.wantDataUrl) out.dataUrl = uiImageToDataUrl(img, this.maxSize);
88
- this.onResult?.(out);
89
- }
90
- imagePickerControllerDidCancel(picker: UIImagePickerController): void {
91
- picker.dismissViewControllerAnimatedCompletion(true, null);
92
- this.onResult?.({ picked: false });
80
+ // UIImagePickerController delegate (camera.capture). Result/options are set as instance fields after
81
+ // construction, replacing the old closure-captured `resolve`/`dataUrl`/`maxSize`.
82
+ @NativeClass()
83
+ class CameraCaptureDelegateImpl extends NSObject implements UIImagePickerControllerDelegate, UINavigationControllerDelegate {
84
+ static ObjCProtocols = [UIImagePickerControllerDelegate, UINavigationControllerDelegate];
85
+ static new(): CameraCaptureDelegateImpl {
86
+ return <CameraCaptureDelegateImpl>super.new();
87
+ }
88
+ wantDataUrl = false;
89
+ maxSize = 1024;
90
+ onResult?: (out: { picked: boolean; width?: number; height?: number; dataUrl?: string }) => void;
91
+ imagePickerControllerDidFinishPickingMediaWithInfo(picker: UIImagePickerController, info: NSDictionary<string, any>): void {
92
+ picker.dismissViewControllerAnimatedCompletion(true, null);
93
+ const img = info.objectForKey(UIImagePickerControllerOriginalImage) as UIImage | null;
94
+ if (!img) return this.onResult?.({ picked: true });
95
+ const out: { picked: boolean; width?: number; height?: number; dataUrl?: string } =
96
+ { picked: true, width: img.size.width, height: img.size.height };
97
+ if (this.wantDataUrl) out.dataUrl = uiImageToDataUrl(img, this.maxSize);
98
+ this.onResult?.(out);
99
+ }
100
+ imagePickerControllerDidCancel(picker: UIImagePickerController): void {
101
+ picker.dismissViewControllerAnimatedCompletion(true, null);
102
+ this.onResult?.({ picked: false });
103
+ }
93
104
  }
105
+ CameraCaptureDelegate = CameraCaptureDelegateImpl;
94
106
  }
95
107
 
96
108
  /**
@@ -98,6 +110,7 @@ class CameraCaptureDelegate extends NSObject implements UIImagePickerControllerD
98
110
  * motion, contacts, calendar, camera. iOS-first like handlers-extended.
99
111
  */
100
112
  export function registerParityHandlers(): void {
113
+ ensureIosDelegates(); // no-op on Android; defines the iOS delegate classes used by the handlers below
101
114
  // ── dialogs (NS Dialogs — cross-platform) ──────────────────────────
102
115
  bridge.register('ui.alert', ({ title, message, ok }: any) =>
103
116
  Dialogs.alert({ title, message: String(message ?? ''), okButtonText: ok ?? 'OK' }).then(() => undefined)
@@ -139,7 +152,7 @@ export function registerParityHandlers(): void {
139
152
 
140
153
  // ── geo.watch (persistent CLLocationManager) ───────────────────────
141
154
  let geoManager: CLLocationManager | null = null;
142
- let geoDelegate: GeoWatchDelegate | null = null;
155
+ let geoDelegate: any = null;
143
156
 
144
157
  bridge.register('geo.watch.start', () => {
145
158
  if (!isIOS) throw iosOnly();
@@ -213,10 +226,10 @@ export function registerParityHandlers(): void {
213
226
  return new Promise((resolve) => {
214
227
  Utils.dispatchToMainThread(() => {
215
228
  const delegate = ContactPickerDelegate.new();
216
- delegate.onSelect = (contact) => {
229
+ delegate.onSelect = (contact: CNContact) => {
217
230
  resolve({ picked: true, ...readContact(contact) });
218
231
  };
219
- delegate.onCancel = (picker) => {
232
+ delegate.onCancel = (picker: CNContactPickerViewController) => {
220
233
  picker.dismissViewControllerAnimatedCompletion(true, null);
221
234
  resolve({ picked: false });
222
235
  };