@symbiote-native/engine 1.2.0 → 1.3.0

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.
Files changed (71) hide show
  1. package/android/CMakeLists.txt +31 -1
  2. package/android/build.gradle +4 -1
  3. package/build/accessibility-info/index.android.js +21 -17
  4. package/build/accessibility-info/index.ios.js +2 -0
  5. package/build/accessibility-info/shared.d.ts +1 -1
  6. package/build/alert/shared.d.ts +1 -1
  7. package/build/alert/shared.js +3 -7
  8. package/build/app-state/index.d.ts +1 -1
  9. package/build/app-state/index.js +35 -27
  10. package/build/back-handler/index.d.ts +7 -2
  11. package/build/back-handler/index.js +19 -10
  12. package/build/events/index.js +10 -1
  13. package/build/fabric-props.js +1 -1
  14. package/build/fabric.d.ts +7 -0
  15. package/build/fabric.js +14 -0
  16. package/build/host-behavior.d.ts +1 -0
  17. package/build/image-loader.d.ts +4 -2
  18. package/build/image-loader.js +14 -25
  19. package/build/image-source-write.d.ts +1 -1
  20. package/build/image-source-write.js +14 -2
  21. package/build/index.d.ts +2 -2
  22. package/build/index.js +6 -6
  23. package/build/invariant.d.ts +1 -0
  24. package/build/invariant.js +10 -0
  25. package/build/keyboard/index.js +11 -32
  26. package/build/linking/index.android.js +5 -3
  27. package/build/linking/shared.d.ts +1 -1
  28. package/build/linking/shared.js +16 -19
  29. package/build/native-events.d.ts +5 -0
  30. package/build/native-events.js +21 -0
  31. package/build/node.js +18 -2
  32. package/build/permissions-android/index.android.d.ts +59 -0
  33. package/build/permissions-android/index.android.js +47 -0
  34. package/build/permissions-android/index.d.ts +1 -115
  35. package/build/permissions-android/index.ios.d.ts +59 -0
  36. package/build/permissions-android/index.ios.js +31 -0
  37. package/build/permissions-android/index.js +3 -184
  38. package/build/permissions-android/shared.d.ts +63 -0
  39. package/build/permissions-android/shared.js +66 -0
  40. package/build/platform/index.android.js +3 -5
  41. package/build/platform/index.ios.js +3 -3
  42. package/build/platform/shared.d.ts +4 -0
  43. package/build/platform/shared.js +9 -0
  44. package/build/platform-color/index.android.d.ts +4 -0
  45. package/build/platform-color/index.android.js +7 -0
  46. package/build/platform-color/index.d.ts +2 -20
  47. package/build/platform-color/index.js +1 -45
  48. package/build/platform-color/shared.d.ts +21 -0
  49. package/build/platform-color/shared.js +41 -0
  50. package/build/settings/index.android.d.ts +6 -0
  51. package/build/settings/index.android.js +21 -0
  52. package/build/settings/index.d.ts +1 -8
  53. package/build/settings/index.ios.d.ts +8 -0
  54. package/build/settings/index.ios.js +122 -0
  55. package/build/settings/index.js +3 -122
  56. package/build/share/index.android.js +9 -32
  57. package/build/share/index.ios.js +14 -15
  58. package/build/share/shared.d.ts +4 -2
  59. package/build/share/shared.js +7 -10
  60. package/build/status-bar/index.android.js +1 -1
  61. package/build/status-bar/index.ios.js +4 -3
  62. package/build/toast-android/index.android.d.ts +10 -0
  63. package/build/toast-android/index.android.js +108 -0
  64. package/build/toast-android/index.d.ts +1 -10
  65. package/build/toast-android/index.ios.d.ts +10 -0
  66. package/build/toast-android/index.ios.js +19 -0
  67. package/build/toast-android/index.js +3 -108
  68. package/cpp/SymbioteFabricProps.cpp +204 -120
  69. package/cpp/SymbioteFabricProps.h +5 -0
  70. package/cpp/SymbioteTree.cpp +20 -6
  71. package/package.json +2 -2
