From 9b4afc4f594ee1b92df0f82308c58cbdbc9c7d44 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 8 Sep 2025 13:43:37 -0500 Subject: [PATCH] Signed-off-by: Matt Bruce --- TheNoiseClock/Services/BatteryService.swift | 76 ++++++ .../Clock/Components/BatteryOverlayView.swift | 229 ++++++++++++------ .../Clock/Components/TopOverlayView.swift | 10 +- 3 files changed, 241 insertions(+), 74 deletions(-) create mode 100644 TheNoiseClock/Services/BatteryService.swift diff --git a/TheNoiseClock/Services/BatteryService.swift b/TheNoiseClock/Services/BatteryService.swift new file mode 100644 index 0000000..08c45fe --- /dev/null +++ b/TheNoiseClock/Services/BatteryService.swift @@ -0,0 +1,76 @@ +// +// BatteryService.swift +// TheNoiseClock +// +// Created by Matt Bruce on 9/7/25. +// + +import Foundation +import Combine +import UIKit + +/// Service for monitoring device battery level and state +@Observable +class BatteryService { + + // MARK: - Properties + static let shared = BatteryService() + + var batteryLevel: Int = 100 + var isCharging: Bool = false + + private var cancellables = Set() + + // MARK: - Initialization + private init() { + setupBatteryMonitoring() + } + + // MARK: - Public Methods + func startMonitoring() { + #if canImport(UIKit) + UIDevice.current.isBatteryMonitoringEnabled = true + updateBatteryInfo() + #endif + } + + func stopMonitoring() { + #if canImport(UIKit) + UIDevice.current.isBatteryMonitoringEnabled = false + #endif + } + + // MARK: - Private Methods + private func setupBatteryMonitoring() { + #if canImport(UIKit) + // Listen for battery level changes + NotificationCenter.default.publisher(for: UIDevice.batteryLevelDidChangeNotification) + .sink { [weak self] _ in + self?.updateBatteryInfo() + } + .store(in: &cancellables) + + // Listen for battery state changes + NotificationCenter.default.publisher(for: UIDevice.batteryStateDidChangeNotification) + .sink { [weak self] _ in + self?.updateBatteryInfo() + } + .store(in: &cancellables) + #endif + } + + private func updateBatteryInfo() { + #if canImport(UIKit) + let device = UIDevice.current + + // Update battery level + let level = device.batteryLevel + if level >= 0 { + batteryLevel = Int((level * 100).rounded()) + } + + // Update charging state + isCharging = device.batteryState == .charging + #endif + } +} diff --git a/TheNoiseClock/Views/Clock/Components/BatteryOverlayView.swift b/TheNoiseClock/Views/Clock/Components/BatteryOverlayView.swift index fb22e0a..4ea1eaf 100644 --- a/TheNoiseClock/Views/Clock/Components/BatteryOverlayView.swift +++ b/TheNoiseClock/Views/Clock/Components/BatteryOverlayView.swift @@ -14,8 +14,7 @@ struct BatteryOverlayView: View { // MARK: - Properties let color: Color let opacity: Double - - @State private var batteryLevel: Int = 100 + let batteryLevel: Int // MARK: - Body var body: some View { @@ -31,27 +30,10 @@ struct BatteryOverlayView: View { } .opacity(clamped) .font(.callout.weight(.semibold)) - .onAppear { - enableBatteryMonitoring() - updateBattery() - startBatteryObserver() - } - .onDisappear { - stopBatteryObserver() - } } // MARK: - Private Methods private func getBatteryIcon() -> String { - #if canImport(UIKit) - let batteryState = UIDevice.current.batteryState - - // Check if device is charging - if batteryState == .charging { - return "bolt.circle.fill" - } - #endif - // Return battery icon based on level switch batteryLevel { case 75...100: @@ -68,15 +50,6 @@ struct BatteryOverlayView: View { } private func getBatteryColor() -> Color { - #if canImport(UIKit) - let batteryState = UIDevice.current.batteryState - - // Green when charging - if batteryState == .charging { - return .green - } - #endif - // Color based on battery level switch batteryLevel { case 50...100: @@ -89,54 +62,164 @@ struct BatteryOverlayView: View { return .red } } - - private func enableBatteryMonitoring() { - #if canImport(UIKit) - UIDevice.current.isBatteryMonitoringEnabled = true - #endif - } - - private func updateBattery() { - #if canImport(UIKit) - let level = UIDevice.current.batteryLevel - if level >= 0 { - batteryLevel = Int((level * 100).rounded()) - } - #endif - } - - private func startBatteryObserver() { - #if canImport(UIKit) - NotificationCenter.default.addObserver( - forName: UIDevice.batteryLevelDidChangeNotification, - object: nil, - queue: .main - ) { _ in - updateBattery() - } +} + +// MARK: - Previews +#Preview("Battery Overlay - Full Charge") { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 100 + ) +} + +#Preview("Battery Overlay - High Charge") { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 75 + ) +} + +#Preview("Battery Overlay - Medium Charge") { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 50 + ) +} + +#Preview("Battery Overlay - Low Charge") { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 25 + ) +} + +#Preview("Battery Overlay - Critical Charge") { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 10 + ) +} + +#Preview("Battery Overlay - Charging") { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 85 + ) +} + +#Preview("Battery Overlay - Different Colors") { + VStack(spacing: 20) { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 75 + ) - NotificationCenter.default.addObserver( - forName: UIDevice.batteryStateDidChangeNotification, - object: nil, - queue: .main - ) { _ in - updateBattery() - } - #endif + BatteryOverlayView( + color: .blue, + opacity: 1.0, + batteryLevel: 50 + ) + + BatteryOverlayView( + color: .green, + opacity: 1.0, + batteryLevel: 25 + ) + + BatteryOverlayView( + color: .red, + opacity: 1.0, + batteryLevel: 10 + ) } - - private func stopBatteryObserver() { - #if canImport(UIKit) - NotificationCenter.default.removeObserver( - self, - name: UIDevice.batteryLevelDidChangeNotification, - object: nil + .padding() + .background(Color.black) +} + +#Preview("Battery Overlay - Different Opacities") { + VStack(spacing: 20) { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 75 ) - NotificationCenter.default.removeObserver( - self, - name: UIDevice.batteryStateDidChangeNotification, - object: nil + + BatteryOverlayView( + color: .white, + opacity: 0.7, + batteryLevel: 50 ) - #endif + + BatteryOverlayView( + color: .white, + opacity: 0.5, + batteryLevel: 25 + ) + + BatteryOverlayView( + color: .white, + opacity: 0.3, + batteryLevel: 10 + ) + } + .padding() + .background(Color.black) +} + +#Preview("Battery Overlay - Dark Background") { + BatteryOverlayView( + color: .white, + opacity: 1.0, + batteryLevel: 75 + ) + .padding() + .background(Color.black) +} + +#Preview("Battery Overlay - Light Background") { + BatteryOverlayView( + color: .black, + opacity: 1.0, + batteryLevel: 50 + ) + .padding() + .background(Color.white) +} + +#Preview("Battery Overlay - Clock Context") { + ZStack { + // Simulate clock background + Color.black + .ignoresSafeArea() + + VStack { + Spacer() + + // Simulate clock display + Text("12:34") + .font(.system(size: 80, weight: .bold, design: .rounded)) + .foregroundColor(.white) + + Spacer() + + // Battery overlay at bottom + HStack { + Spacer() + BatteryOverlayView( + color: .white, + opacity: 0.8, + batteryLevel: 75 + ) + Spacer() + } + .padding(.bottom, 50) + } } } diff --git a/TheNoiseClock/Views/Clock/Components/TopOverlayView.swift b/TheNoiseClock/Views/Clock/Components/TopOverlayView.swift index f11088f..14691c1 100644 --- a/TheNoiseClock/Views/Clock/Components/TopOverlayView.swift +++ b/TheNoiseClock/Views/Clock/Components/TopOverlayView.swift @@ -17,6 +17,8 @@ struct TopOverlayView: View { let opacity: Double let dateFormat: String + @State private var batteryService = BatteryService.shared + // MARK: - Body var body: some View { HStack { @@ -27,12 +29,18 @@ struct TopOverlayView: View { Spacer() if showBattery { - BatteryOverlayView(color: color, opacity: opacity) + BatteryOverlayView(color: color, opacity: opacity, batteryLevel: batteryService.batteryLevel) } } .padding(.horizontal, UIConstants.Spacing.medium) .padding(.vertical, UIConstants.Spacing.small) .cardStyle() .transition(.opacity) + .onAppear { + batteryService.startMonitoring() + } + .onDisappear { + batteryService.stopMonitoring() + } } }