diff --git a/Sources/Core/Extensions/Foundation/Date+PovioKit.swift b/Sources/Core/Extensions/Foundation/Date+PovioKit.swift new file mode 100644 index 00000000..c8f7fdd5 --- /dev/null +++ b/Sources/Core/Extensions/Foundation/Date+PovioKit.swift @@ -0,0 +1,34 @@ +// +// Date+PovioKit.swift +// PovioKit +// +// Created by Borut Tomazin on 14/05/2024. +// Copyright © 2024 Povio Inc. All rights reserved. +// + +import Foundation + +public extension Date { + var isToday: Bool { calendar.isDateInToday(self) } + var isYesterday: Bool { calendar.isDateInYesterday(self) } + var isInFuture: Bool { self > Date() } + + /// Returns first day of the week + var startOfWeek: Date? { + let components: Set = [.yearForWeekOfYear, .weekOfYear, .hour, .minute, .second, .nanosecond] + return calendar.date(from: calendar.dateComponents(components, from: self)) + } + + /// Returns last day of the week + var endOfWeek: Date? { + guard let startOfWeek = Date().startOfWeek else { return nil } + return calendar.date(byAdding: .day, value: 6, to: startOfWeek) + } +} + +// MARK: - Private Methods +private extension Date { + var calendar: Calendar { + Calendar.autoupdatingCurrent + } +} diff --git a/Sources/Core/Extensions/Foundation/String+PovioKit.swift b/Sources/Core/Extensions/Foundation/String+PovioKit.swift index bb59ccc2..8516d331 100644 --- a/Sources/Core/Extensions/Foundation/String+PovioKit.swift +++ b/Sources/Core/Extensions/Foundation/String+PovioKit.swift @@ -38,6 +38,18 @@ public extension String { return emailTest.evaluate(with: self) } + /// Returns initials from string + /// `John Doe` -> `JD` + /// `Elena Wayne Gomez` -> `EWG` + var initials: String { + let formatter = PersonNameComponentsFormatter() + if let components = formatter.personNameComponents(from: self) { + formatter.style = .abbreviated + return formatter.string(from: components) + } + return self + } + /// Returns substring containing up to `maxLength` characters from the beginning of the string. /// /// This method is just a wrapper around swift's standard library `prefix` method, but it ensures only positive values are accepted. diff --git a/Sources/Core/Extensions/SwiftUI/AnyTransition+PovioKit.swift b/Sources/Core/Extensions/SwiftUI/AnyTransition+PovioKit.swift new file mode 100644 index 00000000..34b76d14 --- /dev/null +++ b/Sources/Core/Extensions/SwiftUI/AnyTransition+PovioKit.swift @@ -0,0 +1,19 @@ +// +// AnyTransition+PovioKit.swift +// PovioKit +// +// Created by Borut Tomazin on 14/05/2024. +// Copyright © 2024 Povio Inc. All rights reserved. +// + +import SwiftUI + +public extension AnyTransition { + /// `slideLeft` is a inverse transition of system `slide` + static var slideLeft: AnyTransition { + .asymmetric( + insertion: .move(edge: .trailing), + removal: .move(edge: .leading) + ) + } +} diff --git a/Sources/Core/Extensions/SwiftUI/Color+PovioKit.swift b/Sources/Core/Extensions/SwiftUI/Color+PovioKit.swift new file mode 100644 index 00000000..8dd84d05 --- /dev/null +++ b/Sources/Core/Extensions/SwiftUI/Color+PovioKit.swift @@ -0,0 +1,46 @@ +// +// Color+PovioKit.swift +// PovioKit +// +// Created by Borut Tomazin on 14/05/2024. +// Copyright © 2024 Povio Inc. All rights reserved. +// + +import SwiftUI + +public extension Color { + /// Initialize Color with `red`, `green` and `blue` components. + /// + /// Example: `Color(red: 0, green: 0, blue: 0)` + init(red: Int, green: Int, blue: Int) { + self.init(red: Double(red) / 255.0, green: Double(green) / 255.0, blue: Double(blue) / 255.0) + } + + /// Initialize Color with given `hex` value. + /// + /// Example: `Color(hex: "000000")` + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let alpha, red, green, blue: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (alpha, red, green, blue) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (alpha, red, green, blue) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (alpha, red, green, blue) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (alpha, red, green, blue) = (1, 1, 1, 0) + } + + self.init( + .sRGB, + red: Double(red) / 255, + green: Double(green) / 255, + blue: Double(blue) / 255, + opacity: Double(alpha) / 255 + ) + } +} diff --git a/Sources/Core/Extensions/SwiftUI/View+PovioKit.swift b/Sources/Core/Extensions/SwiftUI/View+PovioKit.swift new file mode 100644 index 00000000..8a6ec67b --- /dev/null +++ b/Sources/Core/Extensions/SwiftUI/View+PovioKit.swift @@ -0,0 +1,26 @@ +// +// View+PovioKit.swift +// PovioKit +// +// Created by Borut Tomazin on 02/03/2024. +// Copyright © 2024 Povio Inc. All rights reserved. +// + +import SwiftUI + +public extension View { + /// Returns square frame for given `size`. + func frame(size: CGFloat? = nil, alignment: Alignment = .center) -> some View { + frame(width: size, height: size, alignment: alignment) + } + + /// Hides view using opacity. + func hidden(_ hidden: Bool) -> some View { + opacity(hidden ? 0 : 1) + } + + /// Disables animation on view. + func noAnimation() -> some View { + animation(nil, value: UUID()) + } +} diff --git a/Sources/Core/Extensions/UIKit/CGSize+PovioKit.swift b/Sources/Core/Extensions/UIKit/CGSize+PovioKit.swift new file mode 100644 index 00000000..e54c923d --- /dev/null +++ b/Sources/Core/Extensions/UIKit/CGSize+PovioKit.swift @@ -0,0 +1,18 @@ +// +// CGSize+PovioKit.swift +// PovioKit +// +// Created by Borut Tomazin on 14/05/2024. +// Copyright © 2024 Povio Inc. All rights reserved. +// + +#if os(iOS) +import UIKit + +public extension CGSize { + init(size: CGFloat) { + self.init(width: size, height: size) + } +} + +#endif diff --git a/Sources/Core/Extensions/UIKit/UIDevice+PovioKit.swift b/Sources/Core/Extensions/UIKit/UIDevice+PovioKit.swift index 33cb6f4e..ee0a1ccc 100644 --- a/Sources/Core/Extensions/UIKit/UIDevice+PovioKit.swift +++ b/Sources/Core/Extensions/UIKit/UIDevice+PovioKit.swift @@ -30,6 +30,18 @@ public extension UIDevice { return identifier + String(UnicodeScalar(UInt8(value))) } } + + /// Returns `UIEdgeInsets` for the possible (top/bottom) safe areas + var safeAreaInsets: UIEdgeInsets { + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + let window = windowScene?.windows.first + return window?.safeAreaInsets ?? .init() + } + + /// Returns `true` on devices with notch + var hasNotch: Bool { + safeAreaInsets.bottom > 0 + } } #endif diff --git a/Sources/Core/Extensions/UIKit/UIImage+PovioKit.swift b/Sources/Core/Extensions/UIKit/UIImage+PovioKit.swift index 49f5c994..5af1511a 100644 --- a/Sources/Core/Extensions/UIKit/UIImage+PovioKit.swift +++ b/Sources/Core/Extensions/UIKit/UIImage+PovioKit.swift @@ -42,6 +42,23 @@ public extension UIImage { UIGraphicsEndImageContext() return image ?? UIImage() } + + /// Returns existing image clipped to a circle + var clipToCircle: UIImage { + let layer = CALayer() + layer.frame = .init(origin: .zero, size: size) + layer.contents = cgImage + layer.masksToBounds = true + + layer.cornerRadius = size.width / 2 + + UIGraphicsBeginImageContext(size) + guard let context = UIGraphicsGetCurrentContext() else { return self } + layer.render(in: context) + let roundedImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return roundedImage ?? self + } } #endif diff --git a/Sources/Core/Extensions/UIKit/UIView+PovioKit.swift b/Sources/Core/Extensions/UIKit/UIView+PovioKit.swift index 6506c94c..0c90395e 100644 --- a/Sources/Core/Extensions/UIKit/UIView+PovioKit.swift +++ b/Sources/Core/Extensions/UIKit/UIView+PovioKit.swift @@ -9,8 +9,8 @@ #if os(iOS) import UIKit -// MARK: - Shadow and border public extension UIView { + // Add shadow and border func dropShadow(path: UIBezierPath?, shadowColor: UIColor, radius: CGFloat, opacity: Float, offset: CGSize) { layer.shadowPath = path?.cgPath layer.shadowColor = shadowColor.cgColor diff --git a/Sources/Core/Extensions/UIKit/UIWindow+PovioKit.swift b/Sources/Core/Extensions/UIKit/UIWindow+PovioKit.swift index 22751caa..56ec2c16 100644 --- a/Sources/Core/Extensions/UIKit/UIWindow+PovioKit.swift +++ b/Sources/Core/Extensions/UIKit/UIWindow+PovioKit.swift @@ -10,11 +10,21 @@ import UIKit public extension UIWindow { - /// Returns `UIEdgeInsets` for the possible (top/bottom) safe areas - static var safeAreaInsets: UIEdgeInsets { - UIApplication.shared.windows - .first { $0.isKeyWindow } - .map { $0.rootViewController?.view?.safeAreaInsets ?? .zero } ?? .zero + /// Returns `viewController` that currently sits on top + var topViewController: UIViewController? { + func topViewController(with rootViewController: UIViewController?) -> UIViewController? { + guard rootViewController != nil else { return nil } + + if let tabBarController = rootViewController as? UITabBarController { + return topViewController(with: tabBarController.selectedViewController) + } else if let navigationController = rootViewController as? UINavigationController { + return topViewController(with: navigationController.visibleViewController) + } else if rootViewController?.presentedViewController != nil { + return topViewController(with: rootViewController?.presentedViewController) + } + return rootViewController + } + return topViewController(with: rootViewController) } }