@rejourneyco/react-native 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.
- package/android/build.gradle.kts +135 -0
- package/android/consumer-rules.pro +10 -0
- package/android/proguard-rules.pro +1 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
- package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
- package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
- package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
- package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
- package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
- package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
- package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
- package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
- package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
- package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
- package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
- package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
- package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
- package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
- package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
- package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
- package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
- package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
- package/ios/Capture/RJANRHandler.h +42 -0
- package/ios/Capture/RJANRHandler.m +328 -0
- package/ios/Capture/RJCaptureEngine.h +275 -0
- package/ios/Capture/RJCaptureEngine.m +2062 -0
- package/ios/Capture/RJCaptureHeuristics.h +80 -0
- package/ios/Capture/RJCaptureHeuristics.m +903 -0
- package/ios/Capture/RJCrashHandler.h +46 -0
- package/ios/Capture/RJCrashHandler.m +313 -0
- package/ios/Capture/RJMotionEvent.h +183 -0
- package/ios/Capture/RJMotionEvent.m +183 -0
- package/ios/Capture/RJPerformanceManager.h +100 -0
- package/ios/Capture/RJPerformanceManager.m +373 -0
- package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
- package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
- package/ios/Capture/RJSegmentUploader.h +146 -0
- package/ios/Capture/RJSegmentUploader.m +778 -0
- package/ios/Capture/RJVideoEncoder.h +247 -0
- package/ios/Capture/RJVideoEncoder.m +1036 -0
- package/ios/Capture/RJViewControllerTracker.h +73 -0
- package/ios/Capture/RJViewControllerTracker.m +508 -0
- package/ios/Capture/RJViewHierarchyScanner.h +215 -0
- package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
- package/ios/Capture/RJViewSerializer.h +119 -0
- package/ios/Capture/RJViewSerializer.m +498 -0
- package/ios/Core/RJConstants.h +124 -0
- package/ios/Core/RJConstants.m +88 -0
- package/ios/Core/RJLifecycleManager.h +85 -0
- package/ios/Core/RJLifecycleManager.m +308 -0
- package/ios/Core/RJLogger.h +61 -0
- package/ios/Core/RJLogger.m +211 -0
- package/ios/Core/RJTypes.h +176 -0
- package/ios/Core/RJTypes.m +66 -0
- package/ios/Core/Rejourney.h +64 -0
- package/ios/Core/Rejourney.mm +2495 -0
- package/ios/Network/RJDeviceAuthManager.h +94 -0
- package/ios/Network/RJDeviceAuthManager.m +967 -0
- package/ios/Network/RJNetworkMonitor.h +68 -0
- package/ios/Network/RJNetworkMonitor.m +267 -0
- package/ios/Network/RJRetryManager.h +73 -0
- package/ios/Network/RJRetryManager.m +325 -0
- package/ios/Network/RJUploadManager.h +267 -0
- package/ios/Network/RJUploadManager.m +2296 -0
- package/ios/Privacy/RJPrivacyMask.h +163 -0
- package/ios/Privacy/RJPrivacyMask.m +922 -0
- package/ios/Rejourney.h +63 -0
- package/ios/Touch/RJGestureClassifier.h +130 -0
- package/ios/Touch/RJGestureClassifier.m +333 -0
- package/ios/Touch/RJTouchInterceptor.h +169 -0
- package/ios/Touch/RJTouchInterceptor.m +772 -0
- package/ios/Utils/RJEventBuffer.h +112 -0
- package/ios/Utils/RJEventBuffer.m +358 -0
- package/ios/Utils/RJGzipUtils.h +33 -0
- package/ios/Utils/RJGzipUtils.m +89 -0
- package/ios/Utils/RJKeychainManager.h +48 -0
- package/ios/Utils/RJKeychainManager.m +111 -0
- package/ios/Utils/RJPerfTiming.h +209 -0
- package/ios/Utils/RJPerfTiming.m +264 -0
- package/ios/Utils/RJTelemetry.h +92 -0
- package/ios/Utils/RJTelemetry.m +320 -0
- package/ios/Utils/RJWindowUtils.h +66 -0
- package/ios/Utils/RJWindowUtils.m +133 -0
- package/lib/commonjs/NativeRejourney.js +40 -0
- package/lib/commonjs/components/Mask.js +79 -0
- package/lib/commonjs/index.js +1381 -0
- package/lib/commonjs/sdk/autoTracking.js +1259 -0
- package/lib/commonjs/sdk/constants.js +151 -0
- package/lib/commonjs/sdk/errorTracking.js +199 -0
- package/lib/commonjs/sdk/index.js +50 -0
- package/lib/commonjs/sdk/metricsTracking.js +204 -0
- package/lib/commonjs/sdk/navigation.js +151 -0
- package/lib/commonjs/sdk/networkInterceptor.js +412 -0
- package/lib/commonjs/sdk/utils.js +363 -0
- package/lib/commonjs/types/expo-router.d.js +2 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/module/NativeRejourney.js +38 -0
- package/lib/module/components/Mask.js +72 -0
- package/lib/module/index.js +1284 -0
- package/lib/module/sdk/autoTracking.js +1233 -0
- package/lib/module/sdk/constants.js +145 -0
- package/lib/module/sdk/errorTracking.js +189 -0
- package/lib/module/sdk/index.js +12 -0
- package/lib/module/sdk/metricsTracking.js +187 -0
- package/lib/module/sdk/navigation.js +143 -0
- package/lib/module/sdk/networkInterceptor.js +401 -0
- package/lib/module/sdk/utils.js +342 -0
- package/lib/module/types/expo-router.d.js +2 -0
- package/lib/module/types/index.js +2 -0
- package/lib/typescript/NativeRejourney.d.ts +147 -0
- package/lib/typescript/components/Mask.d.ts +39 -0
- package/lib/typescript/index.d.ts +117 -0
- package/lib/typescript/sdk/autoTracking.d.ts +204 -0
- package/lib/typescript/sdk/constants.d.ts +120 -0
- package/lib/typescript/sdk/errorTracking.d.ts +32 -0
- package/lib/typescript/sdk/index.d.ts +9 -0
- package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
- package/lib/typescript/sdk/navigation.d.ts +33 -0
- package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
- package/lib/typescript/sdk/utils.d.ts +148 -0
- package/lib/typescript/types/index.d.ts +624 -0
- package/package.json +102 -0
- package/rejourney.podspec +21 -0
- package/src/NativeRejourney.ts +165 -0
- package/src/components/Mask.tsx +80 -0
- package/src/index.ts +1459 -0
- package/src/sdk/autoTracking.ts +1373 -0
- package/src/sdk/constants.ts +134 -0
- package/src/sdk/errorTracking.ts +231 -0
- package/src/sdk/index.ts +11 -0
- package/src/sdk/metricsTracking.ts +232 -0
- package/src/sdk/navigation.ts +157 -0
- package/src/sdk/networkInterceptor.ts +440 -0
- package/src/sdk/utils.ts +369 -0
- package/src/types/expo-router.d.ts +7 -0
- package/src/types/index.ts +739 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
//
|
|
2
|
+
// RJTouchInterceptor.m
|
|
3
|
+
// Rejourney
|
|
4
|
+
//
|
|
5
|
+
// Global touch event interception implementation.
|
|
6
|
+
//
|
|
7
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
// you may not use this file except in compliance with the License.
|
|
9
|
+
// You may obtain a copy of the License at
|
|
10
|
+
//
|
|
11
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
//
|
|
13
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
// See the License for the specific language governing permissions and
|
|
17
|
+
// limitations under the License.
|
|
18
|
+
//
|
|
19
|
+
// Copyright (c) 2026 Rejourney
|
|
20
|
+
//
|
|
21
|
+
|
|
22
|
+
#import "RJTouchInterceptor.h"
|
|
23
|
+
#import "../Capture/RJMotionEvent.h"
|
|
24
|
+
#import "../Core/RJLogger.h"
|
|
25
|
+
#import "../Utils/RJWindowUtils.h"
|
|
26
|
+
#import "RJGestureClassifier.h"
|
|
27
|
+
#import <objc/runtime.h>
|
|
28
|
+
|
|
29
|
+
#pragma mark - Static Variables
|
|
30
|
+
|
|
31
|
+
static void (*_originalSendEvent)(id, SEL, UIEvent *);
|
|
32
|
+
|
|
33
|
+
static BOOL _swizzlingPerformed = NO;
|
|
34
|
+
|
|
35
|
+
#pragma mark - Swizzled Method
|
|
36
|
+
|
|
37
|
+
static void RJ_swizzled_sendEvent(id self, SEL _cmd, UIEvent *event) {
|
|
38
|
+
@try {
|
|
39
|
+
if (_originalSendEvent) {
|
|
40
|
+
_originalSendEvent(self, _cmd, event);
|
|
41
|
+
}
|
|
42
|
+
} @catch (NSException *exception) {
|
|
43
|
+
RJLogError(@"Original sendEvent failed: %@", exception);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@
|
|
47
|
+
try {
|
|
48
|
+
if (event && event.type == UIEventTypeTouches) {
|
|
49
|
+
RJTouchInterceptor *interceptor = [RJTouchInterceptor sharedInstance];
|
|
50
|
+
if (interceptor && interceptor.isTrackingEnabled) {
|
|
51
|
+
[interceptor handleTouchEvent:event];
|
|
52
|
+
} else {
|
|
53
|
+
// Debug: log why we're not handling touch (throttled to avoid spam)
|
|
54
|
+
static int swizzleSkipCount = 0;
|
|
55
|
+
if (++swizzleSkipCount % 500 == 1) {
|
|
56
|
+
RJLogInfo(@"[RJ-TOUCH-SWIZZLE] Touch not handled: interceptor=%@, "
|
|
57
|
+
@"isTrackingEnabled=%d (skipCount=%d)",
|
|
58
|
+
interceptor ? @"exists" : @"nil",
|
|
59
|
+
interceptor ? interceptor.isTrackingEnabled : -1,
|
|
60
|
+
swizzleSkipCount);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} @catch (NSException *exception) {
|
|
65
|
+
RJLogError(@"Touch handling failed: %@", exception);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#pragma mark - Private Interface
|
|
70
|
+
|
|
71
|
+
@interface RJTouchInterceptor ()
|
|
72
|
+
|
|
73
|
+
@property(nonatomic, strong) RJGestureClassifier *classifier;
|
|
74
|
+
|
|
75
|
+
@property(nonatomic, strong)
|
|
76
|
+
NSMutableDictionary<NSNumber *, NSMutableArray<NSDictionary *> *>
|
|
77
|
+
*touchPaths;
|
|
78
|
+
|
|
79
|
+
@property(nonatomic, assign) NSTimeInterval touchStartTime;
|
|
80
|
+
|
|
81
|
+
@property(nonatomic, assign) CGPoint touchStartPoint;
|
|
82
|
+
|
|
83
|
+
@property(nonatomic, assign) NSInteger activeTouchCount;
|
|
84
|
+
|
|
85
|
+
@property(nonatomic, assign) BOOL isTrackingEnabled;
|
|
86
|
+
|
|
87
|
+
@property(nonatomic, assign) CGPoint lastMotionPoint;
|
|
88
|
+
@property(nonatomic, assign) NSTimeInterval lastMotionTimestamp;
|
|
89
|
+
@property(nonatomic, assign) CGFloat motionVelocityX;
|
|
90
|
+
@property(nonatomic, assign) CGFloat motionVelocityY;
|
|
91
|
+
|
|
92
|
+
@property(nonatomic, assign) NSTimeInterval lastProcessedTouchTime;
|
|
93
|
+
|
|
94
|
+
@property(nonatomic, strong)
|
|
95
|
+
NSMutableArray<NSMutableDictionary *> *touchDictPool;
|
|
96
|
+
@property(nonatomic, assign) NSInteger poolSize;
|
|
97
|
+
|
|
98
|
+
@property(nonatomic, strong) NSMutableArray<NSDictionary *> *coalescedTouches;
|
|
99
|
+
@property(nonatomic, assign) NSTimeInterval lastCoalescedProcessTime;
|
|
100
|
+
|
|
101
|
+
@end
|
|
102
|
+
|
|
103
|
+
static const NSTimeInterval kTouchThrottleInterval = 0.016;
|
|
104
|
+
|
|
105
|
+
static const NSInteger kTouchDictPoolSize = 20;
|
|
106
|
+
static const NSTimeInterval kCoalesceInterval = 0.050;
|
|
107
|
+
|
|
108
|
+
#pragma mark - Implementation
|
|
109
|
+
|
|
110
|
+
@implementation RJTouchInterceptor
|
|
111
|
+
|
|
112
|
+
#pragma mark - Singleton
|
|
113
|
+
|
|
114
|
+
+ (instancetype)sharedInstance {
|
|
115
|
+
static RJTouchInterceptor *instance = nil;
|
|
116
|
+
static dispatch_once_t onceToken;
|
|
117
|
+
dispatch_once(&onceToken, ^{
|
|
118
|
+
instance = [[RJTouchInterceptor alloc] init];
|
|
119
|
+
});
|
|
120
|
+
return instance;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#pragma mark - Initialization
|
|
124
|
+
|
|
125
|
+
- (instancetype)init {
|
|
126
|
+
self = [super init];
|
|
127
|
+
if (self) {
|
|
128
|
+
_classifier = [[RJGestureClassifier alloc] init];
|
|
129
|
+
_touchPaths = [NSMutableDictionary new];
|
|
130
|
+
_activeTouchCount = 0;
|
|
131
|
+
_isTrackingEnabled = NO;
|
|
132
|
+
_lastMotionPoint = CGPointZero;
|
|
133
|
+
_lastMotionTimestamp = 0;
|
|
134
|
+
_motionVelocityX = 0;
|
|
135
|
+
_motionVelocityY = 0;
|
|
136
|
+
_lastProcessedTouchTime = 0;
|
|
137
|
+
|
|
138
|
+
_touchDictPool = [NSMutableArray arrayWithCapacity:kTouchDictPoolSize];
|
|
139
|
+
_poolSize = 0;
|
|
140
|
+
|
|
141
|
+
_coalescedTouches = [NSMutableArray arrayWithCapacity:10];
|
|
142
|
+
_lastCoalescedProcessTime = 0;
|
|
143
|
+
}
|
|
144
|
+
return self;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#pragma mark - Setup
|
|
148
|
+
|
|
149
|
+
- (void)enableGlobalTracking {
|
|
150
|
+
RJLogInfo(@"[RJ-TOUCH-SETUP] enableGlobalTracking called, current "
|
|
151
|
+
@"isTrackingEnabled=%d",
|
|
152
|
+
self.isTrackingEnabled);
|
|
153
|
+
static dispatch_once_t onceToken;
|
|
154
|
+
dispatch_once(&onceToken, ^{
|
|
155
|
+
RJLogInfo(@"[RJ-TOUCH-SETUP] First-time setup - performing swizzle");
|
|
156
|
+
Class applicationClass = [UIApplication class];
|
|
157
|
+
SEL originalSelector = @selector(sendEvent:);
|
|
158
|
+
Method originalMethod =
|
|
159
|
+
class_getInstanceMethod(applicationClass, originalSelector);
|
|
160
|
+
|
|
161
|
+
if (originalMethod) {
|
|
162
|
+
_originalSendEvent = (void *)method_getImplementation(originalMethod);
|
|
163
|
+
method_setImplementation(originalMethod, (IMP)RJ_swizzled_sendEvent);
|
|
164
|
+
_swizzlingPerformed = YES;
|
|
165
|
+
self.isTrackingEnabled = YES;
|
|
166
|
+
RJLogInfo(@"[RJ-TOUCH-SETUP] Swizzle complete, isTrackingEnabled=%d",
|
|
167
|
+
self.isTrackingEnabled);
|
|
168
|
+
} else {
|
|
169
|
+
RJLogError(@"Failed to enable touch tracking - sendEvent: not found");
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
RJLogInfo(@"[RJ-TOUCH-SETUP] enableGlobalTracking finished, isTrackingEnabled=%d",
|
|
173
|
+
self.isTrackingEnabled);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#pragma mark - Touch Event Handling
|
|
177
|
+
|
|
178
|
+
- (void)handleTouchEvent:(UIEvent *)event {
|
|
179
|
+
|
|
180
|
+
if (!event || !self.isTrackingEnabled) {
|
|
181
|
+
static int skipCount = 0;
|
|
182
|
+
if (++skipCount % 100 == 1) {
|
|
183
|
+
RJLogInfo(@"[RJ-TOUCH] Skipping touch: event=%@, isTrackingEnabled=%d",
|
|
184
|
+
event ? @"exists" : @"nil", self.isTrackingEnabled);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@try {
|
|
190
|
+
|
|
191
|
+
if (!self.delegate) {
|
|
192
|
+
RJLogInfo(@"[RJ-TOUCH] No delegate set, skipping touch");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
BOOL isRecording = NO;
|
|
197
|
+
@try {
|
|
198
|
+
isRecording = [self.delegate isCurrentlyRecording];
|
|
199
|
+
} @catch (NSException *e) {
|
|
200
|
+
RJLogInfo(@"[RJ-TOUCH] isCurrentlyRecording threw exception: %@", e);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!isRecording) {
|
|
205
|
+
static int notRecordingCount = 0;
|
|
206
|
+
if (++notRecordingCount % 100 == 1) {
|
|
207
|
+
RJLogInfo(@"[RJ-TOUCH] Not recording, skipping touch (count=%d)",
|
|
208
|
+
notRecordingCount);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
UIWindow *keyWindow = [self getKeyWindow];
|
|
214
|
+
if (!keyWindow) {
|
|
215
|
+
RJLogInfo(@"[RJ-TOUCH] No keyWindow available, skipping touch");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
BOOL keyboardVisible = NO;
|
|
220
|
+
CGRect keyboardFrame = CGRectZero;
|
|
221
|
+
@try {
|
|
222
|
+
keyboardVisible = [self.delegate isKeyboardCurrentlyVisible];
|
|
223
|
+
keyboardFrame = [self.delegate currentKeyboardFrame];
|
|
224
|
+
} @catch (NSException *e) {
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970] * 1000;
|
|
228
|
+
|
|
229
|
+
NSSet *allTouches = [event allTouches];
|
|
230
|
+
if (!allTouches)
|
|
231
|
+
return;
|
|
232
|
+
|
|
233
|
+
for (UITouch *touch in allTouches) {
|
|
234
|
+
@try {
|
|
235
|
+
[self processTouch:touch
|
|
236
|
+
inWindow:keyWindow
|
|
237
|
+
timestamp:timestamp
|
|
238
|
+
keyboardVisible:keyboardVisible
|
|
239
|
+
keyboardFrame:keyboardFrame];
|
|
240
|
+
} @catch (NSException *touchException) {
|
|
241
|
+
RJLogWarning(@"Individual touch processing failed: %@", touchException);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} @catch (NSException *exception) {
|
|
245
|
+
RJLogWarning(@"Touch event handling failed: %@", exception);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
- (void)processTouch:(UITouch *)touch
|
|
250
|
+
inWindow:(UIWindow *)window
|
|
251
|
+
timestamp:(NSTimeInterval)timestamp
|
|
252
|
+
keyboardVisible:(BOOL)keyboardVisible
|
|
253
|
+
keyboardFrame:(CGRect)keyboardFrame {
|
|
254
|
+
|
|
255
|
+
if (!touch || !window)
|
|
256
|
+
return;
|
|
257
|
+
|
|
258
|
+
@try {
|
|
259
|
+
CGPoint location = [touch locationInView:window];
|
|
260
|
+
|
|
261
|
+
if (isnan(location.x) || isnan(location.y) || isinf(location.x) ||
|
|
262
|
+
isinf(location.y)) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
NSNumber *touchId = @((NSUInteger)touch);
|
|
267
|
+
BOOL touchOnKeyboard =
|
|
268
|
+
keyboardVisible && CGRectContainsPoint(keyboardFrame, location);
|
|
269
|
+
|
|
270
|
+
CGFloat force = touch.force;
|
|
271
|
+
if (!isnan(force) && !isinf(force) && force > self.classifier.maxForce) {
|
|
272
|
+
self.classifier.maxForce = force;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
switch (touch.phase) {
|
|
276
|
+
case UITouchPhaseBegan:
|
|
277
|
+
[self handleTouchBegan:touch
|
|
278
|
+
touchId:touchId
|
|
279
|
+
location:location
|
|
280
|
+
timestamp:timestamp
|
|
281
|
+
touchOnKeyboard:touchOnKeyboard];
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
case UITouchPhaseMoved:
|
|
285
|
+
[self handleTouchMoved:touchId
|
|
286
|
+
location:location
|
|
287
|
+
timestamp:timestamp
|
|
288
|
+
force:force
|
|
289
|
+
touchOnKeyboard:touchOnKeyboard];
|
|
290
|
+
break;
|
|
291
|
+
|
|
292
|
+
case UITouchPhaseEnded:
|
|
293
|
+
case UITouchPhaseCancelled:
|
|
294
|
+
[self handleTouchEnded:touch
|
|
295
|
+
touchId:touchId
|
|
296
|
+
location:location
|
|
297
|
+
timestamp:timestamp
|
|
298
|
+
touchOnKeyboard:touchOnKeyboard
|
|
299
|
+
window:window];
|
|
300
|
+
break;
|
|
301
|
+
|
|
302
|
+
default:
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
} @catch (NSException *exception) {
|
|
306
|
+
RJLogWarning(@"Touch processing error: %@", exception);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#pragma mark - Touch Phase Handlers
|
|
311
|
+
|
|
312
|
+
- (void)handleTouchBegan:(UITouch *)touch
|
|
313
|
+
touchId:(NSNumber *)touchId
|
|
314
|
+
location:(CGPoint)location
|
|
315
|
+
timestamp:(NSTimeInterval)timestamp
|
|
316
|
+
touchOnKeyboard:(BOOL)touchOnKeyboard {
|
|
317
|
+
|
|
318
|
+
self.activeTouchCount++;
|
|
319
|
+
self.classifier.maxForce = touch.force;
|
|
320
|
+
|
|
321
|
+
if (self.activeTouchCount == 1) {
|
|
322
|
+
self.touchStartTime = timestamp;
|
|
323
|
+
self.touchStartPoint = location;
|
|
324
|
+
[self.touchPaths removeAllObjects];
|
|
325
|
+
|
|
326
|
+
if (self.delegate &&
|
|
327
|
+
[self.delegate respondsToSelector:@selector
|
|
328
|
+
(touchInterceptorDidDetectInteractionStart)]) {
|
|
329
|
+
[self.delegate touchInterceptorDidDetectInteractionStart];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
NSMutableArray *path = [NSMutableArray new];
|
|
334
|
+
if (!touchOnKeyboard) {
|
|
335
|
+
[path addObject:[self touchPointDict:location
|
|
336
|
+
timestamp:timestamp
|
|
337
|
+
force:touch.force]];
|
|
338
|
+
}
|
|
339
|
+
self.touchPaths[touchId] = path;
|
|
340
|
+
|
|
341
|
+
if (self.activeTouchCount == 2) {
|
|
342
|
+
[self calculateInitialPinchState];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
- (void)handleTouchMoved:(NSNumber *)touchId
|
|
347
|
+
location:(CGPoint)location
|
|
348
|
+
timestamp:(NSTimeInterval)timestamp
|
|
349
|
+
force:(CGFloat)force
|
|
350
|
+
touchOnKeyboard:(BOOL)touchOnKeyboard {
|
|
351
|
+
|
|
352
|
+
NSDictionary *touchData = @{
|
|
353
|
+
@"id" : touchId,
|
|
354
|
+
@"x" : @(location.x),
|
|
355
|
+
@"y" : @(location.y),
|
|
356
|
+
@"t" : @(timestamp),
|
|
357
|
+
@"f" : @(force),
|
|
358
|
+
@"kbd" : @(touchOnKeyboard)
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
[self.coalescedTouches addObject:touchData];
|
|
362
|
+
|
|
363
|
+
if (timestamp - self.lastCoalescedProcessTime >= kCoalesceInterval) {
|
|
364
|
+
[self processCoalescedTouches];
|
|
365
|
+
self.lastCoalescedProcessTime = timestamp;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
- (void)processCoalescedTouches {
|
|
370
|
+
if (self.coalescedTouches.count == 0)
|
|
371
|
+
return;
|
|
372
|
+
|
|
373
|
+
NSMutableDictionary<NSNumber *, NSDictionary *> *latestTouches =
|
|
374
|
+
[NSMutableDictionary new];
|
|
375
|
+
|
|
376
|
+
for (NSDictionary *touch in self.coalescedTouches) {
|
|
377
|
+
NSNumber *touchId = touch[@"id"];
|
|
378
|
+
latestTouches[touchId] = touch;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
for (NSDictionary *touch in [latestTouches allValues]) {
|
|
382
|
+
NSNumber *touchId = touch[@"id"];
|
|
383
|
+
CGPoint location =
|
|
384
|
+
CGPointMake([touch[@"x"] floatValue], [touch[@"y"] floatValue]);
|
|
385
|
+
NSTimeInterval timestamp = [touch[@"t"] doubleValue];
|
|
386
|
+
CGFloat force = [touch[@"f"] floatValue];
|
|
387
|
+
BOOL touchOnKeyboard = [touch[@"kbd"] boolValue];
|
|
388
|
+
|
|
389
|
+
NSMutableArray *path = self.touchPaths[touchId];
|
|
390
|
+
if (path && !touchOnKeyboard) {
|
|
391
|
+
[path addObject:[self touchPointDict:location
|
|
392
|
+
timestamp:timestamp
|
|
393
|
+
force:force]];
|
|
394
|
+
|
|
395
|
+
if (self.lastMotionTimestamp > 0) {
|
|
396
|
+
NSTimeInterval dt = (timestamp - self.lastMotionTimestamp) / 1000.0;
|
|
397
|
+
if (dt > 0 && dt < 1.0) {
|
|
398
|
+
self.motionVelocityX = (location.x - self.lastMotionPoint.x) / dt;
|
|
399
|
+
self.motionVelocityY = (location.y - self.lastMotionPoint.y) / dt;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
self.lastMotionPoint = location;
|
|
403
|
+
self.lastMotionTimestamp = timestamp;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
[self.coalescedTouches removeAllObjects];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
- (void)handleTouchEnded:(UITouch *)touch
|
|
411
|
+
touchId:(NSNumber *)touchId
|
|
412
|
+
location:(CGPoint)location
|
|
413
|
+
timestamp:(NSTimeInterval)timestamp
|
|
414
|
+
touchOnKeyboard:(BOOL)touchOnKeyboard
|
|
415
|
+
window:(UIWindow *)window {
|
|
416
|
+
|
|
417
|
+
@try {
|
|
418
|
+
|
|
419
|
+
NSMutableArray *path = self.touchPaths[touchId];
|
|
420
|
+
if (path && !touchOnKeyboard && touch) {
|
|
421
|
+
@try {
|
|
422
|
+
[path addObject:[self touchPointDict:location
|
|
423
|
+
timestamp:timestamp
|
|
424
|
+
force:touch.force]];
|
|
425
|
+
} @catch (NSException *e) {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
self.activeTouchCount = MAX(0, self.activeTouchCount - 1);
|
|
430
|
+
|
|
431
|
+
if (self.activeTouchCount == 0) {
|
|
432
|
+
@try {
|
|
433
|
+
if (touchOnKeyboard) {
|
|
434
|
+
|
|
435
|
+
[self notifyGesture:RJGestureTypeKeyboardTap
|
|
436
|
+
touches:@[]
|
|
437
|
+
duration:0
|
|
438
|
+
targetLabel:nil];
|
|
439
|
+
} else {
|
|
440
|
+
[self finalizeGesture:timestamp location:location window:window];
|
|
441
|
+
}
|
|
442
|
+
} @catch (NSException *gestureException) {
|
|
443
|
+
RJLogWarning(@"Gesture finalization failed: %@", gestureException);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
@
|
|
447
|
+
try {
|
|
448
|
+
[self.touchPaths removeAllObjects];
|
|
449
|
+
} @catch (NSException *e) {
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} @catch (NSException *exception) {
|
|
453
|
+
RJLogWarning(@"Touch ended handling failed: %@", exception);
|
|
454
|
+
self.activeTouchCount = 0;
|
|
455
|
+
[self.touchPaths removeAllObjects];
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
#pragma mark - Gesture Finalization
|
|
460
|
+
|
|
461
|
+
- (void)finalizeGesture:(NSTimeInterval)timestamp
|
|
462
|
+
location:(CGPoint)location
|
|
463
|
+
window:(UIWindow *)window {
|
|
464
|
+
|
|
465
|
+
@try {
|
|
466
|
+
|
|
467
|
+
NSMutableDictionary<NSNumber *, NSArray *> *touchPathsCopy =
|
|
468
|
+
[self.touchPaths mutableCopy];
|
|
469
|
+
NSTimeInterval duration = MAX(0, timestamp - self.touchStartTime);
|
|
470
|
+
NSInteger touchCount = self.touchPaths.count;
|
|
471
|
+
CGPoint startPoint = self.touchStartPoint;
|
|
472
|
+
NSTimeInterval startTime = self.touchStartTime;
|
|
473
|
+
CGFloat velocityX = self.motionVelocityX;
|
|
474
|
+
CGFloat velocityY = self.motionVelocityY;
|
|
475
|
+
|
|
476
|
+
NSString *targetLabel = nil;
|
|
477
|
+
@try {
|
|
478
|
+
if (window) {
|
|
479
|
+
UIView *targetView = [window hitTest:location withEvent:nil];
|
|
480
|
+
targetLabel = [self findAccessibilityLabel:targetView];
|
|
481
|
+
}
|
|
482
|
+
} @catch (NSException *e) {
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
__weak typeof(self) weakSelf = self;
|
|
486
|
+
id<RJTouchInterceptorDelegate> delegate = self.delegate;
|
|
487
|
+
|
|
488
|
+
dispatch_async(
|
|
489
|
+
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
490
|
+
@try {
|
|
491
|
+
|
|
492
|
+
NSMutableArray<NSDictionary *> *allTouches = [NSMutableArray new];
|
|
493
|
+
@try {
|
|
494
|
+
for (NSArray *touchPath in [touchPathsCopy allValues]) {
|
|
495
|
+
if (touchPath && [touchPath isKindOfClass:[NSArray class]]) {
|
|
496
|
+
[allTouches addObjectsFromArray:touchPath];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
} @catch (NSException *e) {
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
NSString *gestureType = RJGestureTypeTap;
|
|
503
|
+
@try {
|
|
504
|
+
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
505
|
+
if (strongSelf && strongSelf.classifier) {
|
|
506
|
+
gestureType = [strongSelf.classifier
|
|
507
|
+
classifyMultiTouchPaths:touchPathsCopy
|
|
508
|
+
duration:duration
|
|
509
|
+
touchCount:touchCount];
|
|
510
|
+
}
|
|
511
|
+
} @catch (NSException *e) {
|
|
512
|
+
RJLogWarning(@"Gesture classification failed: %@", e);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
516
|
+
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
517
|
+
if (!strongSelf || !delegate)
|
|
518
|
+
return;
|
|
519
|
+
|
|
520
|
+
[strongSelf notifyGesture:gestureType ?: RJGestureTypeTap
|
|
521
|
+
touches:allTouches
|
|
522
|
+
duration:duration
|
|
523
|
+
targetLabel:targetLabel];
|
|
524
|
+
|
|
525
|
+
[strongSelf emitMotionEventIfNeeded:gestureType
|
|
526
|
+
duration:duration
|
|
527
|
+
location:location];
|
|
528
|
+
});
|
|
529
|
+
} @catch (NSException *exception) {
|
|
530
|
+
RJLogWarning(@"Async gesture finalization error: %@", exception);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
self.lastMotionPoint = CGPointZero;
|
|
535
|
+
self.lastMotionTimestamp = 0;
|
|
536
|
+
self.motionVelocityX = 0;
|
|
537
|
+
self.motionVelocityY = 0;
|
|
538
|
+
|
|
539
|
+
} @catch (NSException *exception) {
|
|
540
|
+
RJLogWarning(@"Gesture finalization error: %@", exception);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
- (void)emitMotionEventIfNeeded:(NSString *)gestureType
|
|
545
|
+
duration:(NSTimeInterval)duration
|
|
546
|
+
location:(CGPoint)location {
|
|
547
|
+
|
|
548
|
+
if (![gestureType hasPrefix:@"scroll"] && ![gestureType hasPrefix:@"swipe"] &&
|
|
549
|
+
![gestureType hasPrefix:@"pan"]) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
id<RJTouchInterceptorDelegate> delegate = self.delegate;
|
|
554
|
+
if (!delegate || ![delegate respondsToSelector:@selector
|
|
555
|
+
(touchInterceptorDidCaptureMotionEvent:)]) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
@try {
|
|
560
|
+
RJMotionEvent *motionEvent = [[RJMotionEvent alloc] init];
|
|
561
|
+
|
|
562
|
+
if ([gestureType hasPrefix:@"scroll"]) {
|
|
563
|
+
motionEvent.type = RJMotionTypeScroll;
|
|
564
|
+
} else if ([gestureType hasPrefix:@"swipe"]) {
|
|
565
|
+
motionEvent.type = RJMotionTypeSwipe;
|
|
566
|
+
} else {
|
|
567
|
+
motionEvent.type = RJMotionTypePan;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
motionEvent.t0 = self.touchStartTime;
|
|
571
|
+
motionEvent.t1 = self.touchStartTime + duration;
|
|
572
|
+
|
|
573
|
+
motionEvent.dx = location.x - self.touchStartPoint.x;
|
|
574
|
+
motionEvent.dy = location.y - self.touchStartPoint.y;
|
|
575
|
+
|
|
576
|
+
CGFloat velocity = sqrt(self.motionVelocityX * self.motionVelocityX +
|
|
577
|
+
self.motionVelocityY * self.motionVelocityY);
|
|
578
|
+
motionEvent.v0 = velocity;
|
|
579
|
+
motionEvent.v1 = 0;
|
|
580
|
+
|
|
581
|
+
if ([gestureType hasPrefix:@"scroll"]) {
|
|
582
|
+
motionEvent.curve = RJMotionCurveExponentialDecay;
|
|
583
|
+
} else if ([gestureType hasPrefix:@"swipe"]) {
|
|
584
|
+
motionEvent.curve = RJMotionCurveEaseOut;
|
|
585
|
+
} else {
|
|
586
|
+
motionEvent.curve = RJMotionCurveLinear;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
[delegate touchInterceptorDidCaptureMotionEvent:motionEvent];
|
|
590
|
+
|
|
591
|
+
} @catch (NSException *e) {
|
|
592
|
+
RJLogWarning(@"Motion event emission failed: %@", e);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
#pragma mark - Helpers
|
|
597
|
+
|
|
598
|
+
- (NSDictionary *)touchPointDict:(CGPoint)location
|
|
599
|
+
timestamp:(NSTimeInterval)timestamp
|
|
600
|
+
force:(CGFloat)force {
|
|
601
|
+
|
|
602
|
+
return @{
|
|
603
|
+
@"x" : @(location.x),
|
|
604
|
+
@"y" : @(location.y),
|
|
605
|
+
@"timestamp" : @(timestamp),
|
|
606
|
+
@"force" : @(force)
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
- (NSMutableDictionary *)getDictFromPool {
|
|
611
|
+
@synchronized(self.touchDictPool) {
|
|
612
|
+
if (self.poolSize > 0) {
|
|
613
|
+
self.poolSize--;
|
|
614
|
+
NSMutableDictionary *dict = [self.touchDictPool lastObject];
|
|
615
|
+
[self.touchDictPool removeLastObject];
|
|
616
|
+
[dict removeAllObjects];
|
|
617
|
+
return dict;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return [NSMutableDictionary dictionaryWithCapacity:4];
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
- (void)returnDictToPool:(NSMutableDictionary *)dict {
|
|
625
|
+
if (!dict)
|
|
626
|
+
return;
|
|
627
|
+
@synchronized(self.touchDictPool) {
|
|
628
|
+
if (self.poolSize < kTouchDictPoolSize) {
|
|
629
|
+
[dict removeAllObjects];
|
|
630
|
+
[self.touchDictPool addObject:dict];
|
|
631
|
+
self.poolSize++;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
- (void)calculateInitialPinchState {
|
|
637
|
+
NSArray *touchIds = [self.touchPaths allKeys];
|
|
638
|
+
if (touchIds.count < 2)
|
|
639
|
+
return;
|
|
640
|
+
|
|
641
|
+
NSArray *path1 = self.touchPaths[touchIds[0]];
|
|
642
|
+
NSArray *path2 = self.touchPaths[touchIds[1]];
|
|
643
|
+
|
|
644
|
+
if (path1.count == 0 || path2.count == 0)
|
|
645
|
+
return;
|
|
646
|
+
|
|
647
|
+
NSDictionary *point1 = path1.lastObject;
|
|
648
|
+
NSDictionary *point2 = path2.lastObject;
|
|
649
|
+
|
|
650
|
+
CGFloat dx = [point1[@"x"] floatValue] - [point2[@"x"] floatValue];
|
|
651
|
+
CGFloat dy = [point1[@"y"] floatValue] - [point2[@"y"] floatValue];
|
|
652
|
+
|
|
653
|
+
self.classifier.initialPinchDistance = sqrt(dx * dx + dy * dy);
|
|
654
|
+
self.classifier.initialRotationAngle = atan2(dy, dx);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
- (nullable NSString *)findAccessibilityLabel:(UIView *)view {
|
|
658
|
+
UIView *current = view;
|
|
659
|
+
while (current) {
|
|
660
|
+
if (current.accessibilityLabel.length > 0) {
|
|
661
|
+
return current.accessibilityLabel;
|
|
662
|
+
}
|
|
663
|
+
current = current.superview;
|
|
664
|
+
}
|
|
665
|
+
return nil;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
- (BOOL)isViewInteractive:(UIView *)view {
|
|
669
|
+
UIView *current = view;
|
|
670
|
+
while (current) {
|
|
671
|
+
|
|
672
|
+
if ([current isKindOfClass:[UIControl class]] ||
|
|
673
|
+
[current isKindOfClass:[UIButton class]] ||
|
|
674
|
+
[current isKindOfClass:[UITextField class]] ||
|
|
675
|
+
[current isKindOfClass:[UITextView class]] ||
|
|
676
|
+
[current isKindOfClass:[UISwitch class]] ||
|
|
677
|
+
[current isKindOfClass:[UISlider class]] ||
|
|
678
|
+
[current isKindOfClass:[UITableViewCell class]] ||
|
|
679
|
+
[current isKindOfClass:[UICollectionViewCell class]]) {
|
|
680
|
+
return YES;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (current.gestureRecognizers.count > 0) {
|
|
684
|
+
for (UIGestureRecognizer *gr in current.gestureRecognizers) {
|
|
685
|
+
if (gr.enabled &&
|
|
686
|
+
([gr isKindOfClass:[UITapGestureRecognizer class]] ||
|
|
687
|
+
[gr isKindOfClass:[UILongPressGestureRecognizer class]])) {
|
|
688
|
+
return YES;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if ((current.accessibilityTraits & UIAccessibilityTraitButton) ||
|
|
694
|
+
(current.accessibilityTraits & UIAccessibilityTraitLink) ||
|
|
695
|
+
(current.accessibilityTraits & UIAccessibilityTraitKeyboardKey)) {
|
|
696
|
+
return YES;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
current = current.superview;
|
|
700
|
+
}
|
|
701
|
+
return NO;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
- (UIWindow *)getKeyWindow {
|
|
705
|
+
// Use shared helper to avoid accidentally selecting system input windows
|
|
706
|
+
// (UITextEffectsWindow / Keyboard windows) which can become "key".
|
|
707
|
+
UIWindow *window = [RJWindowUtils keyWindow];
|
|
708
|
+
if (window) {
|
|
709
|
+
return window;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (@available(iOS 13.0, *)) {
|
|
713
|
+
// If RJWindowUtils returned nil, fall back to the first key window we can
|
|
714
|
+
// find.
|
|
715
|
+
for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
|
|
716
|
+
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
|
720
|
+
if (windowScene.activationState ==
|
|
721
|
+
UISceneActivationStateForegroundActive) {
|
|
722
|
+
for (UIWindow *w in windowScene.windows) {
|
|
723
|
+
if (w.isKeyWindow) {
|
|
724
|
+
return w;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
#pragma clang diagnostic push
|
|
731
|
+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
732
|
+
return [UIApplication sharedApplication].keyWindow;
|
|
733
|
+
#pragma clang diagnostic pop
|
|
734
|
+
}
|
|
735
|
+
return nil;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
- (void)notifyGesture:(NSString *)gestureType
|
|
739
|
+
touches:(NSArray<NSDictionary *> *)touches
|
|
740
|
+
duration:(NSTimeInterval)duration
|
|
741
|
+
targetLabel:(nullable NSString *)targetLabel {
|
|
742
|
+
|
|
743
|
+
/*
|
|
744
|
+
RJLogInfo(@"[RJ-TOUCH] notifyGesture called: type=%@, touchCount=%lu, "
|
|
745
|
+
@"duration=%.2f",
|
|
746
|
+
gestureType, (unsigned long)touches.count, duration);
|
|
747
|
+
*/
|
|
748
|
+
|
|
749
|
+
@try {
|
|
750
|
+
id<RJTouchInterceptorDelegate> delegate = self.delegate;
|
|
751
|
+
if (delegate &&
|
|
752
|
+
[delegate
|
|
753
|
+
respondsToSelector:@selector
|
|
754
|
+
(touchInterceptorDidRecognizeGesture:
|
|
755
|
+
touches:duration:targetLabel:)]) {
|
|
756
|
+
RJLogInfo(@"[RJ-TOUCH] Calling delegate touchInterceptorDidRecognizeGesture");
|
|
757
|
+
[delegate
|
|
758
|
+
touchInterceptorDidRecognizeGesture:gestureType ?: RJGestureTypeTap
|
|
759
|
+
touches:touches ?: @[]
|
|
760
|
+
duration:MAX(0, duration)
|
|
761
|
+
targetLabel:targetLabel];
|
|
762
|
+
} else {
|
|
763
|
+
RJLogInfo(@"[RJ-TOUCH] Delegate missing or doesn't respond to selector: "
|
|
764
|
+
@"delegate=%@",
|
|
765
|
+
delegate);
|
|
766
|
+
}
|
|
767
|
+
} @catch (NSException *exception) {
|
|
768
|
+
RJLogWarning(@"Gesture notification failed: %@", exception);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
@end
|