@symbiote-native/engine 0.1.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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/build/accessibility-info/index.android.d.ts +3 -0
- package/build/accessibility-info/index.android.js +166 -0
- package/build/accessibility-info/index.d.ts +1 -0
- package/build/accessibility-info/index.ios.d.ts +3 -0
- package/build/accessibility-info/index.ios.js +219 -0
- package/build/accessibility-info/index.js +5 -0
- package/build/accessibility-info/shared.d.ts +34 -0
- package/build/accessibility-info/shared.js +13 -0
- package/build/action-sheet-ios/index.d.ts +36 -0
- package/build/action-sheet-ios/index.js +74 -0
- package/build/alert/index.android.d.ts +5 -0
- package/build/alert/index.android.js +117 -0
- package/build/alert/index.d.ts +1 -0
- package/build/alert/index.ios.d.ts +7 -0
- package/build/alert/index.ios.js +83 -0
- package/build/alert/index.js +8 -0
- package/build/alert/shared.d.ts +19 -0
- package/build/alert/shared.js +17 -0
- package/build/animated/animated-component-shared.d.ts +5 -0
- package/build/animated/animated-component-shared.js +54 -0
- package/build/animated/animation.d.ts +9 -0
- package/build/animated/animation.js +6 -0
- package/build/animated/animations/base.d.ts +27 -0
- package/build/animated/animations/base.js +90 -0
- package/build/animated/animations/composition.d.ts +38 -0
- package/build/animated/animations/composition.js +236 -0
- package/build/animated/animations/decay.d.ts +22 -0
- package/build/animated/animations/decay.js +65 -0
- package/build/animated/animations/raf.d.ts +5 -0
- package/build/animated/animations/raf.js +39 -0
- package/build/animated/animations/spring-config.d.ts +6 -0
- package/build/animated/animations/spring-config.js +55 -0
- package/build/animated/animations/spring.d.ts +50 -0
- package/build/animated/animations/spring.js +207 -0
- package/build/animated/animations/timing.d.ts +27 -0
- package/build/animated/animations/timing.js +101 -0
- package/build/animated/animations/tracking.d.ts +14 -0
- package/build/animated/animations/tracking.js +43 -0
- package/build/animated/bezier.d.ts +1 -0
- package/build/animated/bezier.js +101 -0
- package/build/animated/color.d.ts +37 -0
- package/build/animated/color.js +183 -0
- package/build/animated/easing.d.ts +20 -0
- package/build/animated/easing.js +96 -0
- package/build/animated/event.d.ts +36 -0
- package/build/animated/event.js +252 -0
- package/build/animated/graph.d.ts +38 -0
- package/build/animated/graph.js +227 -0
- package/build/animated/index.d.ts +20 -0
- package/build/animated/index.js +28 -0
- package/build/animated/interpolation-node.d.ts +16 -0
- package/build/animated/interpolation-node.js +57 -0
- package/build/animated/interpolation.d.ts +22 -0
- package/build/animated/interpolation.js +199 -0
- package/build/animated/mock.d.ts +56 -0
- package/build/animated/mock.js +127 -0
- package/build/animated/native/native-animated.d.ts +43 -0
- package/build/animated/native/native-animated.js +146 -0
- package/build/animated/operators.d.ts +80 -0
- package/build/animated/operators.js +266 -0
- package/build/animated/props.d.ts +20 -0
- package/build/animated/props.js +187 -0
- package/build/animated/style.d.ts +26 -0
- package/build/animated/style.js +187 -0
- package/build/animated/value-xy.d.ts +35 -0
- package/build/animated/value-xy.js +106 -0
- package/build/animated/value.d.ts +36 -0
- package/build/animated/value.js +185 -0
- package/build/app-registry/index.d.ts +40 -0
- package/build/app-registry/index.js +144 -0
- package/build/app-state/index.d.ts +16 -0
- package/build/app-state/index.js +105 -0
- package/build/appearance/index.d.ts +12 -0
- package/build/appearance/index.js +84 -0
- package/build/back-handler/index.d.ts +14 -0
- package/build/back-handler/index.js +106 -0
- package/build/commit.d.ts +16 -0
- package/build/commit.js +678 -0
- package/build/debug.d.ts +5 -0
- package/build/debug.js +18 -0
- package/build/dimensions/index.d.ts +28 -0
- package/build/dimensions/index.js +148 -0
- package/build/dispatch.d.ts +2 -0
- package/build/dispatch.js +18 -0
- package/build/events/index.d.ts +1 -0
- package/build/events/index.js +691 -0
- package/build/fabric.d.ts +32 -0
- package/build/fabric.js +59 -0
- package/build/host-instance/index.d.ts +11 -0
- package/build/host-instance/index.js +49 -0
- package/build/i18n-manager/index.d.ts +13 -0
- package/build/i18n-manager/index.js +91 -0
- package/build/index.d.ts +80 -0
- package/build/index.js +72 -0
- package/build/interaction-manager/index.d.ts +45 -0
- package/build/interaction-manager/index.js +222 -0
- package/build/keyboard/index.d.ts +31 -0
- package/build/keyboard/index.js +142 -0
- package/build/layout-animation/index.d.ts +66 -0
- package/build/layout-animation/index.js +183 -0
- package/build/linking/index.android.d.ts +2 -0
- package/build/linking/index.android.js +18 -0
- package/build/linking/index.d.ts +1 -0
- package/build/linking/index.ios.d.ts +2 -0
- package/build/linking/index.ios.js +9 -0
- package/build/linking/index.js +6 -0
- package/build/linking/shared.d.ts +32 -0
- package/build/linking/shared.js +98 -0
- package/build/native-events.d.ts +24 -0
- package/build/native-events.js +129 -0
- package/build/native-modules.d.ts +6 -0
- package/build/native-modules.js +57 -0
- package/build/node.d.ts +36 -0
- package/build/node.js +194 -0
- package/build/pan-responder/index.d.ts +53 -0
- package/build/pan-responder/index.js +353 -0
- package/build/permissions-android/index.d.ts +115 -0
- package/build/permissions-android/index.js +185 -0
- package/build/pixel-ratio/index.d.ts +8 -0
- package/build/pixel-ratio/index.js +27 -0
- package/build/platform/index.android.d.ts +22 -0
- package/build/platform/index.android.js +60 -0
- package/build/platform/index.d.ts +1 -0
- package/build/platform/index.ios.d.ts +18 -0
- package/build/platform/index.ios.js +62 -0
- package/build/platform/index.js +5 -0
- package/build/platform/shared.d.ts +25 -0
- package/build/platform/shared.js +41 -0
- package/build/platform-color.d.ts +19 -0
- package/build/platform-color.js +25 -0
- package/build/post-commit.d.ts +4 -0
- package/build/post-commit.js +16 -0
- package/build/process-aspect-ratio.d.ts +1 -0
- package/build/process-aspect-ratio.js +34 -0
- package/build/process-background-image/index.d.ts +28 -0
- package/build/process-background-image/index.js +557 -0
- package/build/process-box-shadow/index.d.ts +11 -0
- package/build/process-box-shadow/index.js +193 -0
- package/build/process-filter.d.ts +31 -0
- package/build/process-filter.js +304 -0
- package/build/process-font-variant.d.ts +1 -0
- package/build/process-font-variant.js +17 -0
- package/build/process-transform/index.d.ts +5 -0
- package/build/process-transform/index.js +120 -0
- package/build/process-transform-origin/index.d.ts +3 -0
- package/build/process-transform-origin/index.js +108 -0
- package/build/registry.d.ts +31 -0
- package/build/registry.js +145 -0
- package/build/settings/index.d.ts +8 -0
- package/build/settings/index.js +126 -0
- package/build/share/index.android.d.ts +3 -0
- package/build/share/index.android.js +56 -0
- package/build/share/index.d.ts +1 -0
- package/build/share/index.ios.d.ts +3 -0
- package/build/share/index.ios.js +47 -0
- package/build/share/index.js +6 -0
- package/build/share/shared.d.ts +32 -0
- package/build/share/shared.js +32 -0
- package/build/status-bar/index.android.d.ts +5 -0
- package/build/status-bar/index.android.js +83 -0
- package/build/status-bar/index.d.ts +1 -0
- package/build/status-bar/index.ios.d.ts +5 -0
- package/build/status-bar/index.ios.js +66 -0
- package/build/status-bar/index.js +4 -0
- package/build/status-bar/shared.d.ts +22 -0
- package/build/status-bar/shared.js +22 -0
- package/build/style/index.d.ts +1 -0
- package/build/style/index.js +30 -0
- package/build/style-registry/index.d.ts +11 -0
- package/build/style-registry/index.js +165 -0
- package/build/style-sheet/index.d.ts +20 -0
- package/build/style-sheet/index.js +121 -0
- package/build/styles.d.ts +220 -0
- package/build/styles.js +7 -0
- package/build/surface.d.ts +16 -0
- package/build/surface.js +67 -0
- package/build/tags.d.ts +1 -0
- package/build/tags.js +10 -0
- package/build/text-input-state.d.ts +5 -0
- package/build/text-input-state.js +29 -0
- package/build/toast-android/index.d.ts +10 -0
- package/build/toast-android/index.js +108 -0
- package/build/vibration/index.android.d.ts +2 -0
- package/build/vibration/index.android.js +18 -0
- package/build/vibration/index.d.ts +1 -0
- package/build/vibration/index.ios.d.ts +2 -0
- package/build/vibration/index.ios.js +54 -0
- package/build/vibration/index.js +6 -0
- package/build/vibration/shared.d.ts +15 -0
- package/build/vibration/shared.js +68 -0
- package/build/view-config.d.ts +1 -0
- package/build/view-config.js +114 -0
- package/package.json +41 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// PanResponder: pure JS gesture recognition layered on the View responder event
|
|
2
|
+
// props. It reconciles a stream of touch events into one accumulative gesture and
|
|
3
|
+
// exposes a `panHandlers` object the caller spreads onto a View. There is no new
|
|
4
|
+
// native view and no core change: it only consumes the responder props shared
|
|
5
|
+
// already synthesizes (onStartShouldSetResponder / onResponderGrant /
|
|
6
|
+
// onResponderMove / onResponderRelease / onResponderTerminate / ...), exactly
|
|
7
|
+
// as RN's PanResponder consumes them.
|
|
8
|
+
//
|
|
9
|
+
// Ported from react-native/Libraries/Interaction/PanResponder.js. RN sources its
|
|
10
|
+
// touch geometry from a global ResponderTouchHistoryStore; symbiote's synthetic
|
|
11
|
+
// events instead carry the live touches on `event.nativeEvent.touches` (and the
|
|
12
|
+
// changed ones on `changedTouches`), so the centroid/velocity math here reads
|
|
13
|
+
// those directly while keeping RN's accumulate-deltas-over-time behavior.
|
|
14
|
+
import { dlog } from '../debug';
|
|
15
|
+
const SINGLE_TOUCH_COUNT = 1;
|
|
16
|
+
// onShouldBlockNativeResponder defaults to true (RN: block native by default).
|
|
17
|
+
const DEFAULT_BLOCK_NATIVE_RESPONDER = true;
|
|
18
|
+
function isRecord(value) {
|
|
19
|
+
return typeof value === 'object' && value !== null;
|
|
20
|
+
}
|
|
21
|
+
function toFiniteNumber(value) {
|
|
22
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
// Pull one touch out of an untyped array element, skipping anything that does not
|
|
25
|
+
// carry numeric pageX/pageY. `timestamp` falls back to 0 when absent so a touch
|
|
26
|
+
// with coordinates but no time still contributes to the centroid.
|
|
27
|
+
function toTouchPoint(raw) {
|
|
28
|
+
if (!isRecord(raw))
|
|
29
|
+
return undefined;
|
|
30
|
+
const pageX = toFiniteNumber(raw.pageX);
|
|
31
|
+
const pageY = toFiniteNumber(raw.pageY);
|
|
32
|
+
if (pageX === undefined || pageY === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
return { pageX, pageY, timestamp: toFiniteNumber(raw.timestamp) ?? 0 };
|
|
35
|
+
}
|
|
36
|
+
// All current touches on screen, read off event.nativeEvent.touches.
|
|
37
|
+
function readTouches(event) {
|
|
38
|
+
const raw = event.nativeEvent.touches;
|
|
39
|
+
if (!Array.isArray(raw))
|
|
40
|
+
return [];
|
|
41
|
+
const points = [];
|
|
42
|
+
for (const entry of raw) {
|
|
43
|
+
const point = toTouchPoint(entry);
|
|
44
|
+
if (point !== undefined)
|
|
45
|
+
points.push(point);
|
|
46
|
+
}
|
|
47
|
+
return points;
|
|
48
|
+
}
|
|
49
|
+
// Mean of a coordinate across the active touches, the gesture centroid. RN's
|
|
50
|
+
// TouchHistoryMath does the same averaging over active touches.
|
|
51
|
+
function centroidX(touches) {
|
|
52
|
+
if (touches.length === 0)
|
|
53
|
+
return 0;
|
|
54
|
+
let sum = 0;
|
|
55
|
+
for (const touch of touches)
|
|
56
|
+
sum += touch.pageX;
|
|
57
|
+
return sum / touches.length;
|
|
58
|
+
}
|
|
59
|
+
function centroidY(touches) {
|
|
60
|
+
if (touches.length === 0)
|
|
61
|
+
return 0;
|
|
62
|
+
let sum = 0;
|
|
63
|
+
for (const touch of touches)
|
|
64
|
+
sum += touch.pageY;
|
|
65
|
+
return sum / touches.length;
|
|
66
|
+
}
|
|
67
|
+
// The most recent touch timestamp in the current frame, the clock PanResponder
|
|
68
|
+
// advances `_accountsForMovesUpTo` to and divides the velocity by.
|
|
69
|
+
function mostRecentTimestamp(touches) {
|
|
70
|
+
let latest = 0;
|
|
71
|
+
for (const touch of touches) {
|
|
72
|
+
if (touch.timestamp > latest)
|
|
73
|
+
latest = touch.timestamp;
|
|
74
|
+
}
|
|
75
|
+
return latest;
|
|
76
|
+
}
|
|
77
|
+
// #region touchHistory (RN-faithful multitouch geometry)
|
|
78
|
+
// shared attaches a touch-history bank onto nativeEvent (matching RN's
|
|
79
|
+
// ResponderEventPlugin). When present, the gesture math runs through RN's
|
|
80
|
+
// TouchHistoryMath instead of the all-touch centroid, so dx/vx counts only the touches
|
|
81
|
+
// that moved this frame, via each touch's own previous->current delta. Headless callers
|
|
82
|
+
// that invoke the handlers directly (no shared, no store) carry no touchHistory and fall
|
|
83
|
+
// back to the centroid path below, which keeps single-touch behavior.
|
|
84
|
+
function isTouchRecord(value) {
|
|
85
|
+
return (isRecord(value) &&
|
|
86
|
+
typeof value.touchActive === 'boolean' &&
|
|
87
|
+
typeof value.currentPageX === 'number' &&
|
|
88
|
+
typeof value.currentPageY === 'number' &&
|
|
89
|
+
typeof value.currentTimeStamp === 'number' &&
|
|
90
|
+
typeof value.previousPageX === 'number' &&
|
|
91
|
+
typeof value.previousPageY === 'number');
|
|
92
|
+
}
|
|
93
|
+
function isTouchHistory(value) {
|
|
94
|
+
return (isRecord(value) &&
|
|
95
|
+
Array.isArray(value.touchBank) &&
|
|
96
|
+
typeof value.numberActiveTouches === 'number' &&
|
|
97
|
+
typeof value.indexOfSingleActiveTouch === 'number' &&
|
|
98
|
+
typeof value.mostRecentTimeStamp === 'number');
|
|
99
|
+
}
|
|
100
|
+
function touchHistoryOf(event) {
|
|
101
|
+
const raw = event.nativeEvent.touchHistory;
|
|
102
|
+
return isTouchHistory(raw) ? raw : undefined;
|
|
103
|
+
}
|
|
104
|
+
// Ported from RN Interaction/TouchHistoryMath.js:centroidDimension (lines 30-85). Mean
|
|
105
|
+
// of one coordinate over the touches that moved after `touchesChangedAfter`, taking each
|
|
106
|
+
// touch's current or previous position. The single-active-touch fast path uses a strict
|
|
107
|
+
// `>`; the multi-touch scan uses `>=`, both kept from RN.
|
|
108
|
+
function centroidDimension(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
|
|
109
|
+
const { touchBank } = touchHistory;
|
|
110
|
+
let total = 0;
|
|
111
|
+
let count = 0;
|
|
112
|
+
const single = touchHistory.numberActiveTouches === SINGLE_TOUCH_COUNT
|
|
113
|
+
? touchBank[touchHistory.indexOfSingleActiveTouch]
|
|
114
|
+
: undefined;
|
|
115
|
+
if (isTouchRecord(single)) {
|
|
116
|
+
if (single.touchActive && single.currentTimeStamp > touchesChangedAfter) {
|
|
117
|
+
total += dimensionOf(single, isXAxis, ofCurrent);
|
|
118
|
+
count = 1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
for (const record of touchBank) {
|
|
123
|
+
if (isTouchRecord(record) &&
|
|
124
|
+
record.touchActive &&
|
|
125
|
+
record.currentTimeStamp >= touchesChangedAfter) {
|
|
126
|
+
total += dimensionOf(record, isXAxis, ofCurrent);
|
|
127
|
+
count++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return count > 0 ? total / count : NO_CENTROID;
|
|
132
|
+
}
|
|
133
|
+
const NO_CENTROID = -1;
|
|
134
|
+
function dimensionOf(record, isXAxis, ofCurrent) {
|
|
135
|
+
if (ofCurrent)
|
|
136
|
+
return isXAxis ? record.currentPageX : record.currentPageY;
|
|
137
|
+
return isXAxis ? record.previousPageX : record.previousPageY;
|
|
138
|
+
}
|
|
139
|
+
function currentCentroidXOfChanged(touchHistory, after) {
|
|
140
|
+
return centroidDimension(touchHistory, after, true, true);
|
|
141
|
+
}
|
|
142
|
+
function currentCentroidYOfChanged(touchHistory, after) {
|
|
143
|
+
return centroidDimension(touchHistory, after, false, true);
|
|
144
|
+
}
|
|
145
|
+
function previousCentroidXOfChanged(touchHistory, after) {
|
|
146
|
+
return centroidDimension(touchHistory, after, true, false);
|
|
147
|
+
}
|
|
148
|
+
function previousCentroidYOfChanged(touchHistory, after) {
|
|
149
|
+
return centroidDimension(touchHistory, after, false, false);
|
|
150
|
+
}
|
|
151
|
+
function currentCentroidXAll(touchHistory) {
|
|
152
|
+
return centroidDimension(touchHistory, 0, true, true);
|
|
153
|
+
}
|
|
154
|
+
function currentCentroidYAll(touchHistory) {
|
|
155
|
+
return centroidDimension(touchHistory, 0, false, true);
|
|
156
|
+
}
|
|
157
|
+
// RN PanResponder._updateGestureStateOnMove (Interaction/PanResponder.js lines 330-366):
|
|
158
|
+
// accumulate the centroid change of touches that moved after `_accountsForMovesUpTo`,
|
|
159
|
+
// rather than tracking the absolute centroid of all touches. This is what makes a
|
|
160
|
+
// stopped finger in a multi-finger drag stop contributing to dx.
|
|
161
|
+
function updateGestureStateFromHistory(gestureState, touchHistory) {
|
|
162
|
+
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
|
163
|
+
const movedAfter = gestureState._accountsForMovesUpTo;
|
|
164
|
+
gestureState.moveX = currentCentroidXOfChanged(touchHistory, movedAfter);
|
|
165
|
+
gestureState.moveY = currentCentroidYOfChanged(touchHistory, movedAfter);
|
|
166
|
+
const prevX = previousCentroidXOfChanged(touchHistory, movedAfter);
|
|
167
|
+
const x = currentCentroidXOfChanged(touchHistory, movedAfter);
|
|
168
|
+
const prevY = previousCentroidYOfChanged(touchHistory, movedAfter);
|
|
169
|
+
const y = currentCentroidYOfChanged(touchHistory, movedAfter);
|
|
170
|
+
const nextDx = gestureState.dx + (x - prevX);
|
|
171
|
+
const nextDy = gestureState.dy + (y - prevY);
|
|
172
|
+
const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo;
|
|
173
|
+
if (dt > 0) {
|
|
174
|
+
gestureState.vx = (nextDx - gestureState.dx) / dt;
|
|
175
|
+
gestureState.vy = (nextDy - gestureState.dy) / dt;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
gestureState.vx = 0;
|
|
179
|
+
gestureState.vy = 0;
|
|
180
|
+
}
|
|
181
|
+
gestureState.dx = nextDx;
|
|
182
|
+
gestureState.dy = nextDy;
|
|
183
|
+
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
|
|
184
|
+
}
|
|
185
|
+
// #endregion
|
|
186
|
+
function initializeGestureState(gestureState) {
|
|
187
|
+
gestureState.moveX = 0;
|
|
188
|
+
gestureState.moveY = 0;
|
|
189
|
+
gestureState.x0 = 0;
|
|
190
|
+
gestureState.y0 = 0;
|
|
191
|
+
gestureState.dx = 0;
|
|
192
|
+
gestureState.dy = 0;
|
|
193
|
+
gestureState.vx = 0;
|
|
194
|
+
gestureState.vy = 0;
|
|
195
|
+
gestureState.numberActiveTouches = 0;
|
|
196
|
+
gestureState._accountsForMovesUpTo = 0;
|
|
197
|
+
}
|
|
198
|
+
// The timestamp this frame advances to: the touch-history clock when shared attached a
|
|
199
|
+
// store, else the most recent of the live touches (headless direct-call path).
|
|
200
|
+
function frameTimestampOf(event, touches) {
|
|
201
|
+
return touchHistoryOf(event)?.mostRecentTimeStamp ?? mostRecentTimestamp(touches);
|
|
202
|
+
}
|
|
203
|
+
// Advance the gesture for a move frame. With a touch-history store (the device / shared
|
|
204
|
+
// path) this defers to RN's accumulate-per-moved-touch math; without one it falls back to
|
|
205
|
+
// the all-touch centroid delta, correct for the single dragging finger the headless
|
|
206
|
+
// pan-responder smoke exercises. Guards dt === 0 so a zero-gap frame reports zero
|
|
207
|
+
// velocity instead of NaN.
|
|
208
|
+
function updateGestureStateOnMove(gestureState, event, touches) {
|
|
209
|
+
const touchHistory = touchHistoryOf(event);
|
|
210
|
+
if (touchHistory !== undefined) {
|
|
211
|
+
updateGestureStateFromHistory(gestureState, touchHistory);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const currentX = centroidX(touches);
|
|
215
|
+
const currentY = centroidY(touches);
|
|
216
|
+
const frameTimestamp = mostRecentTimestamp(touches);
|
|
217
|
+
gestureState.numberActiveTouches = touches.length;
|
|
218
|
+
gestureState.moveX = currentX;
|
|
219
|
+
gestureState.moveY = currentY;
|
|
220
|
+
const nextDx = currentX - gestureState.x0;
|
|
221
|
+
const nextDy = currentY - gestureState.y0;
|
|
222
|
+
const dt = frameTimestamp - gestureState._accountsForMovesUpTo;
|
|
223
|
+
if (dt > 0) {
|
|
224
|
+
gestureState.vx = (nextDx - gestureState.dx) / dt;
|
|
225
|
+
gestureState.vy = (nextDy - gestureState.dy) / dt;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
gestureState.vx = 0;
|
|
229
|
+
gestureState.vy = 0;
|
|
230
|
+
}
|
|
231
|
+
gestureState.dx = nextDx;
|
|
232
|
+
gestureState.dy = nextDy;
|
|
233
|
+
gestureState._accountsForMovesUpTo = frameTimestamp;
|
|
234
|
+
}
|
|
235
|
+
const PanResponder = {
|
|
236
|
+
create(config) {
|
|
237
|
+
const gestureState = {
|
|
238
|
+
// Random per-gesture id, matching RN. Useful only for debugging.
|
|
239
|
+
stateID: Math.random(),
|
|
240
|
+
moveX: 0,
|
|
241
|
+
moveY: 0,
|
|
242
|
+
x0: 0,
|
|
243
|
+
y0: 0,
|
|
244
|
+
dx: 0,
|
|
245
|
+
dy: 0,
|
|
246
|
+
vx: 0,
|
|
247
|
+
vy: 0,
|
|
248
|
+
numberActiveTouches: 0,
|
|
249
|
+
_accountsForMovesUpTo: 0,
|
|
250
|
+
};
|
|
251
|
+
const panHandlers = {
|
|
252
|
+
onStartShouldSetResponder(event) {
|
|
253
|
+
return config.onStartShouldSetPanResponder === undefined
|
|
254
|
+
? false
|
|
255
|
+
: config.onStartShouldSetPanResponder(event, gestureState);
|
|
256
|
+
},
|
|
257
|
+
onMoveShouldSetResponder(event) {
|
|
258
|
+
return config.onMoveShouldSetPanResponder === undefined
|
|
259
|
+
? false
|
|
260
|
+
: config.onMoveShouldSetPanResponder(event, gestureState);
|
|
261
|
+
},
|
|
262
|
+
onStartShouldSetResponderCapture(event) {
|
|
263
|
+
// A fresh single touch begins a new gesture, so reset the accumulator
|
|
264
|
+
// before any should-set callback inspects it (RN does the same).
|
|
265
|
+
const touches = readTouches(event);
|
|
266
|
+
if (touches.length === SINGLE_TOUCH_COUNT) {
|
|
267
|
+
initializeGestureState(gestureState);
|
|
268
|
+
}
|
|
269
|
+
gestureState.numberActiveTouches =
|
|
270
|
+
touchHistoryOf(event)?.numberActiveTouches ?? touches.length;
|
|
271
|
+
return config.onStartShouldSetPanResponderCapture === undefined
|
|
272
|
+
? false
|
|
273
|
+
: config.onStartShouldSetPanResponderCapture(event, gestureState);
|
|
274
|
+
},
|
|
275
|
+
onMoveShouldSetResponderCapture(event) {
|
|
276
|
+
const touches = readTouches(event);
|
|
277
|
+
// Skip a duplicate dispatch of the same frame: when two touches change at
|
|
278
|
+
// once the responder system fires twice, but the geometry was already
|
|
279
|
+
// folded in on the first call.
|
|
280
|
+
if (gestureState._accountsForMovesUpTo === frameTimestampOf(event, touches)) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
updateGestureStateOnMove(gestureState, event, touches);
|
|
284
|
+
return config.onMoveShouldSetPanResponderCapture === undefined
|
|
285
|
+
? false
|
|
286
|
+
: config.onMoveShouldSetPanResponderCapture(event, gestureState);
|
|
287
|
+
},
|
|
288
|
+
onResponderGrant(event) {
|
|
289
|
+
dlog('PanResponder grant');
|
|
290
|
+
const touches = readTouches(event);
|
|
291
|
+
const touchHistory = touchHistoryOf(event);
|
|
292
|
+
// x0/y0 is the non-cumulative centroid at grant time (RN: currentCentroid).
|
|
293
|
+
gestureState.x0 = touchHistory ? currentCentroidXAll(touchHistory) : centroidX(touches);
|
|
294
|
+
gestureState.y0 = touchHistory ? currentCentroidYAll(touchHistory) : centroidY(touches);
|
|
295
|
+
gestureState.dx = 0;
|
|
296
|
+
gestureState.dy = 0;
|
|
297
|
+
// The grant frame is already accounted for, so the first move's velocity
|
|
298
|
+
// is measured from here, not from time 0.
|
|
299
|
+
gestureState._accountsForMovesUpTo = frameTimestampOf(event, touches);
|
|
300
|
+
gestureState.numberActiveTouches = touchHistory?.numberActiveTouches ?? touches.length;
|
|
301
|
+
config.onPanResponderGrant?.(event, gestureState);
|
|
302
|
+
return config.onShouldBlockNativeResponder === undefined
|
|
303
|
+
? DEFAULT_BLOCK_NATIVE_RESPONDER
|
|
304
|
+
: config.onShouldBlockNativeResponder(event, gestureState);
|
|
305
|
+
},
|
|
306
|
+
onResponderReject(event) {
|
|
307
|
+
config.onPanResponderReject?.(event, gestureState);
|
|
308
|
+
},
|
|
309
|
+
onResponderStart(event) {
|
|
310
|
+
gestureState.numberActiveTouches =
|
|
311
|
+
touchHistoryOf(event)?.numberActiveTouches ?? readTouches(event).length;
|
|
312
|
+
config.onPanResponderStart?.(event, gestureState);
|
|
313
|
+
},
|
|
314
|
+
onResponderMove(event) {
|
|
315
|
+
const touches = readTouches(event);
|
|
316
|
+
// Same duplicate-frame guard as the capture path.
|
|
317
|
+
if (gestureState._accountsForMovesUpTo === frameTimestampOf(event, touches)) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
updateGestureStateOnMove(gestureState, event, touches);
|
|
321
|
+
config.onPanResponderMove?.(event, gestureState);
|
|
322
|
+
},
|
|
323
|
+
onResponderEnd(event) {
|
|
324
|
+
gestureState.numberActiveTouches =
|
|
325
|
+
touchHistoryOf(event)?.numberActiveTouches ?? readTouches(event).length;
|
|
326
|
+
config.onPanResponderEnd?.(event, gestureState);
|
|
327
|
+
},
|
|
328
|
+
onResponderRelease(event) {
|
|
329
|
+
dlog('PanResponder release');
|
|
330
|
+
config.onPanResponderRelease?.(event, gestureState);
|
|
331
|
+
initializeGestureState(gestureState);
|
|
332
|
+
},
|
|
333
|
+
onResponderTerminate(event) {
|
|
334
|
+
dlog('PanResponder terminate');
|
|
335
|
+
config.onPanResponderTerminate?.(event, gestureState);
|
|
336
|
+
initializeGestureState(gestureState);
|
|
337
|
+
},
|
|
338
|
+
onResponderTerminationRequest(event) {
|
|
339
|
+
return config.onPanResponderTerminationRequest === undefined
|
|
340
|
+
? true
|
|
341
|
+
: config.onPanResponderTerminationRequest(event, gestureState);
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
return {
|
|
345
|
+
panHandlers,
|
|
346
|
+
// Deprecated in RN; kept for shape parity. No InteractionManager handle.
|
|
347
|
+
getInteractionHandle() {
|
|
348
|
+
return null;
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
export default PanResponder;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export declare const RESULTS: Readonly<{
|
|
2
|
+
readonly GRANTED: "granted";
|
|
3
|
+
readonly DENIED: "denied";
|
|
4
|
+
readonly NEVER_ASK_AGAIN: "never_ask_again";
|
|
5
|
+
}>;
|
|
6
|
+
export type IPermissionStatus = (typeof RESULTS)[keyof typeof RESULTS];
|
|
7
|
+
export declare const PERMISSIONS: Readonly<{
|
|
8
|
+
readonly READ_CALENDAR: "android.permission.READ_CALENDAR";
|
|
9
|
+
readonly WRITE_CALENDAR: "android.permission.WRITE_CALENDAR";
|
|
10
|
+
readonly CAMERA: "android.permission.CAMERA";
|
|
11
|
+
readonly READ_CONTACTS: "android.permission.READ_CONTACTS";
|
|
12
|
+
readonly WRITE_CONTACTS: "android.permission.WRITE_CONTACTS";
|
|
13
|
+
readonly GET_ACCOUNTS: "android.permission.GET_ACCOUNTS";
|
|
14
|
+
readonly ACCESS_FINE_LOCATION: "android.permission.ACCESS_FINE_LOCATION";
|
|
15
|
+
readonly ACCESS_COARSE_LOCATION: "android.permission.ACCESS_COARSE_LOCATION";
|
|
16
|
+
readonly ACCESS_BACKGROUND_LOCATION: "android.permission.ACCESS_BACKGROUND_LOCATION";
|
|
17
|
+
readonly RECORD_AUDIO: "android.permission.RECORD_AUDIO";
|
|
18
|
+
readonly READ_PHONE_STATE: "android.permission.READ_PHONE_STATE";
|
|
19
|
+
readonly CALL_PHONE: "android.permission.CALL_PHONE";
|
|
20
|
+
readonly READ_CALL_LOG: "android.permission.READ_CALL_LOG";
|
|
21
|
+
readonly WRITE_CALL_LOG: "android.permission.WRITE_CALL_LOG";
|
|
22
|
+
readonly ADD_VOICEMAIL: "com.android.voicemail.permission.ADD_VOICEMAIL";
|
|
23
|
+
readonly READ_VOICEMAIL: "com.android.voicemail.permission.READ_VOICEMAIL";
|
|
24
|
+
readonly WRITE_VOICEMAIL: "com.android.voicemail.permission.WRITE_VOICEMAIL";
|
|
25
|
+
readonly USE_SIP: "android.permission.USE_SIP";
|
|
26
|
+
readonly PROCESS_OUTGOING_CALLS: "android.permission.PROCESS_OUTGOING_CALLS";
|
|
27
|
+
readonly BODY_SENSORS: "android.permission.BODY_SENSORS";
|
|
28
|
+
readonly BODY_SENSORS_BACKGROUND: "android.permission.BODY_SENSORS_BACKGROUND";
|
|
29
|
+
readonly SEND_SMS: "android.permission.SEND_SMS";
|
|
30
|
+
readonly RECEIVE_SMS: "android.permission.RECEIVE_SMS";
|
|
31
|
+
readonly READ_SMS: "android.permission.READ_SMS";
|
|
32
|
+
readonly RECEIVE_WAP_PUSH: "android.permission.RECEIVE_WAP_PUSH";
|
|
33
|
+
readonly RECEIVE_MMS: "android.permission.RECEIVE_MMS";
|
|
34
|
+
readonly READ_EXTERNAL_STORAGE: "android.permission.READ_EXTERNAL_STORAGE";
|
|
35
|
+
readonly READ_MEDIA_IMAGES: "android.permission.READ_MEDIA_IMAGES";
|
|
36
|
+
readonly READ_MEDIA_VIDEO: "android.permission.READ_MEDIA_VIDEO";
|
|
37
|
+
readonly READ_MEDIA_AUDIO: "android.permission.READ_MEDIA_AUDIO";
|
|
38
|
+
readonly READ_MEDIA_VISUAL_USER_SELECTED: "android.permission.READ_MEDIA_VISUAL_USER_SELECTED";
|
|
39
|
+
readonly WRITE_EXTERNAL_STORAGE: "android.permission.WRITE_EXTERNAL_STORAGE";
|
|
40
|
+
readonly BLUETOOTH_CONNECT: "android.permission.BLUETOOTH_CONNECT";
|
|
41
|
+
readonly BLUETOOTH_SCAN: "android.permission.BLUETOOTH_SCAN";
|
|
42
|
+
readonly BLUETOOTH_ADVERTISE: "android.permission.BLUETOOTH_ADVERTISE";
|
|
43
|
+
readonly ACCESS_MEDIA_LOCATION: "android.permission.ACCESS_MEDIA_LOCATION";
|
|
44
|
+
readonly ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER";
|
|
45
|
+
readonly ACTIVITY_RECOGNITION: "android.permission.ACTIVITY_RECOGNITION";
|
|
46
|
+
readonly ANSWER_PHONE_CALLS: "android.permission.ANSWER_PHONE_CALLS";
|
|
47
|
+
readonly READ_PHONE_NUMBERS: "android.permission.READ_PHONE_NUMBERS";
|
|
48
|
+
readonly UWB_RANGING: "android.permission.UWB_RANGING";
|
|
49
|
+
readonly POST_NOTIFICATIONS: "android.permission.POST_NOTIFICATIONS";
|
|
50
|
+
readonly NEARBY_WIFI_DEVICES: "android.permission.NEARBY_WIFI_DEVICES";
|
|
51
|
+
}>;
|
|
52
|
+
export type IPermission = (typeof PERMISSIONS)[keyof typeof PERMISSIONS];
|
|
53
|
+
export type IRationale = {
|
|
54
|
+
title: string;
|
|
55
|
+
message: string;
|
|
56
|
+
buttonPositive?: string;
|
|
57
|
+
buttonNegative?: string;
|
|
58
|
+
buttonNeutral?: string;
|
|
59
|
+
};
|
|
60
|
+
export declare const PermissionsAndroid: {
|
|
61
|
+
PERMISSIONS: Readonly<{
|
|
62
|
+
readonly READ_CALENDAR: "android.permission.READ_CALENDAR";
|
|
63
|
+
readonly WRITE_CALENDAR: "android.permission.WRITE_CALENDAR";
|
|
64
|
+
readonly CAMERA: "android.permission.CAMERA";
|
|
65
|
+
readonly READ_CONTACTS: "android.permission.READ_CONTACTS";
|
|
66
|
+
readonly WRITE_CONTACTS: "android.permission.WRITE_CONTACTS";
|
|
67
|
+
readonly GET_ACCOUNTS: "android.permission.GET_ACCOUNTS";
|
|
68
|
+
readonly ACCESS_FINE_LOCATION: "android.permission.ACCESS_FINE_LOCATION";
|
|
69
|
+
readonly ACCESS_COARSE_LOCATION: "android.permission.ACCESS_COARSE_LOCATION";
|
|
70
|
+
readonly ACCESS_BACKGROUND_LOCATION: "android.permission.ACCESS_BACKGROUND_LOCATION";
|
|
71
|
+
readonly RECORD_AUDIO: "android.permission.RECORD_AUDIO";
|
|
72
|
+
readonly READ_PHONE_STATE: "android.permission.READ_PHONE_STATE";
|
|
73
|
+
readonly CALL_PHONE: "android.permission.CALL_PHONE";
|
|
74
|
+
readonly READ_CALL_LOG: "android.permission.READ_CALL_LOG";
|
|
75
|
+
readonly WRITE_CALL_LOG: "android.permission.WRITE_CALL_LOG";
|
|
76
|
+
readonly ADD_VOICEMAIL: "com.android.voicemail.permission.ADD_VOICEMAIL";
|
|
77
|
+
readonly READ_VOICEMAIL: "com.android.voicemail.permission.READ_VOICEMAIL";
|
|
78
|
+
readonly WRITE_VOICEMAIL: "com.android.voicemail.permission.WRITE_VOICEMAIL";
|
|
79
|
+
readonly USE_SIP: "android.permission.USE_SIP";
|
|
80
|
+
readonly PROCESS_OUTGOING_CALLS: "android.permission.PROCESS_OUTGOING_CALLS";
|
|
81
|
+
readonly BODY_SENSORS: "android.permission.BODY_SENSORS";
|
|
82
|
+
readonly BODY_SENSORS_BACKGROUND: "android.permission.BODY_SENSORS_BACKGROUND";
|
|
83
|
+
readonly SEND_SMS: "android.permission.SEND_SMS";
|
|
84
|
+
readonly RECEIVE_SMS: "android.permission.RECEIVE_SMS";
|
|
85
|
+
readonly READ_SMS: "android.permission.READ_SMS";
|
|
86
|
+
readonly RECEIVE_WAP_PUSH: "android.permission.RECEIVE_WAP_PUSH";
|
|
87
|
+
readonly RECEIVE_MMS: "android.permission.RECEIVE_MMS";
|
|
88
|
+
readonly READ_EXTERNAL_STORAGE: "android.permission.READ_EXTERNAL_STORAGE";
|
|
89
|
+
readonly READ_MEDIA_IMAGES: "android.permission.READ_MEDIA_IMAGES";
|
|
90
|
+
readonly READ_MEDIA_VIDEO: "android.permission.READ_MEDIA_VIDEO";
|
|
91
|
+
readonly READ_MEDIA_AUDIO: "android.permission.READ_MEDIA_AUDIO";
|
|
92
|
+
readonly READ_MEDIA_VISUAL_USER_SELECTED: "android.permission.READ_MEDIA_VISUAL_USER_SELECTED";
|
|
93
|
+
readonly WRITE_EXTERNAL_STORAGE: "android.permission.WRITE_EXTERNAL_STORAGE";
|
|
94
|
+
readonly BLUETOOTH_CONNECT: "android.permission.BLUETOOTH_CONNECT";
|
|
95
|
+
readonly BLUETOOTH_SCAN: "android.permission.BLUETOOTH_SCAN";
|
|
96
|
+
readonly BLUETOOTH_ADVERTISE: "android.permission.BLUETOOTH_ADVERTISE";
|
|
97
|
+
readonly ACCESS_MEDIA_LOCATION: "android.permission.ACCESS_MEDIA_LOCATION";
|
|
98
|
+
readonly ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER";
|
|
99
|
+
readonly ACTIVITY_RECOGNITION: "android.permission.ACTIVITY_RECOGNITION";
|
|
100
|
+
readonly ANSWER_PHONE_CALLS: "android.permission.ANSWER_PHONE_CALLS";
|
|
101
|
+
readonly READ_PHONE_NUMBERS: "android.permission.READ_PHONE_NUMBERS";
|
|
102
|
+
readonly UWB_RANGING: "android.permission.UWB_RANGING";
|
|
103
|
+
readonly POST_NOTIFICATIONS: "android.permission.POST_NOTIFICATIONS";
|
|
104
|
+
readonly NEARBY_WIFI_DEVICES: "android.permission.NEARBY_WIFI_DEVICES";
|
|
105
|
+
}>;
|
|
106
|
+
RESULTS: Readonly<{
|
|
107
|
+
readonly GRANTED: "granted";
|
|
108
|
+
readonly DENIED: "denied";
|
|
109
|
+
readonly NEVER_ASK_AGAIN: "never_ask_again";
|
|
110
|
+
}>;
|
|
111
|
+
check(permission: IPermission): Promise<boolean>;
|
|
112
|
+
request(permission: IPermission, rationale?: IRationale): Promise<IPermissionStatus>;
|
|
113
|
+
requestMultiple(permissions: IPermission[]): Promise<Record<string, IPermissionStatus>>;
|
|
114
|
+
shouldShowRequestPermissionRationale(permission: IPermission): Promise<boolean>;
|
|
115
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// PermissionsAndroid module: JS wrapper over Android's runtime-permissions
|
|
2
|
+
// model. Mirrors RN's Libraries/PermissionsAndroid/PermissionsAndroid.js,
|
|
3
|
+
// exposing the modern API: check / request / requestMultiple /
|
|
4
|
+
// shouldShowRequestPermissionRationale, plus the frozen PERMISSIONS and RESULTS
|
|
5
|
+
// constant maps. Imperative JS->native calls with no Fabric view of their own,
|
|
6
|
+
// like Vibration / Keyboard.
|
|
7
|
+
//
|
|
8
|
+
// symbiote is iOS-first and this module is Android-only, so it MUST degrade
|
|
9
|
+
// gracefully when the native module is absent (on iOS, or headless). RN throws
|
|
10
|
+
// "works only for Android"; we instead resolve a safe default + dlog so a
|
|
11
|
+
// cross-platform smoke never hard-crashes the process. Never reject/throw
|
|
12
|
+
// synchronously on a missing module.
|
|
13
|
+
//
|
|
14
|
+
// The native contract is confirmed from RN's TurboModule spec at
|
|
15
|
+
// specs_DEPRECATED/modules/INativePermissionsAndroid.js (`'PermissionsAndroid'`):
|
|
16
|
+
// checkPermission(permission): Promise<boolean>
|
|
17
|
+
// requestPermission(permission): Promise<string>
|
|
18
|
+
// shouldShowRequestPermissionRationale(permission): Promise<boolean>
|
|
19
|
+
// requestMultiplePermissions(permissions): Promise<{[permission]: string}>
|
|
20
|
+
import { getNativeModule } from '../native-modules';
|
|
21
|
+
import { dlog } from '../debug';
|
|
22
|
+
// The native module name RN registers this TurboModule under
|
|
23
|
+
// (`TurboModuleRegistry.get<Spec>('PermissionsAndroid')`). NOTE: per the
|
|
24
|
+
// symbiote invariant, a module name is only provable on a real host (a headless
|
|
25
|
+
// fake answers to any name); this name is device-verify-pending. See
|
|
26
|
+
// .docs/native-module-platform-routing.md.
|
|
27
|
+
const PERMISSIONS_ANDROID_MODULE = 'PermissionsAndroid';
|
|
28
|
+
// The runtime-permission result strings Android can return. Modeled as a frozen
|
|
29
|
+
// `as const` map: the generated-style constant-map exception to "no magic
|
|
30
|
+
// strings".
|
|
31
|
+
export const RESULTS = Object.freeze({
|
|
32
|
+
GRANTED: 'granted',
|
|
33
|
+
DENIED: 'denied',
|
|
34
|
+
NEVER_ASK_AGAIN: 'never_ask_again',
|
|
35
|
+
});
|
|
36
|
+
// The full Android permission-string map, copied verbatim from RN's source.
|
|
37
|
+
// Frozen `as const` constant map (the same string-map exception as RESULTS).
|
|
38
|
+
export const PERMISSIONS = Object.freeze({
|
|
39
|
+
READ_CALENDAR: 'android.permission.READ_CALENDAR',
|
|
40
|
+
WRITE_CALENDAR: 'android.permission.WRITE_CALENDAR',
|
|
41
|
+
CAMERA: 'android.permission.CAMERA',
|
|
42
|
+
READ_CONTACTS: 'android.permission.READ_CONTACTS',
|
|
43
|
+
WRITE_CONTACTS: 'android.permission.WRITE_CONTACTS',
|
|
44
|
+
GET_ACCOUNTS: 'android.permission.GET_ACCOUNTS',
|
|
45
|
+
ACCESS_FINE_LOCATION: 'android.permission.ACCESS_FINE_LOCATION',
|
|
46
|
+
ACCESS_COARSE_LOCATION: 'android.permission.ACCESS_COARSE_LOCATION',
|
|
47
|
+
ACCESS_BACKGROUND_LOCATION: 'android.permission.ACCESS_BACKGROUND_LOCATION',
|
|
48
|
+
RECORD_AUDIO: 'android.permission.RECORD_AUDIO',
|
|
49
|
+
READ_PHONE_STATE: 'android.permission.READ_PHONE_STATE',
|
|
50
|
+
CALL_PHONE: 'android.permission.CALL_PHONE',
|
|
51
|
+
READ_CALL_LOG: 'android.permission.READ_CALL_LOG',
|
|
52
|
+
WRITE_CALL_LOG: 'android.permission.WRITE_CALL_LOG',
|
|
53
|
+
ADD_VOICEMAIL: 'com.android.voicemail.permission.ADD_VOICEMAIL',
|
|
54
|
+
READ_VOICEMAIL: 'com.android.voicemail.permission.READ_VOICEMAIL',
|
|
55
|
+
WRITE_VOICEMAIL: 'com.android.voicemail.permission.WRITE_VOICEMAIL',
|
|
56
|
+
USE_SIP: 'android.permission.USE_SIP',
|
|
57
|
+
PROCESS_OUTGOING_CALLS: 'android.permission.PROCESS_OUTGOING_CALLS',
|
|
58
|
+
BODY_SENSORS: 'android.permission.BODY_SENSORS',
|
|
59
|
+
BODY_SENSORS_BACKGROUND: 'android.permission.BODY_SENSORS_BACKGROUND',
|
|
60
|
+
SEND_SMS: 'android.permission.SEND_SMS',
|
|
61
|
+
RECEIVE_SMS: 'android.permission.RECEIVE_SMS',
|
|
62
|
+
READ_SMS: 'android.permission.READ_SMS',
|
|
63
|
+
RECEIVE_WAP_PUSH: 'android.permission.RECEIVE_WAP_PUSH',
|
|
64
|
+
RECEIVE_MMS: 'android.permission.RECEIVE_MMS',
|
|
65
|
+
READ_EXTERNAL_STORAGE: 'android.permission.READ_EXTERNAL_STORAGE',
|
|
66
|
+
READ_MEDIA_IMAGES: 'android.permission.READ_MEDIA_IMAGES',
|
|
67
|
+
READ_MEDIA_VIDEO: 'android.permission.READ_MEDIA_VIDEO',
|
|
68
|
+
READ_MEDIA_AUDIO: 'android.permission.READ_MEDIA_AUDIO',
|
|
69
|
+
READ_MEDIA_VISUAL_USER_SELECTED: 'android.permission.READ_MEDIA_VISUAL_USER_SELECTED',
|
|
70
|
+
WRITE_EXTERNAL_STORAGE: 'android.permission.WRITE_EXTERNAL_STORAGE',
|
|
71
|
+
BLUETOOTH_CONNECT: 'android.permission.BLUETOOTH_CONNECT',
|
|
72
|
+
BLUETOOTH_SCAN: 'android.permission.BLUETOOTH_SCAN',
|
|
73
|
+
BLUETOOTH_ADVERTISE: 'android.permission.BLUETOOTH_ADVERTISE',
|
|
74
|
+
ACCESS_MEDIA_LOCATION: 'android.permission.ACCESS_MEDIA_LOCATION',
|
|
75
|
+
ACCEPT_HANDOVER: 'android.permission.ACCEPT_HANDOVER',
|
|
76
|
+
ACTIVITY_RECOGNITION: 'android.permission.ACTIVITY_RECOGNITION',
|
|
77
|
+
ANSWER_PHONE_CALLS: 'android.permission.ANSWER_PHONE_CALLS',
|
|
78
|
+
READ_PHONE_NUMBERS: 'android.permission.READ_PHONE_NUMBERS',
|
|
79
|
+
UWB_RANGING: 'android.permission.UWB_RANGING',
|
|
80
|
+
POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS',
|
|
81
|
+
NEARBY_WIFI_DEVICES: 'android.permission.NEARBY_WIFI_DEVICES',
|
|
82
|
+
});
|
|
83
|
+
function isBoolean(value) {
|
|
84
|
+
return typeof value === 'boolean';
|
|
85
|
+
}
|
|
86
|
+
// Narrow the native string result into a known RESULTS value. Any unrecognized
|
|
87
|
+
// string is passed through as a IPermissionStatus only when it matches; otherwise
|
|
88
|
+
// we fall back to DENIED: a runtime guard at the trust boundary, not an `as`.
|
|
89
|
+
function toPermissionStatus(value) {
|
|
90
|
+
if (value === RESULTS.GRANTED || value === RESULTS.DENIED || value === RESULTS.NEVER_ASK_AGAIN) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
return RESULTS.DENIED;
|
|
94
|
+
}
|
|
95
|
+
// Narrow the native requestMultiplePermissions() return into a per-permission
|
|
96
|
+
// status map. Each value is run through toPermissionStatus; non-object returns
|
|
97
|
+
// yield an empty map.
|
|
98
|
+
function toStatusMap(value) {
|
|
99
|
+
const result = {};
|
|
100
|
+
if (typeof value !== 'object' || value === null)
|
|
101
|
+
return result;
|
|
102
|
+
for (const key of Object.keys(value)) {
|
|
103
|
+
result[key] = toPermissionStatus(Reflect.get(value, key));
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
// Resolved FRESH on every call, deliberately NOT memoized at module load. The original
|
|
108
|
+
// self-contained module resolved once at import, but moving it into the engine made it a
|
|
109
|
+
// shared singleton; a cached null would survive a module being linked later (or the headless
|
|
110
|
+
// smoke flipping a fake module on after its first import). getNativeModule is a cheap proxy
|
|
111
|
+
// lookup, so per-call resolution costs nothing and stays correct. Returns null on iOS / headless.
|
|
112
|
+
function getModule() {
|
|
113
|
+
const module = getNativeModule(PERMISSIONS_ANDROID_MODULE);
|
|
114
|
+
dlog(`PermissionsAndroid: module ${module ? 'resolved' : 'NOT resolved (null)'}`);
|
|
115
|
+
return module;
|
|
116
|
+
}
|
|
117
|
+
export const PermissionsAndroid = {
|
|
118
|
+
PERMISSIONS,
|
|
119
|
+
RESULTS,
|
|
120
|
+
// Resolve whether a permission has already been granted. Without a native
|
|
121
|
+
// module (iOS / headless) resolve false. Never throw.
|
|
122
|
+
async check(permission) {
|
|
123
|
+
const permissionsModule = getModule();
|
|
124
|
+
if (permissionsModule === null) {
|
|
125
|
+
dlog('PermissionsAndroid.check -> module unavailable, resolving false');
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
dlog(`PermissionsAndroid.check -> ${permission}`);
|
|
129
|
+
const granted = await permissionsModule.checkPermission(permission);
|
|
130
|
+
return isBoolean(granted) ? granted : false;
|
|
131
|
+
},
|
|
132
|
+
// Prompt the user for a permission, resolving a RESULTS status. If a rationale
|
|
133
|
+
// is supplied and DialogManagerAndroid is present, show it first; otherwise
|
|
134
|
+
// proceed straight to the native request (dlog the skip). Without the
|
|
135
|
+
// PermissionsAndroid module resolve RESULTS.DENIED. Never throw.
|
|
136
|
+
async request(permission, rationale) {
|
|
137
|
+
const permissionsModule = getModule();
|
|
138
|
+
if (permissionsModule === null) {
|
|
139
|
+
dlog('PermissionsAndroid.request -> module unavailable, resolving DENIED');
|
|
140
|
+
return RESULTS.DENIED;
|
|
141
|
+
}
|
|
142
|
+
dlog(`PermissionsAndroid.request -> ${permission}`);
|
|
143
|
+
if (rationale !== undefined) {
|
|
144
|
+
const shouldShow = await permissionsModule.shouldShowRequestPermissionRationale(permission);
|
|
145
|
+
const dialogModule = getNativeModule('DialogManagerAndroid');
|
|
146
|
+
if (isBoolean(shouldShow) && shouldShow && dialogModule !== null) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
dialogModule.showAlert(rationale, () => reject(new Error('Error showing rationale')), () => {
|
|
149
|
+
permissionsModule
|
|
150
|
+
.requestPermission(permission)
|
|
151
|
+
.then(status => resolve(toPermissionStatus(status)))
|
|
152
|
+
.catch(reject);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
dlog('PermissionsAndroid.request -> rationale skipped (DialogManagerAndroid unavailable)');
|
|
157
|
+
}
|
|
158
|
+
const status = await permissionsModule.requestPermission(permission);
|
|
159
|
+
return toPermissionStatus(status);
|
|
160
|
+
},
|
|
161
|
+
// Prompt for several permissions at once, resolving a per-permission status
|
|
162
|
+
// map. Without a native module resolve an empty map. Never throw.
|
|
163
|
+
async requestMultiple(permissions) {
|
|
164
|
+
const permissionsModule = getModule();
|
|
165
|
+
if (permissionsModule === null) {
|
|
166
|
+
dlog('PermissionsAndroid.requestMultiple -> module unavailable, resolving {}');
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
dlog(`PermissionsAndroid.requestMultiple -> ${permissions.join(', ')}`);
|
|
170
|
+
const statuses = await permissionsModule.requestMultiplePermissions(permissions);
|
|
171
|
+
return toStatusMap(statuses);
|
|
172
|
+
},
|
|
173
|
+
// Whether the OS recommends showing a rationale before re-requesting. Without
|
|
174
|
+
// a native module resolve false. Never throw.
|
|
175
|
+
async shouldShowRequestPermissionRationale(permission) {
|
|
176
|
+
const permissionsModule = getModule();
|
|
177
|
+
if (permissionsModule === null) {
|
|
178
|
+
dlog('PermissionsAndroid.shouldShowRequestPermissionRationale -> module unavailable, resolving false');
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
dlog(`PermissionsAndroid.shouldShowRequestPermissionRationale -> ${permission}`);
|
|
182
|
+
const shouldShow = await permissionsModule.shouldShowRequestPermissionRationale(permission);
|
|
183
|
+
return isBoolean(shouldShow) ? shouldShow : false;
|
|
184
|
+
},
|
|
185
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface IPixelRatioStatic {
|
|
2
|
+
get(): number;
|
|
3
|
+
getFontScale(): number;
|
|
4
|
+
getPixelSizeForLayoutSize(layoutSize: number): number;
|
|
5
|
+
roundToNearestPixel(layoutSize: number): number;
|
|
6
|
+
startDetecting(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare const PixelRatio: IPixelRatioStatic;
|