From ab7acf086cfad4fb31fc775df60d264f26f257c8 Mon Sep 17 00:00:00 2001 From: philips77 Date: Tue, 20 Dec 2022 13:17:44 +0100 Subject: [PATCH 01/22] Minor code improvements --- App/dfu/ContentView.swift | 7 +- App/dfu/bluetooth/BluetoothDevice.swift | 7 +- App/dfu/bluetooth/BluetoothManager.swift | 62 +++--- App/dfu/scanner/ScannerView.swift | 16 +- App/dfu/screens/DeviceSectionView.swift | 13 +- App/dfu/screens/FileSectionView.swift | 15 +- App/dfu/screens/ProgressSectionView.swift | 23 ++- .../screens/ProgressSectionViewEntity.swift | 8 +- App/dfu/screens/SettingsView.swift | 21 +- App/dfu/theme/AbortButtonStyle.swift | 108 +++++------ App/dfu/theme/ButtonStyle.swift | 111 +++++------ App/dfu/theme/DfuIds.swift | 1 - App/dfu/theme/DfuStrings.swift | 10 +- App/dfu/theme/ImageStyle.swift | 4 +- App/dfu/theme/ThemeColor.swift | 7 +- App/dfu/viewmodel/DfuProgress.swift | 2 + App/dfu/viewmodel/DfuViewModel.swift | 24 +-- App/dfu/views/NumberOfPacketsDialog.swift | 12 +- App/dfu/views/ProgressItemView.swift | 10 +- App/dfu/views/ResultItemView.swift | 8 +- App/dfu/views/StatusItemView.swift | 6 +- App/dfu/views/TextAlertDialog.swift | 180 +++++++++--------- App/dfu/welcome/WelcomeScreen.swift | 10 +- 23 files changed, 342 insertions(+), 323 deletions(-) diff --git a/App/dfu/ContentView.swift b/App/dfu/ContentView.swift index 41c6f59f..9522dad1 100644 --- a/App/dfu/ContentView.swift +++ b/App/dfu/ContentView.swift @@ -32,13 +32,14 @@ import SwiftUI import os.log struct ContentView: View { - private let bleManager: BluetoothManager = BluetoothManager() //can be put as Environment - @StateObject private var viewModel = DfuViewModel() + @StateObject + private var viewModel = DfuViewModel() - @State private var showWelcomeScreen: Bool? + @State + private var showWelcomeScreen: Bool? var body: some View { ScrollView { diff --git a/App/dfu/bluetooth/BluetoothDevice.swift b/App/dfu/bluetooth/BluetoothDevice.swift index 7952fe72..03207af3 100644 --- a/App/dfu/bluetooth/BluetoothDevice.swift +++ b/App/dfu/bluetooth/BluetoothDevice.swift @@ -36,18 +36,15 @@ import CoreBluetooth struct BluetoothDevice : Identifiable { let id = UUID() - let peripheral: CBPeripheral - let rssi: NSNumber - let name: String? func getSignalStrength() -> SignalStrength { - if (rssi.compare(NSNumber(-65)) == ComparisonResult.orderedDescending) { + if rssi.compare(NSNumber(-65)) == .orderedDescending { return SignalStrength.strong } - else if (rssi.compare(NSNumber(-85)) == ComparisonResult.orderedDescending) { + else if rssi.compare(NSNumber(-85)) == .orderedDescending { return SignalStrength.normal } else { diff --git a/App/dfu/bluetooth/BluetoothManager.swift b/App/dfu/bluetooth/BluetoothManager.swift index 23140af8..9b7cdc85 100644 --- a/App/dfu/bluetooth/BluetoothManager.swift +++ b/App/dfu/bluetooth/BluetoothManager.swift @@ -35,7 +35,6 @@ import os.log import CoreBluetooth class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { - private let MIN_RSSI = NSNumber(-65) @Published var devices: [BluetoothDevice] = [] @@ -51,7 +50,6 @@ class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { override init() { super.init() - os_log("init") centralManager = CBCentralManager(delegate: self, queue: nil) } @@ -60,8 +58,7 @@ class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { if !nearbyOnlyFilter { return true } - let result: ComparisonResult = device.rssi.compare(MIN_RSSI) - return result == ComparisonResult.orderedDescending + return device.rssi.compare(MIN_RSSI) == .orderedDescending }.filter { device in if !withNameOnlyFilter { return true @@ -92,38 +89,16 @@ class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { extension BluetoothManager: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { + os_log("BluetoothManager status: %@", central.state.name) + if central.state == CBManagerState.poweredOn { - os_log("BLE powered on") // Turned on isBluetoothReady = true runScanningWhenNeeded() } else { isBluetoothReady = false - os_log("Something wrong with BLE") - // Not on, but can have different issues - } - - var consoleLog = "" - - switch central.state { - case .poweredOff: - consoleLog = "BLE is powered off" - case .poweredOn: - consoleLog = "BLE is poweredOn" - case .resetting: - consoleLog = "BLE is resetting" - case .unauthorized: - consoleLog = "BLE is unauthorized" - case .unknown: - consoleLog = "BLE is unknown" - case .unsupported: - consoleLog = "BLE is unsupported" - default: - consoleLog = "default" } - - os_log("BluetoothManager status: %@", consoleLog) } func centralManager( @@ -132,11 +107,9 @@ extension BluetoothManager: CBCentralManagerDelegate { advertisementData: [String : Any], rssi RSSI: NSNumber ) { - os_log("Device: \(peripheral.name ?? "NO_NAME"), Rssi: \(RSSI)") - - let pname = advertisementData[CBAdvertisementDataLocalNameKey] as? String - let device = BluetoothDevice(peripheral: peripheral, rssi: RSSI, name: pname) - let index = devices.map { $0.peripheral }.firstIndex(of: peripheral) + let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String + let device = BluetoothDevice(peripheral: peripheral, rssi: RSSI, name: name) + let index = devices.firstIndex { $0.peripheral == peripheral } if let index = index { devices[index] = device } else { @@ -144,3 +117,26 @@ extension BluetoothManager: CBCentralManagerDelegate { } } } + +private extension CBManagerState { + + var name: String { + switch self { + case .poweredOff: + return "BLE is powered off" + case .poweredOn: + return "BLE is powered on" + case .resetting: + return "BLE is resetting" + case .unauthorized: + return "BLE is unauthorized" + case .unknown: + return "BLE is unknown" + case .unsupported: + return "BLE is unsupported" + default: + return "default" + } + } + +} diff --git a/App/dfu/scanner/ScannerView.swift b/App/dfu/scanner/ScannerView.swift index 51d7a4d9..0597ee62 100644 --- a/App/dfu/scanner/ScannerView.swift +++ b/App/dfu/scanner/ScannerView.swift @@ -36,11 +36,9 @@ struct ScannerView: View { @Environment(\.presentationMode) var presentationMode - @ObservedObject - var viewModel: DfuViewModel + @ObservedObject var viewModel: DfuViewModel - @StateObject - var bluetoothManager: BluetoothManager = BluetoothManager() + @StateObject var bluetoothManager: BluetoothManager = BluetoothManager() var body: some View { List { @@ -64,10 +62,12 @@ struct ScannerView: View { Section(header: Text(DfuStrings.devices.text)) { ForEach(bluetoothManager.filteredDevices()) { device in - Button(action: { - viewModel.device = device - self.presentationMode.wrappedValue.dismiss() - }) { + Button( + action: { + viewModel.device = device + self.presentationMode.wrappedValue.dismiss() + } + ) { HStack { Text(device.name ?? DfuStrings.noName.text) .frame(maxWidth: .infinity, alignment: .leading) diff --git a/App/dfu/screens/DeviceSectionView.swift b/App/dfu/screens/DeviceSectionView.swift index 2b755aa1..63990218 100644 --- a/App/dfu/screens/DeviceSectionView.swift +++ b/App/dfu/screens/DeviceSectionView.swift @@ -32,8 +32,7 @@ import SwiftUI struct DeviceSectionView: View { - @ObservedObject - var viewModel: DfuViewModel + @ObservedObject var viewModel: DfuViewModel @State private var goToScannerView: Bool? @@ -41,14 +40,18 @@ struct DeviceSectionView: View { VStack { HStack { SectionImage(image: DfuImages.bluetooth.imageName) + Text(DfuStrings.device.text) .padding() .font(.title) .frame(maxWidth: .infinity, alignment: .leading) + NavigationLink(destination: ScannerView(viewModel: viewModel), tag: true, selection: $goToScannerView) { } - DfuButton(title: DfuStrings.select.text, action: { - goToScannerView = true - }) + + DfuButton( + title: DfuStrings.select.text, + action: { goToScannerView = true } + ) }.padding() HStack { diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index a75de530..d04aca5e 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -34,22 +34,25 @@ import NordicDFU struct FileSectionView: View { @State private var openFile = false - @ObservedObject - var viewModel: DfuViewModel + @ObservedObject var viewModel: DfuViewModel var body: some View { VStack { HStack { SectionImage(image: DfuImages.fileUpload.rawValue) + Text(DfuStrings.file.rawValue) .font(.title) .padding() .frame(maxWidth: .infinity, alignment: .leading) - DfuButton(title: DfuStrings.select.rawValue, action: { - self.openFile.toggle() - viewModel.clearFileError() - }) + DfuButton( + title: DfuStrings.select.rawValue, + action: { + self.openFile.toggle() + viewModel.clearFileError() + } + ) } .padding() .onOpenURL { url in diff --git a/App/dfu/screens/ProgressSectionView.swift b/App/dfu/screens/ProgressSectionView.swift index 187b7ac0..b9800bc1 100644 --- a/App/dfu/screens/ProgressSectionView.swift +++ b/App/dfu/screens/ProgressSectionView.swift @@ -32,25 +32,28 @@ import SwiftUI struct ProgressSectionView: View { - @ObservedObject - var viewModel: DfuViewModel + @ObservedObject var viewModel: DfuViewModel var body: some View { VStack { HStack { SectionImage(image: DfuImages.upload.rawValue) + Text(DfuStrings.progress.text) .padding() .font(.title) .frame(maxWidth: .infinity, alignment: .leading) + if (viewModel.progressSection.isRunning()) { - AbortButton(title: DfuStrings.abort.rawValue, action: { - viewModel.abort() - }) + AbortButton( + title: DfuStrings.abort.rawValue, + action: { viewModel.abort() } + ) } else { - DfuButton(title: DfuStrings.upload.rawValue, action: { - viewModel.install() - }) + DfuButton( + title: DfuStrings.upload.rawValue, + action: { viewModel.install() } + ) } }.padding() @@ -73,7 +76,7 @@ struct ProgressSectionView: View { private extension DfuUiStateStatus { func getBootloaderString() -> String { - switch (self) { + switch self { case .idle, .error: return DfuStrings.bootloaderIdle.rawValue case .success: @@ -84,7 +87,7 @@ private extension DfuUiStateStatus { } func getDfuString() -> String { - switch (self) { + switch self { case .idle, .error: return DfuStrings.dfuIdle.rawValue case .success: diff --git a/App/dfu/screens/ProgressSectionViewEntity.swift b/App/dfu/screens/ProgressSectionViewEntity.swift index 835a30b3..3b2124d1 100644 --- a/App/dfu/screens/ProgressSectionViewEntity.swift +++ b/App/dfu/screens/ProgressSectionViewEntity.swift @@ -37,7 +37,7 @@ struct ProgressSectionViewEntity { let resultStatus: DfuResultStatus func isRunning() -> Bool { - if (bootloaderStatus != .idle) { + if bootloaderStatus != .idle { if case DfuResultStatus.idle = resultStatus { return true } @@ -45,7 +45,7 @@ struct ProgressSectionViewEntity { return false } - init ( + init( bootloaderStatus: DfuUiStateStatus = DfuUiStateStatus.idle, dfuStatus: DfuUiStateStatus = DfuUiStateStatus.idle, installationStatus: DfuInstallationStatus = DfuInstallationStatus.idle, @@ -103,7 +103,7 @@ struct ProgressSectionViewEntity { } private func switchToError(status: DfuInstallationStatus) -> DfuInstallationStatus { - switch (status) { + switch status { case .success: return .success case .progress, .error, .idle: @@ -112,7 +112,7 @@ struct ProgressSectionViewEntity { } private func switchToError(status: DfuUiStateStatus) -> DfuUiStateStatus { - switch (status) { + switch status { case .success: return .success case .progress, .error, .idle: diff --git a/App/dfu/screens/SettingsView.swift b/App/dfu/screens/SettingsView.swift index 9cdafbf4..b9e286c9 100644 --- a/App/dfu/screens/SettingsView.swift +++ b/App/dfu/screens/SettingsView.swift @@ -38,6 +38,7 @@ struct SettingsView: View { @State private var showWelcomeScreen: Bool? + @State private var title: String = "" @State private var description: String = "" @State private var showingAlert: Bool = false @@ -55,6 +56,7 @@ struct SettingsView: View { Image(systemName: DfuImages.info.imageName) .foregroundColor(ThemeColor.nordicBlue.color) .onTapGesture { + title = DfuStrings.settingsPacketReceiptTitle.text description = DfuStrings.settingsPacketReceiptValue.text showingAlert = true } @@ -77,6 +79,7 @@ struct SettingsView: View { Image(systemName: DfuImages.info.imageName) .foregroundColor(ThemeColor.nordicBlue.color) .onTapGesture { + title = DfuStrings.alternativeAdvertisingNameTitle.text description = DfuStrings.alternativeAdvertisingNameValue.text showingAlert = true } @@ -84,7 +87,6 @@ struct SettingsView: View { } Section(header: Text(DfuStrings.settingsSecureDfu.text)) { - HStack { Toggle(isOn: $viewModel.disableResume) { Text(DfuStrings.settingsDisableResumeTitle.text) @@ -93,6 +95,7 @@ struct SettingsView: View { Image(systemName: DfuImages.info.imageName) .foregroundColor(ThemeColor.nordicBlue.color) .onTapGesture { + title = DfuStrings.settingsDisableResumeTitle.text description = DfuStrings.settingsDisableResumeValue.text showingAlert = true } @@ -100,7 +103,6 @@ struct SettingsView: View { } Section(header: Text(DfuStrings.settingsLegacyDfu.text)) { - HStack { Toggle(isOn: $viewModel.forceScanningInLegacyDfu) { Text(DfuStrings.settingsForceScanningTitle.text) @@ -109,6 +111,7 @@ struct SettingsView: View { Image(systemName: DfuImages.info.imageName) .foregroundColor(ThemeColor.nordicBlue.color) .onTapGesture { + title = DfuStrings.settingsForceScanningTitle.text description = DfuStrings.settingsForceScanningValue.text showingAlert = true } @@ -122,6 +125,7 @@ struct SettingsView: View { Image(systemName: DfuImages.info.imageName) .foregroundColor(ThemeColor.nordicBlue.color) .onTapGesture { + title = DfuStrings.settingsExternalMcuTitle.text description = DfuStrings.settingsExternalMcuValue.text showingAlert = true } @@ -137,11 +141,16 @@ struct SettingsView: View { } } .navigationTitle(DfuStrings.settings.text) - .alert(description, isPresented: $showingAlert) { - Button(DfuStrings.ok.text, role: .cancel) { - showingAlert = false + .alert(title, isPresented: $showingAlert, + actions: { + Button(DfuStrings.ok.text, role: .cancel) { + showingAlert = false + } + }, + message: { + Text(description) } - } + ) .alert( isPresented: $showingNumberOfPacketsDialog, TextAlert( diff --git a/App/dfu/theme/AbortButtonStyle.swift b/App/dfu/theme/AbortButtonStyle.swift index b96e1f92..39097672 100644 --- a/App/dfu/theme/AbortButtonStyle.swift +++ b/App/dfu/theme/AbortButtonStyle.swift @@ -1,70 +1,72 @@ /* -* Copyright (c) 2022, Nordic Semiconductor -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, -* are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this -* list of conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this -* list of conditions and the following disclaimer in the documentation and/or -* other materials provided with the distribution. -* -* 3. Neither the name of the copyright holder nor the names of its contributors may -* be used to endorse or promote products derived from this software without -* specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -* POSSIBILITY OF SUCH DAMAGE. -*/ + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ import SwiftUI //Ref: https://swiftuirecipes.com/blog/custom-swiftui-button-with-disabled-and-pressed-state struct AbortButtonStyle: ButtonStyle { - func makeBody(configuration: Self.Configuration) -> some View { - AbortButtonStyleView(configuration: configuration) - } + func makeBody(configuration: Self.Configuration) -> some View { + AbortButtonStyleView(configuration: configuration) + } } private extension AbortButtonStyle { - struct AbortButtonStyleView: View { - @Environment(\.isEnabled) var isEnabled - - let configuration: AbortButtonStyle.Configuration - - var body: some View { - configuration.label - .frame(minWidth: 80) - .foregroundColor(.white) - .padding(10) - .background(RoundedRectangle(cornerRadius: 30).fill(ThemeColor.error.color)) - .opacity(configuration.isPressed ? 0.8 : 1.0) - .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + struct AbortButtonStyleView: View { + + @Environment(\.isEnabled) var isEnabled + + let configuration: AbortButtonStyle.Configuration + + var body: some View { + configuration.label + .frame(minWidth: 80) + .foregroundColor(.white) + .padding(10) + .background(RoundedRectangle(cornerRadius: 30).fill(ThemeColor.error.color)) + .opacity(configuration.isPressed ? 0.8 : 1.0) + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + } } - } + } struct AbortButton: View { - let title: String - let action: () -> Void - - var body: some View { - Button(action: action) { - Text(self.title).foregroundColor(Color.white) - }.buttonStyle(AbortButtonStyle()) - } + let title: String + let action: () -> Void + + var body: some View { + Button(action: action) { + Text(self.title).foregroundColor(Color.white) + }.buttonStyle(AbortButtonStyle()) + } } diff --git a/App/dfu/theme/ButtonStyle.swift b/App/dfu/theme/ButtonStyle.swift index 76302df7..18628429 100644 --- a/App/dfu/theme/ButtonStyle.swift +++ b/App/dfu/theme/ButtonStyle.swift @@ -1,72 +1,73 @@ /* -* Copyright (c) 2022, Nordic Semiconductor -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, -* are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this -* list of conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this -* list of conditions and the following disclaimer in the documentation and/or -* other materials provided with the distribution. -* -* 3. Neither the name of the copyright holder nor the names of its contributors may -* be used to endorse or promote products derived from this software without -* specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -* POSSIBILITY OF SUCH DAMAGE. -*/ + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ import SwiftUI //Ref: https://swiftuirecipes.com/blog/custom-swiftui-button-with-disabled-and-pressed-state struct DfuButtonStyle: ButtonStyle { - func makeBody(configuration: Self.Configuration) -> some View { - DfuButtonStyleView(configuration: configuration) - } + func makeBody(configuration: Self.Configuration) -> some View { + DfuButtonStyleView(configuration: configuration) + } } private extension DfuButtonStyle { - struct DfuButtonStyleView: View { - @Environment(\.isEnabled) var isEnabled - - let configuration: DfuButtonStyle.Configuration - - var body: some View { - configuration.label - .frame(minWidth: 80) - .foregroundColor(.white) - .padding(10) - .background(RoundedRectangle(cornerRadius: 30) - .fill(isEnabled ? ThemeColor.buttonEnabledBackground.color : ThemeColor.buttonDisabledBackground.color) - ) - .opacity(configuration.isPressed ? 0.8 : 1.0) - .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + struct DfuButtonStyleView: View { + @Environment(\.isEnabled) var isEnabled + + let configuration: DfuButtonStyle.Configuration + + var body: some View { + configuration.label + .frame(minWidth: 80) + .foregroundColor(.white) + .padding(10) + .background( + RoundedRectangle(cornerRadius: 30) + .fill(isEnabled ? ThemeColor.buttonEnabledBackground.color : ThemeColor.buttonDisabledBackground.color) + ) + .opacity(configuration.isPressed ? 0.8 : 1.0) + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + } } - } } struct DfuButton: View { - let title: String - let action: () -> Void - - var body: some View { - Button(action: action) { - Text(self.title).foregroundColor(Color.white) + let title: String + let action: () -> Void + + var body: some View { + Button(action: action) { + Text(self.title).foregroundColor(Color.white) + } + .buttonStyle(DfuButtonStyle()) } - .buttonStyle(DfuButtonStyle()) - } } diff --git a/App/dfu/theme/DfuIds.swift b/App/dfu/theme/DfuIds.swift index f7a5f985..73a83a17 100644 --- a/App/dfu/theme/DfuIds.swift +++ b/App/dfu/theme/DfuIds.swift @@ -8,7 +8,6 @@ import Foundation enum DfuIds : String { - case settingsButton = "SettingsButton" case welcomeButton = "WelcomeButton" diff --git a/App/dfu/theme/DfuStrings.swift b/App/dfu/theme/DfuStrings.swift index be961e23..4f2e8fdd 100644 --- a/App/dfu/theme/DfuStrings.swift +++ b/App/dfu/theme/DfuStrings.swift @@ -29,9 +29,6 @@ */ enum DfuStrings : String { - case testTitle = "Title" - case testLoremIpsumLong = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum" - case dfuTitle = "DFU" case welcomeTitle = "Welcome" @@ -40,7 +37,7 @@ enum DfuStrings : String { case numberOfPackets = "Number of Packets" case numberRequest = "Please provide number:" case cancel = "Cancel" - case ok = "Ok" + case ok = "OK" case empty = "" case select = "Select" case file = "File" @@ -95,9 +92,8 @@ enum DfuStrings : String { case settingsExternalMcuValue = "Enabling this option will prevent from jumping to the DFU Bootloader mode in case there is no DFU Version characteristic." case settingsOther = "Other" case settingsAboutTitle = "About DFU" - case settingsAboutValue = "DFU documentation on Nordic\'s Infocenter." - case settingsWelcome = "Show welcome screen" - case settingsProvideNumberOfPackets = "Please provide integer value:" + case settingsWelcome = "About the app" + case settingsProvideNumberOfPackets = "Please, provide integer value:" case rssi = "RSSI: %@" diff --git a/App/dfu/theme/ImageStyle.swift b/App/dfu/theme/ImageStyle.swift index a6114b38..6fbd382a 100644 --- a/App/dfu/theme/ImageStyle.swift +++ b/App/dfu/theme/ImageStyle.swift @@ -31,9 +31,11 @@ import SwiftUI struct SectionImage: View { - let image: String + @Environment(\.isEnabled) var isEnabled + let image: String + var body: some View { Image(image) .renderingMode(.template) diff --git a/App/dfu/theme/ThemeColor.swift b/App/dfu/theme/ThemeColor.swift index c74d551c..a452b4ae 100644 --- a/App/dfu/theme/ThemeColor.swift +++ b/App/dfu/theme/ThemeColor.swift @@ -50,10 +50,11 @@ enum ThemeColor : String { extension View { func circleBackground() -> some View { - if (Environment(\.isEnabled).wrappedValue) { - return background(Circle().fill(Color.blue).frame(width: 40,height: 40)) + if Environment(\.isEnabled).wrappedValue { + return background(Circle().fill(.blue).frame(width: 40,height: 40)) } else { - return background(Circle().fill(Color.gray).frame(width: 40,height: 40)) + return background(Circle().fill(.gray).frame(width: 40,height: 40)) } } + } diff --git a/App/dfu/viewmodel/DfuProgress.swift b/App/dfu/viewmodel/DfuProgress.swift index 1efb76bd..bcb296af 100644 --- a/App/dfu/viewmodel/DfuProgress.swift +++ b/App/dfu/viewmodel/DfuProgress.swift @@ -47,6 +47,7 @@ struct DfuProgress { } extension DfuProgress { + init() { self.part = 0 self.totalParts = 0 @@ -54,4 +55,5 @@ extension DfuProgress { self.currentSpeedBytesPerSecond = 0 self.avgSpeedBytesPerSecond = 0 } + } diff --git a/App/dfu/viewmodel/DfuViewModel.swift b/App/dfu/viewmodel/DfuViewModel.swift index 3768c66b..3d499ace 100644 --- a/App/dfu/viewmodel/DfuViewModel.swift +++ b/App/dfu/viewmodel/DfuViewModel.swift @@ -35,17 +35,13 @@ import SwiftUI class DfuViewModel : ObservableObject, DFUProgressDelegate, DFUServiceDelegate { - @Published - var fileError: String? = nil + @Published var fileError: String? = nil - @Published - private(set) var zipFile: ZipFile? = nil + @Published private(set) var zipFile: ZipFile? = nil - @Published - var device: BluetoothDevice? = nil + @Published var device: BluetoothDevice? = nil - @Published - var progressSection: ProgressSectionViewEntity = ProgressSectionViewEntity() + @Published var progressSection: ProgressSectionViewEntity = ProgressSectionViewEntity() @AppStorage("packetsReceiptNotification") var packetsReceiptNotification: Bool = false @@ -135,13 +131,17 @@ class DfuViewModel : ObservableObject, DFUProgressDelegate, DFUServiceDelegate { } func dfuProgressDidChange(for part: Int, outOf totalParts: Int, to progress: Int, currentSpeedBytesPerSecond: Double, avgSpeedBytesPerSecond: Double) { - let progress = DfuProgress(part: part, totalParts: totalParts, progress: progress, currentSpeedBytesPerSecond: currentSpeedBytesPerSecond, avgSpeedBytesPerSecond: avgSpeedBytesPerSecond) + let progress = DfuProgress( + part: part, totalParts: totalParts, + progress: progress, + currentSpeedBytesPerSecond: currentSpeedBytesPerSecond, + avgSpeedBytesPerSecond: avgSpeedBytesPerSecond + ) progressSection = progressSection.toProgressState(updated: progress) } func dfuStateDidChange(to state: DFUState) { - print("state: \(state)") - if (state == DFUState.enablingDfuMode) { + if state == DFUState.enablingDfuMode { progressSection = progressSection.toDfuState() } else if (state == DFUState.completed) { progressSection = progressSection.toSuccessState() @@ -156,7 +156,7 @@ class DfuViewModel : ObservableObject, DFUProgressDelegate, DFUServiceDelegate { } func onWelcomeScreenShown() { - if (showWelcomeScreen) { + if showWelcomeScreen { showWelcomeScreen = false } } diff --git a/App/dfu/views/NumberOfPacketsDialog.swift b/App/dfu/views/NumberOfPacketsDialog.swift index 58bd3f6a..65f4e61b 100644 --- a/App/dfu/views/NumberOfPacketsDialog.swift +++ b/App/dfu/views/NumberOfPacketsDialog.swift @@ -32,14 +32,11 @@ import SwiftUI struct NumberOfPacketsDialog: View { - @Binding - var isShowing: Bool + @Binding var isShowing: Bool - @Binding - var value: Int + @Binding var value: Int - @State - var input = NumbersOnlyField() + @State var input = NumbersOnlyField() var body: some View { HStack { @@ -47,8 +44,10 @@ struct NumberOfPacketsDialog: View { Text(DfuStrings.numberOfPackets.text) .font(.body) .frame(maxWidth: .infinity, alignment: .leading) + Spacer() .frame(height: 8) + Text(DfuStrings.numberRequest.text) .font(.footnote) .frame(maxWidth: .infinity, alignment: .leading) @@ -71,6 +70,7 @@ struct NumberOfPacketsDialog: View { } class NumbersOnlyField: ObservableObject { + @Published var value = "" { didSet { let filtered = value.filter { $0.isNumber } diff --git a/App/dfu/views/ProgressItemView.swift b/App/dfu/views/ProgressItemView.swift index 29464c45..eca06eaf 100644 --- a/App/dfu/views/ProgressItemView.swift +++ b/App/dfu/views/ProgressItemView.swift @@ -38,7 +38,6 @@ enum DfuInstallationStatus { } struct ProgressItemView: View { - let status: DfuInstallationStatus var body: some View { @@ -67,13 +66,13 @@ struct ProgressItemView: View { } func getInstallationString() -> String { - switch (status) { + switch status { case .idle, .error: return DfuStrings.firmwareUpload.text case .success: return DfuStrings.firmwareUploaded.rawValue case .progress(let p): - if (p.totalParts == 1) { + if p.totalParts == 1 { return DfuStrings.firmwareUploading.rawValue } else { return String(format: DfuStrings.firmwareUploadPart.rawValue, p.part, p.totalParts) @@ -85,7 +84,7 @@ struct ProgressItemView: View { private extension DfuInstallationStatus { var image: String { - switch (self) { + switch self { case .idle: return DfuImages.idle.rawValue case .success: @@ -98,7 +97,7 @@ private extension DfuInstallationStatus { } var color: Color { - switch (self) { + switch self { case .idle: return ThemeColor.nordicDarkGray5.color case .success: @@ -109,4 +108,5 @@ private extension DfuInstallationStatus { return ThemeColor.error.color } } + } diff --git a/App/dfu/views/ResultItemView.swift b/App/dfu/views/ResultItemView.swift index f682b7f4..5f2dead0 100644 --- a/App/dfu/views/ResultItemView.swift +++ b/App/dfu/views/ResultItemView.swift @@ -37,7 +37,6 @@ enum DfuResultStatus { } struct ResultItemView: View { - let status: DfuResultStatus var body: some View { @@ -53,7 +52,7 @@ struct ResultItemView: View { } func getResultString() -> String { - switch (status) { + switch status { case .idle, .success: return DfuStrings.resultCompleted.text case .error(let error): @@ -65,7 +64,7 @@ struct ResultItemView: View { private extension DfuResultStatus { var image: String { - switch (self) { + switch self { case .idle: return DfuImages.idle.imageName case .success: @@ -76,7 +75,7 @@ private extension DfuResultStatus { } var color: Color { - switch (self) { + switch self { case .idle: return ThemeColor.nordicDarkGray5.color case .success: @@ -85,4 +84,5 @@ private extension DfuResultStatus { return ThemeColor.error.color } } + } diff --git a/App/dfu/views/StatusItemView.swift b/App/dfu/views/StatusItemView.swift index 70b98635..3884393a 100644 --- a/App/dfu/views/StatusItemView.swift +++ b/App/dfu/views/StatusItemView.swift @@ -38,7 +38,6 @@ enum DfuUiStateStatus { } struct StatusItemView: View { - let text: String let status: DfuUiStateStatus @@ -58,7 +57,7 @@ struct StatusItemView: View { private extension DfuUiStateStatus { var image: String { - switch (self) { + switch self { case .idle: return DfuImages.idle.imageName case .success: @@ -71,7 +70,7 @@ private extension DfuUiStateStatus { } var color: Color { - switch (self) { + switch self { case .idle: return ThemeColor.nordicDarkGray5.color case .success: @@ -82,4 +81,5 @@ private extension DfuUiStateStatus { return ThemeColor.error.color } } + } diff --git a/App/dfu/views/TextAlertDialog.swift b/App/dfu/views/TextAlertDialog.swift index 0428e5df..34ea9ced 100644 --- a/App/dfu/views/TextAlertDialog.swift +++ b/App/dfu/views/TextAlertDialog.swift @@ -1,32 +1,32 @@ /* -* Copyright (c) 2022, Nordic Semiconductor -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, -* are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this -* list of conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this -* list of conditions and the following disclaimer in the documentation and/or -* other materials provided with the distribution. -* -* 3. Neither the name of the copyright holder nor the names of its contributors may -* be used to endorse or promote products derived from this software without -* specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -* POSSIBILITY OF SUCH DAMAGE. -*/ + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ import Foundation import SwiftUI @@ -34,80 +34,84 @@ import SwiftUI //Ref: https://swiftuirecipes.com/blog/swiftui-alert-with-textfield public struct TextAlert { - public var title: String // Title of the dialog - public var message: String // Dialog message + public var title: String // Title of the dialog + public var message: String // Dialog message public var placeholder: String = DfuStrings.empty.text // Placeholder text for the TextField public var accept: String = DfuStrings.ok.text // The left-most button label public var cancel: String? = DfuStrings.cancel.text // The optional cancel (right-most) button label - public var secondaryActionTitle: String? = nil // The optional center button label - public var keyboardType: UIKeyboardType = .default // Keyboard tzpe of the TextField - public var action: (String?) -> Void // Triggers when either of the two buttons closes the dialog - public var secondaryAction: (() -> Void)? = nil // Triggers when the optional center button is tapped + public var secondaryActionTitle: String? = nil // The optional center button label + public var keyboardType: UIKeyboardType = .default // Keyboard tzpe of the TextField + public var action: (String?) -> Void // Triggers when either of the two buttons closes the dialog + public var secondaryAction: (() -> Void)? = nil // Triggers when the optional center button is tapped } extension UIAlertController { - convenience init(alert: TextAlert) { - self.init(title: alert.title, message: alert.message, preferredStyle: .alert) - addTextField { - $0.placeholder = alert.placeholder - $0.keyboardType = alert.keyboardType + + convenience init(alert: TextAlert) { + self.init(title: alert.title, message: alert.message, preferredStyle: .alert) + addTextField { + $0.placeholder = alert.placeholder + $0.keyboardType = alert.keyboardType + } + if let cancel = alert.cancel { + addAction(UIAlertAction(title: cancel, style: .cancel) { _ in + alert.action(nil) + }) + } + if let secondaryActionTitle = alert.secondaryActionTitle { + addAction(UIAlertAction(title: secondaryActionTitle, style: .default, handler: { _ in + alert.secondaryAction?() + })) + } + let textField = self.textFields?.first + addAction(UIAlertAction(title: alert.accept, style: .default) { _ in + alert.action(textField?.text) + }) } - if let cancel = alert.cancel { - addAction(UIAlertAction(title: cancel, style: .cancel) { _ in - alert.action(nil) - }) - } - if let secondaryActionTitle = alert.secondaryActionTitle { - addAction(UIAlertAction(title: secondaryActionTitle, style: .default, handler: { _ in - alert.secondaryAction?() - })) - } - let textField = self.textFields?.first - addAction(UIAlertAction(title: alert.accept, style: .default) { _ in - alert.action(textField?.text) - }) - } + } struct AlertWrapper: UIViewControllerRepresentable { - @Binding var isPresented: Bool - let alert: TextAlert - let content: Content - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIHostingController { - UIHostingController(rootView: content) - } - - final class Coordinator { - var alertController: UIAlertController? - init(_ controller: UIAlertController? = nil) { - self.alertController = controller + @Binding var isPresented: Bool + let alert: TextAlert + let content: Content + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIHostingController { + UIHostingController(rootView: content) } - } - - func makeCoordinator() -> Coordinator { - return Coordinator() - } - - func updateUIViewController(_ uiViewController: UIHostingController, context: UIViewControllerRepresentableContext) { - uiViewController.rootView = content - if isPresented && uiViewController.presentedViewController == nil { - var alert = self.alert - alert.action = { - self.isPresented = false - self.alert.action($0) - } - context.coordinator.alertController = UIAlertController(alert: alert) - uiViewController.present(context.coordinator.alertController!, animated: true) + + final class Coordinator { + var alertController: UIAlertController? + init(_ controller: UIAlertController? = nil) { + self.alertController = controller + } } - if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController { - uiViewController.dismiss(animated: true) + + func makeCoordinator() -> Coordinator { + return Coordinator() + } + + func updateUIViewController(_ uiViewController: UIHostingController, context: UIViewControllerRepresentableContext) { + uiViewController.rootView = content + if isPresented && uiViewController.presentedViewController == nil { + var alert = self.alert + alert.action = { + self.isPresented = false + self.alert.action($0) + } + context.coordinator.alertController = UIAlertController(alert: alert) + uiViewController.present(context.coordinator.alertController!, animated: true) + } + if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController { + uiViewController.dismiss(animated: true) + } } - } } extension View { - public func alert(isPresented: Binding, _ alert: TextAlert) -> some View { - AlertWrapper(isPresented: isPresented, alert: alert, content: self) - } + + public func alert(isPresented: Binding, _ alert: TextAlert) -> some View { + AlertWrapper(isPresented: isPresented, alert: alert, content: self) + } + } diff --git a/App/dfu/welcome/WelcomeScreen.swift b/App/dfu/welcome/WelcomeScreen.swift index a2a08ecc..d7906292 100644 --- a/App/dfu/welcome/WelcomeScreen.swift +++ b/App/dfu/welcome/WelcomeScreen.swift @@ -34,8 +34,7 @@ struct WelcomeScreen: View { @Environment(\.presentationMode) var presentationMode - @ObservedObject - var viewModel: DfuViewModel + @ObservedObject var viewModel: DfuViewModel var body: some View { ScrollView { @@ -50,9 +49,10 @@ struct WelcomeScreen: View { Spacer().frame(height: 24) - DfuButton(title: DfuStrings.welcomeStart.text, action: { - self.presentationMode.wrappedValue.dismiss() - }) + DfuButton( + title: DfuStrings.welcomeStart.text, + action: { presentationMode.wrappedValue.dismiss() } + ) }.padding() } .navigationTitle(DfuStrings.welcomeTitle.text) From c4bd4e29535ce8fa97470cbdd7e7a3d4ed03c9e5 Mon Sep 17 00:00:00 2001 From: philips77 Date: Tue, 20 Dec 2022 14:05:41 +0100 Subject: [PATCH 02/22] Minor clean up --- App/dfu/bluetooth/BluetoothManager.swift | 2 +- App/dfu/screens/FileSectionView.swift | 2 +- App/dfu/screens/ProgressSectionView.swift | 2 +- App/dfu/viewmodel/DfuViewModel.swift | 30 +++++++++++++---------- App/dfu/welcome/WelcomeScreen.swift | 2 +- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/App/dfu/bluetooth/BluetoothManager.swift b/App/dfu/bluetooth/BluetoothManager.swift index 9b7cdc85..cd7f3088 100644 --- a/App/dfu/bluetooth/BluetoothManager.swift +++ b/App/dfu/bluetooth/BluetoothManager.swift @@ -78,7 +78,7 @@ class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { } private func runScanningWhenNeeded() { - if (isOnScreen && isBluetoothReady) { + if isOnScreen && isBluetoothReady { centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]) } } diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index d04aca5e..a3b09947 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -58,7 +58,7 @@ struct FileSectionView: View { .onOpenURL { url in guard let location = url["file"], let fileUrl = URL(string: location) else { - return + return } onFileOpen(opened: fileUrl) } diff --git a/App/dfu/screens/ProgressSectionView.swift b/App/dfu/screens/ProgressSectionView.swift index b9800bc1..81c9e8ee 100644 --- a/App/dfu/screens/ProgressSectionView.swift +++ b/App/dfu/screens/ProgressSectionView.swift @@ -44,7 +44,7 @@ struct ProgressSectionView: View { .font(.title) .frame(maxWidth: .infinity, alignment: .leading) - if (viewModel.progressSection.isRunning()) { + if viewModel.progressSection.isRunning() { AbortButton( title: DfuStrings.abort.rawValue, action: { viewModel.abort() } diff --git a/App/dfu/viewmodel/DfuViewModel.swift b/App/dfu/viewmodel/DfuViewModel.swift index 3d499ace..fe3b411a 100644 --- a/App/dfu/viewmodel/DfuViewModel.swift +++ b/App/dfu/viewmodel/DfuViewModel.swift @@ -83,14 +83,13 @@ class DfuViewModel : ObservableObject, DFUProgressDelegate, DFUServiceDelegate { } func install() { - print(zipFile ?? "null") - print(device ?? "null") - os_log("%@", zipFile.debugDescription) - - let selectedFirmware = try! DFUFirmware( - urlToZipFile: zipFile!.url, - type: .softdeviceBootloaderApplication - ) + guard let zipFile = zipFile, + let selectedFirmware = try? DFUFirmware( + urlToZipFile: zipFile.url, + type: .softdeviceBootloaderApplication + ) else { + return + } let initiator = DFUServiceInitiator().with(firmware: selectedFirmware) @@ -130,7 +129,12 @@ class DfuViewModel : ObservableObject, DFUProgressDelegate, DFUServiceDelegate { } } - func dfuProgressDidChange(for part: Int, outOf totalParts: Int, to progress: Int, currentSpeedBytesPerSecond: Double, avgSpeedBytesPerSecond: Double) { + func dfuProgressDidChange( + for part: Int, outOf totalParts: Int, + to progress: Int, + currentSpeedBytesPerSecond: Double, + avgSpeedBytesPerSecond: Double + ) { let progress = DfuProgress( part: part, totalParts: totalParts, progress: progress, @@ -141,11 +145,11 @@ class DfuViewModel : ObservableObject, DFUProgressDelegate, DFUServiceDelegate { } func dfuStateDidChange(to state: DFUState) { - if state == DFUState.enablingDfuMode { + if state == .enablingDfuMode { progressSection = progressSection.toDfuState() - } else if (state == DFUState.completed) { + } else if state == .completed { progressSection = progressSection.toSuccessState() - } else if (state == DFUState.aborted) { + } else if state == .aborted { progressSection = progressSection.toErrorState(message: DfuUiError(error: nil, message: DfuStrings.aborted.text)) } } @@ -155,7 +159,7 @@ class DfuViewModel : ObservableObject, DFUProgressDelegate, DFUServiceDelegate { progressSection = progressSection.toErrorState(message: error) } - func onWelcomeScreenShown() { + func welcomeScreenDidShow() { if showWelcomeScreen { showWelcomeScreen = false } diff --git a/App/dfu/welcome/WelcomeScreen.swift b/App/dfu/welcome/WelcomeScreen.swift index d7906292..4ba49d81 100644 --- a/App/dfu/welcome/WelcomeScreen.swift +++ b/App/dfu/welcome/WelcomeScreen.swift @@ -56,7 +56,7 @@ struct WelcomeScreen: View { }.padding() } .navigationTitle(DfuStrings.welcomeTitle.text) - .onAppear { viewModel.onWelcomeScreenShown() } + .onAppear { viewModel.welcomeScreenDidShow() } } } From 9bf9a66120719ce520042ece0919324c9732c3b1 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 13:38:05 +0100 Subject: [PATCH 03/22] Initial Welcome screen as sheet --- App/dfu/ContentView.swift | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/App/dfu/ContentView.swift b/App/dfu/ContentView.swift index 9522dad1..059d5c8f 100644 --- a/App/dfu/ContentView.swift +++ b/App/dfu/ContentView.swift @@ -39,16 +39,12 @@ struct ContentView: View { private var viewModel = DfuViewModel() @State - private var showWelcomeScreen: Bool? + private var showWelcomeScreen: Bool = false var body: some View { ScrollView { Section { VStack { - NavigationLink(destination: WelcomeScreen(viewModel: viewModel), tag: true, selection: $showWelcomeScreen) { - EmptyView() - }.hidden() - FileSectionView(viewModel: viewModel) DeviceSectionView(viewModel: viewModel) @@ -56,15 +52,20 @@ struct ContentView: View { ProgressSectionView(viewModel: viewModel) }.padding() } - .navigationTitle(DfuStrings.dfuTitle.text) - .navigationBarItems(trailing: - NavigationLink(destination: SettingsView(viewModel: viewModel)) { - Text(DfuStrings.settings.text) - } - .accessibilityIdentifier(DfuIds.settingsButton.rawValue) - ) - Spacer() - }.onAppear { + } + .sheet(isPresented: $showWelcomeScreen) { + NavigationView { + WelcomeScreen(viewModel: viewModel) + } + } + .navigationTitle(DfuStrings.dfuTitle.text) + .navigationBarItems(trailing: + NavigationLink(destination: SettingsView(viewModel: viewModel)) { + Text(DfuStrings.settings.text) + } + .accessibilityIdentifier(DfuIds.settingsButton.rawValue) + ) + .onAppear { if viewModel.showWelcomeScreen { showWelcomeScreen = true } From 9f1eaa06747e0b6668def377539a5ff56391eb29 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 15:55:12 +0100 Subject: [PATCH 04/22] Duplicated colors fixed --- .../colors/NordicFall.colorset/Contents.json | 29 ++-------------- .../colors/NordicGreen.colorset/Contents.json | 34 ++----------------- .../NordicLightGray.colorset/Contents.json | 20 ----------- .../NordicOrange.colorset/Contents.json | 34 ++----------------- .../NordicMediumGray.colorset/Contents.json | 18 ---------- 5 files changed, 6 insertions(+), 129 deletions(-) delete mode 100644 App/dfu/Assets.xcassets/colors/NordicLightGray.colorset/Contents.json diff --git a/App/dfu/Assets.xcassets/colors/NordicFall.colorset/Contents.json b/App/dfu/Assets.xcassets/colors/NordicFall.colorset/Contents.json index 89a09b08..47dd8eb7 100644 --- a/App/dfu/Assets.xcassets/colors/NordicFall.colorset/Contents.json +++ b/App/dfu/Assets.xcassets/colors/NordicFall.colorset/Contents.json @@ -10,7 +10,7 @@ "red" : "0.976" } }, - "idiom" : "iphone" + "idiom" : "universal" }, { "appearances" : [ @@ -23,32 +23,7 @@ "platform" : "ios", "reference" : "systemOrangeColor" }, - "idiom" : "iphone" - }, - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.208", - "green" : "0.584", - "red" : "0.976" - } - }, - "idiom" : "ipad" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "platform" : "ios", - "reference" : "systemOrangeColor" - }, - "idiom" : "ipad" + "idiom" : "universal" } ], "info" : { diff --git a/App/dfu/Assets.xcassets/colors/NordicGreen.colorset/Contents.json b/App/dfu/Assets.xcassets/colors/NordicGreen.colorset/Contents.json index 8957ab71..e8895ad3 100644 --- a/App/dfu/Assets.xcassets/colors/NordicGreen.colorset/Contents.json +++ b/App/dfu/Assets.xcassets/colors/NordicGreen.colorset/Contents.json @@ -10,7 +10,7 @@ "red" : "0.243" } }, - "idiom" : "iphone" + "idiom" : "universal" }, { "appearances" : [ @@ -28,37 +28,7 @@ "red" : "0.196" } }, - "idiom" : "iphone" - }, - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.322", - "green" : "0.816", - "red" : "0.243" - } - }, - "idiom" : "ipad" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.294", - "green" : "0.843", - "red" : "0.196" - } - }, - "idiom" : "ipad" + "idiom" : "universal" } ], "info" : { diff --git a/App/dfu/Assets.xcassets/colors/NordicLightGray.colorset/Contents.json b/App/dfu/Assets.xcassets/colors/NordicLightGray.colorset/Contents.json deleted file mode 100644 index 0e36b892..00000000 --- a/App/dfu/Assets.xcassets/colors/NordicLightGray.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.640", - "green" : "0.597", - "red" : "0.535" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/colors/NordicOrange.colorset/Contents.json b/App/dfu/Assets.xcassets/colors/NordicOrange.colorset/Contents.json index 516d4a4a..af358463 100644 --- a/App/dfu/Assets.xcassets/colors/NordicOrange.colorset/Contents.json +++ b/App/dfu/Assets.xcassets/colors/NordicOrange.colorset/Contents.json @@ -10,7 +10,7 @@ "red" : "0.876" } }, - "idiom" : "iphone" + "idiom" : "universal" }, { "appearances" : [ @@ -28,37 +28,7 @@ "red" : "1.000" } }, - "idiom" : "iphone" - }, - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.088", - "green" : "0.608", - "red" : "0.876" - } - }, - "idiom" : "ipad" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.039", - "green" : "0.624", - "red" : "1.000" - } - }, - "idiom" : "ipad" + "idiom" : "universal" } ], "info" : { diff --git a/App/dfu/Assets.xcassets/gray/NordicMediumGray.colorset/Contents.json b/App/dfu/Assets.xcassets/gray/NordicMediumGray.colorset/Contents.json index 41e9a01a..0e36b892 100644 --- a/App/dfu/Assets.xcassets/gray/NordicMediumGray.colorset/Contents.json +++ b/App/dfu/Assets.xcassets/gray/NordicMediumGray.colorset/Contents.json @@ -11,24 +11,6 @@ } }, "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.573", - "green" : "0.525", - "red" : "0.463" - } - }, - "idiom" : "universal" } ], "info" : { From 97b5bc75f05670bb3b1edcded1bbb623f1bf926c Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 15:57:35 +0100 Subject: [PATCH 05/22] Opening ZIP files in the app --- App/dfu.xcodeproj/project.pbxproj | 4 +++ App/dfu/Info.plist | 13 +++++++++ App/dfu/screens/FileSectionView.swift | 39 ++++++++++++++++----------- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/App/dfu.xcodeproj/project.pbxproj b/App/dfu.xcodeproj/project.pbxproj index af804645..1cd0c2e9 100644 --- a/App/dfu.xcodeproj/project.pbxproj +++ b/App/dfu.xcodeproj/project.pbxproj @@ -543,6 +543,8 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = dfu/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = DFU; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "This app uses Bluetooth to connect and send new firmware to a device."; INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "This app uses Bluetooth to connect and send new firmware to a device."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -580,6 +582,8 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = dfu/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = DFU; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "This app uses Bluetooth to connect and send new firmware to a device."; INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "This app uses Bluetooth to connect and send new firmware to a device."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; diff --git a/App/dfu/Info.plist b/App/dfu/Info.plist index 8b761a7b..3480a070 100644 --- a/App/dfu/Info.plist +++ b/App/dfu/Info.plist @@ -2,6 +2,19 @@ + CFBundleDocumentTypes + + + CFBundleTypeName + ZIP + LSHandlerRank + Default + LSItemContentTypes + + public.zip-archive + + + CFBundleIcons CFBundleIcons~ipad diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index a3b09947..64c4df25 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -55,13 +55,6 @@ struct FileSectionView: View { ) } .padding() - .onOpenURL { url in - guard let location = url["file"], - let fileUrl = URL(string: location) else { - return - } - onFileOpen(opened: fileUrl) - } HStack { RoundedRectangle(cornerRadius: 20) @@ -86,7 +79,7 @@ struct FileSectionView: View { }.padding(.vertical, 8) } } - .fileImporter(isPresented: $openFile, allowedContentTypes: [.zip]) { (res) in + .fileImporter(isPresented: $openFile, allowedContentTypes: [.zip]) { res in do { let fileUrl = try res.get() onFileOpen(opened: fileUrl) @@ -94,6 +87,16 @@ struct FileSectionView: View { onError(error.localizedDescription) } } + .onOpenURL { url in + // Handle https://www.nordicsemi.com/dfu?file=... deeplinks. + if let location = url["file"], + let fileUrl = URL(string: location) { + onFileOpen(opened: fileUrl) + return + } + // Handle files opened from another apps. + onFileOpen(opened: url) + } .disabled(viewModel.isFileButtonDisabled()) } @@ -118,10 +121,12 @@ struct FileSectionView: View { guard let fileURL = urlOrNil else { return } do { - let documentsURL = try FileManager.default.url(for: .documentDirectory, + let documentsURL = try FileManager.default.url( + for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, - create: false) + create: false + ) let savedURL = documentsURL.appendingPathComponent(response.suggestedFilename ?? "file.zip") try? FileManager.default.removeItem(at: savedURL) @@ -131,6 +136,12 @@ struct FileSectionView: View { let fileSize = resources.fileSize! let fileName = resources.name! + // Validate the file by creating a DFUFirmware object. + _ = try DFUFirmware( + urlToZipFile: savedURL, + type: .softdeviceBootloaderApplication + ) + let zipFile = ZipFile(name: fileName, size: fileSize, url: savedURL) try onFileSelected(zipFile) } catch { @@ -141,10 +152,6 @@ struct FileSectionView: View { } private func onFileSelected(_ file: ZipFile) throws { - _ = try DFUFirmware( - urlToZipFile: file.url, - type: .softdeviceBootloaderApplication - ) DispatchQueue.main.async { viewModel.onFileSelected(file) } @@ -164,11 +171,11 @@ struct FileSectionView_Previews: PreviewProvider { } private extension URL { - + subscript(queryParam: String) -> String? { guard let url = URLComponents(string: self.absoluteString) else { return nil } if let parameters = url.queryItems { - return parameters.first(where: { $0.name == queryParam })?.value + return parameters.first { $0.name == queryParam }?.value } else if let paramPairs = url.fragment?.components(separatedBy: "?").last?.components(separatedBy: "&") { for pair in paramPairs where pair.contains(queryParam) { return pair.components(separatedBy: "=").last From a0e35630aedfcfd30d0851e0bd93e2665ec97062 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 15:58:42 +0100 Subject: [PATCH 06/22] Previews --- App/dfu/ContentView.swift | 4 +++- App/dfu/screens/SettingsView.swift | 4 +++- App/dfu/views/ProgressItemView.swift | 14 ++++++++++++++ App/dfu/views/ResultItemView.swift | 11 +++++++++++ App/dfu/views/StatusItemView.swift | 11 +++++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/App/dfu/ContentView.swift b/App/dfu/ContentView.swift index 059d5c8f..82d27a5a 100644 --- a/App/dfu/ContentView.swift +++ b/App/dfu/ContentView.swift @@ -75,6 +75,8 @@ struct ContentView: View { struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView() + NavigationView { + ContentView() + }.navigationViewStyle(.stack) } } diff --git a/App/dfu/screens/SettingsView.swift b/App/dfu/screens/SettingsView.swift index b9e286c9..45ffe343 100644 --- a/App/dfu/screens/SettingsView.swift +++ b/App/dfu/screens/SettingsView.swift @@ -168,6 +168,8 @@ struct SettingsView: View { struct SettingsView_Previews: PreviewProvider { static var previews: some View { - SettingsView(viewModel: DfuViewModel()) + NavigationView { + SettingsView(viewModel: DfuViewModel()) + }.navigationViewStyle(.stack) } } diff --git a/App/dfu/views/ProgressItemView.swift b/App/dfu/views/ProgressItemView.swift index eca06eaf..04571bd6 100644 --- a/App/dfu/views/ProgressItemView.swift +++ b/App/dfu/views/ProgressItemView.swift @@ -110,3 +110,17 @@ private extension DfuInstallationStatus { } } + +struct ProgressItemView_Previews: PreviewProvider { + static var previews: some View { + VStack { + ProgressItemView(status: .idle) + ProgressItemView(status: .progress( + DfuProgress(part: 1, totalParts: 2, progress: 23, currentSpeedBytesPerSecond: 4.5, avgSpeedBytesPerSecond: 4.2)) + ) + ProgressItemView(status: .success) + ProgressItemView(status: .error) + } + } +} + diff --git a/App/dfu/views/ResultItemView.swift b/App/dfu/views/ResultItemView.swift index 5f2dead0..9709bb13 100644 --- a/App/dfu/views/ResultItemView.swift +++ b/App/dfu/views/ResultItemView.swift @@ -86,3 +86,14 @@ private extension DfuResultStatus { } } + +struct ResultItemView_Previews: PreviewProvider { + static var previews: some View { + VStack { + ResultItemView(status: .idle) + ResultItemView(status: .error(DfuUiError(error: nil, message: "Some error"))) + ResultItemView(status: .success) + } + } +} + diff --git a/App/dfu/views/StatusItemView.swift b/App/dfu/views/StatusItemView.swift index 3884393a..2b914bd7 100644 --- a/App/dfu/views/StatusItemView.swift +++ b/App/dfu/views/StatusItemView.swift @@ -83,3 +83,14 @@ private extension DfuUiStateStatus { } } + +struct StatusItemView_Previews: PreviewProvider { + static var previews: some View { + VStack { + StatusItemView(text: "Load", status: .idle) + StatusItemView(text: "Loading...", status: .progress) + StatusItemView(text: "Success", status: .success) + StatusItemView(text: "Error", status: .error) + } + } +} From b1a210fbc533b83c4dfc91cb0ee1747f570c64a9 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 15:59:26 +0100 Subject: [PATCH 07/22] Minor text change --- App/dfu/theme/DfuStrings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/App/dfu/theme/DfuStrings.swift b/App/dfu/theme/DfuStrings.swift index 4f2e8fdd..858478cc 100644 --- a/App/dfu/theme/DfuStrings.swift +++ b/App/dfu/theme/DfuStrings.swift @@ -57,7 +57,7 @@ enum DfuStrings : String { case firmwareUploaded = "Firmware uploaded" case firmwareUploading = "Uploading firmware..." case firmwareUploadPart = "Uploading part %d of %d" - case firmwareUploadSpeed = "Speed %.1f kB/s" + case firmwareUploadSpeed = "%.1f kB/s" case resultCompleted = "Completed" case resultError = "Error: %@" From 0f02d273a3402f893198116d4af32fa6cff3e4b6 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 16:01:30 +0100 Subject: [PATCH 08/22] Minor code improvements --- App/dfu/screens/DeviceSectionView.swift | 3 ++- App/dfu/screens/FileSectionView.swift | 12 ++++++++---- App/dfu/screens/SettingsView.swift | 6 +++--- App/dfu/welcome/WelcomeScreen.swift | 5 +++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/App/dfu/screens/DeviceSectionView.swift b/App/dfu/screens/DeviceSectionView.swift index 63990218..c5ad26f1 100644 --- a/App/dfu/screens/DeviceSectionView.swift +++ b/App/dfu/screens/DeviceSectionView.swift @@ -70,7 +70,8 @@ struct DeviceSectionView: View { } Spacer() } - }.disabled(viewModel.isDeviceButtonDisabled()) + } + .disabled(viewModel.isDeviceButtonDisabled()) } } diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index 64c4df25..23aeaa66 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -65,10 +65,13 @@ struct FileSectionView: View { VStack { if let file = viewModel.zipFile { - Text(String(format: DfuStrings.fileName.rawValue, file.name)).frame(maxWidth: .infinity, alignment: .leading) - Text(String(format: DfuStrings.fileSize.rawValue, file.size)).frame(maxWidth: .infinity, alignment: .leading) + Text(String(format: DfuStrings.fileName.rawValue, file.name)) + .frame(maxWidth: .infinity, alignment: .leading) + Text(String(format: DfuStrings.fileSize.rawValue, file.size)) + .frame(maxWidth: .infinity, alignment: .leading) } else { - Text(DfuStrings.fileSelect.rawValue).frame(maxWidth: .infinity, alignment: .leading) + Text(DfuStrings.fileSelect.rawValue) + .frame(maxWidth: .infinity, alignment: .leading) } if viewModel.fileError != nil { Spacer() @@ -76,7 +79,8 @@ struct FileSectionView: View { .foregroundColor(ThemeColor.nordicRed.color) .frame(maxWidth: .infinity, alignment: .leading) } - }.padding(.vertical, 8) + } + .padding(.vertical, 8) } } .fileImporter(isPresented: $openFile, allowedContentTypes: [.zip]) { res in diff --git a/App/dfu/screens/SettingsView.swift b/App/dfu/screens/SettingsView.swift index 45ffe343..e06d4a4a 100644 --- a/App/dfu/screens/SettingsView.swift +++ b/App/dfu/screens/SettingsView.swift @@ -86,7 +86,7 @@ struct SettingsView: View { } } - Section(header: Text(DfuStrings.settingsSecureDfu.text)) { + Section(DfuStrings.settingsSecureDfu.text) { HStack { Toggle(isOn: $viewModel.disableResume) { Text(DfuStrings.settingsDisableResumeTitle.text) @@ -102,7 +102,7 @@ struct SettingsView: View { } } - Section(header: Text(DfuStrings.settingsLegacyDfu.text)) { + Section(DfuStrings.settingsLegacyDfu.text) { HStack { Toggle(isOn: $viewModel.forceScanningInLegacyDfu) { Text(DfuStrings.settingsForceScanningTitle.text) @@ -132,7 +132,7 @@ struct SettingsView: View { } } - Section(header: Text(DfuStrings.settingsOther.text)) { + Section(DfuStrings.settingsOther.text) { Link(DfuStrings.settingsAboutTitle.text, destination: URL(string: INFOCENTER_LINK)!) NavigationLink(destination: WelcomeScreen(viewModel: viewModel), tag: true, selection: $showWelcomeScreen) { diff --git a/App/dfu/welcome/WelcomeScreen.swift b/App/dfu/welcome/WelcomeScreen.swift index 4ba49d81..6ae352dd 100644 --- a/App/dfu/welcome/WelcomeScreen.swift +++ b/App/dfu/welcome/WelcomeScreen.swift @@ -41,13 +41,14 @@ struct WelcomeScreen: View { VStack { Image(DfuImages.dfu.imageName) .resizable() + .padding() .aspectRatio(contentMode: .fit) - Spacer().frame(height: 24) + Spacer() Text(DfuStrings.welcomeText.text) - Spacer().frame(height: 24) + Spacer() DfuButton( title: DfuStrings.welcomeStart.text, From f581621ef17174db93e7aab0e607e08b8650b242 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 16:02:20 +0100 Subject: [PATCH 09/22] Preview wrapped in NavigationView --- App/dfu/scanner/ScannerView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/App/dfu/scanner/ScannerView.swift b/App/dfu/scanner/ScannerView.swift index 0597ee62..17d89ab1 100644 --- a/App/dfu/scanner/ScannerView.swift +++ b/App/dfu/scanner/ScannerView.swift @@ -86,6 +86,8 @@ struct ScannerView: View { struct ScannerView_Previews: PreviewProvider { static var previews: some View { - ScannerView(viewModel: DfuViewModel()) + NavigationView { + ScannerView(viewModel: DfuViewModel()) + }.navigationViewStyle(.stack) } } From 2dc54d450927bc0c35c5a573d4b42664ec9d2706 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 16:07:08 +0100 Subject: [PATCH 10/22] Minor code improvements --- App/dfu/scanner/ScannerView.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/App/dfu/scanner/ScannerView.swift b/App/dfu/scanner/ScannerView.swift index 17d89ab1..edbc037f 100644 --- a/App/dfu/scanner/ScannerView.swift +++ b/App/dfu/scanner/ScannerView.swift @@ -42,7 +42,7 @@ struct ScannerView: View { var body: some View { List { - Section(header: Text(DfuStrings.filters.text)) { + Section(DfuStrings.filters.text) { HStack { Toggle(DfuStrings.nearbyOnly.text, isOn: $bluetoothManager.nearbyOnlyFilter) .toggleStyle(.switch) @@ -50,22 +50,23 @@ struct ScannerView: View { bluetoothManager.nearbyOnlyFilter = value } - Spacer().frame(width: 16) + Spacer() Toggle(DfuStrings.withName.text, isOn: $bluetoothManager.withNameOnlyFilter) .toggleStyle(.switch) .onChange(of: bluetoothManager.withNameOnlyFilter) { value in bluetoothManager.withNameOnlyFilter = value } - }.padding(.horizontal) + } + .padding(.horizontal) } - Section(header: Text(DfuStrings.devices.text)) { + Section(DfuStrings.devices.text) { ForEach(bluetoothManager.filteredDevices()) { device in Button( action: { viewModel.device = device - self.presentationMode.wrappedValue.dismiss() + presentationMode.wrappedValue.dismiss() } ) { HStack { From f2d51c9f06952302a6d59d60fa5989f865ce51a9 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 16:14:58 +0100 Subject: [PATCH 11/22] Settings screen improvements --- App/dfu/screens/SettingsView.swift | 59 ++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/App/dfu/screens/SettingsView.swift b/App/dfu/screens/SettingsView.swift index e06d4a4a..b20b88d9 100644 --- a/App/dfu/screens/SettingsView.swift +++ b/App/dfu/screens/SettingsView.swift @@ -36,10 +36,9 @@ struct SettingsView: View { @ObservedObject var viewModel: DfuViewModel - @State private var showWelcomeScreen: Bool? - @State private var title: String = "" @State private var description: String = "" + @State private var prn: String = "" @State private var showingAlert: Bool = false @@ -90,7 +89,8 @@ struct SettingsView: View { HStack { Toggle(isOn: $viewModel.disableResume) { Text(DfuStrings.settingsDisableResumeTitle.text) - }.tint(ThemeColor.nordicBlue.color) + } + .tint(ThemeColor.nordicBlue.color) Image(systemName: DfuImages.info.imageName) .foregroundColor(ThemeColor.nordicBlue.color) @@ -106,7 +106,8 @@ struct SettingsView: View { HStack { Toggle(isOn: $viewModel.forceScanningInLegacyDfu) { Text(DfuStrings.settingsForceScanningTitle.text) - }.tint(ThemeColor.nordicBlue.color) + } + .tint(ThemeColor.nordicBlue.color) Image(systemName: DfuImages.info.imageName) .foregroundColor(ThemeColor.nordicBlue.color) @@ -120,7 +121,8 @@ struct SettingsView: View { HStack { Toggle(isOn: $viewModel.externalMcuDfu) { Text(DfuStrings.settingsExternalMcuTitle.text) - }.tint(ThemeColor.nordicBlue.color) + } + .tint(ThemeColor.nordicBlue.color) Image(systemName: DfuImages.info.imageName) .foregroundColor(ThemeColor.nordicBlue.color) @@ -135,37 +137,54 @@ struct SettingsView: View { Section(DfuStrings.settingsOther.text) { Link(DfuStrings.settingsAboutTitle.text, destination: URL(string: INFOCENTER_LINK)!) - NavigationLink(destination: WelcomeScreen(viewModel: viewModel), tag: true, selection: $showWelcomeScreen) { - Text(DfuStrings.settingsWelcome.text) - }.accessibilityIdentifier(DfuIds.welcomeButton.rawValue) + NavigationLink(DfuStrings.settingsWelcome.text) { + WelcomeScreen(viewModel: viewModel) + } + .accessibilityIdentifier(DfuIds.welcomeButton.rawValue) } } .navigationTitle(DfuStrings.settings.text) + .onAppear { + prn = "\(viewModel.numberOfPackets)" + } .alert(title, isPresented: $showingAlert, actions: { - Button(DfuStrings.ok.text, role: .cancel) { - showingAlert = false - } + Button(DfuStrings.ok.text, role: .cancel) {} }, message: { Text(description) } ) - .alert( - isPresented: $showingNumberOfPacketsDialog, - TextAlert( - title: DfuStrings.numberOfPackets.text, - message: DfuStrings.settingsProvideNumberOfPackets.text, - keyboardType: .numberPad - ) { result in - if let result = result { - viewModel.numberOfPackets = Int(result) ?? viewModel.numberOfPackets + .alert(DfuStrings.numberOfPackets.text, isPresented: $showingNumberOfPacketsDialog, + actions: { + TextField("E.g. 12", text: $prn.limit(5)) + .keyboardType(.numberPad) + Button(DfuStrings.cancel.text, role: .cancel) {} + Button(DfuStrings.ok.text) { + guard let prn = Int(prn) else { return } + viewModel.numberOfPackets = prn } + }, + message: { + Text(DfuStrings.settingsProvideNumberOfPackets.text) } ) } } +extension Binding where Value == String { + + func limit(_ limit: Int) -> Self { + if wrappedValue.count > limit { + DispatchQueue.main.async { + self.wrappedValue = String(self.wrappedValue.dropLast()) + } + } + return self + } + +} + struct SettingsView_Previews: PreviewProvider { static var previews: some View { NavigationView { From 3aba5dc8496ed2d37b372bcf13f63fa940c6a1af Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 16:15:26 +0100 Subject: [PATCH 12/22] Setting min iOS version to 16 and related changes --- App/dfu.xcodeproj/project.pbxproj | 2 ++ App/dfu/DfuApp.swift | 4 ++-- App/dfu/scanner/ScannerView.swift | 4 ++-- App/dfu/screens/DeviceSectionView.swift | 17 ++++++++--------- App/dfu/screens/FileSectionView.swift | 12 +++++------- App/dfu/screens/ProgressSectionView.swift | 16 ++++++++-------- App/dfu/theme/AbortButtonStyle.swift | 12 ------------ App/dfu/theme/ButtonStyle.swift | 11 ----------- App/dfu/welcome/WelcomeScreen.swift | 11 ++++++----- 9 files changed, 33 insertions(+), 56 deletions(-) diff --git a/App/dfu.xcodeproj/project.pbxproj b/App/dfu.xcodeproj/project.pbxproj index 1cd0c2e9..0143b721 100644 --- a/App/dfu.xcodeproj/project.pbxproj +++ b/App/dfu.xcodeproj/project.pbxproj @@ -553,6 +553,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -592,6 +593,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/App/dfu/DfuApp.swift b/App/dfu/DfuApp.swift index b3b4ee5b..9e71f40d 100644 --- a/App/dfu/DfuApp.swift +++ b/App/dfu/DfuApp.swift @@ -35,9 +35,9 @@ struct DfuApp: App { var body: some Scene { WindowGroup { - NavigationView { + NavigationStack { ContentView() - }.navigationViewStyle(.stack) + } } } } diff --git a/App/dfu/scanner/ScannerView.swift b/App/dfu/scanner/ScannerView.swift index edbc037f..0a10ad00 100644 --- a/App/dfu/scanner/ScannerView.swift +++ b/App/dfu/scanner/ScannerView.swift @@ -87,8 +87,8 @@ struct ScannerView: View { struct ScannerView_Previews: PreviewProvider { static var previews: some View { - NavigationView { + NavigationStack { ScannerView(viewModel: DfuViewModel()) - }.navigationViewStyle(.stack) + } } } diff --git a/App/dfu/screens/DeviceSectionView.swift b/App/dfu/screens/DeviceSectionView.swift index c5ad26f1..00dc143e 100644 --- a/App/dfu/screens/DeviceSectionView.swift +++ b/App/dfu/screens/DeviceSectionView.swift @@ -34,8 +34,6 @@ struct DeviceSectionView: View { @ObservedObject var viewModel: DfuViewModel - @State private var goToScannerView: Bool? - var body: some View { VStack { HStack { @@ -46,13 +44,14 @@ struct DeviceSectionView: View { .font(.title) .frame(maxWidth: .infinity, alignment: .leading) - NavigationLink(destination: ScannerView(viewModel: viewModel), tag: true, selection: $goToScannerView) { } - - DfuButton( - title: DfuStrings.select.text, - action: { goToScannerView = true } - ) - }.padding() + NavigationLink{ + ScannerView(viewModel: viewModel) + } label: { + Text(DfuStrings.select.text) + } + .buttonStyle(DfuButtonStyle()) + } + .padding() HStack { RoundedRectangle(cornerRadius: 20) diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index 23aeaa66..fe670f81 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -46,13 +46,11 @@ struct FileSectionView: View { .padding() .frame(maxWidth: .infinity, alignment: .leading) - DfuButton( - title: DfuStrings.select.rawValue, - action: { - self.openFile.toggle() - viewModel.clearFileError() - } - ) + Button(DfuStrings.select.text) { + openFile.toggle() + viewModel.clearFileError() + } + .buttonStyle(DfuButtonStyle()) } .padding() diff --git a/App/dfu/screens/ProgressSectionView.swift b/App/dfu/screens/ProgressSectionView.swift index 81c9e8ee..b3a6f87f 100644 --- a/App/dfu/screens/ProgressSectionView.swift +++ b/App/dfu/screens/ProgressSectionView.swift @@ -45,15 +45,15 @@ struct ProgressSectionView: View { .frame(maxWidth: .infinity, alignment: .leading) if viewModel.progressSection.isRunning() { - AbortButton( - title: DfuStrings.abort.rawValue, - action: { viewModel.abort() } - ) + Button(DfuStrings.abort.rawValue) { + viewModel.abort() + } + .buttonStyle(AbortButtonStyle()) } else { - DfuButton( - title: DfuStrings.upload.rawValue, - action: { viewModel.install() } - ) + Button(DfuStrings.upload.rawValue) { + viewModel.install() + } + .buttonStyle(DfuButtonStyle()) } }.padding() diff --git a/App/dfu/theme/AbortButtonStyle.swift b/App/dfu/theme/AbortButtonStyle.swift index 39097672..a64b56d1 100644 --- a/App/dfu/theme/AbortButtonStyle.swift +++ b/App/dfu/theme/AbortButtonStyle.swift @@ -58,15 +58,3 @@ private extension AbortButtonStyle { } } - -struct AbortButton: View { - let title: String - let action: () -> Void - - var body: some View { - Button(action: action) { - Text(self.title).foregroundColor(Color.white) - }.buttonStyle(AbortButtonStyle()) - } -} - diff --git a/App/dfu/theme/ButtonStyle.swift b/App/dfu/theme/ButtonStyle.swift index 18628429..a58276a2 100644 --- a/App/dfu/theme/ButtonStyle.swift +++ b/App/dfu/theme/ButtonStyle.swift @@ -58,16 +58,5 @@ private extension DfuButtonStyle { .scaleEffect(configuration.isPressed ? 0.98 : 1.0) } } -} - -struct DfuButton: View { - let title: String - let action: () -> Void - var body: some View { - Button(action: action) { - Text(self.title).foregroundColor(Color.white) - } - .buttonStyle(DfuButtonStyle()) - } } diff --git a/App/dfu/welcome/WelcomeScreen.swift b/App/dfu/welcome/WelcomeScreen.swift index 6ae352dd..c1c967a3 100644 --- a/App/dfu/welcome/WelcomeScreen.swift +++ b/App/dfu/welcome/WelcomeScreen.swift @@ -50,11 +50,12 @@ struct WelcomeScreen: View { Spacer() - DfuButton( - title: DfuStrings.welcomeStart.text, - action: { presentationMode.wrappedValue.dismiss() } - ) - }.padding() + Button(DfuStrings.welcomeStart.text) { + presentationMode.wrappedValue.dismiss() + } + .buttonStyle(DfuButtonStyle()) + } + .padding() } .navigationTitle(DfuStrings.welcomeTitle.text) .onAppear { viewModel.welcomeScreenDidShow() } From 0ec734ab8b99639eb8430879266a782bb9706a3b Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 16:24:36 +0100 Subject: [PATCH 13/22] Settings descriptions modified --- App/dfu/theme/DfuStrings.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/App/dfu/theme/DfuStrings.swift b/App/dfu/theme/DfuStrings.swift index 858478cc..5670a99f 100644 --- a/App/dfu/theme/DfuStrings.swift +++ b/App/dfu/theme/DfuStrings.swift @@ -78,15 +78,15 @@ enum DfuStrings : String { case dfuFinished = "DFU initialized" case settingsPacketReceiptTitle = "Packets receipt notification" - case settingsPacketReceiptValue = "Enables or disables the Packet Receipt Notification (PRN) procedure. \n\nPRNs are used to synchronize the transmitter with the receiver and ensure data correctness. They are enabled by default on Android Lollipop and earlier." + case settingsPacketReceiptValue = "Enables or disables the Packet Receipt Notification (PRN) procedure.\n\nPRNs are used to synchronize the transmitter with the receiver and ensure data correctness." case settingsSecureDfu = "Secure DFU option" case settingsDisableResumeTitle = "Disable resume" case settingsDisableResumeValue = "This options allows to disable the resume feature in Secure DFU." case settingsLegacyDfu = "Legacy DFU" case settingsForceScanningTitle = "Force scanning" - case settingsForceScanningValue = "Forces scanning for DFU bootloader. By default, the DFU bootloader advertises directly with the same MAC address.\n\nChanging this requires modifying the DFU bootloader or the Buttonless Service implementation on the device." + case settingsForceScanningValue = "Enables scanning for DFU bootloader in Legacy DFU. By default, a Legacy DFU bootloader advertises directly with the same MAC address.\n\nChanging this requires modifying the DFU bootloader or the Buttonless Service implementation on the device." case alternativeAdvertisingNameTitle = "Alternative Advertising Name" - case alternativeAdvertisingNameValue = "Sets advertising name that will be displayed on a device after flashing it." + case alternativeAdvertisingNameValue = "When enabled, the app will send a unique ID to the connected device to be able to reconnect to it in bootloader mode. Otherwise, it will connect to first found device advertising DFU Service UUID." case alternativeAdvertisingNameDialogInfo = "Please set advertising name:" case settingsExternalMcuTitle = "External MCU DFU" case settingsExternalMcuValue = "Enabling this option will prevent from jumping to the DFU Bootloader mode in case there is no DFU Version characteristic." From f10996cf6a1f57bbe83d3738856019ece129a92a Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 16:42:46 +0100 Subject: [PATCH 14/22] Fixed blinking nearby devices --- App/dfu/bluetooth/BluetoothDevice.swift | 27 +++++++++++++++++++----- App/dfu/bluetooth/BluetoothManager.swift | 25 +++++++++------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/App/dfu/bluetooth/BluetoothDevice.swift b/App/dfu/bluetooth/BluetoothDevice.swift index 03207af3..c99683d2 100644 --- a/App/dfu/bluetooth/BluetoothDevice.swift +++ b/App/dfu/bluetooth/BluetoothDevice.swift @@ -34,17 +34,34 @@ import os import os.log import CoreBluetooth -struct BluetoothDevice : Identifiable { +struct BluetoothDevice: Identifiable { let id = UUID() let peripheral: CBPeripheral - let rssi: NSNumber - let name: String? + var rssi: Int + var name: String? + var highestRssi: Int + var hadName: Bool + + init(peripheral: CBPeripheral, rssi: Int, name: String?) { + self.peripheral = peripheral + self.rssi = rssi + self.name = name + self.highestRssi = rssi + self.hadName = name != nil + } + + mutating func update(rssi: Int, name: String?) { + self.rssi = rssi + self.name = name + self.highestRssi = max(rssi, highestRssi) + self.hadName = hadName || name != nil + } func getSignalStrength() -> SignalStrength { - if rssi.compare(NSNumber(-65)) == .orderedDescending { + if rssi > -50 { return SignalStrength.strong } - else if rssi.compare(NSNumber(-85)) == .orderedDescending { + else if rssi > -75 { return SignalStrength.normal } else { diff --git a/App/dfu/bluetooth/BluetoothManager.swift b/App/dfu/bluetooth/BluetoothManager.swift index cd7f3088..a4256bc8 100644 --- a/App/dfu/bluetooth/BluetoothManager.swift +++ b/App/dfu/bluetooth/BluetoothManager.swift @@ -35,7 +35,7 @@ import os.log import CoreBluetooth class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { - private let MIN_RSSI = NSNumber(-65) + private let MIN_RSSI = -50 @Published var devices: [BluetoothDevice] = [] @@ -54,17 +54,9 @@ class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { } func filteredDevices() -> [BluetoothDevice] { - return devices.filter { device in - if !nearbyOnlyFilter { - return true - } - return device.rssi.compare(MIN_RSSI) == .orderedDescending - }.filter { device in - if !withNameOnlyFilter { - return true - } - return device.name != nil - } + return devices + .filter { !nearbyOnlyFilter || $0.highestRssi > MIN_RSSI } + .filter { !withNameOnlyFilter || $0.hadName } } func startScan() { @@ -79,7 +71,10 @@ class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { private func runScanningWhenNeeded() { if isOnScreen && isBluetoothReady { - centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]) + centralManager.scanForPeripherals( + withServices: nil, + options: [CBCentralManagerScanOptionAllowDuplicatesKey: true] + ) } } } @@ -108,11 +103,11 @@ extension BluetoothManager: CBCentralManagerDelegate { rssi RSSI: NSNumber ) { let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String - let device = BluetoothDevice(peripheral: peripheral, rssi: RSSI, name: name) let index = devices.firstIndex { $0.peripheral == peripheral } if let index = index { - devices[index] = device + devices[index].update(rssi: Int(truncating: RSSI), name: name) } else { + let device = BluetoothDevice(peripheral: peripheral, rssi: Int(truncating: RSSI), name: name) devices.append(device) } } From 2452ae63ecc553391865a2d937670567bf9cd7bb Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 17:08:14 +0100 Subject: [PATCH 15/22] More scanner improvements --- App/dfu.xcodeproj/project.pbxproj | 4 ++ App/dfu/bluetooth/BluetoothDevice.swift | 12 ----- App/dfu/scanner/DeviceView.swift | 66 +++++++++++++++++++++++++ App/dfu/scanner/ScannerView.swift | 19 +++---- 4 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 App/dfu/scanner/DeviceView.swift diff --git a/App/dfu.xcodeproj/project.pbxproj b/App/dfu.xcodeproj/project.pbxproj index 0143b721..2155f493 100644 --- a/App/dfu.xcodeproj/project.pbxproj +++ b/App/dfu.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 52F3488F29536223007D0395 /* DeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F3488E29536223007D0395 /* DeviceView.swift */; }; 8D02F955281FD99B0030D67A /* DfuImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D02F954281FD99B0030D67A /* DfuImages.swift */; }; 8D4DEA0B27F4688500C1AD43 /* NumberOfPacketsDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D4DEA0A27F4688500C1AD43 /* NumberOfPacketsDialog.swift */; }; 8D4DEA0D27F4898400C1AD43 /* TextAlertDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D4DEA0C27F4898400C1AD43 /* TextAlertDialog.swift */; }; @@ -56,6 +57,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 52F3488E29536223007D0395 /* DeviceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceView.swift; sourceTree = ""; }; 8D02F954281FD99B0030D67A /* DfuImages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DfuImages.swift; sourceTree = ""; }; 8D086C1D283259B600E63785 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8D0FA0B72840E15800DCA098 /* fastlane */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fastlane; sourceTree = ""; }; @@ -121,6 +123,7 @@ isa = PBXGroup; children = ( 8D6228C127EDB87F00AE3E1B /* ScannerView.swift */, + 52F3488E29536223007D0395 /* DeviceView.swift */, ); path = scanner; sourceTree = ""; @@ -382,6 +385,7 @@ 8D02F955281FD99B0030D67A /* DfuImages.swift in Sources */, 8D6228C227EDB87F00AE3E1B /* ScannerView.swift in Sources */, 8D6228C427EDECA400AE3E1B /* BluetoothDevice.swift in Sources */, + 52F3488F29536223007D0395 /* DeviceView.swift in Sources */, 8DA4AE1A2841097C00BFA79E /* DfuIds.swift in Sources */, 8D9B54A727F5C4EC009A5461 /* ProgressItemView.swift in Sources */, 8DEF02E727EB48A400838144 /* DfuApp.swift in Sources */, diff --git a/App/dfu/bluetooth/BluetoothDevice.swift b/App/dfu/bluetooth/BluetoothDevice.swift index c99683d2..5b818060 100644 --- a/App/dfu/bluetooth/BluetoothDevice.swift +++ b/App/dfu/bluetooth/BluetoothDevice.swift @@ -56,16 +56,4 @@ struct BluetoothDevice: Identifiable { self.highestRssi = max(rssi, highestRssi) self.hadName = hadName || name != nil } - - func getSignalStrength() -> SignalStrength { - if rssi > -50 { - return SignalStrength.strong - } - else if rssi > -75 { - return SignalStrength.normal - } - else { - return SignalStrength.weak - } - } } diff --git a/App/dfu/scanner/DeviceView.swift b/App/dfu/scanner/DeviceView.swift new file mode 100644 index 00000000..f5600e91 --- /dev/null +++ b/App/dfu/scanner/DeviceView.swift @@ -0,0 +1,66 @@ +/* +* Copyright (c) 2022, Nordic Semiconductor +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this +* list of conditions and the following disclaimer in the documentation and/or +* other materials provided with the distribution. +* +* 3. Neither the name of the copyright holder nor the names of its contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +import SwiftUI + +struct DeviceView: View { + let name: String + let rssi: Int + + var body: some View { + HStack { + Text(name) + .frame(maxWidth: .infinity, alignment: .leading) + + SignalStrengthIndicator(signalStrength: getSignalStrength()) + } + } + + private func getSignalStrength() -> SignalStrength { + switch rssi { + case let value where value > -50: + return .strong + case let value where value > -65: + return .normal + default: + return .weak + } + } +} + + +struct DeviceView_Previews: PreviewProvider { + static var previews: some View { + DeviceView(name: "Some name", rssi: -60) + .padding() + .background(.white) + .frame(height: 40) + } +} diff --git a/App/dfu/scanner/ScannerView.swift b/App/dfu/scanner/ScannerView.swift index 0a10ad00..7be10ede 100644 --- a/App/dfu/scanner/ScannerView.swift +++ b/App/dfu/scanner/ScannerView.swift @@ -63,17 +63,14 @@ struct ScannerView: View { Section(DfuStrings.devices.text) { ForEach(bluetoothManager.filteredDevices()) { device in - Button( - action: { - viewModel.device = device - presentationMode.wrappedValue.dismiss() - } - ) { - HStack { - Text(device.name ?? DfuStrings.noName.text) - .frame(maxWidth: .infinity, alignment: .leading) - SignalStrengthIndicator(signalStrength: device.getSignalStrength()) - } + Button { + viewModel.device = device + presentationMode.wrappedValue.dismiss() + } label: { + DeviceView( + name: device.name ?? DfuStrings.noName.text, + rssi: device.rssi + ) } } } From 5dfb8de7a2c285c71a1788b68e14ad8b2528399a Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 17:08:46 +0100 Subject: [PATCH 16/22] Minor texts modifications --- App/dfu/screens/DeviceSectionView.swift | 2 +- App/dfu/screens/FileSectionView.swift | 2 +- App/dfu/theme/DfuStrings.swift | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/App/dfu/screens/DeviceSectionView.swift b/App/dfu/screens/DeviceSectionView.swift index 00dc143e..acefa6e0 100644 --- a/App/dfu/screens/DeviceSectionView.swift +++ b/App/dfu/screens/DeviceSectionView.swift @@ -61,7 +61,7 @@ struct DeviceSectionView: View { .padding(.trailing, 25) if let device = viewModel.device { - Text(String(format: DfuStrings.deviceName.text, device.name ?? DfuStrings.noName.text)) + Text(String(format: DfuStrings.name.text, device.name ?? DfuStrings.noName.text)) .padding(.vertical, 8) } else { Text(DfuStrings.deviceSelect.text) diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index fe670f81..84b50146 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -63,7 +63,7 @@ struct FileSectionView: View { VStack { if let file = viewModel.zipFile { - Text(String(format: DfuStrings.fileName.rawValue, file.name)) + Text(String(format: DfuStrings.name.rawValue, file.name)) .frame(maxWidth: .infinity, alignment: .leading) Text(String(format: DfuStrings.fileSize.rawValue, file.size)) .frame(maxWidth: .infinity, alignment: .leading) diff --git a/App/dfu/theme/DfuStrings.swift b/App/dfu/theme/DfuStrings.swift index 5670a99f..cf5fe3a6 100644 --- a/App/dfu/theme/DfuStrings.swift +++ b/App/dfu/theme/DfuStrings.swift @@ -51,7 +51,7 @@ enum DfuStrings : String { case settings = "Settings" case scanner = "Scanner" case nearbyOnly = "Nearby" - case withName = "With name" + case withName = "Name" case firmwareUpload = "Firmware upload" case firmwareUploaded = "Firmware uploaded" @@ -63,11 +63,10 @@ enum DfuStrings : String { case resultError = "Error: %@" case fileSelect = "Select a .zip file" - case fileName = "Name: %@" case fileSize = "Size: %d bytes" case deviceSelect = "Select a device" - case deviceName = "Device: %@" + case name = "Name: %@" case bootloaderIdle = "Bootloader enablement" case bootloaderInProgress = "Enabling bootloader..." From e42be85b0b6d9d3ad21aa6385e90afcff84663be Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 21 Dec 2022 17:15:14 +0100 Subject: [PATCH 17/22] Filter improvements --- App/dfu/bluetooth/BluetoothManager.swift | 2 +- App/dfu/scanner/ScannerView.swift | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/App/dfu/bluetooth/BluetoothManager.swift b/App/dfu/bluetooth/BluetoothManager.swift index a4256bc8..3b4df573 100644 --- a/App/dfu/bluetooth/BluetoothManager.swift +++ b/App/dfu/bluetooth/BluetoothManager.swift @@ -41,7 +41,7 @@ class BluetoothManager : NSObject, CBPeripheralDelegate, ObservableObject { @Published var nearbyOnlyFilter = false - @Published var withNameOnlyFilter = false + @Published var withNameOnlyFilter = true private var centralManager: CBCentralManager! diff --git a/App/dfu/scanner/ScannerView.swift b/App/dfu/scanner/ScannerView.swift index 7be10ede..6406f271 100644 --- a/App/dfu/scanner/ScannerView.swift +++ b/App/dfu/scanner/ScannerView.swift @@ -44,8 +44,10 @@ struct ScannerView: View { List { Section(DfuStrings.filters.text) { HStack { + Spacer() + Toggle(DfuStrings.nearbyOnly.text, isOn: $bluetoothManager.nearbyOnlyFilter) - .toggleStyle(.switch) + .toggleStyle(.button) .onChange(of: bluetoothManager.nearbyOnlyFilter) { value in bluetoothManager.nearbyOnlyFilter = value } @@ -53,10 +55,12 @@ struct ScannerView: View { Spacer() Toggle(DfuStrings.withName.text, isOn: $bluetoothManager.withNameOnlyFilter) - .toggleStyle(.switch) + .toggleStyle(.button) .onChange(of: bluetoothManager.withNameOnlyFilter) { value in bluetoothManager.withNameOnlyFilter = value } + + Spacer() } .padding(.horizontal) } From 92f02e8c04c20c5ada935af26d886bfbee1c81e9 Mon Sep 17 00:00:00 2001 From: philips77 Date: Thu, 22 Dec 2022 13:45:38 +0100 Subject: [PATCH 18/22] Welcome screen improvements --- .../icons/dfu.imageset/Contents.json | 11 +--------- App/dfu/theme/DfuImages.swift | 1 + App/dfu/theme/DfuStrings.swift | 3 ++- App/dfu/welcome/WelcomeScreen.swift | 20 +++++++++++++++++-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/App/dfu/Assets.xcassets/icons/dfu.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/dfu.imageset/Contents.json index 04cb3627..a68e77a9 100644 --- a/App/dfu/Assets.xcassets/icons/dfu.imageset/Contents.json +++ b/App/dfu/Assets.xcassets/icons/dfu.imageset/Contents.json @@ -2,16 +2,7 @@ "images" : [ { "filename" : "dfu.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/App/dfu/theme/DfuImages.swift b/App/dfu/theme/DfuImages.swift index 4a84c4ef..86e3c9e0 100644 --- a/App/dfu/theme/DfuImages.swift +++ b/App/dfu/theme/DfuImages.swift @@ -37,6 +37,7 @@ enum DfuImages : String { case fileUpload = "file-upload-outline" case bluetooth = "bluetooth" case upload = "upload" + case launch = "rectangle.portrait.and.arrow.forward" case dfu = "dfu" case info = "info.circle" diff --git a/App/dfu/theme/DfuStrings.swift b/App/dfu/theme/DfuStrings.swift index cf5fe3a6..603a7cf7 100644 --- a/App/dfu/theme/DfuStrings.swift +++ b/App/dfu/theme/DfuStrings.swift @@ -33,7 +33,8 @@ enum DfuStrings : String { case welcomeTitle = "Welcome" case welcomeStart = "Start" - case welcomeText = "The Device Firmware Update (DFU) app allows updating the firmware of Bluetooth LE devices over-the-air (OTA). It is compatible with nRF51 and nRF52 devices from Nordic Semiconductor running nRF5 SDK based applications with DFU bootloader enabled.\n\nFor more information about the DFU, see the About DFU section in Settings.\n\nTo update a nRF Connect SDK (NCS) based device, use nRF Connect Device Manager app instead." + case welcomeText = "The Device Firmware Update (DFU) app allows updating the firmware of Bluetooth LE devices over-the-air (OTA). It is compatible with nRF51 and nRF52 devices from Nordic Semiconductor running nRF5 SDK based applications with DFU bootloader enabled.\n\nFor more information about the DFU, click \"About DFU\" in Settings." + case welcomeNote = "To update devices based on nRF\u{00a0}Connect\u{00a0}SDK\u{00a0}(NCS), use nRF\u{00a0}Connect\u{00a0}Device\u{00a0}Manager app instead." case numberOfPackets = "Number of Packets" case numberRequest = "Please provide number:" case cancel = "Cancel" diff --git a/App/dfu/welcome/WelcomeScreen.swift b/App/dfu/welcome/WelcomeScreen.swift index c1c967a3..adfa7ff5 100644 --- a/App/dfu/welcome/WelcomeScreen.swift +++ b/App/dfu/welcome/WelcomeScreen.swift @@ -31,6 +31,7 @@ import SwiftUI struct WelcomeScreen: View { + let nrfCDM = "https://apps.apple.com/us/app/nrf-connect-device-manager/id1519423539" @Environment(\.presentationMode) var presentationMode @@ -41,14 +42,29 @@ struct WelcomeScreen: View { VStack { Image(DfuImages.dfu.imageName) .resizable() - .padding() .aspectRatio(contentMode: .fit) + .frame(maxWidth: 300) Spacer() Text(DfuStrings.welcomeText.text) - Spacer() + Link(destination: URL(string: nrfCDM)!) { + HStack { + Text(DfuStrings.welcomeNote.text) + Image(systemName: DfuImages.launch.imageName) + .padding(.leading) + } + .padding() + .foregroundColor(.primary) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(.orange) + .opacity(0.3) + ) + } + + Spacer(minLength: 24) Button(DfuStrings.welcomeStart.text) { presentationMode.wrappedValue.dismiss() From 92fa2182c3910a6e6dfcdeb47bcd9d7ff69cf7f6 Mon Sep 17 00:00:00 2001 From: philips77 Date: Thu, 22 Dec 2022 13:48:04 +0100 Subject: [PATCH 19/22] Setting main title to Firmware Upgrade to match Android version --- App/dfu/theme/DfuStrings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/App/dfu/theme/DfuStrings.swift b/App/dfu/theme/DfuStrings.swift index 603a7cf7..e049574c 100644 --- a/App/dfu/theme/DfuStrings.swift +++ b/App/dfu/theme/DfuStrings.swift @@ -29,7 +29,7 @@ */ enum DfuStrings : String { - case dfuTitle = "DFU" + case dfuTitle = "Firmware Upgrade" case welcomeTitle = "Welcome" case welcomeStart = "Start" From 5e052fe8a80c62cfe71ff055eaa5389d40f6ec1d Mon Sep 17 00:00:00 2001 From: philips77 Date: Thu, 22 Dec 2022 14:04:31 +0100 Subject: [PATCH 20/22] Downloading indicator added --- App/dfu/screens/FileSectionView.swift | 8 ++++++++ App/dfu/theme/DfuStrings.swift | 1 + 2 files changed, 9 insertions(+) diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index 84b50146..fe73f81b 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -33,6 +33,7 @@ import NordicDFU struct FileSectionView: View { @State private var openFile = false + @State private var loading = false @ObservedObject var viewModel: DfuViewModel @@ -64,9 +65,13 @@ struct FileSectionView: View { VStack { if let file = viewModel.zipFile { Text(String(format: DfuStrings.name.rawValue, file.name)) + .lineLimit(1) .frame(maxWidth: .infinity, alignment: .leading) Text(String(format: DfuStrings.fileSize.rawValue, file.size)) .frame(maxWidth: .infinity, alignment: .leading) + } else if loading { + Text(DfuStrings.fileLoading.rawValue) + .frame(maxWidth: .infinity, alignment: .leading) } else { Text(DfuStrings.fileSelect.rawValue) .frame(maxWidth: .infinity, alignment: .leading) @@ -150,17 +155,20 @@ struct FileSectionView: View { onError(error.localizedDescription) } } + loading = true downloadTask.resume() } private func onFileSelected(_ file: ZipFile) throws { DispatchQueue.main.async { + loading = false viewModel.onFileSelected(file) } } private func onError(_ message: String) { DispatchQueue.main.async { + loading = false viewModel.onFileError(message) } } diff --git a/App/dfu/theme/DfuStrings.swift b/App/dfu/theme/DfuStrings.swift index e049574c..c43cf524 100644 --- a/App/dfu/theme/DfuStrings.swift +++ b/App/dfu/theme/DfuStrings.swift @@ -63,6 +63,7 @@ enum DfuStrings : String { case resultCompleted = "Completed" case resultError = "Error: %@" + case fileLoading = "Downloading..." case fileSelect = "Select a .zip file" case fileSize = "Size: %d bytes" From ea784a3e18a7c81624914f2a36276f5f3aff6845 Mon Sep 17 00:00:00 2001 From: philips77 Date: Thu, 22 Dec 2022 15:16:14 +0100 Subject: [PATCH 21/22] Importing files fixed --- App/dfu/screens/FileSectionView.swift | 28 ++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index fe73f81b..bc1cc7a2 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -63,15 +63,15 @@ struct FileSectionView: View { .padding(.trailing, 25) VStack { - if let file = viewModel.zipFile { + if loading { + Text(DfuStrings.fileLoading.rawValue) + .frame(maxWidth: .infinity, alignment: .leading) + } else if let file = viewModel.zipFile { Text(String(format: DfuStrings.name.rawValue, file.name)) .lineLimit(1) .frame(maxWidth: .infinity, alignment: .leading) Text(String(format: DfuStrings.fileSize.rawValue, file.size)) .frame(maxWidth: .infinity, alignment: .leading) - } else if loading { - Text(DfuStrings.fileLoading.rawValue) - .frame(maxWidth: .infinity, alignment: .leading) } else { Text(DfuStrings.fileSelect.rawValue) .frame(maxWidth: .infinity, alignment: .leading) @@ -108,25 +108,32 @@ struct FileSectionView: View { } private func onFileOpen(opened fileUrl: URL) { - let downloadTask = URLSession.shared.downloadTask(with: fileUrl) { - urlOrNil, responseOrNil, errorOrNil in - if let error = errorOrNil { + guard !fileUrl.isFileURL || fileUrl.startAccessingSecurityScopedResource() else { + onError("Permission denied") + return + } + + loading = true + + let downloadTask = URLSession.shared.downloadTask(with: fileUrl) { url, response, error in + fileUrl.stopAccessingSecurityScopedResource() + if let error = error { onError(error.localizedDescription) return } - if let response = responseOrNil as? HTTPURLResponse { + if let response = response as? HTTPURLResponse { guard response.statusCode >= 200 && response.statusCode < 300 else { onError(DfuStrings.fileDownloadError.text) return } } - guard let response = responseOrNil else { + guard let response = response else { onError(DfuStrings.fileError.text) return } - guard let fileURL = urlOrNil else { return } + guard let fileURL = url else { return } do { let documentsURL = try FileManager.default.url( for: .documentDirectory, @@ -155,7 +162,6 @@ struct FileSectionView: View { onError(error.localizedDescription) } } - loading = true downloadTask.resume() } From 2b5e186fc0d48cdfa6d713e121872bfb18fc697d Mon Sep 17 00:00:00 2001 From: philips77 Date: Thu, 22 Dec 2022 15:44:39 +0100 Subject: [PATCH 22/22] Icons improvements --- .../icons/bluetooth.imageset/Contents.json | 21 ----- .../icons/bluetooth.imageset/bluetooth.svg | 1 - .../icons/error.imageset/Contents.json | 21 ----- .../icons/error.imageset/error.svg | 1 - .../Contents.json | 21 ----- .../file-upload-outline.svg | 1 - .../filled-circle.imageset/Contents.json | 21 ----- .../filled-circle.imageset/filled-circle.svg | 3 - .../icons/progress.imageset/Contents.json | 21 ----- .../icons/progress.imageset/progress.svg | 1 - .../icons/settings.imageset/Contents.json | 21 ----- .../icons/settings.imageset/settings.svg | 1 - .../icons/success.imageset/Contents.json | 21 ----- .../icons/success.imageset/check.svg | 1 - .../icons/upload.imageset/Contents.json | 81 ------------------- .../icons/upload.imageset/upload.svg | 1 - App/dfu/screens/DeviceSectionView.swift | 2 +- App/dfu/screens/FileSectionView.swift | 2 +- App/dfu/screens/ProgressSectionView.swift | 2 +- App/dfu/theme/DfuImages.swift | 14 ++-- App/dfu/theme/ImageStyle.swift | 4 +- App/dfu/views/ProgressItemView.swift | 14 ++-- App/dfu/views/ResultItemView.swift | 12 +-- App/dfu/views/StatusItemView.swift | 14 ++-- 24 files changed, 32 insertions(+), 270 deletions(-) delete mode 100644 App/dfu/Assets.xcassets/icons/bluetooth.imageset/Contents.json delete mode 100644 App/dfu/Assets.xcassets/icons/bluetooth.imageset/bluetooth.svg delete mode 100644 App/dfu/Assets.xcassets/icons/error.imageset/Contents.json delete mode 100644 App/dfu/Assets.xcassets/icons/error.imageset/error.svg delete mode 100644 App/dfu/Assets.xcassets/icons/file-upload-outline.imageset/Contents.json delete mode 100644 App/dfu/Assets.xcassets/icons/file-upload-outline.imageset/file-upload-outline.svg delete mode 100644 App/dfu/Assets.xcassets/icons/filled-circle.imageset/Contents.json delete mode 100644 App/dfu/Assets.xcassets/icons/filled-circle.imageset/filled-circle.svg delete mode 100644 App/dfu/Assets.xcassets/icons/progress.imageset/Contents.json delete mode 100644 App/dfu/Assets.xcassets/icons/progress.imageset/progress.svg delete mode 100644 App/dfu/Assets.xcassets/icons/settings.imageset/Contents.json delete mode 100644 App/dfu/Assets.xcassets/icons/settings.imageset/settings.svg delete mode 100644 App/dfu/Assets.xcassets/icons/success.imageset/Contents.json delete mode 100644 App/dfu/Assets.xcassets/icons/success.imageset/check.svg delete mode 100644 App/dfu/Assets.xcassets/icons/upload.imageset/Contents.json delete mode 100644 App/dfu/Assets.xcassets/icons/upload.imageset/upload.svg diff --git a/App/dfu/Assets.xcassets/icons/bluetooth.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/bluetooth.imageset/Contents.json deleted file mode 100644 index a7c7ca0f..00000000 --- a/App/dfu/Assets.xcassets/icons/bluetooth.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "bluetooth.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/icons/bluetooth.imageset/bluetooth.svg b/App/dfu/Assets.xcassets/icons/bluetooth.imageset/bluetooth.svg deleted file mode 100644 index 2d7ae3a8..00000000 --- a/App/dfu/Assets.xcassets/icons/bluetooth.imageset/bluetooth.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/App/dfu/Assets.xcassets/icons/error.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/error.imageset/Contents.json deleted file mode 100644 index 8d95c25b..00000000 --- a/App/dfu/Assets.xcassets/icons/error.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "error.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/icons/error.imageset/error.svg b/App/dfu/Assets.xcassets/icons/error.imageset/error.svg deleted file mode 100644 index 18691d7e..00000000 --- a/App/dfu/Assets.xcassets/icons/error.imageset/error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/App/dfu/Assets.xcassets/icons/file-upload-outline.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/file-upload-outline.imageset/Contents.json deleted file mode 100644 index 3a02eda0..00000000 --- a/App/dfu/Assets.xcassets/icons/file-upload-outline.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "file-upload-outline.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/icons/file-upload-outline.imageset/file-upload-outline.svg b/App/dfu/Assets.xcassets/icons/file-upload-outline.imageset/file-upload-outline.svg deleted file mode 100644 index e54f168f..00000000 --- a/App/dfu/Assets.xcassets/icons/file-upload-outline.imageset/file-upload-outline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/App/dfu/Assets.xcassets/icons/filled-circle.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/filled-circle.imageset/Contents.json deleted file mode 100644 index b08efff3..00000000 --- a/App/dfu/Assets.xcassets/icons/filled-circle.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "filled-circle.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/icons/filled-circle.imageset/filled-circle.svg b/App/dfu/Assets.xcassets/icons/filled-circle.imageset/filled-circle.svg deleted file mode 100644 index 4f390284..00000000 --- a/App/dfu/Assets.xcassets/icons/filled-circle.imageset/filled-circle.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/App/dfu/Assets.xcassets/icons/progress.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/progress.imageset/Contents.json deleted file mode 100644 index 641b6511..00000000 --- a/App/dfu/Assets.xcassets/icons/progress.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "progress.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/icons/progress.imageset/progress.svg b/App/dfu/Assets.xcassets/icons/progress.imageset/progress.svg deleted file mode 100644 index e0ed6c22..00000000 --- a/App/dfu/Assets.xcassets/icons/progress.imageset/progress.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/App/dfu/Assets.xcassets/icons/settings.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/settings.imageset/Contents.json deleted file mode 100644 index b322f1ec..00000000 --- a/App/dfu/Assets.xcassets/icons/settings.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "settings.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/icons/settings.imageset/settings.svg b/App/dfu/Assets.xcassets/icons/settings.imageset/settings.svg deleted file mode 100644 index 4af1870f..00000000 --- a/App/dfu/Assets.xcassets/icons/settings.imageset/settings.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/App/dfu/Assets.xcassets/icons/success.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/success.imageset/Contents.json deleted file mode 100644 index 17203ccb..00000000 --- a/App/dfu/Assets.xcassets/icons/success.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "check.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/icons/success.imageset/check.svg b/App/dfu/Assets.xcassets/icons/success.imageset/check.svg deleted file mode 100644 index 68e30c18..00000000 --- a/App/dfu/Assets.xcassets/icons/success.imageset/check.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/App/dfu/Assets.xcassets/icons/upload.imageset/Contents.json b/App/dfu/Assets.xcassets/icons/upload.imageset/Contents.json deleted file mode 100644 index a19c6eea..00000000 --- a/App/dfu/Assets.xcassets/icons/upload.imageset/Contents.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "images" : [ - { - "filename" : "upload.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "light" - } - ], - "idiom" : "universal", - "scale" : "1x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "light" - } - ], - "idiom" : "universal", - "scale" : "2x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "light" - } - ], - "idiom" : "universal", - "scale" : "3x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/App/dfu/Assets.xcassets/icons/upload.imageset/upload.svg b/App/dfu/Assets.xcassets/icons/upload.imageset/upload.svg deleted file mode 100644 index c8e62db1..00000000 --- a/App/dfu/Assets.xcassets/icons/upload.imageset/upload.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/App/dfu/screens/DeviceSectionView.swift b/App/dfu/screens/DeviceSectionView.swift index acefa6e0..b96a6822 100644 --- a/App/dfu/screens/DeviceSectionView.swift +++ b/App/dfu/screens/DeviceSectionView.swift @@ -37,7 +37,7 @@ struct DeviceSectionView: View { var body: some View { VStack { HStack { - SectionImage(image: DfuImages.bluetooth.imageName) + SectionImage(image: DfuImages.bluetooth) Text(DfuStrings.device.text) .padding() diff --git a/App/dfu/screens/FileSectionView.swift b/App/dfu/screens/FileSectionView.swift index bc1cc7a2..e8f53174 100644 --- a/App/dfu/screens/FileSectionView.swift +++ b/App/dfu/screens/FileSectionView.swift @@ -40,7 +40,7 @@ struct FileSectionView: View { var body: some View { VStack { HStack { - SectionImage(image: DfuImages.fileUpload.rawValue) + SectionImage(image: DfuImages.fileUpload) Text(DfuStrings.file.rawValue) .font(.title) diff --git a/App/dfu/screens/ProgressSectionView.swift b/App/dfu/screens/ProgressSectionView.swift index b3a6f87f..07adcde3 100644 --- a/App/dfu/screens/ProgressSectionView.swift +++ b/App/dfu/screens/ProgressSectionView.swift @@ -37,7 +37,7 @@ struct ProgressSectionView: View { var body: some View { VStack { HStack { - SectionImage(image: DfuImages.upload.rawValue) + SectionImage(image: DfuImages.upload) Text(DfuStrings.progress.text) .padding() diff --git a/App/dfu/theme/DfuImages.swift b/App/dfu/theme/DfuImages.swift index 86e3c9e0..67285ea4 100644 --- a/App/dfu/theme/DfuImages.swift +++ b/App/dfu/theme/DfuImages.swift @@ -29,14 +29,14 @@ */ enum DfuImages : String { - case idle = "filled-circle" - case success = "success" - case progress = "progress" - case error = "error" + case idle = "circle.fill" + case success = "checkmark.circle" + case progress = "arrow.forward.circle" + case error = "x.circle.fill" - case fileUpload = "file-upload-outline" - case bluetooth = "bluetooth" - case upload = "upload" + case fileUpload = "doc.zipper" + case bluetooth = "circle.square.fill" + case upload = "square.and.arrow.up" case launch = "rectangle.portrait.and.arrow.forward" case dfu = "dfu" diff --git a/App/dfu/theme/ImageStyle.swift b/App/dfu/theme/ImageStyle.swift index 6fbd382a..16b73c32 100644 --- a/App/dfu/theme/ImageStyle.swift +++ b/App/dfu/theme/ImageStyle.swift @@ -34,10 +34,10 @@ struct SectionImage: View { @Environment(\.isEnabled) var isEnabled - let image: String + let image: DfuImages var body: some View { - Image(image) + Image(systemName: image.imageName) .renderingMode(.template) .foregroundColor(.white) .background(Circle() diff --git a/App/dfu/views/ProgressItemView.swift b/App/dfu/views/ProgressItemView.swift index 04571bd6..6c02d2c9 100644 --- a/App/dfu/views/ProgressItemView.swift +++ b/App/dfu/views/ProgressItemView.swift @@ -43,7 +43,7 @@ struct ProgressItemView: View { var body: some View { VStack { HStack { - Image(status.image) + Image(systemName: status.image.imageName) .renderingMode(.template) .foregroundColor(status.color) .frame(width: 24, height: 24) @@ -83,23 +83,23 @@ struct ProgressItemView: View { private extension DfuInstallationStatus { - var image: String { + var image: DfuImages { switch self { case .idle: - return DfuImages.idle.rawValue + return DfuImages.idle case .success: - return DfuImages.success.rawValue + return DfuImages.success case .progress: - return DfuImages.progress.rawValue + return DfuImages.progress case .error: - return DfuImages.error.rawValue + return DfuImages.error } } var color: Color { switch self { case .idle: - return ThemeColor.nordicDarkGray5.color + return .gray case .success: return ThemeColor.nordicGreen.color case .progress: diff --git a/App/dfu/views/ResultItemView.swift b/App/dfu/views/ResultItemView.swift index 9709bb13..325b5273 100644 --- a/App/dfu/views/ResultItemView.swift +++ b/App/dfu/views/ResultItemView.swift @@ -41,7 +41,7 @@ struct ResultItemView: View { var body: some View { HStack { - Image(status.image) + Image(systemName: status.image.imageName) .renderingMode(.template) .foregroundColor(status.color) .frame(width: 24, height: 24) @@ -63,21 +63,21 @@ struct ResultItemView: View { private extension DfuResultStatus { - var image: String { + var image: DfuImages { switch self { case .idle: - return DfuImages.idle.imageName + return DfuImages.idle case .success: - return DfuImages.success.imageName + return DfuImages.success case .error: - return DfuImages.error.imageName + return DfuImages.error } } var color: Color { switch self { case .idle: - return ThemeColor.nordicDarkGray5.color + return .gray case .success: return ThemeColor.nordicGreen.color case .error: diff --git a/App/dfu/views/StatusItemView.swift b/App/dfu/views/StatusItemView.swift index 2b914bd7..6a61c438 100644 --- a/App/dfu/views/StatusItemView.swift +++ b/App/dfu/views/StatusItemView.swift @@ -43,7 +43,7 @@ struct StatusItemView: View { var body: some View { HStack { - Image(status.image) + Image(systemName: status.image.imageName) .renderingMode(.template) .foregroundColor(status.color) .frame(width: 24, height: 24) @@ -56,23 +56,23 @@ struct StatusItemView: View { private extension DfuUiStateStatus { - var image: String { + var image: DfuImages { switch self { case .idle: - return DfuImages.idle.imageName + return DfuImages.idle case .success: - return DfuImages.success.imageName + return DfuImages.success case .progress: - return DfuImages.progress.imageName + return DfuImages.progress case .error: - return DfuImages.error.imageName + return DfuImages.error } } var color: Color { switch self { case .idle: - return ThemeColor.nordicDarkGray5.color + return .gray case .success: return ThemeColor.nordicGreen.color case .progress: