Widget Features: - Add inline voting to timeline widget when no entry exists for today - Show random prompt from notification strings in voting mode - Update vote widget to use simple icon style for selection - Make stats bar full width in voted state view - Add Localizable.strings to widget extension target Bug Fixes: - Fix inverted date calculation in InsightsViewModel streak logic - Replace force unwraps with safe optional handling in widgets - Replace fatalError calls with graceful error handling - Fix CSV import safety in SettingsView Warning Fixes: - Add @retroactive to Color and Date extension conformances - Update deprecated onChange(of:perform:) to new syntax - Replace deprecated applicationIconBadgeNumber with setBadgeCount - Replace deprecated UIApplication.shared.windows API - Add @preconcurrency for Swift 6 protocol conformances - Add missing widget family cases to switch statement - Remove unused variables and #warning directives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
122 lines
3.6 KiB
Swift
122 lines
3.6 KiB
Swift
//
|
|
// Color+Codable.swift
|
|
// FirestoreCodableSamples
|
|
//
|
|
// Created by Peter Friese on 18.03.21.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
// Inspired by https://cocoacasts.com/from-hex-to-uicolor-and-back-in-swift
|
|
// Make Color codable. This includes support for transparency.
|
|
// See https://www.digitalocean.com/community/tutorials/css-hex-code-colors-alpha-values
|
|
extension Color: @retroactive Codable {
|
|
init(hex: String) {
|
|
let rgba = hex.toRGBA()
|
|
|
|
self.init(.sRGB,
|
|
red: Double(rgba.r),
|
|
green: Double(rgba.g),
|
|
blue: Double(rgba.b),
|
|
opacity: Double(rgba.alpha))
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.singleValueContainer()
|
|
let hex = try container.decode(String.self)
|
|
|
|
self.init(hex: hex)
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(toHex)
|
|
}
|
|
|
|
var toHex: String? {
|
|
return toHex()
|
|
}
|
|
|
|
func toHex(alpha: Bool = false) -> String? {
|
|
guard let components = cgColor?.components, components.count >= 3 else {
|
|
return nil
|
|
}
|
|
|
|
let r = Float(components[0])
|
|
let g = Float(components[1])
|
|
let b = Float(components[2])
|
|
var a = Float(1.0)
|
|
|
|
if components.count >= 4 {
|
|
a = Float(components[3])
|
|
}
|
|
|
|
if alpha {
|
|
return String(format: "%02lX%02lX%02lX%02lX",
|
|
lroundf(r * 255),
|
|
lroundf(g * 255),
|
|
lroundf(b * 255),
|
|
lroundf(a * 255))
|
|
}
|
|
else {
|
|
return String(format: "%02lX%02lX%02lX",
|
|
lroundf(r * 255),
|
|
lroundf(g * 255),
|
|
lroundf(b * 255))
|
|
}
|
|
}
|
|
}
|
|
|
|
extension String {
|
|
func toRGBA() -> (r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat) {
|
|
var hexSanitized = self.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
|
|
|
|
var rgb: UInt64 = 0
|
|
|
|
var r: CGFloat = 0.0
|
|
var g: CGFloat = 0.0
|
|
var b: CGFloat = 0.0
|
|
var a: CGFloat = 1.0
|
|
|
|
let length = hexSanitized.count
|
|
|
|
Scanner(string: hexSanitized).scanHexInt64(&rgb)
|
|
|
|
if length == 6 {
|
|
r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
|
|
g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
|
|
b = CGFloat(rgb & 0x0000FF) / 255.0
|
|
}
|
|
else if length == 8 {
|
|
r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
|
|
g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
|
|
b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
|
|
a = CGFloat(rgb & 0x000000FF) / 255.0
|
|
}
|
|
|
|
return (r, g, b, a)
|
|
}
|
|
}
|
|
|
|
extension Color: @retroactive RawRepresentable {
|
|
// TODO: Sort out alpha
|
|
public init?(rawValue: Int) {
|
|
let red = Double((rawValue & 0xFF0000) >> 16) / 0xFF
|
|
let green = Double((rawValue & 0x00FF00) >> 8) / 0xFF
|
|
let blue = Double(rawValue & 0x0000FF) / 0xFF
|
|
self = Color(red: red, green: green, blue: blue)
|
|
}
|
|
|
|
public var rawValue: Int {
|
|
let red = Int(coreImageColor.red * 255 + 0.5)
|
|
let green = Int(coreImageColor.green * 255 + 0.5)
|
|
let blue = Int(coreImageColor.blue * 255 + 0.5)
|
|
return (red << 16) | (green << 8) | blue
|
|
}
|
|
|
|
private var coreImageColor: CIColor {
|
|
return CIColor(color: UIColor(self))
|
|
}
|
|
}
|