From 5407b4605401461b8165dbb6191c37fc16981cc0 Mon Sep 17 00:00:00 2001 From: Ladislas de Toldi Date: Thu, 9 Nov 2023 14:57:04 +0100 Subject: [PATCH 1/5] :truck: (RobotKit): Move ConnectedRobotInformationViewModel to RobotKit --- .../Views/ConnectedRobotInformationView.swift | 1 + .../ConnectedRobotInformationViewModel.swift | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) rename Modules/RobotKit/{Examples/RobotKitExample/Sources => Sources/UI}/ViewModels/ConnectedRobotInformationViewModel.swift (82%) diff --git a/Modules/RobotKit/Examples/RobotKitExample/Sources/Views/ConnectedRobotInformationView.swift b/Modules/RobotKit/Examples/RobotKitExample/Sources/Views/ConnectedRobotInformationView.swift index 11fbdbebc9..0acb64309b 100644 --- a/Modules/RobotKit/Examples/RobotKitExample/Sources/Views/ConnectedRobotInformationView.swift +++ b/Modules/RobotKit/Examples/RobotKitExample/Sources/Views/ConnectedRobotInformationView.swift @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 import Combine +import RobotKit import SwiftUI struct ConnectedRobotInformationView: View { diff --git a/Modules/RobotKit/Examples/RobotKitExample/Sources/ViewModels/ConnectedRobotInformationViewModel.swift b/Modules/RobotKit/Sources/UI/ViewModels/ConnectedRobotInformationViewModel.swift similarity index 82% rename from Modules/RobotKit/Examples/RobotKitExample/Sources/ViewModels/ConnectedRobotInformationViewModel.swift rename to Modules/RobotKit/Sources/UI/ViewModels/ConnectedRobotInformationViewModel.swift index 3443850331..132307b3b6 100644 --- a/Modules/RobotKit/Examples/RobotKitExample/Sources/ViewModels/ConnectedRobotInformationViewModel.swift +++ b/Modules/RobotKit/Sources/UI/ViewModels/ConnectedRobotInformationViewModel.swift @@ -3,23 +3,22 @@ // SPDX-License-Identifier: Apache-2.0 import Combine -import RobotKit import SwiftUI -class ConnectedRobotInformationViewModel: ObservableObject { +public class ConnectedRobotInformationViewModel: ObservableObject { - @Published var name: String = "(n/a)" - @Published var serialNumber: String = "(n/a)" - @Published var osVersion: String = "(n/a)" + @Published public var name: String = "(n/a)" + @Published public var serialNumber: String = "(n/a)" + @Published public var osVersion: String = "(n/a)" - @Published var battery: Int = 0 - @Published var isCharging: Bool = false + @Published public var battery: Int = 0 + @Published public var isCharging: Bool = false let robot = Robot.shared private var cancellables: Set = [] - init() { + public init() { getRobotInformation() } From 470aaa68de837ff74af0e768cea380a7c84e51b6 Mon Sep 17 00:00:00 2001 From: Ladislas de Toldi Date: Thu, 9 Nov 2023 17:36:06 +0100 Subject: [PATCH 2/5] :recycle: (RobotKit): ConnectedRobotInfoVM - published isConnected/NotConnected --- .../ConnectedRobotInformationViewModel.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/RobotKit/Sources/UI/ViewModels/ConnectedRobotInformationViewModel.swift b/Modules/RobotKit/Sources/UI/ViewModels/ConnectedRobotInformationViewModel.swift index 132307b3b6..e510a40397 100644 --- a/Modules/RobotKit/Sources/UI/ViewModels/ConnectedRobotInformationViewModel.swift +++ b/Modules/RobotKit/Sources/UI/ViewModels/ConnectedRobotInformationViewModel.swift @@ -7,6 +7,14 @@ import SwiftUI public class ConnectedRobotInformationViewModel: ObservableObject { + @Published public var isConnected: Bool = false { + didSet { + self.isNotConnected = !isConnected + } + } + + @Published public var isNotConnected: Bool = true + @Published public var name: String = "(n/a)" @Published public var serialNumber: String = "(n/a)" @Published public var osVersion: String = "(n/a)" @@ -26,7 +34,8 @@ public class ConnectedRobotInformationViewModel: ObservableObject { robot.isConnected .receive(on: DispatchQueue.main) .sink { isConnected in - guard !isConnected else { return } + self.isConnected = isConnected + guard self.isNotConnected else { return } self.name = "(not connected)" self.serialNumber = "" self.osVersion = "" From 92cdef1e1a251f443bc1ffe19f20684abd6c7cfa Mon Sep 17 00:00:00 2001 From: Ladislas de Toldi Date: Thu, 9 Nov 2023 17:49:33 +0100 Subject: [PATCH 3/5] :recycle: (RobotKit): Extract BatteryViewModel from RobotDiscoveryViewModel --- .../UI/ViewModels/BatteryViewModel.swift | 41 +++++++++++++++++++ .../ViewModels/RobotDiscoveryViewModel.swift | 29 ++----------- .../Sources/UI/Views/RobotDiscoveryView.swift | 2 +- 3 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 Modules/RobotKit/Sources/UI/ViewModels/BatteryViewModel.swift diff --git a/Modules/RobotKit/Sources/UI/ViewModels/BatteryViewModel.swift b/Modules/RobotKit/Sources/UI/ViewModels/BatteryViewModel.swift new file mode 100644 index 0000000000..b003003889 --- /dev/null +++ b/Modules/RobotKit/Sources/UI/ViewModels/BatteryViewModel.swift @@ -0,0 +1,41 @@ +// Leka - iOS Monorepo +// Copyright 2023 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +import BLEKit +import SwiftUI + +public struct BatteryViewModel: Equatable { + + public let level: Int + public let name: String + public let color: Color + + public init(level: Int) { + self.level = level + switch level { + case 0..<10: + self.name = "battery.0" + self.color = .red + case 10..<25: + self.name = "battery.25" + self.color = .red + case 25..<45: + self.name = "battery.25" + self.color = .orange + case 45..<70: + self.name = "battery.50" + self.color = .yellow + case 70..<95: + self.name = "battery.75" + self.color = .green + case 95...100: + self.name = "battery.100" + self.color = .green + default: + self.name = "battery.0" + self.color = .gray + } + } + +} diff --git a/Modules/RobotKit/Sources/UI/ViewModels/RobotDiscoveryViewModel.swift b/Modules/RobotKit/Sources/UI/ViewModels/RobotDiscoveryViewModel.swift index 81acf6cdb8..732421e827 100644 --- a/Modules/RobotKit/Sources/UI/ViewModels/RobotDiscoveryViewModel.swift +++ b/Modules/RobotKit/Sources/UI/ViewModels/RobotDiscoveryViewModel.swift @@ -7,12 +7,6 @@ import SwiftUI public struct RobotDiscoveryViewModel: Identifiable { - public struct Battery: Equatable { - let value: Int - let name: String - let color: Color - } - public enum Status: CaseIterable { case connected case unselected @@ -24,7 +18,7 @@ public struct RobotDiscoveryViewModel: Identifiable { public let status: Status public let isCharging: Bool public let osVersion: String - public let battery: Battery + public let battery: BatteryViewModel init( name: String, battery: Int, isCharging: Bool, osVersion: String, status: Status = .unselected @@ -34,7 +28,7 @@ public struct RobotDiscoveryViewModel: Identifiable { self.status = status self.isCharging = isCharging self.osVersion = "LekaOS \(osVersion)" - self.battery = computeBatteryImage(for: battery) + self.battery = BatteryViewModel(level: battery) } init(discovery: RobotDiscoveryModel, status: Status = .unselected) { @@ -43,7 +37,7 @@ public struct RobotDiscoveryViewModel: Identifiable { self.status = status self.isCharging = discovery.isCharging self.osVersion = "LekaOS \(discovery.osVersion)" - self.battery = computeBatteryImage(for: discovery.battery) + self.battery = BatteryViewModel(level: discovery.battery) } } @@ -57,20 +51,3 @@ extension RobotDiscoveryViewModel: Equatable { } } - -private func computeBatteryImage(for value: Int) -> RobotDiscoveryViewModel.Battery { - switch value { - case 0..<10: - RobotDiscoveryViewModel.Battery(value: value, name: "battery.0", color: .red) - case 10..<25: - RobotDiscoveryViewModel.Battery(value: value, name: "battery.25", color: .red) - case 25..<45: - RobotDiscoveryViewModel.Battery(value: value, name: "battery.25", color: .orange) - case 45..<70: - RobotDiscoveryViewModel.Battery(value: value, name: "battery.50", color: .yellow) - case 70..<95: - RobotDiscoveryViewModel.Battery(value: value, name: "battery.75", color: .green) - default: - RobotDiscoveryViewModel.Battery(value: value, name: "battery.100", color: .green) - } -} diff --git a/Modules/RobotKit/Sources/UI/Views/RobotDiscoveryView.swift b/Modules/RobotKit/Sources/UI/Views/RobotDiscoveryView.swift index 18cc28512f..8596ca59e5 100644 --- a/Modules/RobotKit/Sources/UI/Views/RobotDiscoveryView.swift +++ b/Modules/RobotKit/Sources/UI/Views/RobotDiscoveryView.swift @@ -95,7 +95,7 @@ struct RobotDiscoveryView: View { HStack(spacing: 5) { Image(systemName: discovery.battery.name) .foregroundColor(discovery.battery.color) - Text("\(discovery.battery.value)%") + Text("\(discovery.battery.level)%") .foregroundColor(.gray) } } From 2fd2aef15ecb17de926a1824ff05d7acb9cf77b0 Mon Sep 17 00:00:00 2001 From: Ladislas de Toldi Date: Thu, 9 Nov 2023 19:55:05 +0100 Subject: [PATCH 4/5] :sparkles: (LekaApp): Implement connected behavior for Connection button --- .../Components/RobotBatteryIndicator.swift | 2 +- .../Components/RobotConnectionIndicator.swift | 16 ++++--- .../RobotButton/GoToRobotConnectButton.swift | 47 ++++++++++++++----- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotBatteryIndicator.swift b/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotBatteryIndicator.swift index 55ed234f30..7e9887b2c2 100644 --- a/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotBatteryIndicator.swift +++ b/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotBatteryIndicator.swift @@ -6,7 +6,7 @@ import SwiftUI struct RobotBatteryIndicator: View { - @Binding var level: Double + @Binding var level: Int @Binding var charging: Bool @State private var opacity: Double = 1 diff --git a/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotConnectionIndicator.swift b/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotConnectionIndicator.swift index d5baaebdf4..f89ff88378 100644 --- a/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotConnectionIndicator.swift +++ b/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotConnectionIndicator.swift @@ -3,13 +3,15 @@ // SPDX-License-Identifier: Apache-2.0 import DesignKit +import RobotKit import SwiftUI struct RobotConnectionIndicator: View { - @EnvironmentObject var robotVM: RobotViewModel @EnvironmentObject var metrics: UIMetrics - // Animation + + @StateObject var robotViewModel: ConnectedRobotInformationViewModel = ConnectedRobotInformationViewModel() + @State private var isAnimated: Bool = false @State private var diameter: CGFloat = 0 @@ -17,14 +19,14 @@ struct RobotConnectionIndicator: View { ZStack { Circle() .fill( - robotVM.robotIsConnected + robotViewModel.isConnected ? DesignKitAsset.Colors.lekaGreen.swiftUIColor : DesignKitAsset.Colors.lekaDarkGray.swiftUIColor ) .opacity(0.4) Image( uiImage: - robotVM.robotIsConnected + robotViewModel.isConnected ? DesignKitAsset.Images.robotConnected.image : DesignKitAsset.Images.robotDisconnected.image ) .resizable() @@ -34,7 +36,7 @@ struct RobotConnectionIndicator: View { Circle() .stroke( - robotVM.robotIsConnected + robotViewModel.isConnected ? DesignKitAsset.Colors.lekaGreen.swiftUIColor : DesignKitAsset.Colors.lekaDarkGray.swiftUIColor, lineWidth: 4 @@ -50,7 +52,7 @@ struct RobotConnectionIndicator: View { .animation( Animation.easeInOut(duration: 1.5).delay(5).repeatForever(autoreverses: false), value: diameter ) - .opacity(robotVM.robotIsConnected ? 1 : 0.0) + .opacity(robotViewModel.isConnected ? 1 : 0.0) ) .overlay( alignment: .topTrailing, @@ -65,7 +67,7 @@ struct RobotConnectionIndicator: View { } @ViewBuilder private var badgeView: some View { - if !robotVM.robotIsConnected { + if !robotViewModel.isConnected { Image(systemName: "exclamationmark.circle.fill") .symbolRenderingMode(.palette) .foregroundStyle(.white, .red) diff --git a/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/GoToRobotConnectButton.swift b/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/GoToRobotConnectButton.swift index 1abec28c34..f372038fe0 100644 --- a/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/GoToRobotConnectButton.swift +++ b/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/GoToRobotConnectButton.swift @@ -3,14 +3,16 @@ // SPDX-License-Identifier: Apache-2.0 import DesignKit +import RobotKit import SwiftUI struct GoToRobotConnectButton: View { - @EnvironmentObject var robotVM: RobotViewModel @EnvironmentObject var metrics: UIMetrics @EnvironmentObject var navigationVM: NavigationViewModel + @StateObject var robotViewModel: ConnectedRobotInformationViewModel = ConnectedRobotInformationViewModel() + var body: some View { Button { navigationVM.showRobotPicker.toggle() @@ -31,26 +33,49 @@ struct GoToRobotConnectButton: View { @ViewBuilder private var buttonContent: some View { - if robotVM.robotIsConnected { + if robotViewModel.isConnected { VStack(alignment: .leading, spacing: 4) { Text("Connecté à") - Text(robotVM.currentlyConnectedRobotName) + Text(robotViewModel.name) .font(metrics.reg16) - HStack(spacing: 4) { - RobotBatteryIndicator( - level: $robotVM.robotChargeLevel, - charging: $robotVM.robotIsCharging) - Text(robotVM.robotOSVersion) - .foregroundColor(DesignKitAsset.Colors.lekaDarkGray.swiftUIColor) - } + robotCharginStatusAndBattery } .font(metrics.reg12) .foregroundColor(DesignKitAsset.Colors.lekaDarkBlue.swiftUIColor) } else { - Text("Connectez vous à votre LEKA.") + Text("Connectez-vous à votre Leka") .font(metrics.reg16) .multilineTextAlignment(.leading) .foregroundColor(DesignKitAsset.Colors.lekaDarkBlue.swiftUIColor) } } + + private var robotCharginStatusAndBattery: some View { + HStack(spacing: 5) { + Text(verbatim: "LekaOS v\(robotViewModel.osVersion)") + .foregroundColor(.gray) + + if robotViewModel.isCharging { + Image(systemName: "bolt.circle.fill") + .foregroundColor(.blue) + } else { + Image(systemName: "bolt.slash.circle") + .foregroundColor(.gray.opacity(0.6)) + } + + let battery = BatteryViewModel(level: robotViewModel.battery) + Image(systemName: battery.name) + .foregroundColor(battery.color) + Text(verbatim: "\(battery.level)%") + .foregroundColor(.gray) + .monospacedDigit() + } + } + +} + +#Preview { + GoToRobotConnectButton() + .environmentObject(UIMetrics()) + .environmentObject(NavigationViewModel()) } From f505ec362b07160827f8271b7a1b0a6f727dc90d Mon Sep 17 00:00:00 2001 From: Ladislas de Toldi Date: Thu, 9 Nov 2023 19:56:32 +0100 Subject: [PATCH 5/5] :fire: (LekaApp): Remove deprecated RobotBatteryIndicator --- .../Components/RobotBatteryIndicator.swift | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotBatteryIndicator.swift diff --git a/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotBatteryIndicator.swift b/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotBatteryIndicator.swift deleted file mode 100644 index 7e9887b2c2..0000000000 --- a/Apps/LekaApp/Sources/Views/Sidebar/Components/Header/RobotButton/Components/RobotBatteryIndicator.swift +++ /dev/null @@ -1,67 +0,0 @@ -// Leka - iOS Monorepo -// Copyright 2023 APF France handicap -// SPDX-License-Identifier: Apache-2.0 - -import SwiftUI - -struct RobotBatteryIndicator: View { - - @Binding var level: Int - @Binding var charging: Bool - - @State private var opacity: Double = 1 - private let battery100Bolt = "battery.100.bolt" - - var body: some View { - Image(systemName: imageGivenValue()) - .symbolRenderingMode(.palette) - .foregroundStyle(primaryColorGivenValue(), .tertiary) - .imageScale(.large) - .font(.system(size: 13)) - .padding([.vertical, .trailing], 5) - .opacity(opacity) - .animation(level < 10 ? .easeIn(duration: 1.0).repeatForever(autoreverses: false) : .easeIn, value: opacity) - .animation(.easeIn(duration: 0.3), value: imageGivenValue()) - .animation(.easeIn(duration: 0.3), value: primaryColorGivenValue()) - .onChange( - of: level, - perform: { value in - if value < 10 { - opacity = 0 - } else { - opacity = 1 - } - } - ) - } - - private func imageGivenValue() -> String { - if level < 25 { - return charging ? battery100Bolt : "battery.25" - } else if 25..<50 ~= level { - return charging ? battery100Bolt : "battery.25" - } else if 50..<75 ~= level { - return charging ? battery100Bolt : "battery.50" - } else if 75..<100 ~= level { - return charging ? battery100Bolt : "battery.75" - } else { - return charging ? battery100Bolt : "battery.100" - } - } - - private func primaryColorGivenValue() -> Color { - if level < 25 { - return .red - } else if 25..<50 ~= level { - return .yellow - } else { - return .green - } - } -} - -struct RobotBatteryIndicator_Previews: PreviewProvider { - static var previews: some View { - RobotBatteryIndicator(level: .constant(98), charging: .constant(true)) - } -}