@blueid/access-capacitor 0.104.0 → 0.106.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/BlueidAccessCapacitor.podspec +2 -1
- package/dist/esm/BlueCore_pb.d.ts +12 -0
- package/dist/esm/BlueCore_pb.js +11 -0
- package/dist/esm/BlueCore_pb.js.map +1 -1
- package/dist/esm/BlueSDK_pb.d.ts +52 -0
- package/dist/esm/BlueSDK_pb.js +13 -0
- package/dist/esm/BlueSDK_pb.js.map +1 -1
- package/dist/esm/BlueSystem_pb.d.ts +12 -0
- package/dist/esm/BlueSystem_pb.js +3 -0
- package/dist/esm/BlueSystem_pb.js.map +1 -1
- package/dist/plugin.cjs.js +27 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +27 -0
- package/dist/plugin.js.map +1 -1
- package/ios/CBlueIDAccess.xcframework/Info.plist +5 -5
- package/ios/CBlueIDAccess.xcframework/ios-arm64/Headers/core/BlueCore.pb.h +9 -4
- package/ios/CBlueIDAccess.xcframework/ios-arm64/libCBlueIDAccess.a +0 -0
- package/ios/CBlueIDAccess.xcframework/ios-arm64_x86_64-simulator/Headers/core/BlueCore.pb.h +9 -4
- package/ios/CBlueIDAccess.xcframework/ios-arm64_x86_64-simulator/libCBlueIDAccess.a +0 -0
- package/ios/CBlueIDAccess.xcframework/macos-arm64_x86_64/Headers/core/BlueCore.pb.h +9 -4
- package/ios/CBlueIDAccess.xcframework/macos-arm64_x86_64/libCBlueIDAccess.a +0 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueAPI.swift +7 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueAPIProtocol.swift +20 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueAccess.swift +50 -24
- package/ios/Plugin/BlueIDAccessSDK/BlueCommands.swift +3 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueCore.pb.swift +25 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueDFU/BlueDFUPeripheralService.swift +73 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueDFU/BlueUpdateAccessDeviceFirmwareCommand.swift +252 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueDevices.swift +24 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueError.swift +1 -1
- package/ios/Plugin/BlueIDAccessSDK/BlueFetch.swift +10 -2
- package/ios/Plugin/BlueIDAccessSDK/BlueModal/BlueModal.swift +30 -5
- package/ios/Plugin/BlueIDAccessSDK/BlueModal/{BlueSynchronizeAccessDeviceModalSession.swift → BlueStepProgressModalSession.swift} +4 -4
- package/ios/Plugin/BlueIDAccessSDK/BlueModal/{BlueSynchronizeAccessDeviceModalView.swift → BlueStepProgressModalView.swift} +59 -19
- package/ios/Plugin/BlueIDAccessSDK/BlueSDK.pb.swift +234 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueSystem.pb.swift +51 -0
- package/ios/Plugin/BlueIDAccessSDK/BlueTaskRunner.swift +42 -5
- package/ios/Plugin/BlueIDAccessSDK/BlueZip.swift +30 -0
- package/package.json +1 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import CoreBluetooth
|
|
2
|
+
import Foundation
|
|
3
|
+
import NordicDFU
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @class BlueUpdateAccessDeviceFirmwareCommand
|
|
7
|
+
* A SDK command for updating firmware of nRF51 and nRF52 devices over Bluetooth LE.
|
|
8
|
+
*/
|
|
9
|
+
public class BlueUpdateAccessDeviceFirmwareCommand: BlueSdkAsyncCommand {
|
|
10
|
+
override func runAsync(arg0: Any?, arg1: Any?, arg2: Any?) async throws -> Any? {
|
|
11
|
+
return try await runAsync(
|
|
12
|
+
credentialID: blueCastArg(String.self, arg0),
|
|
13
|
+
deviceID: blueCastArg(String.self, arg1),
|
|
14
|
+
testVersion: blueCastArg(Bool.self, arg2)
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public func runAsync(credentialID: String, deviceID: String, testVersion: Bool? = false) async throws {
|
|
19
|
+
guard let credential = blueGetAccessCredential(credentialID: credentialID) else {
|
|
20
|
+
throw BlueError(.sdkCredentialNotFound)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
guard blueGetDevice(deviceID) != nil else {
|
|
24
|
+
throw BlueError(.sdkDeviceNotFound)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try await BlueUpdateAccessDeviceFirmware(sdkService)
|
|
28
|
+
.update(credential, deviceID, testVersion)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
internal class BlueUpdateAccessDeviceFirmware: LoggerDelegate, DFUServiceDelegate, DFUProgressDelegate {
|
|
33
|
+
public enum BlueUpdateAccessDeviceFirmwareTaskId {
|
|
34
|
+
case getAuthenticationToken
|
|
35
|
+
case checkLatestFirmware
|
|
36
|
+
case downloadLatestFirmware
|
|
37
|
+
case prepareUpdate
|
|
38
|
+
case startBootloader
|
|
39
|
+
case findDFUPeripheral
|
|
40
|
+
case updateFirmware
|
|
41
|
+
case waitRestart
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private let sdkService: BlueSdkService
|
|
45
|
+
|
|
46
|
+
private var semaphore: DispatchSemaphore?
|
|
47
|
+
private var controller: DFUServiceController?
|
|
48
|
+
private var task: BlueTask?
|
|
49
|
+
private var error: BlueError? = nil
|
|
50
|
+
|
|
51
|
+
init(_ sdkService: BlueSdkService) {
|
|
52
|
+
self.sdkService = sdkService
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public func update(_ credential: BlueAccessCredential, _ deviceID: String, _ testVersion: Bool? = false) async throws {
|
|
56
|
+
|
|
57
|
+
let tasks = [
|
|
58
|
+
BlueTask(
|
|
59
|
+
id: BlueUpdateAccessDeviceFirmwareTaskId.getAuthenticationToken,
|
|
60
|
+
label: blueI18n.dfuGetAuthenticationTokenTaskLabel
|
|
61
|
+
) { _, _ in
|
|
62
|
+
.result(try await self.sdkService.authenticationTokenService.getTokenAuthentication(credential: credential))
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
BlueTask(
|
|
66
|
+
id: BlueUpdateAccessDeviceFirmwareTaskId.checkLatestFirmware,
|
|
67
|
+
label: blueI18n.dfuCheckLatestFwlabel
|
|
68
|
+
) { _, runner in
|
|
69
|
+
let tokenAuthentication: BlueTokenAuthentication = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.getAuthenticationToken)
|
|
70
|
+
|
|
71
|
+
return .result(try await self.sdkService.apiService.getLatestFirmware(deviceID: deviceID, with: tokenAuthentication).getData())
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
BlueTask(
|
|
75
|
+
id: BlueUpdateAccessDeviceFirmwareTaskId.downloadLatestFirmware,
|
|
76
|
+
label: blueI18n.dfuDownloadLatestFwlabel
|
|
77
|
+
) { task, runner in
|
|
78
|
+
let latestFW: BlueGetLatestFirmwareResult = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.checkLatestFirmware)
|
|
79
|
+
|
|
80
|
+
task.updateLabel("\(blueI18n.dfuDownloadLatestFwlabel) (\(self.getFirmwareVersion(latestFW, testVersion)))")
|
|
81
|
+
|
|
82
|
+
let url = testVersion == true ? latestFW.test?.url : latestFW.production?.url
|
|
83
|
+
|
|
84
|
+
guard let url = url else {
|
|
85
|
+
throw BlueError(.sdkInvalidFirmwareURL, detail: "NULL")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return .result(try await self.downloadLatestFirmware(url: url))
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
BlueTask(
|
|
92
|
+
id: BlueUpdateAccessDeviceFirmwareTaskId.prepareUpdate,
|
|
93
|
+
label: blueI18n.dfuPrepareUpdateLabel
|
|
94
|
+
) { _, runner in
|
|
95
|
+
let zip: Data = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.downloadLatestFirmware)
|
|
96
|
+
|
|
97
|
+
return .result(try self.prepareUpdate(zip))
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
BlueTask(
|
|
101
|
+
id: BlueUpdateAccessDeviceFirmwareTaskId.startBootloader,
|
|
102
|
+
label: blueI18n.dfuStartBootloaderLabel
|
|
103
|
+
) { _, _ in
|
|
104
|
+
return .result(try await self.startBootloader(deviceID))
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
BlueTask(
|
|
108
|
+
id: BlueUpdateAccessDeviceFirmwareTaskId.findDFUPeripheral,
|
|
109
|
+
label: blueI18n.dfuInitializationLabel
|
|
110
|
+
) { _, _ in
|
|
111
|
+
return .result(try await self.findPeripheral())
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
BlueTask(
|
|
115
|
+
id: BlueUpdateAccessDeviceFirmwareTaskId.updateFirmware,
|
|
116
|
+
label: blueI18n.dfuUpdateFwlabel,
|
|
117
|
+
progress: 0,
|
|
118
|
+
cancelHandler: {
|
|
119
|
+
_ = self.controller?.abort()
|
|
120
|
+
},
|
|
121
|
+
handler: { task, runner in
|
|
122
|
+
let latestFW: BlueGetLatestFirmwareResult = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.checkLatestFirmware)
|
|
123
|
+
let urlToZipFile: URL = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.prepareUpdate)
|
|
124
|
+
let peripheral: CBPeripheral = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.findDFUPeripheral)
|
|
125
|
+
|
|
126
|
+
task.updateLabel("\(blueI18n.dfuUpdateFwlabel) (\(self.getFirmwareVersion(latestFW, testVersion)))")
|
|
127
|
+
|
|
128
|
+
self.task = task
|
|
129
|
+
|
|
130
|
+
return .result(try self.update(peripheral, urlToZipFile))
|
|
131
|
+
}
|
|
132
|
+
),
|
|
133
|
+
|
|
134
|
+
BlueTask(
|
|
135
|
+
id: BlueUpdateAccessDeviceFirmwareTaskId.waitRestart,
|
|
136
|
+
label: blueI18n.dfuWaitForDeviceToRestartTaskLabel
|
|
137
|
+
) { _, _ in
|
|
138
|
+
return .result(try await waitForDeviceAvailability(deviceID, timeout: 5, maxRetries: 6))
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
let runner = BlueSerialTaskRunner(tasks)
|
|
143
|
+
|
|
144
|
+
#if os(iOS) || os(watchOS)
|
|
145
|
+
try await blueShowUpdateAccessDeviceFirmwareModal(runner)
|
|
146
|
+
#else
|
|
147
|
+
try await runner.execute(true)
|
|
148
|
+
#endif
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private func getFirmwareVersion(_ latestFW: BlueGetLatestFirmwareResult, _ testVersion: Bool? = false) -> String {
|
|
152
|
+
if testVersion == true {
|
|
153
|
+
if let testFW = latestFW.test {
|
|
154
|
+
if let minor = testFW.testVersion {
|
|
155
|
+
return "\(minor)"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} else if let prodFW = latestFW.production {
|
|
159
|
+
return "\(prodFW.version)"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return "?"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private func downloadLatestFirmware(url string: String) async throws -> Data {
|
|
166
|
+
guard let url = URL(string: string) else {
|
|
167
|
+
throw BlueError(.sdkInvalidFirmwareURL)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return try await BlueFetch.get(url: url).getData()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private func prepareUpdate(_ zip: Data) throws -> URL {
|
|
174
|
+
let extractedURL = try BlueZip.extract(data: zip)
|
|
175
|
+
|
|
176
|
+
return extractedURL.appendingPathComponent("dfu_application.zip")
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private func startBootloader(_ deviceID: String) async throws {
|
|
180
|
+
try await blueTerminalRun(deviceID: deviceID, action: "BOOTLD")
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private func findPeripheral() async throws -> CBPeripheral {
|
|
184
|
+
let service = BlueDFUPeripheralService()
|
|
185
|
+
|
|
186
|
+
defer {
|
|
187
|
+
service.destroy()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return try await service.find()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private func update(_ peripheral: CBPeripheral, _ urlToZipFile: URL) throws {
|
|
194
|
+
self.semaphore = DispatchSemaphore(value: 0)
|
|
195
|
+
|
|
196
|
+
let firmware = try DFUFirmware(urlToZipFile: urlToZipFile)
|
|
197
|
+
|
|
198
|
+
let initiator = DFUServiceInitiator()
|
|
199
|
+
initiator.logger = self
|
|
200
|
+
initiator.delegate = self
|
|
201
|
+
initiator.progressDelegate = self
|
|
202
|
+
|
|
203
|
+
self.controller = initiator
|
|
204
|
+
.with(firmware: firmware)
|
|
205
|
+
.start(targetWithIdentifier: peripheral.identifier)
|
|
206
|
+
|
|
207
|
+
self.semaphore?.wait()
|
|
208
|
+
|
|
209
|
+
if let error = error {
|
|
210
|
+
throw error
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// MARK: - LoggerDelegate API
|
|
215
|
+
|
|
216
|
+
public func logWith(_ level: NordicDFU.LogLevel, message: String) {
|
|
217
|
+
switch (level) {
|
|
218
|
+
case .debug, .application, .verbose:
|
|
219
|
+
blueLogDebug(message)
|
|
220
|
+
break
|
|
221
|
+
case .info:
|
|
222
|
+
blueLogInfo(message)
|
|
223
|
+
break
|
|
224
|
+
case .warning:
|
|
225
|
+
blueLogWarn(message)
|
|
226
|
+
break
|
|
227
|
+
case .error:
|
|
228
|
+
blueLogError(message)
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// MARK: - DFUServiceDelegate API
|
|
234
|
+
|
|
235
|
+
public func dfuStateDidChange(to state: NordicDFU.DFUState) {
|
|
236
|
+
if state == .completed || state == .aborted {
|
|
237
|
+
semaphore?.signal()
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public func dfuError(_ error: NordicDFU.DFUError, didOccurWithMessage message: String) {
|
|
242
|
+
self.error = BlueError(.error, detail: message)
|
|
243
|
+
|
|
244
|
+
semaphore?.signal()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// MARK: - DFUProgressDelegate API
|
|
248
|
+
|
|
249
|
+
public func dfuProgressDidChange(for part: Int, outOf totalParts: Int, to progress: Int, currentSpeedBytesPerSecond: Double, avgSpeedBytesPerSecond: Double) {
|
|
250
|
+
self.task?.updateProgress(Float(progress))
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -10,6 +10,30 @@ internal func blueSetMaxDeviceAgeSeconds(_ newMaxDeviceAgeSeconds: Double) {
|
|
|
10
10
|
maxDeviceAgeSeconds = max(newMaxDeviceAgeSeconds, 1)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/// Waits asynchronously for a device with the specified ID to become available.
|
|
14
|
+
/// This function waits for a device with the given ID to be discovered within a predefined timeout period, with a maximum number of retries.
|
|
15
|
+
/// - parameters:
|
|
16
|
+
/// - deviceID: The ID of the device to wait for.
|
|
17
|
+
/// - timeout: The duration in seconds to wait for the device to be discovered each time. Default value is 10 seconds.
|
|
18
|
+
/// - maxRetries: The maximum number of retries before giving up. Default value is 3.
|
|
19
|
+
/// - throws: An error of type `BlueError` if the device is not found within the specified timeout period and maximum retries.
|
|
20
|
+
|
|
21
|
+
internal func waitForDeviceAvailability(_ deviceID: String, timeout: Int = 10, maxRetries: Int = 3) async throws {
|
|
22
|
+
var attempts = 0
|
|
23
|
+
|
|
24
|
+
while attempts < maxRetries {
|
|
25
|
+
try? await Task.sleep(nanoseconds: UInt64(blueSecondsToNanoseconds(timeout)))
|
|
26
|
+
|
|
27
|
+
if blueGetDevice(deviceID) != nil {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
attempts += 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw BlueError(.sdkDeviceNotFound)
|
|
35
|
+
}
|
|
36
|
+
|
|
13
37
|
internal func blueGetDevice(_ deviceID: String) -> BlueDevice? {
|
|
14
38
|
for device in blueDevices {
|
|
15
39
|
if (device.info.deviceID == deviceID) {
|
|
@@ -45,7 +45,7 @@ public final class BlueError: Error, LocalizedError, Equatable {
|
|
|
45
45
|
self.detail = nil
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
public init(_ returnCode: BlueReturnCode, cause: Error, detail: String? = nil) {
|
|
48
|
+
public init(_ returnCode: BlueReturnCode, cause: Error? = nil, detail: String? = nil) {
|
|
49
49
|
self.returnCode = returnCode
|
|
50
50
|
self.cause = cause
|
|
51
51
|
self.detail = detail
|
|
@@ -15,6 +15,10 @@ struct BlueFetchResponse<T> where T: Decodable {
|
|
|
15
15
|
|
|
16
16
|
func getData() throws -> T {
|
|
17
17
|
guard let data = data else {
|
|
18
|
+
if let rawData = rawData as? T {
|
|
19
|
+
return rawData
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
let status: String = statusCode?.description ?? "Unknown"
|
|
19
23
|
var description = ""
|
|
20
24
|
|
|
@@ -40,7 +44,6 @@ struct BlueFetchResponse<T> where T: Decodable {
|
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
@available(macOS 12.0, *)
|
|
44
47
|
class BlueFetch {
|
|
45
48
|
|
|
46
49
|
static func post<T>(url: URL, data: Data?, config: BlueFetchConfig? = nil) async throws -> BlueFetchResponse<T> where T: Decodable {
|
|
@@ -65,18 +68,23 @@ class BlueFetch {
|
|
|
65
68
|
|
|
66
69
|
var statusCode: Int?
|
|
67
70
|
var decodedData: T?
|
|
71
|
+
var contentType: String?
|
|
68
72
|
|
|
69
73
|
do {
|
|
70
74
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
71
75
|
|
|
72
76
|
if let httpResponse = response as? HTTPURLResponse {
|
|
73
77
|
statusCode = httpResponse.statusCode
|
|
78
|
+
contentType = httpResponse.value(forHTTPHeaderField: "content-type")
|
|
74
79
|
|
|
75
80
|
blueLogDebug("Status code: \(httpResponse.statusCode)")
|
|
81
|
+
blueLogDebug("Content-Type: \(String(describing: contentType))")
|
|
76
82
|
blueLogDebug("Data: \(String(describing: String(data: data, encoding: .utf8)))")
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
|
|
85
|
+
let isJSON = contentType == "application/json"
|
|
86
|
+
|
|
87
|
+
if (statusCode == 200 && isJSON) {
|
|
80
88
|
do {
|
|
81
89
|
decodedData = try JSONDecoder().decode(T.self, from: data)
|
|
82
90
|
} catch {
|
|
@@ -60,11 +60,36 @@ public func blueShowAccessDeviceModal(_ task: @escaping () async throws -> BlueO
|
|
|
60
60
|
/// Displays a modal view (sheet) which performs tasks related to synchronizing a device.
|
|
61
61
|
/// - parameter runner: The Task Runner.
|
|
62
62
|
public func blueShowSynchronizeAccessDeviceModal(_ runner: BlueTaskRunner) async throws {
|
|
63
|
-
|
|
63
|
+
try await blueShowStepProgressModal(
|
|
64
|
+
title: blueI18n.syncDeviceInProgressTitle,
|
|
65
|
+
failedTitle: blueI18n.syncDeviceFailedTitle,
|
|
66
|
+
completedTitle: blueI18n.syncDeviceCompletedTitle,
|
|
67
|
+
runner: runner
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Displays a modal view (sheet) which performs tasks related to updating a device firmware.
|
|
72
|
+
/// - parameter runner: The Task Runner.
|
|
73
|
+
public func blueShowUpdateAccessDeviceFirmwareModal(_ runner: BlueTaskRunner) async throws {
|
|
74
|
+
try await blueShowStepProgressModal(
|
|
75
|
+
title: blueI18n.dfuInProgressTitle,
|
|
76
|
+
failedTitle: blueI18n.dfuFailedTitle,
|
|
77
|
+
completedTitle: blueI18n.dfuCompletedTitle,
|
|
78
|
+
runner: runner
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private func blueShowStepProgressModal(
|
|
83
|
+
title: String,
|
|
84
|
+
failedTitle: String,
|
|
85
|
+
completedTitle: String,
|
|
86
|
+
runner: BlueTaskRunner
|
|
87
|
+
) async throws {
|
|
88
|
+
let session = BlueStepProgressModalSession()
|
|
64
89
|
|
|
65
90
|
blueRunInMainThread {
|
|
66
91
|
session.begin(
|
|
67
|
-
title:
|
|
92
|
+
title: title,
|
|
68
93
|
tasks: runner.getTasks(),
|
|
69
94
|
dismiss: blueI18n.cmnCancelLabel
|
|
70
95
|
) {
|
|
@@ -87,17 +112,17 @@ public func blueShowSynchronizeAccessDeviceModal(_ runner: BlueTaskRunner) async
|
|
|
87
112
|
session.updateDismiss(blueI18n.cmnCloseLabel)
|
|
88
113
|
|
|
89
114
|
if runner.isFailed() {
|
|
90
|
-
session.updateTitle(
|
|
115
|
+
session.updateTitle(failedTitle)
|
|
91
116
|
BlueSound.shared.play(BlueNegativeSoundSystemID)
|
|
92
117
|
} else {
|
|
93
|
-
session.updateTitle(
|
|
118
|
+
session.updateTitle(completedTitle)
|
|
94
119
|
BlueSound.shared.play(BluePositiveSoundSystemID)
|
|
95
120
|
}
|
|
96
121
|
}
|
|
97
122
|
}
|
|
98
123
|
} catch {
|
|
99
124
|
blueRunInMainThread {
|
|
100
|
-
session.updateTitle(
|
|
125
|
+
session.updateTitle(failedTitle)
|
|
101
126
|
session.updateDismiss(blueI18n.cmnCloseLabel)
|
|
102
127
|
BlueSound.shared.play(BlueNegativeSoundSystemID)
|
|
103
128
|
}
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import AVFoundation
|
|
3
3
|
import SwiftUI
|
|
4
4
|
|
|
5
|
-
private class HostingController: UIHostingController<
|
|
5
|
+
private class HostingController: UIHostingController<BlueStepProgressModalView> {
|
|
6
6
|
override var prefersHomeIndicatorAutoHidden: Bool {
|
|
7
7
|
return true
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
internal class
|
|
12
|
-
private let viewModel =
|
|
11
|
+
internal class BlueStepProgressModalSession {
|
|
12
|
+
private let viewModel = BlueStepProgressModalViewModel()
|
|
13
13
|
private var isInvalidated: Bool = false
|
|
14
14
|
|
|
15
15
|
func begin(title: String, tasks: [BlueTask], dismiss: String, _ onDismiss: @escaping () -> Void) {
|
|
@@ -18,7 +18,7 @@ internal class BlueSynchronizeAccessDeviceModalSession {
|
|
|
18
18
|
viewModel.dismiss = dismiss
|
|
19
19
|
|
|
20
20
|
let hostingController = HostingController(
|
|
21
|
-
rootView:
|
|
21
|
+
rootView: BlueStepProgressModalView(viewModel) { onDismiss() }
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
hostingController.view.backgroundColor = .clear
|
|
@@ -42,24 +42,28 @@ private func getSymbol(_ status: BlueTaskStatus) -> String {
|
|
|
42
42
|
class BlueTaskModel: ObservableObject, Identifiable {
|
|
43
43
|
@Published var label: String
|
|
44
44
|
@Published var status: BlueTaskStatus
|
|
45
|
+
@Published var progress: Float?
|
|
45
46
|
@Published var statusColor: Color
|
|
46
47
|
@Published var textColor: Color
|
|
47
48
|
@Published var symbol: String
|
|
48
49
|
@Published var errorDescription: String
|
|
49
50
|
@Published var isLast: Bool
|
|
50
51
|
|
|
51
|
-
private var
|
|
52
|
+
private var labelSubscriber: AnyCancellable?
|
|
53
|
+
private var statusSubscriber: AnyCancellable?
|
|
54
|
+
private var progressSubscriber: AnyCancellable?
|
|
52
55
|
|
|
53
56
|
init(_ task: BlueTask, _ isLast: Bool) {
|
|
54
|
-
self.label = task.label
|
|
57
|
+
self.label = task.label.value
|
|
55
58
|
self.status = task.status.value
|
|
59
|
+
self.progress = task.progress?.value
|
|
56
60
|
self.textColor = getTextColor(task.status.value)
|
|
57
61
|
self.statusColor = getColor(task.status.value)
|
|
58
62
|
self.symbol = getSymbol(task.status.value)
|
|
59
63
|
self.errorDescription = task.errorDescription ?? ""
|
|
60
64
|
self.isLast = isLast
|
|
61
65
|
|
|
62
|
-
self.
|
|
66
|
+
self.statusSubscriber = task.status.sink{ [weak self] status in
|
|
63
67
|
guard let self = self else { return }
|
|
64
68
|
|
|
65
69
|
self.status = status
|
|
@@ -70,6 +74,20 @@ class BlueTaskModel: ObservableObject, Identifiable {
|
|
|
70
74
|
|
|
71
75
|
self.objectWillChange.send()
|
|
72
76
|
}
|
|
77
|
+
|
|
78
|
+
self.progressSubscriber = task.progress?.sink{ [weak self] progress in
|
|
79
|
+
guard let self = self else { return }
|
|
80
|
+
|
|
81
|
+
self.progress = progress
|
|
82
|
+
self.objectWillChange.send()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
self.labelSubscriber = task.label.sink{ [weak self] label in
|
|
86
|
+
guard let self = self else { return }
|
|
87
|
+
|
|
88
|
+
self.label = label
|
|
89
|
+
self.objectWillChange.send()
|
|
90
|
+
}
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
93
|
|
|
@@ -98,11 +116,12 @@ struct TaskView: View {
|
|
|
98
116
|
|
|
99
117
|
HStack(alignment: .center, spacing: 0) {
|
|
100
118
|
ZStack(alignment: .leading) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
if (!task.isLast) {
|
|
120
|
+
Rectangle()
|
|
121
|
+
.fill(task.statusColor)
|
|
122
|
+
.frame(width: 2)
|
|
123
|
+
.padding(.horizontal, 10.5)
|
|
124
|
+
}
|
|
106
125
|
|
|
107
126
|
if !task.errorDescription.isEmpty {
|
|
108
127
|
Text(task.errorDescription)
|
|
@@ -112,6 +131,12 @@ struct TaskView: View {
|
|
|
112
131
|
.padding(.bottom, 5)
|
|
113
132
|
.fixedSize(horizontal: false, vertical: true)
|
|
114
133
|
.alignmentGuide(.leading) { d in d[.leading] }
|
|
134
|
+
} else {
|
|
135
|
+
if task.status == .started && task.progress != nil {
|
|
136
|
+
ProgressView(value: task.progress, total: 100)
|
|
137
|
+
.padding(.leading, 32)
|
|
138
|
+
.alignmentGuide(.leading) { d in d[.leading] }
|
|
139
|
+
}
|
|
115
140
|
}
|
|
116
141
|
}
|
|
117
142
|
}
|
|
@@ -119,7 +144,7 @@ struct TaskView: View {
|
|
|
119
144
|
}
|
|
120
145
|
}
|
|
121
146
|
|
|
122
|
-
class
|
|
147
|
+
class BlueStepProgressModalViewModel: ObservableObject {
|
|
123
148
|
@Published var title = ""
|
|
124
149
|
@Published var dismiss: String = ""
|
|
125
150
|
@Published var dismissEnabled: Bool = true
|
|
@@ -133,10 +158,10 @@ class BlueSynchronizeAccessDeviceModalViewModel: ObservableObject {
|
|
|
133
158
|
}
|
|
134
159
|
}
|
|
135
160
|
|
|
136
|
-
struct
|
|
137
|
-
@ObservedObject private var vm:
|
|
161
|
+
struct BlueStepProgressModalView: View {
|
|
162
|
+
@ObservedObject private var vm: BlueStepProgressModalViewModel
|
|
138
163
|
|
|
139
|
-
internal var height: CGFloat =
|
|
164
|
+
internal var height: CGFloat = 570
|
|
140
165
|
internal var backgroundColor: UIColor = .white
|
|
141
166
|
internal var foregroundColor: UIColor = .black
|
|
142
167
|
|
|
@@ -144,7 +169,7 @@ struct BlueSynchronizeAccessDeviceModalView: View {
|
|
|
144
169
|
private let cornerRadius: CGFloat = 35
|
|
145
170
|
|
|
146
171
|
public init(
|
|
147
|
-
_ vm:
|
|
172
|
+
_ vm: BlueStepProgressModalViewModel,
|
|
148
173
|
_ onDismiss: @escaping () -> Void)
|
|
149
174
|
{
|
|
150
175
|
self.vm = vm
|
|
@@ -212,12 +237,12 @@ struct BlueSynchronizeAccessDeviceModalView: View {
|
|
|
212
237
|
}
|
|
213
238
|
}
|
|
214
239
|
|
|
215
|
-
let noop: (BlueSerialTaskRunner) async throws -> BlueTaskResult = { _ in .result(nil) }
|
|
240
|
+
let noop: (BlueTask, BlueSerialTaskRunner) async throws -> BlueTaskResult = { _, _ in .result(nil) }
|
|
216
241
|
|
|
217
|
-
struct
|
|
242
|
+
struct BlueStepProgressModalView_Preview: PreviewProvider {
|
|
218
243
|
static var previews: some View {
|
|
219
|
-
|
|
220
|
-
|
|
244
|
+
BlueStepProgressModalView(
|
|
245
|
+
BlueStepProgressModalViewModel(
|
|
221
246
|
title: "Synchronization has failed (Worst case)",
|
|
222
247
|
dismiss: "Done",
|
|
223
248
|
tasks: Array(1..<12).map { element in
|
|
@@ -232,8 +257,8 @@ struct BlueSynchronizeAccessDeviceModalView_Preview: PreviewProvider {
|
|
|
232
257
|
)
|
|
233
258
|
) {}
|
|
234
259
|
|
|
235
|
-
|
|
236
|
-
|
|
260
|
+
BlueStepProgressModalView(
|
|
261
|
+
BlueStepProgressModalViewModel(
|
|
237
262
|
title: "Cancelling...",
|
|
238
263
|
dismiss: "Cancel",
|
|
239
264
|
dismissEnabled: false,
|
|
@@ -248,6 +273,21 @@ struct BlueSynchronizeAccessDeviceModalView_Preview: PreviewProvider {
|
|
|
248
273
|
}
|
|
249
274
|
)
|
|
250
275
|
) {}
|
|
276
|
+
|
|
277
|
+
BlueStepProgressModalView(
|
|
278
|
+
BlueStepProgressModalViewModel(
|
|
279
|
+
title: "DFU",
|
|
280
|
+
dismiss: "Cancel",
|
|
281
|
+
tasks: Array(1..<7).enumerated().map { (index, element) in
|
|
282
|
+
BlueTask(
|
|
283
|
+
id: element.description,
|
|
284
|
+
label: "Task label - \(element.description)",
|
|
285
|
+
status: .ready,
|
|
286
|
+
handler: noop
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
) {}
|
|
251
291
|
}
|
|
252
292
|
}
|
|
253
293
|
#endif
|