@livx.cc/appwrap 0.61.8 → 0.61.10
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
CHANGED
|
@@ -8,6 +8,7 @@ import { setStatusBarStyle } from './status-bar';
|
|
|
8
8
|
import { buildCapabilityMap } from './capabilities.manifest';
|
|
9
9
|
import { ACTIVE_MODULE_NAMES, PACK_MODULES } from './active-modules.generated';
|
|
10
10
|
import { consumePendingBackgroundTaskId } from './background-context';
|
|
11
|
+
import { settleShare, type ShareOutcome } from './share-outcome';
|
|
11
12
|
|
|
12
13
|
/** Build identifier for the native shell bundle — bump per deploy to spot stale bundles. */
|
|
13
14
|
export const SHELL_BUILD = 'share-target-1';
|
|
@@ -18,6 +19,40 @@ let lastWebVersion: WebVersionInfo = {};
|
|
|
18
19
|
/** Latest version status the web reported — read by the dev-menu App Info screen. */
|
|
19
20
|
export function getReportedWebVersion(): WebVersionInfo { return lastWebVersion; }
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Present the iOS share sheet and resolve when it ENDS — not when it opens — with how it ended
|
|
24
|
+
* (`{ completed, activity? }`, e.g. activity `com.apple.UIKit.activity.SaveToCameraRoll` for "Save
|
|
25
|
+
* Video"). That is what lets a caller say "Saved to Photos"; the sheet itself gives no feedback.
|
|
26
|
+
*/
|
|
27
|
+
function presentShareSheet(items: NSMutableArray<any>): Promise<ShareOutcome> {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const controller = UIActivityViewController.alloc().initWithActivityItemsApplicationActivities(items, null);
|
|
30
|
+
const rootVC = Utils.ios.getRootViewController();
|
|
31
|
+
// iPad requires a popover anchor
|
|
32
|
+
if (controller.popoverPresentationController) {
|
|
33
|
+
controller.popoverPresentationController.sourceView = rootVC.view;
|
|
34
|
+
}
|
|
35
|
+
let done = false;
|
|
36
|
+
const finish = (o: ShareOutcome | null) => {
|
|
37
|
+
if (done || !o) return;
|
|
38
|
+
done = true;
|
|
39
|
+
resolve(o);
|
|
40
|
+
};
|
|
41
|
+
const presented = () => !!controller.presentingViewController && !controller.beingDismissed;
|
|
42
|
+
controller.completionWithItemsHandler = (activityType: string, completed: boolean) => {
|
|
43
|
+
// Recover the WebView on dismiss — a presented sheet can orphan a touch-stealing window / leave
|
|
44
|
+
// the renderer throttled (see CustomWebView.recoverAfterNativeSurface).
|
|
45
|
+
bridge.getWebView()?.recoverAfterNativeSurface();
|
|
46
|
+
const now = settleShare(activityType, !!completed, presented());
|
|
47
|
+
if (now) return finish(now);
|
|
48
|
+
// Still presented: a cancelled sub-activity (sheet stays) OR a dismissal still animating out.
|
|
49
|
+
// Re-check once it would have finished; a later handler call wins if it comes first.
|
|
50
|
+
setTimeout(() => finish(settleShare(null, false, presented())), 700);
|
|
51
|
+
};
|
|
52
|
+
rootVC.presentViewControllerAnimatedCompletion(controller, true, null);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
21
56
|
/** Register all protocol-v1 handlers. */
|
|
22
57
|
export function registerHandlers(): void {
|
|
23
58
|
bridge.register('app.handshake', () => {
|
|
@@ -84,20 +119,10 @@ export function registerHandlers(): void {
|
|
|
84
119
|
if (isIOS) {
|
|
85
120
|
const items = NSMutableArray.new();
|
|
86
121
|
if (content) items.addObject(content);
|
|
87
|
-
|
|
88
|
-
items,
|
|
89
|
-
null
|
|
90
|
-
);
|
|
91
|
-
const rootVC = Utils.ios.getRootViewController();
|
|
92
|
-
// iPad requires a popover anchor
|
|
93
|
-
if (controller.popoverPresentationController) {
|
|
94
|
-
controller.popoverPresentationController.sourceView = rootVC.view;
|
|
95
|
-
}
|
|
96
|
-
// Recover the WebView on dismiss — a presented sheet can orphan a touch-stealing window / leave
|
|
97
|
-
// the renderer throttled (see CustomWebView.recoverAfterNativeSurface).
|
|
98
|
-
controller.completionWithItemsHandler = () => { bridge.getWebView()?.recoverAfterNativeSurface(); };
|
|
99
|
-
rootVC.presentViewControllerAnimatedCompletion(controller, true, null);
|
|
122
|
+
return presentShareSheet(items);
|
|
100
123
|
} else if (isAndroid) {
|
|
124
|
+
// The chooser cannot report what (or whether) anything was picked, so there is no outcome to
|
|
125
|
+
// return: resolves undefined, which callers treat as "unknown" (no success feedback).
|
|
101
126
|
const intent = new android.content.Intent(android.content.Intent.ACTION_SEND);
|
|
102
127
|
intent.setType('text/plain');
|
|
103
128
|
if (title) intent.putExtra(android.content.Intent.EXTRA_SUBJECT, title);
|
|
@@ -123,15 +148,7 @@ export function registerHandlers(): void {
|
|
|
123
148
|
data.writeToFileAtomically(filePath, true);
|
|
124
149
|
items.addObject(NSURL.fileURLWithPath(filePath));
|
|
125
150
|
});
|
|
126
|
-
|
|
127
|
-
items,
|
|
128
|
-
null
|
|
129
|
-
);
|
|
130
|
-
const rootVC = Utils.ios.getRootViewController();
|
|
131
|
-
if (controller.popoverPresentationController) {
|
|
132
|
-
controller.popoverPresentationController.sourceView = rootVC.view; // iPad anchor
|
|
133
|
-
}
|
|
134
|
-
rootVC.presentViewControllerAnimatedCompletion(controller, true, null);
|
|
151
|
+
return presentShareSheet(items);
|
|
135
152
|
}
|
|
136
153
|
);
|
|
137
154
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** How a share sheet ended — what `share.share` / `share.files` resolve with (native-kit `ShareResult`). */
|
|
2
|
+
export interface ShareOutcome {
|
|
3
|
+
completed: boolean;
|
|
4
|
+
activity?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Decide whether ONE `UIActivityViewController.completionWithItemsHandler` call ends the share.
|
|
9
|
+
* iOS 13+ calls the handler with (activityType, completed=false) when a sub-activity (Mail, Messages)
|
|
10
|
+
* is cancelled while the sheet STAYS open for another pick — resolving there would report "cancelled"
|
|
11
|
+
* and lose the real outcome. So: a completed activity ends it; otherwise only a sheet that is no longer
|
|
12
|
+
* presented does. `null` = not over yet, wait for the next call (or the dismissal re-check).
|
|
13
|
+
*/
|
|
14
|
+
export function settleShare(
|
|
15
|
+
activityType: string | null | undefined,
|
|
16
|
+
completed: boolean,
|
|
17
|
+
sheetStillPresented: boolean
|
|
18
|
+
): ShareOutcome | null {
|
|
19
|
+
if (completed) return activityType ? { completed: true, activity: String(activityType) } : { completed: true };
|
|
20
|
+
return sheetStillPresented ? null : { completed: false };
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iOS share sheet outcome — `share.share` / `share.files` resolve when the sheet ENDS with
|
|
3
|
+
* `{ completed, activity? }`. Contract: a completed activity ends it (with its type), a dismissed sheet
|
|
4
|
+
* ends it as not completed, and a cancelled sub-activity while the sheet stays open does NOT end it.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
import { settleShare } from '../app/shell/share-outcome';
|
|
8
|
+
|
|
9
|
+
describe('settleShare', () => {
|
|
10
|
+
test('"Save Video" completes with its activity type', () => {
|
|
11
|
+
expect(settleShare('com.apple.UIKit.activity.SaveToCameraRoll', true, false))
|
|
12
|
+
.toEqual({ completed: true, activity: 'com.apple.UIKit.activity.SaveToCameraRoll' });
|
|
13
|
+
});
|
|
14
|
+
test('completed while the sheet is still animating out still completes', () => {
|
|
15
|
+
expect(settleShare('com.apple.UIKit.activity.CopyToPasteboard', true, true))
|
|
16
|
+
.toEqual({ completed: true, activity: 'com.apple.UIKit.activity.CopyToPasteboard' });
|
|
17
|
+
});
|
|
18
|
+
test('completed with no activity type carries no activity key', () => {
|
|
19
|
+
expect(settleShare(null, true, false)).toEqual({ completed: true });
|
|
20
|
+
});
|
|
21
|
+
test('sheet dismissed without a pick → not completed', () => {
|
|
22
|
+
expect(settleShare(null, false, false)).toEqual({ completed: false });
|
|
23
|
+
});
|
|
24
|
+
test('sub-activity cancelled while the sheet stays open → not over yet', () => {
|
|
25
|
+
expect(settleShare('com.apple.UIKit.activity.Mail', false, true)).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
});
|