Version 1.0.0

This commit is contained in:
Daniel Arantes Loverde
2025-03-23 00:27:49 -03:00
commit e4538f84e2
74 changed files with 12161 additions and 0 deletions

View File

@@ -0,0 +1,602 @@
//
// Copyright (c) 2018 Loverde Co.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import UIKit
import AVFoundation
#if os(watchOS)
import WatchKit
#endif
//import CommonCrypto
precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator ^^ : PowerPrecedence
public func ^^ (radix: Float, power: Float) -> Float {
return Float(pow(Double(radix), Double(power)))
}
/// Loverde Co: Custom Logs
public func printLog(
title: String,
msg: Any,
prettyPrint: Bool = false
){
if prettyPrint {
print(
"\n<========================= \(title) - START =========================>"
)
print(
msg
)
print(
"\n<========================= \(title) - END ===========================>"
)
}else{
print(
"\(title): \(msg)"
)
}
}
public func printInfo(
title: String,
msg: Any,
prettyPrint: Bool = false,
function: String = #function,
file: String = #file,
line: Int = #line,
column: Int = #column
){
if prettyPrint {
print(
"\n<========================= INFO: \(title) - START =========================>"
)
print(
"[\(file): FUNC: \(function): LINE: \(line) - COLUMN: \(column)]\n"
)
print(
msg
)
print(
"\n<========================= INFO: \(title) - END ===========================>"
)
}else{
print(
" INFO: \(title): \(msg)"
)
}
}
public func printWarn(
title: String,
msg: Any,
prettyPrint: Bool = false,
function: String = #function,
file: String = #file,
line: Int = #line,
column: Int = #column
){
if prettyPrint {
print(
"\n<========================= 🟡 WARN: \(title) - START =========================>"
)
print(
"[\(file): FUNC: \(function): LINE: \(line) - COLUMN: \(column)]\n"
)
print(
msg
)
print(
"\n<========================= 🟡 WARN: \(title) - END ===========================>"
)
}else{
print(
"🟡 WARN: \(title): \(msg)"
)
}
}
public func printError(
title: String,
msg: Any,
prettyPrint: Bool = false,
function: String = #function,
file: String = #file,
line: Int = #line,
column: Int = #column
){
if prettyPrint {
print(
"\n<========================= 🔴 ERROR: \(title) - START =========================>"
)
print(
"[\(file): FUNC: \(function): LINE: \(line) - COLUMN: \(column)]\n"
)
print(
msg
)
print(
"\n<========================= 🔴 ERROR: \(title) - END ===========================>"
)
}else{
print(
"🔴 ERROR: \(title): \(msg)"
)
}
}
public struct LCEssentials {
public static let DEFAULT_ERROR_DOMAIN = "LoverdeCoErrorDomain"
public static let DEFAULT_ERROR_CODE = -99
public static let DEFAULT_ERROR_MSG = "Error Unknow"
private static let cache = URLCache(
memoryCapacity: 50 * 1024 * 1024, // 50 MB em memória
diskCapacity: 200 * 1024 * 1024, // 200 MB em disco
diskPath: "file_cache"
)
#if os(iOS) || os(macOS)
/// Extract the file name from the file path
///
/// - Parameter filePath: Full file path in bundle
/// - Returns: File Name with extension
public static func sourceFileName(filePath: String) -> String {
let components = filePath.components(separatedBy: "/")
return components.isEmpty ? "" : components.last!
}
#endif
#if !os(macOS)
/// - LoverdeCo: App's name (if applicable).
@MainActor
public static var appDisplayName: String? {
return UIApplication.displayName
}
#endif
#if !os(macOS)
/// - LoverdeCo: App's bundle ID (if applicable).
public static var appBundleID: String? {
return Bundle.main.bundleIdentifier
}
#endif
#if !os(macOS)
/// - LoverdeCo: App current build number (if applicable).
@MainActor
public static var appBuild: String? {
return UIApplication.buildNumber
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Application icon badge current number.
@MainActor
public static var applicationIconBadgeNumber: Int {
get {
return UIApplication.shared.applicationIconBadgeNumber
}
set {
UIApplication.shared.applicationIconBadgeNumber = newValue
}
}
#endif
#if !os(macOS)
/// - LoverdeCo: App's current version (if applicable).
@MainActor
public static var appVersion: String? {
return UIApplication.version
}
#endif
#if os(iOS)
/// - LoverdeCo: Current battery level.
@MainActor
public static var batteryLevel: Float {
return UIDevice.current.batteryLevel
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Shared instance of current device.
@MainActor
public static var currentDevice: UIDevice {
return UIDevice.current
}
#elseif os(watchOS)
/// - LoverdeCo: Shared instance of current device.
public static var currentDevice: WKInterfaceDevice {
return WKInterfaceDevice.current()
}
#endif
#if !os(macOS)
/// - LoverdeCo: Screen height.
@MainActor
public static var screenHeight: CGFloat {
#if os(iOS) || os(tvOS)
return UIScreen.main.bounds.height
#elseif os(watchOS)
return currentDevice.screenBounds.height
#endif
}
#endif
#if os(iOS)
/// - LoverdeCo: Current orientation of device.
@MainActor
public static var deviceOrientation: UIDeviceOrientation {
return currentDevice.orientation
}
#endif
#if !os(macOS)
/// - LoverdeCo: Screen width.
@MainActor
public static var screenWidth: CGFloat {
#if os(iOS) || os(tvOS)
return UIScreen.main.bounds.width
#elseif os(watchOS)
return currentDevice.screenBounds.width
#endif
}
#endif
/// - LoverdeCo: Check if app is running in debug mode.
@MainActor
public static var isInDebuggingMode: Bool {
return UIApplication.inferredEnvironment == .debug
}
#if !os(macOS)
/// - LoverdeCo: Check if app is running in TestFlight mode.
@MainActor
public static var isInTestFlight: Bool {
return UIApplication.inferredEnvironment == .testFlight
}
#endif
#if os(iOS)
/// - LoverdeCo: Check if multitasking is supported in current device.
@MainActor
public static var isMultitaskingSupported: Bool {
return UIDevice.current.isMultitaskingSupported
}
#endif
#if os(iOS)
/// - LoverdeCo: Check if device is iPad.
@MainActor
public static var isPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
#endif
#if os(iOS)
/// - LoverdeCo: Check if device is iPhone.
@MainActor
public static var isPhone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Check if device is registered for remote notifications for current app (read-only).
@MainActor
public static var isRegisteredForRemoteNotifications: Bool {
return UIApplication.shared.isRegisteredForRemoteNotifications
}
#endif
/// - LoverdeCo: Check if application is running on simulator (read-only).
@MainActor
public static var isRunningOnSimulator: Bool {
// http://stackoverflow.com/questions/24869481/detect-if-app-is-being-built-for-device-or-simulator-in-swift
#if targetEnvironment(simulator)
return true
#else
return false
#endif
}
#if os(iOS)
///- LoverdeCo: Status bar visibility state.
@MainActor
public static var isStatusBarHidden: Bool {
get {
if #available(iOS 13.0, *) {
let window = LCEssentials.keyWindow
return window?.windowScene?.statusBarManager?.isStatusBarHidden ?? true
} else {
return UIApplication.shared.isStatusBarHidden
}
}
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Key window (read only, if applicable).
@MainActor
public static var keyWindow: UIWindow? {
if #available(iOS 13.0, *) {
return UIApplication.shared.windows.filter {$0.isKeyWindow}.first
} else {
return UIApplication.shared.keyWindow
}
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Most top view controller (if applicable).
@MainActor
public static func getTopViewController(base: UIViewController? = nil, aboveBars: Bool = true) -> UIViewController? {
var viewController = base
if viewController == nil {
// iOS 13+ - Obter o rootViewController da UIWindow correta
if #available(iOS 13.0, *) {
// Acessa a janela ativa na cena principal
if let windowScene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
let window = windowScene.windows.first(where: { $0.isKeyWindow }) {
viewController = window.rootViewController
}
} else {
// Pré-iOS 13
viewController = UIApplication.shared.keyWindow?.rootViewController
}
}
// Navegação - UINavigationController
if let nav = viewController as? UINavigationController {
if aboveBars {
return nav
}
return getTopViewController(base: nav.visibleViewController)
}
// Tabs - UITabBarController
if let tab = viewController as? UITabBarController,
let selected = tab.selectedViewController {
if aboveBars {
return tab
}
return getTopViewController(base: selected)
}
// Apresentações modais
if let presented = viewController?.presentedViewController {
return getTopViewController(base: presented)
}
return viewController
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Shared instance UIApplication.
@MainActor
public static var sharedApplication: UIApplication {
return UIApplication.shared
}
#endif
#if !os(macOS)
/// - LoverdeCo: System current version (read-only).
@MainActor
public static var systemVersion: String {
return currentDevice.systemVersion
}
#endif
public init(){}
}
// MARK: - Methods
public extension LCEssentials {
#if os(iOS) || os(macOS)
/// - LoverdeCo: Share link with message
///
/// - Parameters:
/// - message: String with message you whant to send
/// - url: String with url you want to share
@MainActor
static func shareApp(message: String = "", url: String = ""){
let textToShare = message
let root = self.getTopViewController(aboveBars: true)
var objectsToShare = [textToShare] as [Any]
if let myWebsite = NSURL(string: url) {
objectsToShare.append(myWebsite)
}
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = root?.view
root?.modalPresentationStyle = .fullScreen
root?.present(activityVC, animated: true, completion: nil)
}
/// - LoverdeCo: Make a call
///
/// - Parameters:
/// - number: string phone number
@MainActor
static func call(_ number: String!) {
UIApplication.openURL(urlStr: "tel://" + number)
}
/// - LoverdeCo: Open link on Safari
///
/// - Parameters:
/// - urlStr: url string to open
@MainActor
static func openSafari(_ urlStr: String){
UIApplication.openURL(urlStr: urlStr)
}
#endif
// MARK: - Background Thread
@MainActor
static func backgroundThread(delay: Double = 0.0, background: (@Sendable () -> Void)? = nil, completion: (@Sendable () -> Void)? = nil) {
DispatchQueue.global().async {
background?()
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
completion?()
}
}
}
@MainActor
static func dispatchAsync(completion: @escaping () -> Void) {
DispatchQueue.main.async {
completion()
}
}
/// - LoverdeCo: Delay function or closure call.
///
/// - Parameters:
/// - milliseconds: execute closure after the given delay.
/// - queue: a queue that completion closure should be executed on (default is DispatchQueue.main).
/// - completion: closure to be executed after delay.
/// - Returns: DispatchWorkItem task. You can call .cancel() on it to cancel delayed execution.
@discardableResult
static func delay(milliseconds: Double,
queue: DispatchQueue = .main,
completion: @escaping () -> Void) -> DispatchWorkItem {
let task = DispatchWorkItem { completion() }
queue.asyncAfter(deadline: .now() + (milliseconds/1000), execute: task)
return task
}
/// - LoverdeCo: Debounce function or closure call.
///
/// - Parameters:
/// - millisecondsOffset: allow execution of method if it was not called since millisecondsOffset.
/// - queue: a queue that action closure should be executed on (default is DispatchQueue.main).
/// - action: closure to be executed in a debounced way.
@MainActor
static func debounce(millisecondsDelay: Int,
queue: DispatchQueue = .main,
action: @escaping (() -> Void)) -> () -> Void {
// http://stackoverflow.com/questions/27116684/how-can-i-debounce-a-method-call
var lastFireTime = DispatchTime.now()
let dispatchDelay = DispatchTimeInterval.milliseconds(millisecondsDelay)
let dispatchTime: DispatchTime = lastFireTime + dispatchDelay
return {
queue.asyncAfter(deadline: dispatchTime) {
let when: DispatchTime = lastFireTime + dispatchDelay
let now = DispatchTime.now()
if now.rawValue >= when.rawValue {
lastFireTime = DispatchTime.now()
action()
}
}
}
}
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Called when user takes a screenshot
///
/// - Parameter action: a closure to run when user takes a screenshot
@MainActor
static func didTakeScreenShot(_ action: @escaping (_ notification: Notification) -> Void) {
// http://stackoverflow.com/questions/13484516/ios-detection-of-screenshot
_ = NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification,
object: nil,
queue: OperationQueue.main) { notification in
action(notification)
}
}
#endif
#if os(iOS)
static func cachedFileURL(for url: URL) -> URL? {
let request = URLRequest(url: url)
guard let cachedResponse = cache.cachedResponse(for: request) else { return nil }
let tempDir = FileManager.default.temporaryDirectory
let tempFile = tempDir.appendingPathComponent(url.lastPathComponent)
do {
try cachedResponse.data.write(to: tempFile)
return tempFile
} catch {
return nil
}
}
@MainActor
static func downloadFileWithCache(from url: URL, completion: @escaping (Result<URL, Error>) -> Void) {
// Verifica se já existe no cache
if let cachedURL = cachedFileURL(for: url) {
completion(.success(cachedURL))
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, let response = response else {
completion(.failure(error ?? URLError(.badServerResponse)))
return
}
// Armazena no cache
let cachedResponse = CachedURLResponse(response: response, data: data)
cache.storeCachedResponse(cachedResponse, for: request)
// Escreve no diretório temporário
let tempDir = FileManager.default.temporaryDirectory
let tempFile = tempDir.appendingPathComponent(url.lastPathComponent)
do {
try data.write(to: tempFile)
completion(.success(tempFile))
} catch {
completion(.failure(error))
}
}
task.resume()
}
static func cleanExpiredCache(expiration: TimeInterval = 7 * 24 * 60 * 60) { // 1 semana padrão
cache.removeCachedResponses(since: Date().addingTimeInterval(-expiration))
// Limpa arquivos temporários antigos
let tempDir = FileManager.default.temporaryDirectory
do {
let files = try FileManager.default.contentsOfDirectory(at: tempDir, includingPropertiesForKeys: [.creationDateKey])
let expirationDate = Date().addingTimeInterval(-expiration)
for file in files {
let attributes = try FileManager.default.attributesOfItem(atPath: file.path)
if let creationDate = attributes[.creationDate] as? Date, creationDate < expirationDate {
try FileManager.default.removeItem(at: file)
}
}
} catch {
print("Erro ao limpar cache: \(error)")
}
}
#endif
}