@@ -0,0 +1,122 @@
1
+ // Settings module: reads/writes the app's persisted defaults (iOS NSUserDefaults
2
+ // via the native SettingsManager). A JS-side snapshot seeded from the native module
3
+ // constants answers `get`; `set` writes through to native and updates the snapshot;
4
+ // `watchKeys`/`clearWatch` are a pure-JS subscription registry that fires when a
5
+ // watched key's value changes. Native re-broadcasts external edits through the
6
+ // device event `settingsUpdated`, which feeds the same change->fire path. Mirrors
7
+ // RN's Libraries/Settings/Settings.ios.js, iOS surface only.
8
+ import { installDeviceEventHub, NativeEventEmitter, } from '../native-events.js';
9
+ import { getNativeModule } from '../native-modules/index.js';
10
+ import { dlog } from '../debug.js';
11
+ import { isRecord } from '../type-guards.js';
12
+ // The iOS native module name RN registers this under. NOTE: this is the name the
13
+ // iOS JS wrapper resolves via `TurboModuleRegistry.getEnforcing('SettingsManager')`.
14
+ // The spec filename is `INativeSettingsManager`. A module name is only provable on a
15
+ // real host (a headless fake answers to any name); this iOS name is device-verify-pending.
16
+ const SETTINGS_MODULE = 'SettingsManager';
17
+ // The device event native emits when the app's defaults change out from under JS
18
+ // (e.g. a Settings.bundle edit). Its payload is a record of changed key->value.
19
+ const SETTINGS_UPDATED_EVENT = 'settingsUpdated';
20
+ // watchIds are handed out as the registry index, so the first watcher is 0. A
21
+ // cleared slot keeps its index (the array never shrinks) so later ids stay stable.
22
+ const FIRST_WATCH_ID = 0;
23
+ // Lazily resolved so importing this module has no native side effect. `null` when
24
+ // the module isn't linked (headless): the snapshot then starts empty and `set`
25
+ // updates only JS state + fires watchers.
26
+ let settingsModule;
27
+ function getModule() {
28
+ if (settingsModule === undefined) {
29
+ settingsModule = getNativeModule(SETTINGS_MODULE);
30
+ dlog(`Settings: module ${settingsModule ? 'resolved' : 'NOT resolved (null)'}`);
31
+ }
32
+ return settingsModule;
33
+ }
34
+ // The JS-side mirror of native defaults: seeded once from the module constants,
35
+ // kept in sync by `set` and by the `settingsUpdated` device event.
36
+ let snapshot;
37
+ function getSnapshot() {
38
+ if (snapshot === undefined) {
39
+ const module = getModule();
40
+ const constants = module?.getConstants().settings;
41
+ snapshot = isRecord(constants) ? { ...constants } : {};
42
+ dlog(`Settings: snapshot seeded with ${Object.keys(snapshot).length} key(s)`);
43
+ }
44
+ return snapshot;
45
+ }
46
+ const subscriptions = [];
47
+ // Fire every watcher whose key set covers `key`. Called only for keys that changed.
48
+ function fireWatchers(key) {
49
+ for (const sub of subscriptions) {
50
+ if (sub.callback !== null && sub.keys.includes(key))
51
+ sub.callback();
52
+ }
53
+ }
54
+ // Fold a batch of new values into the snapshot, firing watchers for each key whose
55
+ // value actually changed. Shared by `set` (JS-originated) and the device event
56
+ // (native-originated), matching RN's `_sendObservations`.
57
+ function applyChanges(values) {
58
+ const current = getSnapshot();
59
+ for (const key of Object.keys(values)) {
60
+ const next = values[key];
61
+ const didChange = current[key] !== next;
62
+ current[key] = next;
63
+ if (didChange)
64
+ fireWatchers(key);
65
+ }
66
+ }
67
+ let emitter;
68
+ // Subscribe to native's `settingsUpdated` so external edits flow into the snapshot.
69
+ // WHY lazy + module-gated: importing this file must have no native side effect, and
70
+ // with no native module there is nothing native to observe, so a headless run that
71
+ // never installs the device hub doesn't crash. Idempotent.
72
+ function subscribeToNative() {
73
+ if (emitter !== undefined)
74
+ return;
75
+ const module = getModule();
76
+ if (module === null)
77
+ return;
78
+ installDeviceEventHub();
79
+ emitter = new NativeEventEmitter(module);
80
+ emitter.addListener(SETTINGS_UPDATED_EVENT, payload => {
81
+ if (!isRecord(payload))
82
+ return;
83
+ applyChanges(payload);
84
+ });
85
+ }
86
+ class SettingsImpl {
87
+ // Read the current value for `key` from the JS snapshot. `undefined` if unset.
88
+ get(key) {
89
+ subscribeToNative();
90
+ return getSnapshot()[key];
91
+ }
92
+ // Persist `settings` to native, update the snapshot, and fire watchers for the
93
+ // keys that changed. Without a native module (headless) only JS state updates.
94
+ set(settings) {
95
+ subscribeToNative();
96
+ const module = getModule();
97
+ if (module === null) {
98
+ dlog('Settings.set -> no module (JS snapshot + watchers only)');
99
+ }
100
+ else {
101
+ module.setValues(settings);
102
+ }
103
+ applyChanges(settings);
104
+ }
105
+ // Register a watcher for one or more keys. Returns a numeric watchId (the registry
106
+ // index) to pass to `clearWatch`.
107
+ watchKeys(keys, callback) {
108
+ subscribeToNative();
109
+ const watched = typeof keys === 'string' ? [keys] : keys;
110
+ const watchId = subscriptions.length + FIRST_WATCH_ID;
111
+ subscriptions.push({ keys: watched, callback });
112
+ return watchId;
113
+ }
114
+ // Disarm the watcher with the given id. Its slot is emptied (kept, not removed) so
115
+ // every other watchId stays valid.
116
+ clearWatch(watchId) {
117
+ if (watchId >= FIRST_WATCH_ID && watchId < subscriptions.length) {
118
+ subscriptions[watchId] = { keys: [], callback: null };
119
+ }
120
+ }
121
+ }
122
+ export const Settings = new SettingsImpl();
@@ -1,122 +1,3 @@
1
- // Settings module: reads/writes the app's persisted defaults (iOS NSUserDefaults
2
- // via the native SettingsManager). A JS-side snapshot seeded from the native module
3
- // constants answers `get`; `set` writes through to native and updates the snapshot;
4
- // `watchKeys`/`clearWatch` are a pure-JS subscription registry that fires when a
5
- // watched key's value changes. Native re-broadcasts external edits through the
6
- // device event `settingsUpdated`, which feeds the same change->fire path. Mirrors
7
- // RN's Libraries/Settings/Settings.ios.js, iOS surface only.
8
- import { installDeviceEventHub, NativeEventEmitter, } from '../native-events.js';
9
- import { getNativeModule } from '../native-modules/index.js';
10
- import { dlog } from '../debug.js';
11
- import { isRecord } from '../type-guards.js';
12
- // The iOS native module name RN registers this under. NOTE: this is the name the
13
- // iOS JS wrapper resolves via `TurboModuleRegistry.getEnforcing('SettingsManager')`.
14
- // The spec filename is `INativeSettingsManager`. A module name is only provable on a
15
- // real host (a headless fake answers to any name); this iOS name is device-verify-pending.
16
- const SETTINGS_MODULE = 'SettingsManager';
17
- // The device event native emits when the app's defaults change out from under JS
18
- // (e.g. a Settings.bundle edit). Its payload is a record of changed key->value.
19
- const SETTINGS_UPDATED_EVENT = 'settingsUpdated';
20
- // watchIds are handed out as the registry index, so the first watcher is 0. A
21
- // cleared slot keeps its index (the array never shrinks) so later ids stay stable.
22
- const FIRST_WATCH_ID = 0;
23
- // Lazily resolved so importing this module has no native side effect. `null` when
24
- // the module isn't linked (headless): the snapshot then starts empty and `set`
25
- // updates only JS state + fires watchers.
26
- let settingsModule;
27
- function getModule() {
28
- if (settingsModule === undefined) {
29
- settingsModule = getNativeModule(SETTINGS_MODULE);
30
- dlog(`Settings: module ${settingsModule ? 'resolved' : 'NOT resolved (null)'}`);
31
- }
32
- return settingsModule;
33
- }
34
- // The JS-side mirror of native defaults: seeded once from the module constants,
35
- // kept in sync by `set` and by the `settingsUpdated` device event.
36
- let snapshot;
37
- function getSnapshot() {
38
- if (snapshot === undefined) {
39
- const module = getModule();
40
- const constants = module?.getConstants().settings;
41
- snapshot = isRecord(constants) ? { ...constants } : {};
42
- dlog(`Settings: snapshot seeded with ${Object.keys(snapshot).length} key(s)`);
43
- }
44
- return snapshot;
45
- }
46
- const subscriptions = [];
47
- // Fire every watcher whose key set covers `key`. Called only for keys that changed.
48
- function fireWatchers(key) {
49
- for (const sub of subscriptions) {
50
- if (sub.callback !== null && sub.keys.includes(key))
51
- sub.callback();
52
- }
53
- }
54
- // Fold a batch of new values into the snapshot, firing watchers for each key whose
55
- // value actually changed. Shared by `set` (JS-originated) and the device event
56
- // (native-originated), matching RN's `_sendObservations`.
57
- function applyChanges(values) {
58
- const current = getSnapshot();
59
- for (const key of Object.keys(values)) {
60
- const next = values[key];
61
- const didChange = current[key] !== next;
62
- current[key] = next;
63
- if (didChange)
64
- fireWatchers(key);
65
- }
66
- }
67
- let emitter;
68
- // Subscribe to native's `settingsUpdated` so external edits flow into the snapshot.
69
- // WHY lazy + module-gated: importing this file must have no native side effect, and
70
- // with no native module there is nothing native to observe, so a headless run that
71
- // never installs the device hub doesn't crash. Idempotent.
72
- function subscribeToNative() {
73
- if (emitter !== undefined)
74
- return;
75
- const module = getModule();
76
- if (module === null)
77
- return;
78
- installDeviceEventHub();
79
- emitter = new NativeEventEmitter(module);
80
- emitter.addListener(SETTINGS_UPDATED_EVENT, payload => {
81
- if (!isRecord(payload))
82
- return;
83
- applyChanges(payload);
84
- });
85
- }
86
- class SettingsImpl {
87
- // Read the current value for `key` from the JS snapshot. `undefined` if unset.
88
- get(key) {
89
- subscribeToNative();
90
- return getSnapshot()[key];
91
- }
92
- // Persist `settings` to native, update the snapshot, and fire watchers for the
93
- // keys that changed. Without a native module (headless) only JS state updates.
94
- set(settings) {
95
- subscribeToNative();
96
- const module = getModule();
97
- if (module === null) {
98
- dlog('Settings.set -> no module (JS snapshot + watchers only)');
99
- }
100
- else {
101
- module.setValues(settings);
102
- }
103
- applyChanges(settings);
104
- }
105
- // Register a watcher for one or more keys. Returns a numeric watchId (the registry
106
- // index) to pass to `clearWatch`.
107
- watchKeys(keys, callback) {
108
- subscribeToNative();
109
- const watched = typeof keys === 'string' ? [keys] : keys;
110
- const watchId = subscriptions.length + FIRST_WATCH_ID;
111
- subscriptions.push({ keys: watched, callback });
112
- return watchId;
113
- }
114
- // Disarm the watcher with the given id. Its slot is emptied (kept, not removed) so
115
- // every other watchId stays valid.
116
- clearWatch(watchId) {
117
- if (watchId >= FIRST_WATCH_ID && watchId < subscriptions.length) {
118
- subscriptions[watchId] = { keys: [], callback: null };
119
- }
120
- }
121
- }
122
- export const Settings = new SettingsImpl();
1
+ // Settings: base / headless build. Metro picks index.ios.ts / index.android.ts on a device; off
2
+ // those, the iOS build (the SettingsManager-backed one) is the default.
3
+ export * from './index.ios.js';
@@ -9,47 +9,24 @@
9
9
  // name, so it is only proven on a real Android host (a bridgeless resolution log there).
