@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "QuickBrickApple",
3
- "version": "6.11.0-alpha.0",
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.0-alpha.0"
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(isPreferredFocusEnvironmentDisabled, BOOL);
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 initialView = await manager.getItem(withId: initialItemId, inGroup: initialItemId)
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
- _ context: UIFocusUpdateContext) {
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 = customPreferredFocusEnvironment?.first as? FocusableView,
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?.updateFocus(currentlyActive: isActive, context: context)
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 canBacomeFocusable: Bool {
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 @objc public var preferredFocus: Bool = false {
49
+ @MainActor private weak var focusableGroup: FocusableGroupProtocol? {
52
50
  didSet {
53
- guard preferredFocus else {
54
- return
55
- }
51
+ manager.notifyFocusableItemDidUpdatePreferredFocus(focusableView: self)
52
+ }
53
+ }
56
54
 
57
- if let focusableGroup {
58
- // Update Prefered focus view in group
59
- focusableGroup.updatePreferredFocusEnv(with: self)
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.canBacomeFocusable && focusable
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
+ // }
@@ -6,6 +6,6 @@
6
6
  //
7
7
 
8
8
  protocol FocusableGroupProtocol: AnyObject {
9
- var canBacomeFocusable: Bool { get }
10
- @MainActor func updatePreferredFocusEnv(with view: UIFocusEnvironment)
9
+ var canBecomeFocusable: Bool { get }
10
+ var itemId: String? { get }
11
11
  }
@@ -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, RCTBubblingEventBlock);
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: RCTBubblingEventBlock?
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
- return window.bounds.contains(getViewFrameInWindow())
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.scheduledTimer(withTimeInterval: 1.0,
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.0-alpha.0",
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"