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
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()
}
NotificationCenter.default.addObserver(
forName: UIDevice.batteryStateDidChangeNotification,
object: nil,
queue: .main
) { _ in
updateBattery()
}
#endif
}
private func stopBatteryObserver() {
#if canImport(UIKit)
NotificationCenter.default.removeObserver(
self,
name: UIDevice.batteryLevelDidChangeNotification,
object: nil
// MARK: - Previews
#Preview("Battery Overlay - Full Charge") {
BatteryOverlayView(
color: .white,
opacity: 1.0,
batteryLevel: 100
)
NotificationCenter.default.removeObserver(
self,
name: UIDevice.batteryStateDidChangeNotification,
object: nil
}
#Preview("Battery Overlay - High Charge") {
BatteryOverlayView(
color: .white,
opacity: 1.0,
batteryLevel: 75
)
#endif
}
#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
)
BatteryOverlayView(
color: .blue,
opacity: 1.0,
batteryLevel: 50
)
BatteryOverlayView(
color: .green,
opacity: 1.0,
batteryLevel: 25
)
BatteryOverlayView(
color: .red,
opacity: 1.0,
batteryLevel: 10
)
}
.padding()
.background(Color.black)
}
#Preview("Battery Overlay - Different Opacities") {
VStack(spacing: 20) {
BatteryOverlayView(
color: .white,
opacity: 1.0,
batteryLevel: 75
)
BatteryOverlayView(
color: .white,
opacity: 0.7,
batteryLevel: 50
)
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 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()
}
}
}