Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-09-08 13:43:37 -05:00
parent 46f5a5b586
commit 9b4afc4f59
3 changed files with 241 additions and 74 deletions

View File

@ -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<AnyCancellable>()
// 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
}
}

View File

@ -14,8 +14,7 @@ struct BatteryOverlayView: View {
// MARK: - Properties // MARK: - Properties
let color: Color let color: Color
let opacity: Double let opacity: Double
let batteryLevel: Int
@State private var batteryLevel: Int = 100
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
@ -31,27 +30,10 @@ struct BatteryOverlayView: View {
} }
.opacity(clamped) .opacity(clamped)
.font(.callout.weight(.semibold)) .font(.callout.weight(.semibold))
.onAppear {
enableBatteryMonitoring()
updateBattery()
startBatteryObserver()
}
.onDisappear {
stopBatteryObserver()
}
} }
// MARK: - Private Methods // MARK: - Private Methods
private func getBatteryIcon() -> String { 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 // Return battery icon based on level
switch batteryLevel { switch batteryLevel {
case 75...100: case 75...100:
@ -68,15 +50,6 @@ struct BatteryOverlayView: View {
} }
private func getBatteryColor() -> Color { 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 // Color based on battery level
switch batteryLevel { switch batteryLevel {
case 50...100: case 50...100:
@ -89,54 +62,164 @@ struct BatteryOverlayView: View {
return .red return .red
} }
} }
}
private func enableBatteryMonitoring() {
#if canImport(UIKit) // MARK: - Previews
UIDevice.current.isBatteryMonitoringEnabled = true #Preview("Battery Overlay - Full Charge") {
#endif BatteryOverlayView(
} color: .white,
opacity: 1.0,
private func updateBattery() { batteryLevel: 100
#if canImport(UIKit) )
let level = UIDevice.current.batteryLevel }
if level >= 0 {
batteryLevel = Int((level * 100).rounded()) #Preview("Battery Overlay - High Charge") {
} BatteryOverlayView(
#endif color: .white,
} opacity: 1.0,
batteryLevel: 75
private func startBatteryObserver() { )
#if canImport(UIKit) }
NotificationCenter.default.addObserver(
forName: UIDevice.batteryLevelDidChangeNotification, #Preview("Battery Overlay - Medium Charge") {
object: nil, BatteryOverlayView(
queue: .main color: .white,
) { _ in opacity: 1.0,
updateBattery() 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( BatteryOverlayView(
forName: UIDevice.batteryStateDidChangeNotification, color: .blue,
object: nil, opacity: 1.0,
queue: .main batteryLevel: 50
) { _ in )
updateBattery()
} BatteryOverlayView(
#endif color: .green,
opacity: 1.0,
batteryLevel: 25
)
BatteryOverlayView(
color: .red,
opacity: 1.0,
batteryLevel: 10
)
} }
.padding()
private func stopBatteryObserver() { .background(Color.black)
#if canImport(UIKit) }
NotificationCenter.default.removeObserver(
self, #Preview("Battery Overlay - Different Opacities") {
name: UIDevice.batteryLevelDidChangeNotification, VStack(spacing: 20) {
object: nil BatteryOverlayView(
color: .white,
opacity: 1.0,
batteryLevel: 75
) )
NotificationCenter.default.removeObserver(
self, BatteryOverlayView(
name: UIDevice.batteryStateDidChangeNotification, color: .white,
object: nil 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)
}
} }
} }

View File

@ -17,6 +17,8 @@ struct TopOverlayView: View {
let opacity: Double let opacity: Double
let dateFormat: String let dateFormat: String
@State private var batteryService = BatteryService.shared
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
HStack { HStack {
@ -27,12 +29,18 @@ struct TopOverlayView: View {
Spacer() Spacer()
if showBattery { if showBattery {
BatteryOverlayView(color: color, opacity: opacity) BatteryOverlayView(color: color, opacity: opacity, batteryLevel: batteryService.batteryLevel)
} }
} }
.padding(.horizontal, UIConstants.Spacing.medium) .padding(.horizontal, UIConstants.Spacing.medium)
.padding(.vertical, UIConstants.Spacing.small) .padding(.vertical, UIConstants.Spacing.small)
.cardStyle() .cardStyle()
.transition(.opacity) .transition(.opacity)
.onAppear {
batteryService.startMonitoring()
}
.onDisappear {
batteryService.stopMonitoring()
}
} }
} }