@applicaster/quick-brick-native-apple 6.11.0-alpha.0 → 6.11.1
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/apple/QuickBrickApple.podspec.json +2 -2
- package/apple/tvos/Helpers/FocusableGroupManager/FocusableGroupManager.swift +10 -0
- package/apple/tvos/Helpers/FocusableGroupManager/Protocols/UpdateService.swift +5 -0
- package/apple/tvos/ReactNativeModulesExportstvOS.m +1 -1
- package/apple/tvos/Views/FocusableGroupView/FocusableGroupView.swift +26 -10
- package/apple/tvos/Views/FocusableView/FocusableView.swift +23 -26
- package/apple/tvos/Views/ParallaxView/Protocols/FocusableGroupProtocol.swift +2 -2
- package/apple/universal/ReactNative/ReactNativeModulesExports.m +2 -1
- package/apple/universal/Views/TrackedView.swift +27 -5
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "QuickBrickApple",
|
|
3
|
-
"version": "6.11.
|
|
3
|
+
"version": "6.11.1",
|
|
4
4
|
"platforms": {
|
|
5
5
|
"ios": "16.0",
|
|
6
6
|
"tvos": "16.0"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"authors": "Applicaster LTD.",
|
|
17
17
|
"source": {
|
|
18
18
|
"git": "https://github.com/applicaster/Zapp-Frameworks.git",
|
|
19
|
-
"tag": "@@applicaster/quick-brick-native-apple/6.11.
|
|
19
|
+
"tag": "@@applicaster/quick-brick-native-apple/6.11.1"
|
|
20
20
|
},
|
|
21
21
|
"requires_arc": true,
|
|
22
22
|
"source_files": "universal/**/*.{m,swift}",
|
|
@@ -13,6 +13,7 @@ import React
|
|
|
13
13
|
protocol FocusableGroupManagerUpdater {
|
|
14
14
|
var focusableGroupRegistrationUpdates: PassthroughSubject<FocusableGroupView, Never> { get }
|
|
15
15
|
var focusableItemsUpdatedAddedToGroupUpdate: PassthroughSubject<FocusableGroupUpdateEvent, Never> { get }
|
|
16
|
+
var focusableItemDidUpdatePreferredFocus: PassthroughSubject<FocusableView, Never> { get }
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/// Class control focusable group view with focusable items
|
|
@@ -66,6 +67,7 @@ class FocusableGroupManager {
|
|
|
66
67
|
await notifyGroupView(groupId: groupId)
|
|
67
68
|
if let itemGroup = await getGroup(by: groupId) {
|
|
68
69
|
// Handler in case group was registerd later then items
|
|
70
|
+
// TODO: Check this it gives a lot of calls, need to be optimized
|
|
69
71
|
updateService.sendFocusableGroupRegisteredUpdate(itemGroup)
|
|
70
72
|
}
|
|
71
73
|
}
|
|
@@ -94,6 +96,10 @@ class FocusableGroupManager {
|
|
|
94
96
|
focusableItems: groupItems)
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
public func notifyFocusableItemDidUpdatePreferredFocus(focusableView: FocusableView) {
|
|
100
|
+
updateService.sendFocusableItemDidUpdatePreferredFocus(focusableView)
|
|
101
|
+
}
|
|
102
|
+
|
|
97
103
|
/// Make focus item focusable if exists and registered
|
|
98
104
|
///
|
|
99
105
|
/// - Parameters:
|
|
@@ -161,6 +167,10 @@ class FocusableGroupManager {
|
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
extension FocusableGroupManager: FocusableGroupManagerUpdater {
|
|
170
|
+
var focusableItemDidUpdatePreferredFocus: PassthroughSubject<FocusableView, Never> {
|
|
171
|
+
updateService.focusableItemDidUpdatePreferredFocus
|
|
172
|
+
}
|
|
173
|
+
|
|
164
174
|
var focusableGroupRegistrationUpdates: PassthroughSubject<FocusableGroupView, Never> {
|
|
165
175
|
updateService.focusableGroupRegistrationUpdates
|
|
166
176
|
}
|
|
@@ -15,6 +15,7 @@ struct FocusableGroupUpdateEvent {
|
|
|
15
15
|
class UpdateService {
|
|
16
16
|
let focusableGroupRegistrationUpdates = PassthroughSubject<FocusableGroupView, Never>()
|
|
17
17
|
let focusableItemsUpdatedAddedToGroupUpdate = PassthroughSubject<FocusableGroupUpdateEvent, Never>()
|
|
18
|
+
let focusableItemDidUpdatePreferredFocus = PassthroughSubject<FocusableView, Never>()
|
|
18
19
|
|
|
19
20
|
public func sendFocusableGroupRegisteredUpdate(_ focusableGroupView: FocusableGroupView) {
|
|
20
21
|
focusableGroupRegistrationUpdates.send(focusableGroupView)
|
|
@@ -26,4 +27,8 @@ class UpdateService {
|
|
|
26
27
|
focusableItems: focusableItems)
|
|
27
28
|
focusableItemsUpdatedAddedToGroupUpdate.send(event)
|
|
28
29
|
}
|
|
30
|
+
|
|
31
|
+
public func sendFocusableItemDidUpdatePreferredFocus(_ focusableView: FocusableView) {
|
|
32
|
+
focusableItemDidUpdatePreferredFocus.send(focusableView)
|
|
33
|
+
}
|
|
29
34
|
}
|
|
@@ -23,7 +23,7 @@ RCT_EXPORT_VIEW_PROPERTY(onGroupBlur, RCTDirectEventBlock)
|
|
|
23
23
|
RCT_EXPORT_VIEW_PROPERTY(itemId, NSString);
|
|
24
24
|
RCT_EXPORT_VIEW_PROPERTY(groupId, NSString);
|
|
25
25
|
RCT_EXPORT_VIEW_PROPERTY(initialItemId, NSString);
|
|
26
|
-
RCT_EXPORT_VIEW_PROPERTY(
|
|
26
|
+
RCT_EXPORT_VIEW_PROPERTY(isPreferredFocusDisabled, BOOL);
|
|
27
27
|
RCT_EXPORT_VIEW_PROPERTY(isFocusDisabled, BOOL);
|
|
28
28
|
@end
|
|
29
29
|
|
|
@@ -23,7 +23,7 @@ public class FocusableGroupView: RCTTVView {
|
|
|
23
23
|
/// Completion that will be used when focus manager forcing to update focusable group
|
|
24
24
|
public var didFocusCallBack: (completion: () -> Void, focusableItemId: String)?
|
|
25
25
|
|
|
26
|
-
private var activeStateNotifier: FocusableGroupStateNotifier
|
|
26
|
+
private lazy var activeStateNotifier: FocusableGroupStateNotifier = FocusableGroupStateNotifierDefault(action: notifyReactNativeFocusUpdate)
|
|
27
27
|
|
|
28
28
|
@objc public var onGroupFocus: RCTDirectEventBlock?
|
|
29
29
|
@objc public var onGroupBlur: RCTDirectEventBlock?
|
|
@@ -58,6 +58,8 @@ public class FocusableGroupView: RCTTVView {
|
|
|
58
58
|
/// ID of the parent group, if relevant
|
|
59
59
|
@objc public var groupId: String?
|
|
60
60
|
|
|
61
|
+
private var userPreferredFocusEnvironments: [UIFocusEnvironment]?
|
|
62
|
+
|
|
61
63
|
private var manager: FocusableGroupManager {
|
|
62
64
|
FocusableGroupManager.shared
|
|
63
65
|
}
|
|
@@ -69,7 +71,6 @@ public class FocusableGroupView: RCTTVView {
|
|
|
69
71
|
private weak var module: FocusableGroupViewModule?
|
|
70
72
|
public func setModule(_ module: FocusableGroupViewModule) {
|
|
71
73
|
self.module = module
|
|
72
|
-
activeStateNotifier = FocusableGroupStateNotifierDefault(action: notifyReactNativeFocusUpdate)
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
override init(bridge: RCTBridge) {
|
|
@@ -105,7 +106,6 @@ public class FocusableGroupView: RCTTVView {
|
|
|
105
106
|
///
|
|
106
107
|
/// - Parameter view: view instance that should be preferred
|
|
107
108
|
@MainActor func updatePreferredFocusEnv(with view: UIFocusEnvironment) {
|
|
108
|
-
focusGuide.preferredFocusEnvironments = [view]
|
|
109
109
|
customPreferredFocusEnvironment = [view]
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -126,6 +126,21 @@ public class FocusableGroupView: RCTTVView {
|
|
|
126
126
|
guard let self else { return }
|
|
127
127
|
updateInitialItemId()
|
|
128
128
|
}.store(in: &cancellables)
|
|
129
|
+
|
|
130
|
+
manager.focusableItemDidUpdatePreferredFocus
|
|
131
|
+
.filter { self.groupId != nil && $0.groupId == self.itemId }
|
|
132
|
+
.sink { [weak self] focusableView in
|
|
133
|
+
guard let self else { return }
|
|
134
|
+
guard focusableView.preferredFocus == false else {
|
|
135
|
+
userPreferredFocusEnvironments = [focusableView]
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if let currentPreferred = userPreferredFocusEnvironments?.first as? FocusableView,
|
|
140
|
+
currentPreferred == focusableView {
|
|
141
|
+
userPreferredFocusEnvironments = nil
|
|
142
|
+
}
|
|
143
|
+
}.store(in: &cancellables)
|
|
129
144
|
}
|
|
130
145
|
|
|
131
146
|
/// Update initial item id
|
|
@@ -135,7 +150,8 @@ public class FocusableGroupView: RCTTVView {
|
|
|
135
150
|
|
|
136
151
|
guard
|
|
137
152
|
let initialItemId,
|
|
138
|
-
let
|
|
153
|
+
let groupId,
|
|
154
|
+
let initialView = await manager.getItem(withId: initialItemId, inGroup: groupId)
|
|
139
155
|
else {
|
|
140
156
|
return
|
|
141
157
|
}
|
|
@@ -161,8 +177,8 @@ public class FocusableGroupView: RCTTVView {
|
|
|
161
177
|
return params
|
|
162
178
|
}
|
|
163
179
|
|
|
164
|
-
private func notifyReactNativeFocusUpdate(_ type: FocusableGroupNotifierActionType,
|
|
165
|
-
|
|
180
|
+
@MainActor private func notifyReactNativeFocusUpdate(_ type: FocusableGroupNotifierActionType,
|
|
181
|
+
_ context: UIFocusUpdateContext) {
|
|
166
182
|
switch type {
|
|
167
183
|
case .onBlur:
|
|
168
184
|
onGroupBlur?(createFocusEventParams(context: context, isActive: false))
|
|
@@ -240,7 +256,7 @@ public class FocusableGroupView: RCTTVView {
|
|
|
240
256
|
// MARK: Focus Engine
|
|
241
257
|
|
|
242
258
|
override public var preferredFocusEnvironments: [UIFocusEnvironment] {
|
|
243
|
-
customPreferredFocusEnvironment ?? super.preferredFocusEnvironments
|
|
259
|
+
userPreferredFocusEnvironments ?? customPreferredFocusEnvironment ?? super.preferredFocusEnvironments
|
|
244
260
|
}
|
|
245
261
|
|
|
246
262
|
override public func shouldUpdateFocus(in _: UIFocusUpdateContext) -> Bool {
|
|
@@ -252,7 +268,7 @@ public class FocusableGroupView: RCTTVView {
|
|
|
252
268
|
private func needsFocusUpdateDueToWrongFocus(context: UIFocusUpdateContext,
|
|
253
269
|
with coordinator: UIFocusAnimationCoordinator) -> Bool {
|
|
254
270
|
guard
|
|
255
|
-
let preferredFocusedItem =
|
|
271
|
+
let preferredFocusedItem = preferredFocusEnvironments.first as? FocusableView,
|
|
256
272
|
let nextFocusedItem = context.nextFocusedItem as? FocusableView,
|
|
257
273
|
let previouslyFocusedItem = context.previouslyFocusedItem as? FocusableView else { return false }
|
|
258
274
|
|
|
@@ -293,7 +309,7 @@ public class FocusableGroupView: RCTTVView {
|
|
|
293
309
|
}
|
|
294
310
|
|
|
295
311
|
let isActive = focusItemIsDescendant(nextFocusItem: context.nextFocusedItem)
|
|
296
|
-
activeStateNotifier
|
|
312
|
+
activeStateNotifier.updateFocus(currentlyActive: isActive, context: context)
|
|
297
313
|
|
|
298
314
|
tryDidFocusCallCallback(context: context)
|
|
299
315
|
}
|
|
@@ -321,7 +337,7 @@ public class FocusableGroupView: RCTTVView {
|
|
|
321
337
|
}
|
|
322
338
|
|
|
323
339
|
extension FocusableGroupView: FocusableGroupProtocol {
|
|
324
|
-
var
|
|
340
|
+
var canBecomeFocusable: Bool {
|
|
325
341
|
!isFocusDisabled
|
|
326
342
|
}
|
|
327
343
|
}
|
|
@@ -24,8 +24,6 @@ public class FocusableView: ParallaxView {
|
|
|
24
24
|
self.module = module
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
@MainActor private weak var focusableGroup: FocusableGroupProtocol?
|
|
28
|
-
|
|
29
27
|
@objc public var onViewFocus: RCTDirectEventBlock?
|
|
30
28
|
@objc public var onViewPress: RCTDirectEventBlock?
|
|
31
29
|
@objc public var onViewBlur: RCTDirectEventBlock?
|
|
@@ -48,16 +46,15 @@ public class FocusableView: ParallaxView {
|
|
|
48
46
|
FocusableGroupManager.shared
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
@MainActor
|
|
49
|
+
@MainActor private weak var focusableGroup: FocusableGroupProtocol? {
|
|
52
50
|
didSet {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
manager.notifyFocusableItemDidUpdatePreferredFocus(focusableView: self)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
55
|
+
@MainActor @objc public var preferredFocus: Bool = false {
|
|
56
|
+
didSet {
|
|
57
|
+
manager.notifyFocusableItemDidUpdatePreferredFocus(focusableView: self)
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
@@ -87,7 +84,6 @@ public class FocusableView: ParallaxView {
|
|
|
87
84
|
DispatchQueue.main.async {
|
|
88
85
|
self.focusableGroup = focusableGroup
|
|
89
86
|
}
|
|
90
|
-
|
|
91
87
|
}.store(in: &cancellables)
|
|
92
88
|
}
|
|
93
89
|
|
|
@@ -131,20 +127,6 @@ public class FocusableView: ParallaxView {
|
|
|
131
127
|
}
|
|
132
128
|
}
|
|
133
129
|
|
|
134
|
-
// TODO: Example of solution for future with groupId and focuse id
|
|
135
|
-
// func hintLeftFocusId() {
|
|
136
|
-
// if let onFocusLeft = dictFromReactNative,
|
|
137
|
-
// let groupId = onFocusLeft["groupId"],
|
|
138
|
-
// let id = onFocusLeft["id"],
|
|
139
|
-
// let view = FocusableGroupManager.item(byGroupId: groupId,
|
|
140
|
-
// andItemId: id) {
|
|
141
|
-
// _ = addFocusGuide(from: self,
|
|
142
|
-
// to: view,
|
|
143
|
-
// direction: .left,
|
|
144
|
-
// debugMode: true)
|
|
145
|
-
// }
|
|
146
|
-
// }
|
|
147
|
-
|
|
148
130
|
override public func removeFromSuperview() {
|
|
149
131
|
super.removeFromSuperview()
|
|
150
132
|
guard let itemId, let groupId else {
|
|
@@ -225,6 +207,7 @@ public class FocusableView: ParallaxView {
|
|
|
225
207
|
}
|
|
226
208
|
|
|
227
209
|
DispatchQueue.main.async { [weak self] in
|
|
210
|
+
|
|
228
211
|
FocusableGroupManager.shared.updateFocus(groupId: self?.groupId,
|
|
229
212
|
itemId: self?.itemId,
|
|
230
213
|
needsForceUpdate: true)
|
|
@@ -237,6 +220,20 @@ public class FocusableView: ParallaxView {
|
|
|
237
220
|
return focusable
|
|
238
221
|
}
|
|
239
222
|
|
|
240
|
-
return focusableGroup.
|
|
223
|
+
return focusableGroup.canBecomeFocusable && focusable
|
|
241
224
|
}
|
|
242
225
|
}
|
|
226
|
+
|
|
227
|
+
// TODO: Example of solution for future with groupId and focuse id
|
|
228
|
+
// func hintLeftFocusId() {
|
|
229
|
+
// if let onFocusLeft = dictFromReactNative,
|
|
230
|
+
// let groupId = onFocusLeft["groupId"],
|
|
231
|
+
// let id = onFocusLeft["id"],
|
|
232
|
+
// let view = FocusableGroupManager.item(byGroupId: groupId,
|
|
233
|
+
// andItemId: id) {
|
|
234
|
+
// _ = addFocusGuide(from: self,
|
|
235
|
+
// to: view,
|
|
236
|
+
// direction: .left,
|
|
237
|
+
// debugMode: true)
|
|
238
|
+
// }
|
|
239
|
+
// }
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
@interface RCT_EXTERN_MODULE (TrackedView, RCTViewManager)
|
|
14
14
|
|
|
15
15
|
RCT_EXPORT_VIEW_PROPERTY(groupId, NSString);
|
|
16
|
-
RCT_EXPORT_VIEW_PROPERTY(onPositionUpdated,
|
|
16
|
+
RCT_EXPORT_VIEW_PROPERTY(onPositionUpdated, RCTDirectEventBlock);
|
|
17
|
+
RCT_EXPORT_VIEW_PROPERTY(clipThreshold, NSNumber);
|
|
17
18
|
|
|
18
19
|
@end
|
|
19
20
|
|
|
@@ -9,17 +9,33 @@ import Foundation
|
|
|
9
9
|
import React
|
|
10
10
|
|
|
11
11
|
public class TrackedComponentView: RCTView {
|
|
12
|
-
@objc public var onPositionUpdated:
|
|
12
|
+
@objc public var onPositionUpdated: RCTDirectEventBlock?
|
|
13
|
+
@objc public var clipThreshold: NSNumber = 0
|
|
13
14
|
|
|
14
15
|
/// Convert the view frame to the window's coordinate system
|
|
15
16
|
private func getViewFrameInWindow() -> CGRect {
|
|
16
17
|
convert(bounds, to: window)
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
// Check if the view is completely within the window's bounds
|
|
20
|
+
// Check if the view is completely within the window's bounds with a threshold
|
|
20
21
|
private var isCompletelyVisibleOnScreen: Bool {
|
|
21
22
|
guard let window else { return false }
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
guard clipThreshold.intValue > 0 else {
|
|
25
|
+
return window.bounds.contains(getViewFrameInWindow())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let fullRect = getViewFrameInWindow()
|
|
29
|
+
let visibleRect = fullRect.intersection(window.bounds)
|
|
30
|
+
|
|
31
|
+
guard !visibleRect.isNull else { return false }
|
|
32
|
+
|
|
33
|
+
let threshold = CGFloat(truncating: clipThreshold)
|
|
34
|
+
|
|
35
|
+
let clippedWidth = fullRect.width - visibleRect.width
|
|
36
|
+
let clippedHeight = fullRect.height - visibleRect.height
|
|
37
|
+
|
|
38
|
+
return clippedWidth <= threshold && clippedHeight <= threshold
|
|
23
39
|
}
|
|
24
40
|
|
|
25
41
|
deinit {
|
|
@@ -46,8 +62,7 @@ public class TrackedComponentView: RCTView {
|
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
func startObserver() {
|
|
49
|
-
timer = Timer
|
|
50
|
-
repeats: true) { [weak self] _ in
|
|
65
|
+
timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in
|
|
51
66
|
guard let self else { return }
|
|
52
67
|
let viewFrameInWindow = getViewFrameInWindow()
|
|
53
68
|
|
|
@@ -59,6 +74,13 @@ public class TrackedComponentView: RCTView {
|
|
|
59
74
|
nil)
|
|
60
75
|
}
|
|
61
76
|
}
|
|
77
|
+
|
|
78
|
+
// Known limitation using timers is that they won’t fire when the user is interacting with your app.
|
|
79
|
+
// To avoid this we could manually add timer to the run loop in .common mode
|
|
80
|
+
// See this article: https://www.hackingwithswift.com/articles/117/the-ultimate-guide-to-timer#Working%20with%20runloops
|
|
81
|
+
if let timer {
|
|
82
|
+
RunLoop.current.add(timer, forMode: .common)
|
|
83
|
+
}
|
|
62
84
|
}
|
|
63
85
|
|
|
64
86
|
func stopObserver() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-native-apple",
|
|
3
|
-
"version": "6.11.
|
|
3
|
+
"version": "6.11.1",
|
|
4
4
|
"description": "iOS and tvOS native code for QuickBrick applications. This package is used to provide native logic for QuickBrick",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|