// // Extensions.swift // Werkout_ios // // Created by Trey Tartt on 6/20/23. // import Foundation import UIKit import SwiftUI private enum DateFormatterCache { static let lock = NSLock() static let serverDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssX" formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() static let plannedDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() static let weekDayFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "EEE" formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() static let monthFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "MMM" formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() static let dayFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "d" formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() static func withLock(_ block: () -> T) -> T { lock.lock() defer { lock.unlock() } return block() } } private enum DurationFormatterCache { static let lock = NSLock() static let formatter: DateComponentsFormatter = { let formatter = DateComponentsFormatter() formatter.allowedUnits = [.hour, .minute, .second, .nanosecond] return formatter }() static func string(from seconds: Double, style: DateComponentsFormatter.UnitsStyle) -> String { lock.lock() defer { lock.unlock() } formatter.unitsStyle = style return formatter.string(from: seconds) ?? "" } } extension Dictionary { func percentEncoded() -> Data? { map { key, value in let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" return escapedKey + "=" + escapedValue } .joined(separator: "&") .data(using: .utf8) } } extension CharacterSet { static let urlQueryValueAllowed: CharacterSet = { let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 let subDelimitersToEncode = "!$&'()*+,;=" var allowed: CharacterSet = .urlQueryAllowed allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") return allowed }() } extension Date { var timeFormatForUpload: String { DateFormatterCache.withLock { DateFormatterCache.serverDateFormatter.string(from: self) } } } extension String { var dateFromServerDate: Date? { DateFormatterCache.withLock { DateFormatterCache.serverDateFormatter.date(from: self) } } var plannedDate: Date? { DateFormatterCache.withLock { DateFormatterCache.plannedDateFormatter.date(from: self) } } } extension Date { func get(_ components: Calendar.Component..., calendar: Calendar = Calendar.current) -> DateComponents { return calendar.dateComponents(Set(components), from: self) } func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int { return calendar.component(component, from: self) } var formatForPlannedWorkout: String { DateFormatterCache.withLock { DateFormatterCache.plannedDateFormatter.string(from: self) } } var weekDay: String { DateFormatterCache.withLock { DateFormatterCache.weekDayFormatter.string(from: self) } } var monthString: String { DateFormatterCache.withLock { DateFormatterCache.monthFormatter.string(from: self) } } var dateString: String { DateFormatterCache.withLock { DateFormatterCache.dayFormatter.string(from: self) } } } extension Bundle { public var icon: UIImage? { if let icons = infoDictionary?["CFBundleIcons"] as? [String: Any], let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], let lastIcon = iconFiles.last { return UIImage(named: lastIcon) } return nil } } extension Double { /* 10000.asString(style: .positional) // 2:46:40 10000.asString(style: .abbreviated) // 2h 46m 40s 10000.asString(style: .short) // 2 hr, 46 min, 40 sec 10000.asString(style: .full) // 2 hours, 46 minutes, 40 seconds 10000.asString(style: .spellOut) // two hours, forty-six minutes, forty seconds 10000.asString(style: .brief) // 2hr 46min 40sec */ func asString(style: DateComponentsFormatter.UnitsStyle) -> String { DurationFormatterCache.string(from: self, style: style) } } extension View { func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { clipShape( RoundedCorner(radius: radius, corners: corners) ) } } struct RoundedCorner: Shape { var radius: CGFloat = .infinity var corners: UIRectCorner = .allCorners func path(in rect: CGRect) -> Path { let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) return Path(path.cgPath) } }