10
10
  import { dlog } from '../debug.js';
11
11
  import { getNativeModule } from '../native-modules/index.js';
12
- import { validateContent, shareActions, SHARED_ACTION, DISMISSED_ACTION, } from './shared.js';
12
+ import { assertShareArgs, invariant, shareActions } from './shared.js';
13
13
  const SHARE_MODULE = 'ShareModule';
14
- // ShareModule.share resolves an untyped value at the native boundary; narrow it before
15
- // reading `action` (no `as`).
16
- function isShareResult(value) {
17
- return (typeof value === 'object' &&
18
- value !== null &&
19
- 'action' in value &&
20
- typeof value.action === 'string');
21
- }
22
14
  export const Share = {
23
15
  ...shareActions,
24
- // Open the Android share dialog for `content`. Resolves with the user's action
25
- // (Android always resolves sharedAction); rejects on invalid content, an unexpected
26
- // native result, or a missing module (explicit reject, never a hung Promise).
16
+ // Share.js's Android branch: every check is a synchronous invariant, and the native result
17
+ // passes through untouched under `activityType: null`.
27
18
  share(content, options = {}) {
28
- const invalid = validateContent(content);
29
- if (invalid !== null) {
30
- dlog(`Share.share -> invalid content: ${invalid.message}`);
31
- return Promise.reject(invalid);
32
- }
19
+ assertShareArgs(content, options);
33
20
  dlog('Share.share (android)');
34
21
  const shareModule = getNativeModule(SHARE_MODULE);
35
- if (shareModule === null) {
36
- dlog(`Share: "${SHARE_MODULE}" unresolved`);
37
- return Promise.reject(new Error('Share: ShareModule native module unavailable'));
38
- }
22
+ invariant(shareModule !== null, 'ShareModule should be registered on Android.');
23
+ invariant(content.title == null || typeof content.title === 'string', 'Invalid title: title should be a string.');
39
24
  const newContent = {
40
25
  title: content.title,
41
26
  message: typeof content.message === 'string' ? content.message : undefined,
42
27
  };
43
- return shareModule.share(newContent, options.dialogTitle).then(result => {
44
- if (!isShareResult(result)) {
45
- dlog('Share.share -> android result missing action');
46
- throw new Error('Share: ShareModule returned an unexpected result');
47
- }
48
- dlog(`Share.share -> android action=${result.action}`);
49
- return {
50
- action: result.action === DISMISSED_ACTION ? DISMISSED_ACTION : SHARED_ACTION,
51
- activityType: null,
52
- };
53
- });
28
+ return shareModule
29
+ .share(newContent, options.dialogTitle)
30
+ .then(result => ({ activityType: null, ...result }));
54
31
  },
55
32
  };
