@applicaster/quick-brick-native-apple 6.16.0 → 6.17.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/apple/QuickBrickApple.podspec.json +2 -2
- package/apple/ios/ReactNative/PushBridge.swift +2 -2
- package/apple/ios/ReactNative/QuickBrickViewController.swift +3 -3
- package/apple/tvos/Helpers/FocusableGroupManager/Coordinator/GroupProxyCoordinator.swift +70 -0
- package/apple/tvos/Helpers/FocusableGroupManager/Coordinator/SetItemFocus.swift +105 -0
- package/apple/tvos/Helpers/FocusableGroupManager/Coordinator/SetPreferredFocus.swift +90 -0
- package/apple/tvos/Helpers/FocusableGroupManager/FocusableManagerModule.swift +1 -2
- package/apple/tvos/Helpers/FocusableGroupManager/GroupProxy.swift +82 -42
- package/apple/tvos/Helpers/FocusableGroupManager/GroupProxyManager.swift +79 -49
- package/apple/tvos/ReactNative/QuickBrickViewController.swift +2 -2
- package/apple/tvos/ReactNativeModulesExportstvOS.m +2 -0
- package/apple/tvos/Views/FocusableGroupView/FocusableGroupStateNotifier.swift +2 -5
- package/apple/tvos/Views/FocusableGroupView/FocusableGroupView.swift +134 -34
- package/apple/tvos/Views/FocusableView/FocusableView.swift +32 -1
- package/apple/universal/ReactNative/AnalyticsBridge.swift +2 -2
- package/apple/universal/ReactNative/AppLoaderBridge.swift +14 -14
- package/apple/universal/ReactNative/EventBusBridge.swift +2 -2
- package/apple/universal/ReactNative/LocalNotification/LocalNotificationBridge.swift +4 -4
- package/apple/universal/ReactNative/OfflineAssetsBridge.swift +11 -11
- package/apple/universal/ReactNative/PluginsManagerBridge.swift +17 -17
- package/apple/universal/ReactNative/QuickBrickExceptionManagerDelegate.swift +2 -2
- package/apple/universal/ReactNative/ReactNativeCommunicationModule.swift +10 -10
- package/apple/universal/Storages/LocalStorage/LocalStorageBridge.swift +29 -29
- package/apple/universal/Storages/SessionStorage/SessionStorageBridge.swift +15 -15
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "QuickBrickApple",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.17.0",
|
|
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.
|
|
19
|
+
"tag": "@@applicaster/quick-brick-native-apple/6.17.0"
|
|
20
20
|
},
|
|
21
21
|
"requires_arc": true,
|
|
22
22
|
"source_files": "universal/**/*.{m,swift}",
|
|
@@ -21,12 +21,12 @@ class PushBridge: NSObject, RCTBridgeModule {
|
|
|
21
21
|
"PushBridge"
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
class func requiresMainQueueSetup() -> Bool {
|
|
25
25
|
true
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/// prefered thread on which to run this native module
|
|
29
|
-
@objc
|
|
29
|
+
@objc var methodQueue: DispatchQueue {
|
|
30
30
|
DispatchQueue.main
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -20,7 +20,7 @@ class QuickBrickViewController: UIViewController, UILayerViewControllerProtocol
|
|
|
20
20
|
|
|
21
21
|
var orientationMask: UIInterfaceOrientationMask = QuickBrickViewController.initialOrientationMask
|
|
22
22
|
|
|
23
|
-
override
|
|
23
|
+
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
|
24
24
|
orientationMask
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ class QuickBrickViewController: UIViewController, UILayerViewControllerProtocol
|
|
|
28
28
|
homeIndicatorAutoHidden
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
override
|
|
31
|
+
override var shouldAutorotate: Bool {
|
|
32
32
|
true
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -64,7 +64,7 @@ class QuickBrickViewController: UIViewController, UILayerViewControllerProtocol
|
|
|
64
64
|
UIDevice.current.orientation
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
func allowOrientationForScreen(_ orientation: ReactNativeOrientation) {
|
|
68
68
|
orientationMask = orientation.toInterfaceOrientationMask()
|
|
69
69
|
forceOrientationWithMaskIfNeeded(orientationMask)
|
|
70
70
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// GroupProxyCoordinator.swift
|
|
3
|
+
// QuickBrickApple
|
|
4
|
+
//
|
|
5
|
+
// Created by Anton Kononenko on 12/22/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import React
|
|
10
|
+
|
|
11
|
+
class GroupProxyCoordinator {
|
|
12
|
+
private var commandsQueue: [Command] = []
|
|
13
|
+
private let lock = NSLock()
|
|
14
|
+
|
|
15
|
+
protocol Command {
|
|
16
|
+
var groupId: String { get }
|
|
17
|
+
func canBeApplied() -> Bool
|
|
18
|
+
func apply()
|
|
19
|
+
func cancel()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func tryApply(_ command: Command) {
|
|
23
|
+
lock.lock()
|
|
24
|
+
defer { lock.unlock() }
|
|
25
|
+
|
|
26
|
+
if command.canBeApplied() {
|
|
27
|
+
command.apply()
|
|
28
|
+
} else {
|
|
29
|
+
commandsQueue.append(command)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private func processQueue() {
|
|
34
|
+
lock.lock()
|
|
35
|
+
let commands = commandsQueue
|
|
36
|
+
commandsQueue.removeAll()
|
|
37
|
+
lock.unlock()
|
|
38
|
+
|
|
39
|
+
for command in commands {
|
|
40
|
+
if command.canBeApplied() {
|
|
41
|
+
command.apply()
|
|
42
|
+
} else {
|
|
43
|
+
lock.lock()
|
|
44
|
+
commandsQueue.append(command)
|
|
45
|
+
lock.unlock()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func onGroupBound(groupId _: String) {
|
|
51
|
+
processQueue()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func onChildAdded(groupId _: String, itemId _: String) {
|
|
55
|
+
processQueue()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func cancelCommandsForGroup(groupId: String) {
|
|
59
|
+
lock.lock()
|
|
60
|
+
defer { lock.unlock() }
|
|
61
|
+
|
|
62
|
+
commandsQueue.removeAll { command in
|
|
63
|
+
if command.groupId == groupId {
|
|
64
|
+
command.cancel()
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SetItemFocus.swift
|
|
3
|
+
// QuickBrickApple
|
|
4
|
+
//
|
|
5
|
+
// Created by Anton Kononenko on 12/22/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
|
|
11
|
+
extension GroupProxyCoordinator {
|
|
12
|
+
class SetItemFocus: Command {
|
|
13
|
+
let groupId: String
|
|
14
|
+
let itemId: String
|
|
15
|
+
let needsForceUpdate: Bool
|
|
16
|
+
let completion: ((Bool) -> Void)?
|
|
17
|
+
weak var manager: GroupProxyManager?
|
|
18
|
+
private var isCancelled = false
|
|
19
|
+
private let lock = NSLock()
|
|
20
|
+
|
|
21
|
+
init(groupId: String,
|
|
22
|
+
itemId: String,
|
|
23
|
+
needsForceUpdate: Bool,
|
|
24
|
+
completion: ((Bool) -> Void)?,
|
|
25
|
+
manager: GroupProxyManager) {
|
|
26
|
+
self.groupId = groupId
|
|
27
|
+
self.itemId = itemId
|
|
28
|
+
self.needsForceUpdate = needsForceUpdate
|
|
29
|
+
self.completion = completion
|
|
30
|
+
self.manager = manager
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func canBeApplied() -> Bool {
|
|
34
|
+
lock.lock()
|
|
35
|
+
defer { lock.unlock() }
|
|
36
|
+
|
|
37
|
+
if isCancelled {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
guard let manager,
|
|
42
|
+
let proxy = manager.getProxy(forGroupId: groupId),
|
|
43
|
+
let group = proxy.getGroup() else {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if group.isFocusDisabled {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if !proxy.isExistingChild(itemId) {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
guard let focusableView = proxy.getChild(by: itemId) else {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if focusableView.window == nil || focusableView.frame.isEmpty {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func apply() {
|
|
67
|
+
lock.lock()
|
|
68
|
+
if isCancelled {
|
|
69
|
+
lock.unlock()
|
|
70
|
+
completion?(false)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
lock.unlock()
|
|
74
|
+
|
|
75
|
+
guard let manager,
|
|
76
|
+
let proxy = manager.getProxy(forGroupId: groupId),
|
|
77
|
+
let groupView = proxy.getGroup(),
|
|
78
|
+
let focusableView = proxy.getChild(by: itemId) else {
|
|
79
|
+
completion?(false)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
manager.setForceFocusUpdateData(itemId: itemId, groupId: groupId)
|
|
84
|
+
|
|
85
|
+
DispatchQueue.main.async { [weak manager] in
|
|
86
|
+
groupView.forceFocus(
|
|
87
|
+
view: focusableView,
|
|
88
|
+
needsForceUpdate: self.needsForceUpdate,
|
|
89
|
+
callback: { success in
|
|
90
|
+
manager?.clearForceFocusUpdate()
|
|
91
|
+
self.completion?(success)
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func cancel() {
|
|
98
|
+
lock.lock()
|
|
99
|
+
isCancelled = true
|
|
100
|
+
lock.unlock()
|
|
101
|
+
|
|
102
|
+
completion?(false)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SetPreferredFocus.swift
|
|
3
|
+
// QuickBrickApple
|
|
4
|
+
//
|
|
5
|
+
// Created by Anton Kononenko on 12/23/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
|
|
11
|
+
extension GroupProxyCoordinator {
|
|
12
|
+
class SetPreferredFocus: Command {
|
|
13
|
+
let groupId: String
|
|
14
|
+
let itemId: String
|
|
15
|
+
let isPreferred: Bool
|
|
16
|
+
weak var manager: GroupProxyManager?
|
|
17
|
+
private var isCancelled = false
|
|
18
|
+
private let lock = NSLock()
|
|
19
|
+
|
|
20
|
+
init(groupId: String,
|
|
21
|
+
itemId: String,
|
|
22
|
+
isPreferred: Bool,
|
|
23
|
+
manager: GroupProxyManager) {
|
|
24
|
+
self.groupId = groupId
|
|
25
|
+
self.itemId = itemId
|
|
26
|
+
self.isPreferred = isPreferred
|
|
27
|
+
self.manager = manager
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func canBeApplied() -> Bool {
|
|
31
|
+
lock.lock()
|
|
32
|
+
defer { lock.unlock() }
|
|
33
|
+
|
|
34
|
+
if isCancelled {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
guard let manager,
|
|
39
|
+
let proxy = manager.getProxy(forGroupId: groupId),
|
|
40
|
+
let group = proxy.getGroup() else {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if !proxy.isExistingChild(itemId) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
guard let focusableView = proxy.getChild(by: itemId) else {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if focusableView.window == nil || focusableView.frame.isEmpty {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func apply() {
|
|
60
|
+
lock.lock()
|
|
61
|
+
if isCancelled {
|
|
62
|
+
lock.unlock()
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
lock.unlock()
|
|
66
|
+
|
|
67
|
+
guard let manager,
|
|
68
|
+
let proxy = manager.getProxy(forGroupId: groupId),
|
|
69
|
+
let group = proxy.getGroup(),
|
|
70
|
+
let focusableView = proxy.getChild(by: itemId) else {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
DispatchQueue.main.async {
|
|
75
|
+
if self.isPreferred {
|
|
76
|
+
group.setUserPreferredFocusEnvironments([focusableView])
|
|
77
|
+
} else if let currentPreferred = group.getUserPreferredFocusEnvironments()?.first as? FocusableView,
|
|
78
|
+
currentPreferred === focusableView {
|
|
79
|
+
group.setUserPreferredFocusEnvironments(nil)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func cancel() {
|
|
85
|
+
lock.lock()
|
|
86
|
+
isCancelled = true
|
|
87
|
+
lock.unlock()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -15,7 +15,7 @@ let kFocusableManagerModule = "FocusableManagerModule"
|
|
|
15
15
|
class FocusableManagerModule: NSObject, RCTBridgeModule {
|
|
16
16
|
/// Delay timer before focus will be invoked, was done to resolve async problems.
|
|
17
17
|
/// Sometimes focusale item did has superview when it render that may cayse glitches.
|
|
18
|
-
var delayTimer =
|
|
18
|
+
var delayTimer = 0
|
|
19
19
|
|
|
20
20
|
/// main React bridge
|
|
21
21
|
var bridge: RCTBridge?
|
|
@@ -58,7 +58,6 @@ class FocusableManagerModule: NSObject, RCTBridgeModule {
|
|
|
58
58
|
@objc func forceFocus(_ groupId: String?, itemId: String?, callback: RCTResponseSenderBlock?) {
|
|
59
59
|
// We need delay to make sure that on native side group will have superview
|
|
60
60
|
let delay = DispatchTime.now() + DispatchTimeInterval.milliseconds(delayTimer)
|
|
61
|
-
|
|
62
61
|
DispatchQueue.main.asyncAfter(deadline: delay) {
|
|
63
62
|
GroupProxyManager.shared.updateFocus(groupId: groupId,
|
|
64
63
|
itemId: itemId,
|
|
@@ -11,50 +11,80 @@ import XrayLogger
|
|
|
11
11
|
class GroupProxy {
|
|
12
12
|
let groupId: String
|
|
13
13
|
private weak var group: FocusableGroupView?
|
|
14
|
-
private var
|
|
14
|
+
private weak var coordinator: GroupProxyCoordinator?
|
|
15
|
+
private var children: [String: WeakBox<FocusableView>] = [:]
|
|
15
16
|
private let lock = NSLock()
|
|
16
17
|
|
|
17
|
-
var delayedPreferredFocus: FocusableView?
|
|
18
|
-
|
|
19
18
|
private lazy var logger = Logger.getLogger(for: "QuickBrickApple/FocusableGroupManager/GroupProxy")
|
|
20
19
|
|
|
21
|
-
init(groupId: String) {
|
|
20
|
+
init(groupId: String, coordinator: GroupProxyCoordinator) {
|
|
22
21
|
self.groupId = groupId
|
|
22
|
+
self.coordinator = coordinator
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func getChild(by itemId: String) -> FocusableView? {
|
|
26
|
+
lock.lock()
|
|
27
|
+
defer { lock.unlock() }
|
|
28
|
+
|
|
29
|
+
return children[itemId]?.value
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
// MARK: - Child Management
|
|
26
33
|
|
|
27
34
|
func addChild(_ view: FocusableView) {
|
|
28
35
|
lock.lock()
|
|
29
|
-
|
|
36
|
+
|
|
37
|
+
guard let itemId = view.itemId else {
|
|
38
|
+
lock.unlock()
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
children[itemId] = WeakBox(view)
|
|
30
43
|
lock.unlock()
|
|
44
|
+
|
|
45
|
+
view.sendViewRegisteredEvent()
|
|
46
|
+
|
|
47
|
+
coordinator?.onChildAdded(groupId: groupId, itemId: itemId)
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
func removeChild(_ view: FocusableView) {
|
|
34
51
|
lock.lock()
|
|
35
|
-
defer { lock.unlock() }
|
|
36
52
|
|
|
37
|
-
|
|
53
|
+
if let itemId = view.itemId {
|
|
54
|
+
children.removeValue(forKey: itemId)
|
|
55
|
+
}
|
|
38
56
|
|
|
39
|
-
|
|
57
|
+
let shouldRemoveProxy = children.isEmpty && group == nil
|
|
58
|
+
lock.unlock()
|
|
59
|
+
|
|
60
|
+
if shouldRemoveProxy {
|
|
61
|
+
coordinator?.cancelCommandsForGroup(groupId: groupId)
|
|
40
62
|
GroupProxyManager.shared.removeProxy(forGroupId: groupId)
|
|
41
63
|
}
|
|
64
|
+
}
|
|
42
65
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
66
|
+
func isExistingChild(_ itemId: String) -> Bool {
|
|
67
|
+
lock.lock()
|
|
68
|
+
defer { lock.unlock() }
|
|
69
|
+
return children[itemId]?.value != nil
|
|
46
70
|
}
|
|
47
71
|
|
|
48
72
|
// MARK: - Group Binding
|
|
49
73
|
|
|
50
74
|
@MainActor func bindGroup(_ group: FocusableGroupView) {
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
lock.lock()
|
|
76
|
+
let existingGroup = self.group
|
|
77
|
+
lock.unlock()
|
|
78
|
+
|
|
79
|
+
if let existingGroup, existingGroup !== group {
|
|
53
80
|
logger?.warningLog(message: """
|
|
54
81
|
⚠️ Duplicate group binding detected for groupId: '\(groupId)'
|
|
55
82
|
Previous group: \(existingGroup.debugDescription)
|
|
56
83
|
New group: \(group.debugDescription)
|
|
57
|
-
|
|
84
|
+
identifier1: \(ObjectIdentifier(existingGroup))
|
|
85
|
+
identifier2: \(ObjectIdentifier(group))
|
|
86
|
+
Previous group will be unable to unbind (React Native created duplicate itemIds).
|
|
87
|
+
Old group's cleanup() will handle releasing its proxy reference.
|
|
58
88
|
""")
|
|
59
89
|
|
|
60
90
|
#if DEBUG
|
|
@@ -62,53 +92,63 @@ class GroupProxy {
|
|
|
62
92
|
#endif
|
|
63
93
|
}
|
|
64
94
|
|
|
95
|
+
lock.lock()
|
|
65
96
|
self.group = group
|
|
97
|
+
lock.unlock()
|
|
66
98
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
delayedPreferredFocus = nil
|
|
71
|
-
}
|
|
99
|
+
group.sendGroupRegisteredEvent()
|
|
100
|
+
|
|
101
|
+
coordinator?.onGroupBound(groupId: groupId)
|
|
72
102
|
}
|
|
73
103
|
|
|
74
|
-
func unbindGroup() {
|
|
104
|
+
func unbindGroup(expectedGroup: FocusableGroupView) {
|
|
105
|
+
if let group,
|
|
106
|
+
group !== expectedGroup {
|
|
107
|
+
logger?.warningLog(message: """
|
|
108
|
+
⚠️ Mismatched group unbinding detected for groupId: '\(groupId)'
|
|
109
|
+
Expected group: \(expectedGroup.debugDescription)
|
|
110
|
+
Current bound group: \(String(describing: group.debugDescription))
|
|
111
|
+
identifier1: \(ObjectIdentifier(expectedGroup))
|
|
112
|
+
identifier2: \(ObjectIdentifier(group)))
|
|
113
|
+
Skipping unbind.
|
|
114
|
+
""")
|
|
115
|
+
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
75
119
|
lock.lock()
|
|
76
|
-
|
|
120
|
+
let shouldRemoveProxy = children.isEmpty
|
|
121
|
+
lock.unlock()
|
|
77
122
|
|
|
78
|
-
if
|
|
123
|
+
if shouldRemoveProxy {
|
|
124
|
+
coordinator?.cancelCommandsForGroup(groupId: groupId)
|
|
79
125
|
GroupProxyManager.shared.removeProxy(forGroupId: groupId)
|
|
80
126
|
}
|
|
81
127
|
|
|
128
|
+
lock.lock()
|
|
82
129
|
group = nil
|
|
130
|
+
lock.unlock()
|
|
83
131
|
}
|
|
84
132
|
|
|
85
133
|
// MARK: - Preferred Focus
|
|
86
134
|
|
|
87
|
-
@MainActor
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
} else if let currentPreferred = currentGroup.getUserPreferredFocusEnvironments()?.first as? FocusableView,
|
|
91
|
-
currentPreferred === view {
|
|
92
|
-
currentGroup.setUserPreferredFocusEnvironments(nil)
|
|
135
|
+
@MainActor func updatePreferredFocus(_ view: FocusableView) {
|
|
136
|
+
guard let itemId = view.itemId else {
|
|
137
|
+
return
|
|
93
138
|
}
|
|
94
|
-
}
|
|
95
139
|
|
|
96
|
-
|
|
97
|
-
lock.lock()
|
|
98
|
-
let currentGroup = group
|
|
99
|
-
lock.unlock()
|
|
140
|
+
coordinator?.cancelCommandsForGroup(groupId: groupId)
|
|
100
141
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
let command = GroupProxyCoordinator.SetPreferredFocus(
|
|
143
|
+
groupId: groupId,
|
|
144
|
+
itemId: itemId,
|
|
145
|
+
isPreferred: view.preferredFocus,
|
|
146
|
+
manager: GroupProxyManager.shared
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
coordinator?.tryApply(command)
|
|
109
150
|
}
|
|
110
151
|
|
|
111
|
-
/// Get the bound group (if any)
|
|
112
152
|
func getGroup() -> FocusableGroupView? {
|
|
113
153
|
lock.lock()
|
|
114
154
|
defer { lock.unlock() }
|