增加换肤功能
This commit is contained in:
94
TUIKit/TUICallKit/TUICallKit-Swift/Utils/GCDTimer.swift
Normal file
94
TUIKit/TUICallKit/TUICallKit-Swift/Utils/GCDTimer.swift
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// GCDTimer.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/2/13.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
typealias GCDTimerTaskEvent = () -> Void
|
||||
|
||||
class GCDTimer {
|
||||
|
||||
static var timerDic: [String: DispatchSourceTimer] = Dictionary()
|
||||
|
||||
static func start(interval:Int, repeats: Bool, async: Bool, task:@escaping GCDTimerTaskEvent) -> String {
|
||||
|
||||
let codeTimer = DispatchSource.makeTimerSource(queue: async ? DispatchQueue.global() : DispatchQueue.main)
|
||||
|
||||
let timerName = String(Date().timeIntervalSinceReferenceDate)
|
||||
timerDic[timerName] = codeTimer
|
||||
|
||||
codeTimer.schedule(deadline: .now(), repeating: .seconds(interval))
|
||||
codeTimer.setEventHandler(handler: {
|
||||
DispatchQueue.main.async {
|
||||
task()
|
||||
}
|
||||
if !repeats {
|
||||
self.cancel(timerName: timerName) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if codeTimer.isCancelled { return timerName}
|
||||
codeTimer.resume()
|
||||
return timerName
|
||||
}
|
||||
|
||||
static func cancel(timerName: String, task: @escaping GCDTimerTaskEvent) {
|
||||
if let timer = timerDic[timerName] {
|
||||
timer.cancel()
|
||||
timerDic.removeValue(forKey: timerName)
|
||||
}
|
||||
task()
|
||||
}
|
||||
|
||||
static func secondToHMS(second: Int) -> (hour: Int, min: Int, sec: Int) {
|
||||
var sec: Int = 0
|
||||
var min: Int = 0
|
||||
var hour: Int = 0
|
||||
|
||||
let oneHour = 3_600
|
||||
let oneMin = 60
|
||||
|
||||
hour = second / oneHour
|
||||
min = (second % oneHour) / oneMin
|
||||
sec = (second % oneHour) % oneMin
|
||||
|
||||
return (hour, min, sec)
|
||||
}
|
||||
|
||||
static func secondToHMSString(second: Int) -> String {
|
||||
let time = GCDTimer.secondToHMS(second: second)
|
||||
var hour: String
|
||||
var min: String
|
||||
var seconds: String
|
||||
|
||||
if time.hour <= 0 {
|
||||
hour = ""
|
||||
} else if time.hour >= 0 && time.hour < 10 {
|
||||
hour = "0\(time.hour):"
|
||||
} else {
|
||||
hour = String(time.hour) + ":"
|
||||
}
|
||||
|
||||
if time.min <= 0 {
|
||||
min = "00:"
|
||||
} else if time.min >= 0 && time.min < 10 {
|
||||
min = "0\(time.min):"
|
||||
} else {
|
||||
min = String(time.min) + ":"
|
||||
}
|
||||
|
||||
if time.sec <= 0 {
|
||||
seconds = "00"
|
||||
} else if time.sec >= 0 && time.sec < 10 {
|
||||
seconds = "0\(time.sec)"
|
||||
} else {
|
||||
seconds = String(time.sec)
|
||||
}
|
||||
|
||||
return "\(hour)\(min)\(seconds)"
|
||||
}
|
||||
}
|
||||
50
TUIKit/TUICallKit/TUICallKit-Swift/Utils/Localized.swift
Normal file
50
TUIKit/TUICallKit/TUICallKit-Swift/Utils/Localized.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Localized.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/2/13.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TUICore
|
||||
|
||||
// MARK: Base
|
||||
func TUICallKitBundle() -> Bundle? {
|
||||
var bundle: Bundle?
|
||||
let onceToken = DispatchSemaphore(value: 1)
|
||||
onceToken.wait()
|
||||
defer {
|
||||
onceToken.signal()
|
||||
}
|
||||
if bundle == nil {
|
||||
if let bundleUrl = Bundle.main.url(forResource: "TUICallKitBundle", withExtension: "bundle") {
|
||||
bundle = Bundle(url: bundleUrl)
|
||||
} else {
|
||||
var bundleUrl = Bundle.main.url(forResource: "Frameworks", withExtension: nil)
|
||||
bundleUrl = bundleUrl?.appendingPathComponent("TUICallKit")
|
||||
bundleUrl = bundleUrl?.appendingPathExtension("framework")
|
||||
guard let url = bundleUrl else { return nil }
|
||||
guard let associateBundle = Bundle(url: url) else { return nil }
|
||||
guard let bundleUrl = associateBundle.url(forResource: "TUICallKitBundle", withExtension: "bundle") else { return nil }
|
||||
bundle = Bundle(url: bundleUrl)
|
||||
}
|
||||
}
|
||||
return bundle
|
||||
}
|
||||
|
||||
func TUICallKitLocalizeFromTable(key: String, table: String) -> String? {
|
||||
guard let bundlePath = TUICallKitBundle()?.path(forResource: TUIGlobalization.getPreferredLanguage() ?? "",
|
||||
ofType: "lproj") else { return nil}
|
||||
let bundle = Bundle(path: bundlePath)
|
||||
return bundle?.localizedString(forKey: key, value: "", table: table)
|
||||
}
|
||||
|
||||
func TUICallKitLocalizerFromTableAndCommon(key: String, common: String, table: String) -> String? {
|
||||
return TUICallKitLocalizeFromTable(key: key, table: table)
|
||||
}
|
||||
|
||||
// MARK: CallKit
|
||||
let TUICallKit_Localize_TableName = "Localized"
|
||||
func TUICallKitLocalize(key: String) -> String? {
|
||||
return TUICallKitLocalizeFromTable(key: key, table: TUICallKit_Localize_TableName)
|
||||
}
|
||||
42
TUIKit/TUICallKit/TUICallKit-Swift/Utils/Logger.swift
Normal file
42
TUIKit/TUICallKit/TUICallKit-Swift/Utils/Logger.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Logger.swift
|
||||
// Pods
|
||||
//
|
||||
// Created by vincepzhang on 2024/11/22.
|
||||
//
|
||||
|
||||
#if canImport(TXLiteAVSDK_TRTC)
|
||||
import TXLiteAVSDK_TRTC
|
||||
#elseif canImport(TXLiteAVSDK_Professional)
|
||||
import TXLiteAVSDK_Professional
|
||||
#endif
|
||||
|
||||
class Logger {
|
||||
|
||||
static func info(_ message: String) {
|
||||
log(message, level: 0)
|
||||
}
|
||||
|
||||
static func warning(_ message: String) {
|
||||
log(message, level: 1)
|
||||
}
|
||||
|
||||
static func error(_ message: String) {
|
||||
log(message, level: 2)
|
||||
}
|
||||
|
||||
private static func log(_ message: String, level: Int = 0) {
|
||||
let dictionary: [String : Any] = ["api": "TuikitLog",
|
||||
"params" : ["level": level,
|
||||
"message": "TUICallKit - \(message)",
|
||||
"file": "/some_path/.../foo.c",
|
||||
"line": 90]]
|
||||
do {
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
|
||||
guard let jsonString = String(data: jsonData, encoding: .utf8) else { return }
|
||||
TRTCCloud.sharedInstance().callExperimentalAPI(jsonString)
|
||||
} catch {
|
||||
print("Error converting dictionary to JSON: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
87
TUIKit/TUICallKit/TUICallKit-Swift/Utils/Observers.swift
Normal file
87
TUIKit/TUICallKit/TUICallKit-Swift/Utils/Observers.swift
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Observers.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/4/3.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
class Observer {}
|
||||
|
||||
public class Observable<Type> {
|
||||
|
||||
// MARK: - Callback
|
||||
fileprivate class Callback {
|
||||
fileprivate weak var observer: AnyObject?
|
||||
fileprivate let options: [ObservableOptions]
|
||||
fileprivate let closure: (Type, ObservableOptions) -> Void
|
||||
|
||||
fileprivate init(
|
||||
observer: AnyObject,
|
||||
options: [ObservableOptions],
|
||||
closure: @escaping (Type, ObservableOptions) -> Void) {
|
||||
self.observer = observer
|
||||
self.options = options
|
||||
self.closure = closure
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties Use Swift's didSet feature to call back the value to the callback
|
||||
public var value: Type {
|
||||
didSet {
|
||||
removeNilObserverCallbacks()
|
||||
notifyCallbacks(value: oldValue, option: .old)
|
||||
notifyCallbacks(value: value, option: .new)
|
||||
}
|
||||
}
|
||||
|
||||
private func removeNilObserverCallbacks() {
|
||||
callbacks = callbacks.filter { $0.observer != nil }
|
||||
}
|
||||
|
||||
// MARK: Callback to callback to implement closure callback
|
||||
private func notifyCallbacks(value: Type, option: ObservableOptions) {
|
||||
let callbacksToNotify = callbacks.filter { $0.options.contains(option) }
|
||||
callbacksToNotify.forEach { $0.closure(value, option) }
|
||||
}
|
||||
|
||||
// MARK: - Object Lifecycle
|
||||
public init(_ value: Type) {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
// MARK: - Managing Observers
|
||||
private var callbacks: [Callback] = []
|
||||
|
||||
public func addObserver(
|
||||
_ observer: AnyObject,
|
||||
removeIfExists: Bool = true,
|
||||
options: [ObservableOptions] = [.new],
|
||||
closure: @escaping (Type, ObservableOptions) -> Void) {
|
||||
if removeIfExists {
|
||||
removeObserver(observer)
|
||||
}
|
||||
let callback = Callback(observer: observer, options: options, closure: closure)
|
||||
callbacks.append(callback)
|
||||
if options.contains(.initial) {
|
||||
closure(value, .initial)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeObserver(_ observer: AnyObject) {
|
||||
callbacks = callbacks.filter { $0.observer !== observer }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ObservableOptions
|
||||
public struct ObservableOptions: OptionSet {
|
||||
public static let initial = ObservableOptions(rawValue: 1 << 0)
|
||||
public static let old = ObservableOptions(rawValue: 1 << 1)
|
||||
public static let new = ObservableOptions(rawValue: 1 << 2)
|
||||
|
||||
public var rawValue: Int
|
||||
|
||||
public init(rawValue: Int) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// TUICallKitCommon.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/8/14.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TUICallEngine
|
||||
import AVFoundation
|
||||
|
||||
@objc
|
||||
public enum AuthorizationDeniedType: Int {
|
||||
case audio
|
||||
case video
|
||||
}
|
||||
|
||||
@objc
|
||||
public class TUICallKitCommon: NSObject {
|
||||
|
||||
static func createRoomId() -> UInt32 {
|
||||
return 1 + arc4random() % (UINT32_MAX / 2 - 1)
|
||||
}
|
||||
|
||||
static func getTUICallKitBundle() -> Bundle? {
|
||||
guard let url: URL = Bundle.main.url(forResource: "TUICallKitBundle", withExtension: "bundle") else { return nil }
|
||||
return Bundle(url: url)
|
||||
}
|
||||
|
||||
static func getBundleImage(name: String) -> UIImage? {
|
||||
return UIImage(named: name, in: self.getTUICallKitBundle(), compatibleWith: nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
public static func checkAuthorizationStatusIsDenied(mediaType: TUICallMediaType) -> Bool {
|
||||
let statusAudio: AVAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .audio)
|
||||
let statusVideo: AVAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
|
||||
|
||||
if mediaType == TUICallMediaType.video && statusVideo == .denied {
|
||||
return true
|
||||
}
|
||||
|
||||
if mediaType == TUICallMediaType.audio && statusAudio == .denied {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@objc
|
||||
public static func showAuthorizationAlert(deniedType: AuthorizationDeniedType,
|
||||
openSettingHandler: @escaping () -> Void,
|
||||
cancelHandler: @escaping () -> Void) {
|
||||
var title: String
|
||||
var message: String
|
||||
var laterMessage: String
|
||||
var openSettingMessage: String
|
||||
|
||||
switch deniedType {
|
||||
case .audio:
|
||||
title = TUICallKitLocalize(key: "TUICallKit.FailedToGetMicrophonePermission.Title") ?? ""
|
||||
message = TUICallKitLocalize(key: "TUICallKit.FailedToGetMicrophonePermission.Tips") ?? ""
|
||||
laterMessage = TUICallKitLocalize(key: "TUICallKit.FailedToGetMicrophonePermission.Later") ?? ""
|
||||
openSettingMessage = TUICallKitLocalize(key: "TUICallKit.FailedToGetMicrophonePermission.Enable") ?? ""
|
||||
case .video:
|
||||
title = TUICallKitLocalize(key: "TUICallKit.FailedToGetCameraPermission.Title") ?? ""
|
||||
message = TUICallKitLocalize(key: "TUICallKit.FailedToGetCameraPermission.Tips") ?? ""
|
||||
laterMessage = TUICallKitLocalize(key: "TUICallKit.FailedToGetCameraPermission.Later") ?? ""
|
||||
openSettingMessage = TUICallKitLocalize(key: "TUICallKit.FailedToGetCameraPermission.Enable") ?? ""
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert )
|
||||
|
||||
alertController.addAction(UIAlertAction(title: laterMessage, style: .cancel, handler: { action in
|
||||
cancelHandler()
|
||||
}))
|
||||
|
||||
alertController.addAction(UIAlertAction(title: openSettingMessage, style: .default, handler: { action in
|
||||
openSettingHandler()
|
||||
let app = UIApplication.shared
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
if app.canOpenURL(url) {
|
||||
if #available(iOS 10.0, *) {
|
||||
app.open(url)
|
||||
} else {
|
||||
app.openURL(url)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
DispatchQueue.main.async {
|
||||
UIWindow.getKeyWindow()?.rootViewController?.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// TUICoreDefineConvert.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/8/14.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TUICore
|
||||
|
||||
|
||||
class TUICoreDefineConvert {
|
||||
|
||||
static func getTUIKitLocalizableString(key: String) -> String {
|
||||
return TUIGlobalization.getLocalizedString(forKey: key, bundle: TUIKitLocalizableBundle)
|
||||
}
|
||||
|
||||
static func getTIMCommonLocalizableString(key: String) -> String? {
|
||||
return TUIGlobalization.getLocalizedString(forKey: key, bundle: TIMCommonLocalizableBundle)
|
||||
}
|
||||
|
||||
static func getTUICoreBundleThemeImage(imageKey: String, defaultImageName: String) -> UIImage? {
|
||||
let tuiCoreCommonBundleImage = UIImage(contentsOfFile: getTUIGetBundlePath(TUICoreBundle, TUICoreBundle_Key_Class) + "/" + defaultImageName)
|
||||
return TUITheme.dynamicImage(imageKey, module: TUIThemeModule.core, defaultImage: tuiCoreCommonBundleImage ?? UIImage())
|
||||
}
|
||||
|
||||
static func getTUIDynamicImage(imageKey: String, module: TUIThemeModule, defaultImage: UIImage) -> UIImage? {
|
||||
return TUITheme.dynamicImage(imageKey, module: module, defaultImage: defaultImage)
|
||||
}
|
||||
|
||||
static func getTUIContactImagePathMinimalist(imageName: String) -> String {
|
||||
return getTUIGetBundlePath(TUIContactBundle_Minimalist, TUIContactBundle_Key_Class) + "/" + imageName
|
||||
}
|
||||
|
||||
static func getTUICallKitDynamicColor(colorKey: String, defaultHex: String) -> UIColor? {
|
||||
return TUITheme.dynamicColor(colorKey, module: TUIThemeModule.calling, defaultColor: defaultHex)
|
||||
}
|
||||
|
||||
static func getTUICoreDynamicColor(colorKey: String, defaultHex: String) -> UIColor? {
|
||||
return TUITheme.dynamicColor(colorKey, module: TUIThemeModule.core, defaultColor: defaultHex)
|
||||
}
|
||||
|
||||
static func getDefaultAvatarImage() -> UIImage {
|
||||
return TUIConfig.default().defaultAvatarImage
|
||||
}
|
||||
|
||||
static func getDefaultGroupAvatarImage() -> UIImage {
|
||||
return TUIConfig.default().defaultAvatarImage
|
||||
}
|
||||
|
||||
static func getIsRTL() -> Bool {
|
||||
return TUIGlobalization.getRTLOption()
|
||||
}
|
||||
|
||||
static func getTUICallKitThemePath() -> String {
|
||||
return getTUIGetBundlePath("TUICallKitTheme", "TUICallingService")
|
||||
}
|
||||
}
|
||||
73
TUIKit/TUICallKit/TUICallKit-Swift/Utils/Utils.swift
Normal file
73
TUIKit/TUICallKit/TUICallKit-Swift/Utils/Utils.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// Utils.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/1/6.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func DispatchCallKitMainAsyncSafe(closure: @escaping () -> Void) {
|
||||
if Thread.current.isMainThread {
|
||||
closure()
|
||||
} else {
|
||||
DispatchQueue.main.async(execute: closure)
|
||||
}
|
||||
}
|
||||
|
||||
extension CGRect {
|
||||
func dividedIntegral(fraction: CGFloat, from fromEdge: CGRectEdge) -> (first: CGRect, second: CGRect) {
|
||||
let dimension: CGFloat
|
||||
|
||||
switch fromEdge {
|
||||
case .minXEdge, .maxXEdge:
|
||||
dimension = self.size.width
|
||||
case .minYEdge, .maxYEdge:
|
||||
dimension = self.size.height
|
||||
}
|
||||
|
||||
let distance = (dimension * fraction).rounded(.up)
|
||||
var slices = self.divided(atDistance: distance, from: fromEdge)
|
||||
|
||||
switch fromEdge {
|
||||
case .minXEdge, .maxXEdge:
|
||||
slices.remainder.origin.x += 1
|
||||
slices.remainder.size.width -= 1
|
||||
case .minYEdge, .maxYEdge:
|
||||
slices.remainder.origin.y += 1
|
||||
slices.remainder.size.height -= 1
|
||||
}
|
||||
|
||||
return (first: slices.slice, second: slices.remainder)
|
||||
}
|
||||
}
|
||||
|
||||
extension CGFloat {
|
||||
|
||||
public func scaleWidth(_ exceptPad: Bool = true) -> CGFloat {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
return exceptPad ? self * 1.5 : self * (Screen_Width / 375.00)
|
||||
}
|
||||
return self * (Screen_Width / 375.00)
|
||||
}
|
||||
|
||||
public func scaleHeight(_ exceptPad: Bool = true) -> CGFloat {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
return exceptPad ? self * 1.5 : self * (Screen_Height / 812.00)
|
||||
}
|
||||
return self * (Screen_Height / 812.00)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Int {
|
||||
|
||||
public func scaleWidth(_ exceptPad: Bool = true) -> CGFloat {
|
||||
return CGFloat(self).scaleWidth()
|
||||
}
|
||||
|
||||
public func scaleHeight(_ exceptPad: Bool = true) -> CGFloat {
|
||||
return CGFloat(self).scaleHeight()
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user