@@ -7,39 +7,38 @@
7
7
  import { dlog } from '../debug.js';
8
8
  import { getNativeModule } from '../native-modules/index.js';
9
9
  import { ACTION_SHEET_MANAGER, } from '../action-sheet-ios/index.js';
10
- import { validateContent, shareActions, SHARED_ACTION, DISMISSED_ACTION, } from './shared.js';
10
+ import { isProcessableColor, processColor } from '../platform-color';
11
+ import { assertShareArgs, invariant, shareActions, SHARED_ACTION, DISMISSED_ACTION, } from './shared.js';
11
12
  export const Share = {
12
13
  ...shareActions,
13
14
  // Open the iOS share sheet for `content`. Resolves with the user's action
14
15
  // (sharedAction / dismissedAction); rejects on invalid content, a native failure,
15
16
  // or a missing module (explicit reject rather than a Promise that never settles).
16
17
  share(content, options = {}) {
17
- const invalid = validateContent(content);
18
- if (invalid !== null) {
19
- dlog(`Share.share -> invalid content: ${invalid.message}`);
20
- return Promise.reject(invalid);
21
- }
18
+ assertShareArgs(content, options);
22
19
  dlog('Share.share (ios)');
23
- const manager = getNativeModule(ACTION_SHEET_MANAGER);
24
- if (manager === null) {
25
- dlog(`Share: "${ACTION_SHEET_MANAGER}" unresolved`);
26
- return Promise.reject(new Error('Share: ActionSheetManager native module unavailable'));
27
- }
20
+ // Share.js's iOS branch: both invariants run INSIDE the executor, so they reject.
28
21
  return new Promise((resolve, reject) => {
22
+ const tintColor = isProcessableColor(options.tintColor)
23
+ ? processColor(options.tintColor)
24
+ : undefined;
25
+ invariant(tintColor == null || typeof tintColor === 'number', 'Unexpected color given for options.tintColor');
26
+ const manager = getNativeModule(ACTION_SHEET_MANAGER);
27
+ invariant(manager !== null, 'NativeActionSheetManager is not registered on iOS, but it should be.');
29
28
  manager.showShareActionSheetWithOptions({
30
29
  message: typeof content.message === 'string' ? content.message : undefined,
31
30
  url: typeof content.url === 'string' ? content.url : undefined,
32
31
  subject: options.subject,
33
- tintColor: options.tintColor,
34
- anchor: options.anchor,
32
+ tintColor: typeof tintColor === 'number' ? tintColor : undefined,
33
+ anchor: typeof options.anchor === 'number' ? options.anchor : undefined,
35
34
  excludedActivityTypes: options.excludedActivityTypes,
36
35
  }, error => {
37
36
  dlog('Share.share -> failure');
38
- reject(new Error(error.message));
37
+ reject(error);
39
38
  }, (completed, activityType) => {
40
39
  dlog(`Share.share -> success completed=${completed}`);
41
40
  resolve(completed
42
- ? { action: SHARED_ACTION, activityType: activityType ?? null }
41
+ ? { action: SHARED_ACTION, activityType }
43
42
  : { action: DISMISSED_ACTION, activityType: null });
44
43
  });
45
44
  });
@@ -1,3 +1,5 @@
1
+ import { invariant } from '../invariant';
2
+ export { invariant };
1
3
  export type IShareContent = {
2
4
  title?: string;
3
5
  url: string;
@@ -17,7 +19,7 @@ export interface IShareOptions {
17
19
  export declare const SHARED_ACTION = "sharedAction";
18
20
  export declare const DISMISSED_ACTION = "dismissedAction";
19
21
  export interface IShareAction {
20
- action: typeof SHARED_ACTION | typeof DISMISSED_ACTION;
22
+ action: string;
21
23
  activityType?: string | null;
22
24
  }
23
25
  export interface IShareStatic {
@@ -29,4 +31,4 @@ export declare const shareActions: {
29
31
  sharedAction: typeof SHARED_ACTION;
30
32
  dismissedAction: typeof DISMISSED_ACTION;
31
33
  };
32
- export declare function validateContent(content: IShareContent): Error | null;
34
+ export declare function assertShareArgs(content: IShareContent, options: IShareOptions): void;
@@ -7,6 +7,8 @@
7
7
  // Metro selects the platform file on a real host (share.android.ts > share.ts); the base
8
8
  // share.ts re-exports the iOS build for web/headless. There is no runtime `Platform.OS`
9
9
  // read: the filename is the selector.
10
+ import { invariant } from '../invariant.js';
11
+ export { invariant };
10
12
  // RN's Share action constants (RN Share.js ~173/179). These back the documented
11
13
  // `result.action === Share.dismissedAction` pattern. True statically-known literals,
12
14
  // so CONSTANT_CASE; the public fields (Share.sharedAction / Share.dismissedAction) are
@@ -19,14 +21,9 @@ export const shareActions = {
19
21
  sharedAction: SHARED_ACTION,
20
22
  dismissedAction: DISMISSED_ACTION,
21
23
  };
22
- // RN's invariant: return an Error (caller rejects rather than throws) so a bad call
23
- // can't unmount the tree on device. Shared because the rule is identical per platform.
24
- export function validateContent(content) {
25
- if (typeof content !== 'object' || content === null) {
26
- return new Error('Content to share must be a valid object');
27
- }
28
- if (typeof content.url !== 'string' && typeof content.message !== 'string') {
29
- return new Error('At least one of URL or message is required');
30
- }
31
- return null;
24
+ // Share.js's three shared invariants, checked before any promise exists on both platforms.
25
+ export function assertShareArgs(content, options) {
26
+ invariant(typeof content === 'object' && content !== null, 'Content to share must be a valid object');
27
+ invariant(typeof content.url === 'string' || typeof content.message === 'string', 'At least one of URL or message is required');
28
+ invariant(typeof options === 'object' && options !== null, 'Options must be a valid object');
32
29
  }
@@ -11,7 +11,7 @@
11
11
  // on device: show/hide + light/dark text survive the relayout without blanking.
12
12
  import { getNativeModule } from '../native-modules/index.js';
13
13
  import { dlog } from '../debug.js';
14
- import { processColor } from '../platform-color/index.js';
14
+ import { processColor } from '../platform-color';
15
15
  import { STATUS_BAR_MANAGER, } from './shared.js';
16
16
  // processColor returns `unknown` (its result is platform-dependent); narrow to the
17
17
  // number Fabric/native expects, like RN's invariant before setColor. A non-number
@@ -17,9 +17,10 @@ import { STATUS_BAR_MANAGER, STATIC_HIDE_TRANSITION, hideTransition, } from './s
17
17
  // __turboModuleProxy is installed. Non-enforcing: a declarative StatusBar must NOT crash the
18
18
  // whole render if the module can't resolve.
19
19
  //
20
- // Simplification vs RN: RN maintains a prop-merge stack so nested StatusBars compose
21
- // (deepest/last wins); we direct-apply a single component's props, which is correct for one
22
- // StatusBar and a fine first cut.
20
+ // TODO(rn-parity): each StatusBar applies its props directly. RN keeps a stack instead:
21
+ // `_propsStack` over `_defaultProps`, unmount restores the previous entry, one batched native
22
+ // update, public push/pop/replaceStackEntry, `showHideTransition`, and static setters that warn
23
+ // on the wrong platform. Port it for both platforms and all 5 adapters.
23
24
  export function applyStatusBarProps(props) {
24
25
  const { barStyle, hidden, animated = false, networkActivityIndicatorVisible, } = props;
25
26
  const manager = getNativeModule(STATUS_BAR_MANAGER);
@@ -0,0 +1,10 @@
1
+ export declare const ToastAndroid: {
2
+ SHORT: number;
3
+ LONG: number;
4
+ TOP: number;
5
+ BOTTOM: number;
6
+ CENTER: number;
7
+ show(message: string, duration: number): void;
8
+ showWithGravity(message: string, duration: number, gravity: number): void;
9
+ showWithGravityAndOffset(message: string, duration: number, gravity: number, xOffset: number, yOffset: number): void;
10
+ };
@@ -0,0 +1,108 @@
1
+ // ToastAndroid module: pops a brief Android system toast from JS. Mirrors RN's
2
+ // Libraries/Components/ToastAndroid/ToastAndroid.android.js: `show` takes a message
3
+ // and a duration; `showWithGravity` adds a layout gravity; `showWithGravityAndOffset`
4
+ // adds an x/y pixel offset. No Fabric view, pure JS->native.
5
+ //
6
+ // symbiote is iOS-first and this is an Android-only module. RN's iOS fallback
7
+ // (ToastAndroid.ios.js) throws "not supported"; we prefer a no-op + dlog so a
8
+ // cross-platform smoke never crashes when the native module is absent.
9
+ //
10
+ // The native contract is confirmed from RN's TurboModule spec at
11
+ // specs_DEPRECATED/modules/INativeToastAndroid.js:
12
+ // getConstants(): { SHORT, LONG, TOP, BOTTOM, CENTER }
13
+ // show(message: string, duration: number)
14
+ // showWithGravity(message: string, duration: number, gravity: number)
15
+ // showWithGravityAndOffset(message, duration, gravity, xOffset, yOffset)
16
+ import { dlog } from '../debug.js';
17
+ import { getNativeModule } from '../native-modules/index.js';
18
+ // The native module name RN registers this under. NOTE: this is the name the spec
19
+ // resolves via `TurboModuleRegistry.getEnforcing<Spec>('ToastAndroid')`, NOT the
20
+ // spec filename `INativeToastAndroid`. A module name is only provable on a real
21
+ // host (a headless fake answers to any name); this Android name is confirmed from
22
+ // RN source but not yet exercised on a device.
23
+ const TOAST_MODULE = 'ToastAndroid';
24
+ // Conventional RN values for SHORT/LONG/TOP/BOTTOM/CENTER. On a real device the
25
+ // numbers come from native getConstants() (resolved below); these are the fallbacks
26
+ // that keep the constants object populated headless and on a host without the module.
27
+ const FALLBACK_CONSTANTS = {
28
+ SHORT: 0,
29
+ LONG: 1,
30
+ TOP: 48,
31
+ BOTTOM: 80,
32
+ CENTER: 17,
33
+ };
34
+ // Lazily resolved so importing this module has no native side effect.
35
+ let toastModule;
36
+ function getModule() {
37
+ if (toastModule === undefined) {
38
+ toastModule = getNativeModule(TOAST_MODULE);
39
+ dlog(`ToastAndroid: ToastAndroid module ${toastModule ? 'resolved' : 'NOT resolved (null)'}`);
40
+ }
41
+ return toastModule;
42
+ }
43
+ // Read a single numeric constant from the native getConstants() payload, narrowing
44
+ // with a `typeof` guard (no cast) and falling back to the conventional RN value when
45
+ // the module or the key is absent.
46
+ function readConstant(raw, key) {
47
+ const value = raw[key];
48
+ return typeof value === 'number' ? value : FALLBACK_CONSTANTS[key];
49
+ }
50
+ // Resolve the constants once: real numbers from native on a device, fallbacks
51
+ // otherwise. Always returns a fully-populated object so the constants are never
52
+ // undefined, even headless.
53
+ function resolveConstants() {
54
+ const module = getModule();
55
+ if (module === null) {
56
+ dlog('ToastAndroid: constants from fallbacks (native module unavailable)');
57
+ return { ...FALLBACK_CONSTANTS };
58
+ }
59
+ const raw = module.getConstants();
60
+ return {
61
+ SHORT: readConstant(raw, 'SHORT'),
62
+ LONG: readConstant(raw, 'LONG'),
63
+ TOP: readConstant(raw, 'TOP'),
64
+ BOTTOM: readConstant(raw, 'BOTTOM'),
65
+ CENTER: readConstant(raw, 'CENTER'),
66
+ };
67
+ }
68
+ const constants = resolveConstants();
69
+ export const ToastAndroid = {
70
+ // Toast duration constants.
71
+ SHORT: constants.SHORT,
72
+ LONG: constants.LONG,
73
+ // Toast gravity constants.
74
+ TOP: constants.TOP,
75
+ BOTTOM: constants.BOTTOM,
76
+ CENTER: constants.CENTER,
77
+ // Show a toast with the given message and duration. Degrades to a no-op (logged)
78
+ // when the module is absent; a missing optional native module must never throw.
79
+ show(message, duration) {
80
+ const module = getModule();
81
+ if (module === null) {
82
+ dlog('ToastAndroid.show -> ToastAndroid native module unavailable, no-op');
83
+ return;
84
+ }
85
+ dlog(`ToastAndroid.show -> "${message}" (${duration})`);
86
+ module.show(message, duration);
87
+ },
88
+ // Show a toast at the given layout gravity (TOP / BOTTOM / CENTER).
89
+ showWithGravity(message, duration, gravity) {
90
+ const module = getModule();
91
+ if (module === null) {
92
+ dlog('ToastAndroid.showWithGravity -> ToastAndroid native module unavailable, no-op');
93
+ return;
94
+ }
95
+ dlog(`ToastAndroid.showWithGravity -> "${message}" (${duration}, gravity=${gravity})`);
96
+ module.showWithGravity(message, duration, gravity);
97
+ },
98
+ // Show a toast at the given gravity, offset by xOffset/yOffset pixels.
99
+ showWithGravityAndOffset(message, duration, gravity, xOffset, yOffset) {
100
+ const module = getModule();
101
+ if (module === null) {
102
+ dlog('ToastAndroid.showWithGravityAndOffset -> ToastAndroid native module unavailable, no-op');
103
+ return;
104
+ }
105
+ dlog(`ToastAndroid.showWithGravityAndOffset -> "${message}" (${duration}, gravity=${gravity}, ${xOffset}, ${yOffset})`);
106
+ module.showWithGravityAndOffset(message, duration, gravity, xOffset, yOffset);
107
+ },
108
+ };
@@ -1,10 +1 @@
1
- export declare const ToastAndroid: {
2
- SHORT: number;
3
- LONG: number;
4
- TOP: number;
5
- BOTTOM: number;
6
- CENTER: number;
7
- show(message: string, duration: number): void;
8
- showWithGravity(message: string, duration: number, gravity: number): void;
9
- showWithGravityAndOffset(message: string, duration: number, gravity: number, xOffset: number, yOffset: number): void;
10
- };
1
+ export * from './index.ios';
@@ -0,0 +1,10 @@
1
+ export declare const ToastAndroid: {
2
+ SHORT: number;
3
+ LONG: number;
4
+ TOP: number;
5
+ BOTTOM: number;
6
+ CENTER: number;
7
+ show(_message: string, _duration: number): void;
8
+ showWithGravity(_message: string, _duration: number, _gravity: number): void;
9
+ showWithGravityAndOffset(_message: string, _duration: number, _gravity: number, _xOffset: number, _yOffset: number): void;
10
+ };
@@ -0,0 +1,19 @@
1
+ // ToastAndroid off Android: RN's ToastAndroidFallback (ToastAndroid.ios.js). Zeroed constants and
2
+ // RN's own user-facing warning on every call; nothing reaches native.
3
+ const UNSUPPORTED = 'ToastAndroid is not supported on this platform.';
4
+ export const ToastAndroid = {
5
+ SHORT: 0,
6
+ LONG: 0,
7
+ TOP: 0,
8
+ BOTTOM: 0,
9
+ CENTER: 0,
10
+ show(_message, _duration) {
11
+ console.warn(UNSUPPORTED);
12
+ },
13
+ showWithGravity(_message, _duration, _gravity) {
14
+ console.warn(UNSUPPORTED);
15
+ },
16
+ showWithGravityAndOffset(_message, _duration, _gravity, _xOffset, _yOffset) {
17
+ console.warn(UNSUPPORTED);
18
+ },
19
+ };