-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathGeneral.swift
More file actions
128 lines (116 loc) · 4.86 KB
/
General.swift
File metadata and controls
128 lines (116 loc) · 4.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import Client
import ComposableArchitecture
import Foundation
import LaunchAgentManager
import Status
import SwiftUI
import XPCShared
import Logger
@Reducer
struct General {
@ObservableState
struct State: Equatable {
var xpcServiceVersion: String?
var isAccessibilityPermissionGranted: ObservedAXStatus = .unknown
var isReloading = false
}
enum Action: Equatable {
case appear
case setupLaunchAgentIfNeeded
case openExtensionManager
case reloadStatus
case finishReloading(xpcServiceVersion: String, permissionGranted: ObservedAXStatus)
case failedReloading
case retryReloading
}
@Dependency(\.toast) var toast
struct ReloadStatusCancellableId: Hashable {}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .appear:
return .run { send in
await send(.setupLaunchAgentIfNeeded)
for await _ in DistributedNotificationCenter.default().notifications(named: .serviceStatusDidChange) {
await send(.reloadStatus)
}
}
case .setupLaunchAgentIfNeeded:
return .run { send in
#if DEBUG
// do not auto install on debug build
await send(.reloadStatus)
#else
Task {
do {
try await LaunchAgentManager()
.setupLaunchAgentForTheFirstTimeIfNeeded()
} catch {
Logger.ui.error("Failed to setup launch agent. \(error.localizedDescription)")
toast(error.localizedDescription, .error)
}
await send(.reloadStatus)
}
#endif
}
case .openExtensionManager:
return .run { send in
let service = try getService()
do {
_ = try await service
.send(requestBody: ExtensionServiceRequests.OpenExtensionManager())
} catch {
toast(error.localizedDescription, .error)
await send(.failedReloading)
}
}
case .reloadStatus:
guard !state.isReloading else { return .none }
state.isReloading = true
return .run { send in
let service = try getService()
do {
let isCommunicationReady = try await service.launchIfNeeded()
if isCommunicationReady {
let xpcServiceVersion = try await service.getXPCServiceVersion().version
let isAccessibilityPermissionGranted = try await service
.getXPCServiceAccessibilityPermission()
await send(.finishReloading(
xpcServiceVersion: xpcServiceVersion,
permissionGranted: isAccessibilityPermissionGranted
))
} else {
toast("Launching service app.", .info)
try await Task.sleep(nanoseconds: 5_000_000_000)
await send(.retryReloading)
}
} catch let error as XPCCommunicationBridgeError {
Logger.ui.error("Failed to reach communication bridge. \(error.localizedDescription)")
toast(
"Failed to reach communication bridge. \(error.localizedDescription)",
.error
)
await send(.failedReloading)
} catch {
Logger.ui.error("Failed to reload status. \(error.localizedDescription)")
toast(error.localizedDescription, .error)
await send(.failedReloading)
}
}.cancellable(id: ReloadStatusCancellableId(), cancelInFlight: true)
case let .finishReloading(version, granted):
state.xpcServiceVersion = version
state.isAccessibilityPermissionGranted = granted
state.isReloading = false
return .none
case .failedReloading:
state.isReloading = false
return .none
case .retryReloading:
state.isReloading = false
return .run { send in
await send(.reloadStatus)
}
}
}
}
}