@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.
Files changed (39) hide show
  1. package/BlueidAccessCapacitor.podspec +2 -1
  2. package/dist/esm/BlueCore_pb.d.ts +12 -0
  3. package/dist/esm/BlueCore_pb.js +11 -0
  4. package/dist/esm/BlueCore_pb.js.map +1 -1
  5. package/dist/esm/BlueSDK_pb.d.ts +52 -0
  6. package/dist/esm/BlueSDK_pb.js +13 -0
  7. package/dist/esm/BlueSDK_pb.js.map +1 -1
  8. package/dist/esm/BlueSystem_pb.d.ts +12 -0
  9. package/dist/esm/BlueSystem_pb.js +3 -0
  10. package/dist/esm/BlueSystem_pb.js.map +1 -1
  11. package/dist/plugin.cjs.js +27 -0
  12. package/dist/plugin.cjs.js.map +1 -1
  13. package/dist/plugin.js +27 -0
  14. package/dist/plugin.js.map +1 -1
  15. package/ios/CBlueIDAccess.xcframework/Info.plist +5 -5
  16. package/ios/CBlueIDAccess.xcframework/ios-arm64/Headers/core/BlueCore.pb.h +9 -4
  17. package/ios/CBlueIDAccess.xcframework/ios-arm64/libCBlueIDAccess.a +0 -0
  18. package/ios/CBlueIDAccess.xcframework/ios-arm64_x86_64-simulator/Headers/core/BlueCore.pb.h +9 -4
  19. package/ios/CBlueIDAccess.xcframework/ios-arm64_x86_64-simulator/libCBlueIDAccess.a +0 -0
  20. package/ios/CBlueIDAccess.xcframework/macos-arm64_x86_64/Headers/core/BlueCore.pb.h +9 -4
  21. package/ios/CBlueIDAccess.xcframework/macos-arm64_x86_64/libCBlueIDAccess.a +0 -0
  22. package/ios/Plugin/BlueIDAccessSDK/BlueAPI.swift +7 -0
  23. package/ios/Plugin/BlueIDAccessSDK/BlueAPIProtocol.swift +20 -0
  24. package/ios/Plugin/BlueIDAccessSDK/BlueAccess.swift +50 -24
  25. package/ios/Plugin/BlueIDAccessSDK/BlueCommands.swift +3 -0
  26. package/ios/Plugin/BlueIDAccessSDK/BlueCore.pb.swift +25 -0
  27. package/ios/Plugin/BlueIDAccessSDK/BlueDFU/BlueDFUPeripheralService.swift +73 -0
  28. package/ios/Plugin/BlueIDAccessSDK/BlueDFU/BlueUpdateAccessDeviceFirmwareCommand.swift +252 -0
  29. package/ios/Plugin/BlueIDAccessSDK/BlueDevices.swift +24 -0
  30. package/ios/Plugin/BlueIDAccessSDK/BlueError.swift +1 -1
  31. package/ios/Plugin/BlueIDAccessSDK/BlueFetch.swift +10 -2
  32. package/ios/Plugin/BlueIDAccessSDK/BlueModal/BlueModal.swift +30 -5
  33. package/ios/Plugin/BlueIDAccessSDK/BlueModal/{BlueSynchronizeAccessDeviceModalSession.swift → BlueStepProgressModalSession.swift} +4 -4
  34. package/ios/Plugin/BlueIDAccessSDK/BlueModal/{BlueSynchronizeAccessDeviceModalView.swift → BlueStepProgressModalView.swift} +59 -19
  35. package/ios/Plugin/BlueIDAccessSDK/BlueSDK.pb.swift +234 -0
  36. package/ios/Plugin/BlueIDAccessSDK/BlueSystem.pb.swift +51 -0
  37. package/ios/Plugin/BlueIDAccessSDK/BlueTaskRunner.swift +42 -5
  38. package/ios/Plugin/BlueIDAccessSDK/BlueZip.swift +30 -0
  39. 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
- if (statusCode == 200) {
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
- let session = BlueSynchronizeAccessDeviceModalSession()
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: blueI18n.syncDeviceInProgressTitle,
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(blueI18n.syncDeviceFailedTitle)
115
+ session.updateTitle(failedTitle)
91
116
  BlueSound.shared.play(BlueNegativeSoundSystemID)
92
117
  } else {
93
- session.updateTitle(blueI18n.syncDeviceCompletedTitle)
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(blueI18n.syncDeviceFailedTitle)
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<BlueSynchronizeAccessDeviceModalView> {
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 BlueSynchronizeAccessDeviceModalSession {
12
- private let viewModel = BlueSynchronizeAccessDeviceModalViewModel()
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: BlueSynchronizeAccessDeviceModalView(viewModel) { onDismiss() }
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 subscriber: AnyCancellable?
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.subscriber = task.status.sink{ [weak self] status in
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
- Rectangle()
102
- .fill(task.statusColor)
103
- .frame(width: 2)
104
- .padding(.horizontal, 10.5)
105
- .hidden(task.isLast)
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 BlueSynchronizeAccessDeviceModalViewModel: ObservableObject {
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 BlueSynchronizeAccessDeviceModalView: View {
137
- @ObservedObject private var vm: BlueSynchronizeAccessDeviceModalViewModel
161
+ struct BlueStepProgressModalView: View {
162
+ @ObservedObject private var vm: BlueStepProgressModalViewModel
138
163
 
139
- internal var height: CGFloat = 550
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: BlueSynchronizeAccessDeviceModalViewModel,
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 BlueSynchronizeAccessDeviceModalView_Preview: PreviewProvider {
242
+ struct BlueStepProgressModalView_Preview: PreviewProvider {
218
243
  static var previews: some View {
219
- BlueSynchronizeAccessDeviceModalView(
220
- BlueSynchronizeAccessDeviceModalViewModel(
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
- BlueSynchronizeAccessDeviceModalView(
236
- BlueSynchronizeAccessDeviceModalViewModel(
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