@honeypathkar/react-native-predictive-back-gesture 1.0.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.
@@ -0,0 +1,138 @@
1
+ package com.predictiveback
2
+
3
+ import android.os.Build
4
+ import android.util.Log
5
+ import androidx.activity.ComponentActivity
6
+ import androidx.activity.OnBackPressedCallback
7
+ import androidx.activity.BackEventCompat
8
+ import com.facebook.react.bridge.*
9
+ import com.facebook.react.modules.core.DeviceEventManagerModule
10
+
11
+ class PredictiveBackModule(reactContext: ReactApplicationContext) :
12
+ ReactContextBaseJavaModule(reactContext) {
13
+
14
+ private val backCallback = object : OnBackPressedCallback(false) {
15
+ override fun handleOnBackStarted(backEvent: BackEventCompat) {
16
+ emit(EVENT_START, backEvent.toEventMap())
17
+ }
18
+ override fun handleOnBackProgressed(backEvent: BackEventCompat) {
19
+ emit(EVENT_PROGRESS, backEvent.toEventMap())
20
+ }
21
+ override fun handleOnBackCancelled() {
22
+ emit(EVENT_CANCEL, Arguments.createMap())
23
+ }
24
+ override fun handleOnBackPressed() {
25
+ emit(EVENT_COMMIT, Arguments.createMap())
26
+ }
27
+ }
28
+
29
+ private var isRegistered = false
30
+ private var reactCallback: OnBackPressedCallback? = null
31
+ private var reactCallbackHost: ComponentActivity? = null
32
+
33
+ override fun getName(): String = NAME
34
+
35
+ override fun getConstants(): MutableMap<String, Any> =
36
+ hashMapOf(
37
+ "progressAvailable" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
38
+ )
39
+
40
+ @ReactMethod
41
+ fun setMode(mode: String) {
42
+ UiThreadUtil.runOnUiThread {
43
+ val activity = reactApplicationContext.currentActivity as? ComponentActivity
44
+ when (mode) {
45
+ MODE_APP -> {
46
+ if (activity != null) {
47
+ attachTo(activity)
48
+ setReactCallbackEnabled(activity, true)
49
+ }
50
+ backCallback.isEnabled = true
51
+ }
52
+ MODE_SYSTEM -> {
53
+ backCallback.isEnabled = false
54
+ if (activity != null) setReactCallbackEnabled(activity, false)
55
+ }
56
+ MODE_DEFAULT -> {
57
+ backCallback.isEnabled = false
58
+ if (activity != null) setReactCallbackEnabled(activity, true)
59
+ }
60
+ else -> Log.w(NAME, "Unknown back mode: $mode")
61
+ }
62
+ }
63
+ }
64
+
65
+ @ReactMethod fun addListener(eventName: String) = Unit
66
+ @ReactMethod fun removeListeners(count: Int) = Unit
67
+
68
+ override fun invalidate() {
69
+ UiThreadUtil.runOnUiThread {
70
+ backCallback.isEnabled = false
71
+ if (isRegistered) { backCallback.remove(); isRegistered = false }
72
+ reactCallback?.isEnabled = true
73
+ reactCallback = null
74
+ reactCallbackHost = null
75
+ }
76
+ super.invalidate()
77
+ }
78
+
79
+ private fun attachTo(activity: ComponentActivity) {
80
+ if (isRegistered) backCallback.remove()
81
+ activity.onBackPressedDispatcher.addCallback(backCallback)
82
+ isRegistered = true
83
+ }
84
+
85
+ private fun setReactCallbackEnabled(activity: ComponentActivity, enabled: Boolean) {
86
+ findReactCallback(activity)?.isEnabled = enabled
87
+ }
88
+
89
+ private fun findReactCallback(activity: ComponentActivity): OnBackPressedCallback? {
90
+ if (reactCallbackHost === activity) return reactCallback
91
+ var cls: Class<*>? = activity.javaClass
92
+ while (cls != null && cls != ComponentActivity::class.java) {
93
+ for (field in cls.declaredFields) {
94
+ if (OnBackPressedCallback::class.java.isAssignableFrom(field.type)) {
95
+ try {
96
+ field.isAccessible = true
97
+ val found = field.get(activity) as? OnBackPressedCallback
98
+ if (found != null) {
99
+ reactCallback = found; reactCallbackHost = activity
100
+ return found
101
+ }
102
+ } catch (e: Exception) {
103
+ Log.w(NAME, "Could not read React Native's back callback", e)
104
+ return null
105
+ }
106
+ }
107
+ }
108
+ cls = cls.superclass
109
+ }
110
+ Log.w(NAME, "React Native's back callback not found; system back mode is a no-op")
111
+ return null
112
+ }
113
+
114
+ private fun emit(event: String, payload: WritableMap) {
115
+ val context = reactApplicationContext
116
+ if (!context.hasActiveReactInstance()) return
117
+ context.emitDeviceEvent(event, payload)
118
+ }
119
+
120
+ private fun BackEventCompat.toEventMap(): WritableMap =
121
+ Arguments.createMap().apply {
122
+ putDouble("progress", progress.toDouble())
123
+ putInt("swipeEdge", swipeEdge) // 0 = left, 1 = right
124
+ putDouble("touchX", touchX.toDouble())
125
+ putDouble("touchY", touchY.toDouble())
126
+ }
127
+
128
+ companion object {
129
+ const val NAME = "PredictiveBackModule"
130
+ private const val MODE_APP = "app"
131
+ private const val MODE_DEFAULT = "default"
132
+ private const val MODE_SYSTEM = "system"
133
+ private const val EVENT_START = "predictiveBackStart"
134
+ private const val EVENT_PROGRESS = "predictiveBackProgress"
135
+ private const val EVENT_CANCEL = "predictiveBackCancel"
136
+ private const val EVENT_COMMIT = "predictiveBackCommit"
137
+ }
138
+ }
@@ -0,0 +1,16 @@
1
+ package com.predictiveback
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class PredictiveBackPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(PredictiveBackModule(reactContext))
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ // Add this code inside your file where you have declared all the screens navigation or in the App.jsx
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const PredictiveBack_1 = require("../native/PredictiveBack");
5
+ // Inside the AppNavigator component:
6
+ const syncBackMode = React.useCallback(() => {
7
+ (0, PredictiveBack_1.setFallbackBackMode)(navigationRef.isReady() && navigationRef.canGoBack()
8
+ ? PredictiveBack_1.BACK_MODE_DEFAULT
9
+ : PredictiveBack_1.BACK_MODE_SYSTEM);
10
+ }, []);
11
+ // On the NavigationContainer:
12
+ <NavigationContainer ref={navigationRef} onReady={syncBackMode} onStateChange={syncBackMode}></NavigationContainer>;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ // src/native/PredictiveBack.js — New File (JS Bridge + Ownership Model)
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.releasePredictiveBack = exports.acquirePredictiveBack = exports.setFallbackBackMode = exports.BACK_MODE_SYSTEM = exports.BACK_MODE_DEFAULT = exports.EDGE_RIGHT = exports.EDGE_LEFT = exports.PREDICTIVE_BACK_HAS_PROGRESS = exports.PREDICTIVE_BACK_SUPPORTED = void 0;
5
+ // Create at src/native/PredictiveBack.js (adapt path to your project).
6
+ const react_native_1 = require("react-native");
7
+ const PredictiveBackModule = react_native_1.Platform.OS === 'android' ? react_native_1.NativeModules.PredictiveBackModule : null;
8
+ /** True when the native back-event bridge is present (Android only). */
9
+ exports.PREDICTIVE_BACK_SUPPORTED = PredictiveBackModule != null;
10
+ /**
11
+ * True when the OS also reports live gesture progress (Android 14 / API 34+).
12
+ * Below that only the commit arrives — callers should play a timed animation.
13
+ */
14
+ exports.PREDICTIVE_BACK_HAS_PROGRESS = exports.PREDICTIVE_BACK_SUPPORTED && PredictiveBackModule.progressAvailable === true;
15
+ exports.EDGE_LEFT = 0;
16
+ exports.EDGE_RIGHT = 1;
17
+ /** Back is React Native's to handle (BackHandler + React Navigation). */
18
+ exports.BACK_MODE_DEFAULT = 'default';
19
+ /**
20
+ * Nothing in the app claims back → Android plays its own back-to-home animation.
21
+ * Set this at the root of the stack (nothing to go back to).
22
+ */
23
+ exports.BACK_MODE_SYSTEM = 'system';
24
+ let owner = null;
25
+ let handlers = null;
26
+ let fallbackMode = exports.BACK_MODE_DEFAULT;
27
+ let appliedMode = null;
28
+ let subscriptions = null;
29
+ const apply = () => {
30
+ const mode = owner ? 'app' : fallbackMode;
31
+ if (mode === appliedMode)
32
+ return;
33
+ appliedMode = mode;
34
+ PredictiveBackModule.setMode(mode);
35
+ };
36
+ const dispatch = (name, event) => {
37
+ const handler = handlers && handlers[name];
38
+ if (handler)
39
+ handler(event);
40
+ };
41
+ const ensureSubscribed = () => {
42
+ if (subscriptions || !PredictiveBackModule)
43
+ return;
44
+ const emitter = new react_native_1.NativeEventEmitter(PredictiveBackModule);
45
+ subscriptions = [
46
+ emitter.addListener('predictiveBackStart', e => dispatch('onStart', e)),
47
+ emitter.addListener('predictiveBackProgress', e => dispatch('onProgress', e)),
48
+ emitter.addListener('predictiveBackCancel', () => dispatch('onCancel')),
49
+ emitter.addListener('predictiveBackCommit', () => dispatch('onCommit')),
50
+ ];
51
+ };
52
+ /**
53
+ * What happens when no screen has claimed back.
54
+ * Call from navigator on state change.
55
+ */
56
+ const setFallbackBackMode = mode => {
57
+ if (!PredictiveBackModule || fallbackMode === mode)
58
+ return;
59
+ fallbackMode = mode;
60
+ apply();
61
+ };
62
+ exports.setFallbackBackMode = setFallbackBackMode;
63
+ /**
64
+ * Route back events to `nextHandlers` and enable the native callback.
65
+ * `token` is any stable object (e.g. useRef({}).current) that identifies the caller.
66
+ */
67
+ const acquirePredictiveBack = (token, nextHandlers) => {
68
+ if (!PredictiveBackModule)
69
+ return;
70
+ ensureSubscribed();
71
+ owner = token;
72
+ handlers = nextHandlers;
73
+ appliedMode = null; // force re-apply even if mode string unchanged
74
+ apply();
75
+ };
76
+ exports.acquirePredictiveBack = acquirePredictiveBack;
77
+ /** Give back control, but only if `token` still holds it. */
78
+ const releasePredictiveBack = token => {
79
+ if (!PredictiveBackModule || owner !== token)
80
+ return;
81
+ owner = null;
82
+ handlers = null;
83
+ apply();
84
+ };
85
+ exports.releasePredictiveBack = releasePredictiveBack;
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const react_1 = __importStar(require("react"));
37
+ const react_native_1 = require("react-native");
38
+ const react_native_gesture_handler_1 = require("react-native-gesture-handler");
39
+ const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
40
+ const native_1 = require("@react-navigation/native");
41
+ const react_native_paper_1 = require("react-native-paper");
42
+ const PredectiveBack_1 = require("./PredectiveBack");
43
+ const { width, height } = react_native_1.Dimensions.get("window");
44
+ // Material predictive-back peek: the card shrinks and drifts a little way to the right
45
+ // while the gesture is in flight, revealing the screen underneath. It only leaves the
46
+ // screen once the user commits.
47
+ const MAX_SCALE_DOWN = 0.13;
48
+ const MAX_PEEK_X = width * 0.12;
49
+ const MAX_PEEK_Y = height * 0.03;
50
+ const CORNER_RADIUS = 32;
51
+ const MAX_DIM = 0.5;
52
+ // Screens enter from the right and leave back to the right — the exit always mirrors
53
+ // the entry, whichever edge the gesture came from.
54
+ const ENTER_DURATION = 280;
55
+ const EXIT_DURATION = 240;
56
+ const EASING = react_native_reanimated_1.Easing.out(react_native_reanimated_1.Easing.bezierFn(0.25, 0.46, 0.45, 0.94));
57
+ const SPRING = { damping: 28, stiffness: 260, mass: 0.85 };
58
+ // Drag gesture: how far the finger travels for a full peek, and what commits it.
59
+ const EDGE_WIDTH = 60;
60
+ // Keeps the strip clear of the header, so the back button stays tappable.
61
+ const HEADER_INSET = 75;
62
+ const DRAG_RANGE = width * 0.6;
63
+ const PEEK_THRESHOLD = 0.35;
64
+ const VELOCITY_THRESHOLD = 900;
65
+ const MIN_VELOCITY_DISTANCE = 50;
66
+ const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
67
+ const navigation = (0, native_1.useNavigation)();
68
+ const theme = (0, react_native_paper_1.useTheme)();
69
+ // Gesture progress (0…1) — drives the peek: shrink, drift right, rounded corners.
70
+ const peek = (0, react_native_reanimated_1.useSharedValue)(0);
71
+ // Dismissal progress (0…1) — slides the card clear to the right. Also runs in
72
+ // reverse on mount so the entry mirrors the exit.
73
+ const exit = (0, react_native_reanimated_1.useSharedValue)(1);
74
+ // -1…1, how far the touch sits above/below centre; tilts the peek vertically.
75
+ const pivot = (0, react_native_reanimated_1.useSharedValue)(0);
76
+ // Set while the OS gesture owns the animation, so the pan cannot drive it too.
77
+ const nativeActive = (0, react_native_reanimated_1.useSharedValue)(0);
78
+ // Guards against a second back event landing while the exit animation is running.
79
+ const isDismissing = (0, react_1.useRef)(false);
80
+ // ─── Mount: slide in from the right ─────────────────────────────────────
81
+ (0, react_1.useEffect)(() => {
82
+ exit.value = (0, react_native_reanimated_1.withTiming)(0, { duration: ENTER_DURATION, easing: EASING });
83
+ }, [exit]);
84
+ // ─── JS-thread helpers ───────────────────────────────────────────────────
85
+ const fireHaptic = (0, react_1.useCallback)(() => {
86
+ try {
87
+ if (onHaptic)
88
+ onHaptic();
89
+ }
90
+ catch (e) { }
91
+ }, [onHaptic]);
92
+ const settle = (0, react_1.useCallback)(() => {
93
+ // Put the card back at rest — used when a pop is refused or cancelled.
94
+ isDismissing.current = false;
95
+ nativeActive.value = 0;
96
+ peek.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 160, easing: EASING });
97
+ exit.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 200, easing: EASING });
98
+ }, [peek, exit, nativeActive]);
99
+ const goBack = (0, react_1.useCallback)(() => {
100
+ if (!navigation.canGoBack()) {
101
+ settle();
102
+ return;
103
+ }
104
+ navigation.goBack();
105
+ // goBack dispatches through `beforeRemove`, so it may not actually pop.
106
+ requestAnimationFrame(() => {
107
+ if (navigation.isFocused()) {
108
+ settle();
109
+ }
110
+ });
111
+ }, [navigation, settle]);
112
+ // ─── Commit / cancel ─────────────────────────────────────────────────────
113
+ const commit = (0, react_1.useCallback)((duration = EXIT_DURATION) => {
114
+ if (isDismissing.current) {
115
+ return;
116
+ }
117
+ isDismissing.current = true;
118
+ fireHaptic();
119
+ (0, react_native_reanimated_1.cancelAnimation)(exit);
120
+ exit.value = (0, react_native_reanimated_1.withTiming)(1, { duration, easing: EASING }, (finished) => {
121
+ "worklet";
122
+ if (finished) {
123
+ (0, react_native_reanimated_1.runOnJS)(goBack)();
124
+ }
125
+ });
126
+ }, [exit, fireHaptic, goBack]);
127
+ const cancel = (0, react_1.useCallback)(() => {
128
+ nativeActive.value = 0;
129
+ (0, react_native_reanimated_1.cancelAnimation)(peek);
130
+ peek.value = (0, react_native_reanimated_1.withSpring)(0, SPRING);
131
+ }, [peek, nativeActive]);
132
+ // ─── System back gesture (Android) ───────────────────────────────────────
133
+ const token = (0, react_1.useRef)({}).current;
134
+ const isActive = enabled && navigation.canGoBack();
135
+ (0, native_1.useFocusEffect)((0, react_1.useCallback)(() => {
136
+ if (!PredectiveBack_1.PREDICTIVE_BACK_SUPPORTED || !isActive) {
137
+ return undefined;
138
+ }
139
+ const track = (event) => {
140
+ peek.value = event.progress;
141
+ pivot.value = (event.touchY / height) * 2 - 1;
142
+ };
143
+ (0, PredectiveBack_1.acquirePredictiveBack)(token, {
144
+ onStart: (event) => {
145
+ if (isDismissing.current) {
146
+ return;
147
+ }
148
+ nativeActive.value = 1;
149
+ (0, react_native_reanimated_1.cancelAnimation)(peek);
150
+ track(event);
151
+ },
152
+ onProgress: (event) => {
153
+ if (!isDismissing.current) {
154
+ track(event);
155
+ }
156
+ },
157
+ onCancel: cancel,
158
+ // Without live progress (pre-Android 14, or a 3-button back press) the card
159
+ // has not moved yet, so give the slide-out a little more room to breathe.
160
+ onCommit: () => commit(PredectiveBack_1.PREDICTIVE_BACK_HAS_PROGRESS ? EXIT_DURATION : ENTER_DURATION),
161
+ });
162
+ return () => (0, PredectiveBack_1.releasePredictiveBack)(token);
163
+ }, [token, isActive, peek, pivot, nativeActive, cancel, commit]));
164
+ // ─── Drag-to-close from the left edge ────────────────────────────────────
165
+ // On Android this covers the strip just inside the system gesture zone; on iOS it
166
+ // is the only way back. Both drive the same peek, so the card looks identical
167
+ // however the gesture arrived.
168
+ const panGesture = react_native_gesture_handler_1.Gesture.Pan()
169
+ .enabled(isActive)
170
+ .hitSlop({ left: 0, width: EDGE_WIDTH, top: -HEADER_INSET })
171
+ .activeOffsetX([10, 500])
172
+ .failOffsetY([-18, 18])
173
+ .onBegin(() => {
174
+ "worklet";
175
+ if (nativeActive.value) {
176
+ return;
177
+ }
178
+ (0, react_native_reanimated_1.cancelAnimation)(peek);
179
+ })
180
+ .onUpdate((event) => {
181
+ "worklet";
182
+ if (nativeActive.value || event.translationX <= 0) {
183
+ return;
184
+ }
185
+ peek.value = Math.min(event.translationX / DRAG_RANGE, 1);
186
+ pivot.value = (event.y / height) * 2 - 1;
187
+ })
188
+ .onEnd((event) => {
189
+ "worklet";
190
+ if (nativeActive.value) {
191
+ return;
192
+ }
193
+ const { translationX, velocityX } = event;
194
+ const shouldCommit = peek.value > PEEK_THRESHOLD ||
195
+ (velocityX > VELOCITY_THRESHOLD &&
196
+ translationX > MIN_VELOCITY_DISTANCE);
197
+ if (shouldCommit) {
198
+ (0, react_native_reanimated_1.runOnJS)(commit)(velocityX > VELOCITY_THRESHOLD ? 160 : EXIT_DURATION);
199
+ }
200
+ else {
201
+ peek.value = (0, react_native_reanimated_1.withSpring)(0, SPRING);
202
+ }
203
+ });
204
+ // ─── Card ────────────────────────────────────────────────────────────────
205
+ const screenStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
206
+ const p = peek.value;
207
+ return {
208
+ transform: [
209
+ { translateX: p * MAX_PEEK_X + exit.value * width },
210
+ { translateY: pivot.value * p * MAX_PEEK_Y },
211
+ { scale: 1 - MAX_SCALE_DOWN * p },
212
+ ],
213
+ borderRadius: (0, react_native_reanimated_1.interpolate)(p, [0, 0.05, 1], [0, CORNER_RADIUS, CORNER_RADIUS], react_native_reanimated_1.Extrapolation.CLAMP),
214
+ overflow: "hidden",
215
+ };
216
+ });
217
+ // Dims the screen underneath (visible because the navigator presents screens as
218
+ // transparent modals) and clears as this card moves out of the way.
219
+ const backdropStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
220
+ const revealed = Math.max(peek.value, exit.value);
221
+ return {
222
+ opacity: (0, react_native_reanimated_1.interpolate)(revealed, [0, 1], [MAX_DIM, 0], react_native_reanimated_1.Extrapolation.CLAMP),
223
+ };
224
+ });
225
+ return (<react_native_1.View style={styles.outerWrapper}>
226
+ <react_native_reanimated_1.default.View pointerEvents="none" style={[styles.backdrop, backdropStyle]}/>
227
+
228
+ <react_native_gesture_handler_1.GestureDetector gesture={panGesture}>
229
+ <react_native_reanimated_1.default.View style={[
230
+ styles.screen,
231
+ { backgroundColor: theme.colors.background },
232
+ screenStyle,
233
+ style,
234
+ ]}>
235
+ {children}
236
+ </react_native_reanimated_1.default.View>
237
+ </react_native_gesture_handler_1.GestureDetector>
238
+ </react_native_1.View>);
239
+ };
240
+ exports.default = SwipeableScreen;
241
+ const styles = react_native_1.StyleSheet.create({
242
+ outerWrapper: {
243
+ flex: 1,
244
+ },
245
+ backdrop: {
246
+ ...react_native_1.StyleSheet.absoluteFillObject,
247
+ backgroundColor: "#000000",
248
+ zIndex: 0,
249
+ },
250
+ screen: {
251
+ flex: 1,
252
+ zIndex: 1,
253
+ },
254
+ });
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.releasePredictiveBack = exports.acquirePredictiveBack = exports.setFallbackBackMode = exports.BACK_MODE_SYSTEM = exports.BACK_MODE_DEFAULT = exports.EDGE_RIGHT = exports.EDGE_LEFT = exports.PREDICTIVE_BACK_HAS_PROGRESS = exports.PREDICTIVE_BACK_SUPPORTED = exports.SwipeableScreen = void 0;
7
+ const SwipableScreen_1 = __importDefault(require("./SwipableScreen"));
8
+ exports.SwipeableScreen = SwipableScreen_1.default;
9
+ const PredectiveBack_1 = require("./PredectiveBack");
10
+ Object.defineProperty(exports, "PREDICTIVE_BACK_SUPPORTED", { enumerable: true, get: function () { return PredectiveBack_1.PREDICTIVE_BACK_SUPPORTED; } });
11
+ Object.defineProperty(exports, "PREDICTIVE_BACK_HAS_PROGRESS", { enumerable: true, get: function () { return PredectiveBack_1.PREDICTIVE_BACK_HAS_PROGRESS; } });
12
+ Object.defineProperty(exports, "EDGE_LEFT", { enumerable: true, get: function () { return PredectiveBack_1.EDGE_LEFT; } });
13
+ Object.defineProperty(exports, "EDGE_RIGHT", { enumerable: true, get: function () { return PredectiveBack_1.EDGE_RIGHT; } });
14
+ Object.defineProperty(exports, "BACK_MODE_DEFAULT", { enumerable: true, get: function () { return PredectiveBack_1.BACK_MODE_DEFAULT; } });
15
+ Object.defineProperty(exports, "BACK_MODE_SYSTEM", { enumerable: true, get: function () { return PredectiveBack_1.BACK_MODE_SYSTEM; } });
16
+ Object.defineProperty(exports, "setFallbackBackMode", { enumerable: true, get: function () { return PredectiveBack_1.setFallbackBackMode; } });
17
+ Object.defineProperty(exports, "acquirePredictiveBack", { enumerable: true, get: function () { return PredectiveBack_1.acquirePredictiveBack; } });
18
+ Object.defineProperty(exports, "releasePredictiveBack", { enumerable: true, get: function () { return PredectiveBack_1.releasePredictiveBack; } });
19
+ exports.default = SwipableScreen_1.default;
@@ -0,0 +1 @@
1
+ {"root":["../../src/appnavigator.jsx","../../src/predectiveback.js","../../src/swipablescreen.jsx","../../src/index.js"],"version":"5.9.3"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ /** True when the native back-event bridge is present (Android only). */
2
+ export const PREDICTIVE_BACK_SUPPORTED: boolean;
3
+ /**
4
+ * True when the OS also reports live gesture progress (Android 14 / API 34+).
5
+ * Below that only the commit arrives — callers should play a timed animation.
6
+ */
7
+ export const PREDICTIVE_BACK_HAS_PROGRESS: boolean;
8
+ export const EDGE_LEFT: 0;
9
+ export const EDGE_RIGHT: 1;
10
+ /** Back is React Native's to handle (BackHandler + React Navigation). */
11
+ export const BACK_MODE_DEFAULT: "default";
12
+ /**
13
+ * Nothing in the app claims back → Android plays its own back-to-home animation.
14
+ * Set this at the root of the stack (nothing to go back to).
15
+ */
16
+ export const BACK_MODE_SYSTEM: "system";
17
+ export function setFallbackBackMode(mode: any): void;
18
+ export function acquirePredictiveBack(token: any, nextHandlers: any): void;
19
+ export function releasePredictiveBack(token: any): void;
@@ -0,0 +1,8 @@
1
+ export default SwipeableScreen;
2
+ declare function SwipeableScreen({ children, enabled, style, onHaptic }: {
3
+ children: any;
4
+ enabled?: boolean;
5
+ style: any;
6
+ onHaptic: any;
7
+ }): React.JSX.Element;
8
+ import React from "react";
@@ -0,0 +1,12 @@
1
+ export default SwipeableScreen;
2
+ import SwipeableScreen from './SwipableScreen';
3
+ import { PREDICTIVE_BACK_SUPPORTED } from './PredectiveBack';
4
+ import { PREDICTIVE_BACK_HAS_PROGRESS } from './PredectiveBack';
5
+ import { EDGE_LEFT } from './PredectiveBack';
6
+ import { EDGE_RIGHT } from './PredectiveBack';
7
+ import { BACK_MODE_DEFAULT } from './PredectiveBack';
8
+ import { BACK_MODE_SYSTEM } from './PredectiveBack';
9
+ import { setFallbackBackMode } from './PredectiveBack';
10
+ import { acquirePredictiveBack } from './PredectiveBack';
11
+ import { releasePredictiveBack } from './PredectiveBack';
12
+ export { SwipeableScreen, PREDICTIVE_BACK_SUPPORTED, PREDICTIVE_BACK_HAS_PROGRESS, EDGE_LEFT, EDGE_RIGHT, BACK_MODE_DEFAULT, BACK_MODE_SYSTEM, setFallbackBackMode, acquirePredictiveBack, releasePredictiveBack };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@honeypathkar/react-native-predictive-back-gesture",
3
+ "version": "1.0.0",
4
+ "description": "Android 14+ Predictive Back gesture animations and swipeable screen component for React Native.",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/module/index.js",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "files": [
9
+ "src",
10
+ "lib",
11
+ "android/src",
12
+ "android/build.gradle",
13
+ "react-native-predictive-back-gesture.podspec",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc --build",
18
+ "prepare": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "react-native",
22
+ "predictive-back",
23
+ "android-14",
24
+ "gesture",
25
+ "swipeable-screen",
26
+ "reanimated"
27
+ ],
28
+ "author": "honeypathkar",
29
+ "license": "MIT",
30
+ "peerDependencies": {
31
+ "react": "*",
32
+ "react-native": "*",
33
+ "react-native-gesture-handler": "*",
34
+ "react-native-reanimated": "*"
35
+ },
36
+ "devDependencies": {
37
+ "@types/react": "^18.3.31",
38
+ "react": "^18.2.0",
39
+ "react-native": "^0.72.0",
40
+ "react-native-gesture-handler": "^2.12.0",
41
+ "react-native-reanimated": "^3.3.0",
42
+ "typescript": "^5.9.3"
43
+ }
44
+ }
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "react-native-predictive-back-gesture"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = "https://github.com/honeypathkar/react-native-predictive-back-gesture"
10
+ s.license = "MIT"
11
+ s.author = { package["author"] => package["author"] }
12
+ s.platforms = { :ios => "12.0" }
13
+ s.source = { :git => "https://github.com/honeypathkar/react-native-predictive-back-gesture.git", :tag => "#{s.version}" }
14
+
15
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
16
+ s.requires_arc = true
17
+
18
+ s.dependency "React-Core"
19
+ end
@@ -0,0 +1,23 @@
1
+ // Add this code inside your file where you have declared all the screens navigation or in the App.jsx
2
+
3
+ import {
4
+ BACK_MODE_DEFAULT,
5
+ BACK_MODE_SYSTEM,
6
+ setFallbackBackMode,
7
+ } from "../native/PredictiveBack";
8
+
9
+ // Inside the AppNavigator component:
10
+ const syncBackMode = React.useCallback(() => {
11
+ setFallbackBackMode(
12
+ navigationRef.isReady() && navigationRef.canGoBack()
13
+ ? BACK_MODE_DEFAULT
14
+ : BACK_MODE_SYSTEM,
15
+ );
16
+ }, []);
17
+
18
+ // On the NavigationContainer:
19
+ <NavigationContainer
20
+ ref={navigationRef}
21
+ onReady={syncBackMode}
22
+ onStateChange={syncBackMode}
23
+ ></NavigationContainer>;