feat: 添加项目基础文件和依赖管理

新增.gitignore、Podfile和Podfile.lock文件以管理项目依赖,添加README.md文件提供项目简介和安装步骤,创建NIMSessionManager、ClientConfig、LogManager和NetworkManager等管理类以支持网络请求和日志记录功能,更新AppDelegate和ContentView以集成NIM SDK和实现用户登录功能。
This commit is contained in:
edwinQQQ
2025-05-29 16:14:28 +08:00
parent 374cc654d7
commit a0200c8859
25 changed files with 2081 additions and 23 deletions

59
yana/APIs/API.swift Normal file
View File

@@ -0,0 +1,59 @@
import Foundation
import Alamofire
enum HttpRequestMethod: String {
case get = "GET"
case post = "POST"
//
}
typealias HttpRequestCompletion = (Result<Data, Error>) -> Void
class API {
//
static func makeRequest(
route: String,
method: HttpRequestMethod,
params: [String: Any],
completion: @escaping HttpRequestCompletion
) {
let httpMethod: HTTPMethod = {
switch method {
case .get: return .get
case .post: return .post
}
}()
NetworkManager.shared.request(route, method: httpMethod, parameters: params) { (result: Result<Data, NetworkError>) in
switch result {
case .success(let data):
completion(.success(data))
case .failure(let error):
completion(.failure(error))
}
}
}
//
static func getUserInfo(uid: String, completion: @escaping HttpRequestCompletion) {
let route = "user/get"
let params = ["uid": uid]
makeRequest(route: route, method: .get, params: params, completion: completion)
}
static func phoneSmsCode(mobile: String, type: String, phoneAreaCode: String, completion: @escaping HttpRequestCompletion) {
let route = "sms/getCode"
let params = ["mobile": mobile, "type": type, "phoneAreaCode": phoneAreaCode]
makeRequest(route: route, method: .post, params: params, completion: completion)
}
}
extension API //ClientConfig
{
static func clientInit(completion: @escaping HttpRequestCompletion) {
makeRequest(route: "client/init",
method: .get,
params: [:],
completion: completion)
}
}

9
yana/AppDelegate.swift Normal file
View File

@@ -0,0 +1,9 @@
import UIKit
import NIMSDK
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
NIMConfigurationManager.setupNimSDK()
return true
}
}

View File

@@ -15,9 +15,9 @@ struct AppConfig {
static var baseURL: String {
switch current {
case .development:
return "https://dev-api.yourdomain.com/v1"
return "http://beta.api.molistar.xyz"
case .production:
return "https://api.yourdomain.com/v1"
return "https://api.hfighting.com"
}
}

View File

@@ -0,0 +1,32 @@
import Foundation
final class ClientConfig {
static let shared = ClientConfig()
private init() {}
func initializeClient() {
print("开始初始化客户端")
NetworkManager.shared.enhancedRequest(
path: "client/init",
method: .get,
responseType: Data.self
) { result in
switch result {
case .success(let response):
print("初始化成功,状态码:\(response.statusCode)")
if let data = response.data {
do {
let json = try JSONSerialization.jsonObject(with: data)
print("响应数据:\(json)")
} catch {
print("JSON解析失败\(error)")
}
}
case .failure(let error):
print("初始化失败:\(error.localizedDescription)")
}
}
}
}

View File

@@ -8,15 +8,71 @@
import SwiftUI
struct ContentView: View {
@State private var account = ""
@State private var password = ""
#if DEBUG
init() {
_account = State(initialValue: "3184")
_password = State(initialValue: "a0d5da073d14731cc7a01ecaa17b9174")
}
#endif
@State private var isLoading = false
@State private var loginError: String?
var body: some View {
VStack {
//
Button("测试初始化") {
ClientConfig.shared.initializeClient()
}
.padding(.top, 20)
TextField("账号", text: $account)
.textFieldStyle(.roundedBorder)
.padding()
SecureField("密码", text: $password)
.textFieldStyle(.roundedBorder)
.padding()
Button(action: handleLogin) {
if isLoading {
ProgressView()
} else {
Text("登录")
}
}
.disabled(isLoading)
.alert("登录错误", isPresented: .constant(loginError != nil)) {
Button("确定") { loginError = nil }
} message: {
Text(loginError ?? "")
}
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
Text("Hello, yana!")
}
.padding()
}
private func handleLogin() {
isLoading = true
NIMSessionManager.shared
.autoLogin(account: account, token: password) { error in
if let error = error {
loginError = error.localizedDescription
} else {
//
}
}
// NIMSessionManager.shared.login(account: account, token: password) { error in
// isLoading = false
//
// }
}
}
#Preview {

11
yana/Info.plist Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,51 @@
import Foundation
///
public enum LogLevel: Int {
case verbose = 0
case debug
case info
case warn
case error
}
public class LogManager {
///
public static let shared = LogManager()
private init() {}
///
/// - Parameters:
/// - level:
/// - message:
/// - onlyRelease: Release falseDebug
public func log(_ level: LogLevel, _ message: @autoclosure () -> String, onlyRelease: Bool = false) {
#if DEBUG
if onlyRelease { return }
print("[\(level)] \(message())")
#else
print("[\(level)] \(message())")
#endif
}
}
// MARK: -
public func logVerbose(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
LogManager.shared.log(.verbose, message(), onlyRelease: onlyRelease)
}
public func logDebug(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
LogManager.shared.log(.debug, message(), onlyRelease: onlyRelease)
}
public func logInfo(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
LogManager.shared.log(.info, message(), onlyRelease: onlyRelease)
}
public func logWarn(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
LogManager.shared.log(.warn, message(), onlyRelease: onlyRelease)
}
public func logError(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
LogManager.shared.log(.error, message(), onlyRelease: onlyRelease)
}

View File

@@ -1,10 +1,33 @@
import NIMSDK
import NECoreKit
import NECoreIM2Kit
import NEChatKit
import NEChatUIKit
struct NIMConfigurationManager {
static func setupSDK() {
NIMSDK.shared().register(
withAppID: "79bc37000f4018a2a24ea9dc6ca08d32",
cerName: "pikoDevelopPush"
)
static func setupNimSDK() {
let option = configureNIMSDKOption()
setupSDK(with: option)
setupChatSDK(with: option)
}
}
static func setupSDK(with option: NIMSDKOption) {
NIMSDK.shared().register(with: option)
NIMSDKConfig.shared().shouldConsiderRevokedMessageUnreadCount = true
NIMSDKConfig.shared().shouldSyncStickTopSessionInfos = true
}
static func setupChatSDK(with option: NIMSDKOption) {
let v2Option = V2NIMSDKOption()
v2Option.enableV2CloudConversation = false
IMKitClient.instance.setupIM2(option, v2Option)
}
static func configureNIMSDKOption() -> NIMSDKOption {
let option = NIMSDKOption()
option.appKey = "79bc37000f4018a2a24ea9dc6ca08d32"
option.apnsCername = "pikoDevelopPush"
return option
}
}

View File

@@ -0,0 +1,127 @@
import Foundation
import NIMSDK
// MARK: -
extension Notification.Name {
static let NIMNetworkStateChanged = Notification.Name("NIMNetworkStateChangedNotification")
static let NIMTokenExpired = Notification.Name("NIMTokenExpiredNotification")
}
@objc
@objcMembers
final class NIMSessionManager: NSObject {
static let shared = NIMSessionManager()
// MARK: -
func autoLogin(account: String, token: String, completion: @escaping (Error?) -> Void) {
NIMSDK.shared().v2LoginService.add(self)
let data = NIMAutoLoginData()
data.account = account
data.token = token
data.forcedMode = false
NIMSDK.shared().loginManager.autoLogin(data)
}
func login(account: String, token: String, completion: @escaping (Error?) -> Void) {
NIMSDK.shared().loginManager.login(account, token: token) { error in
if error == nil {
self.registerObservers()
}
completion(error)
}
}
func logout() {
NIMSDK.shared().loginManager.logout { _ in
self.removeObservers()
}
}
// MARK: -
private func registerObservers() {
// autoLogin
// NIMSDK.shared().v2LoginService.add(self as! V2NIMLoginServiceDelegate)
// registerObservers
// NIMSDK.shared().v2LoginService.add(self as! V2NIMLoginServiceDelegate)
// removeObservers
// NIMSDK.shared().v2LoginService.remove(self as! V2NIMLoginServiceDelegate)
NIMSDK.shared().chatManager.add(self)
NIMSDK.shared().loginManager.add(self)
}
private func removeObservers() {
NIMSDK.shared().v2LoginService.remove(self)
NIMSDK.shared().chatManager.remove(self)
NIMSDK.shared().loginManager.remove(self)
}
}
// MARK: - NIMChatManagerDelegate
extension NIMSessionManager: NIMChatManagerDelegate {
func onRecvMessages(_ messages: [NIMMessage]) {
NotificationCenter.default.post(
name: .NIMDidReceiveMessage,
object: messages
)
}
}
// MARK: - NIMLoginManagerDelegate
extension NIMSessionManager: NIMLoginManagerDelegate {
func onLogin(_ step: NIMLoginStep) {
NotificationCenter.default.post(
name: .NIMLoginStateChanged,
object: step
)
}
func onAutoLoginFailed(_ error: Error) {
if (error as NSError).code == 302 {
NotificationCenter.default.post(name: .NIMTokenExpired, object: nil)
}
}
}
// MARK: -
extension Notification.Name {
static let NIMDidReceiveMessage = Notification.Name("NIMDidReceiveMessageNotification")
static let NIMLoginStateChanged = Notification.Name("NIMLoginStateChangedNotification")
}
// MARK: - NIMV2LoginServiceDelegate
extension NIMSessionManager: V2NIMLoginListener {
func onLoginStatus(_ status: V2NIMLoginStatus) {
}
func onLoginFailed(_ error: V2NIMError) {
}
func onKickedOffline(_ detail: V2NIMKickedOfflineDetail) {
}
func onLoginClientChanged(
_ change: V2NIMLoginClientChange,
clients: [V2NIMLoginClient]?
) {
}
// @objc func onLoginProcess(step: NIMV2LoginStep) {
// NotificationCenter.default.post(
// name: .NIMV2LoginStateChanged,
// object: step
// )
// }
//
// @objc func onKickOut(result: NIMKickOutResult) {
// NotificationCenter.default.post(
// name: .NIMKickOutNotification,
// object: result
// )
// }
}

View File

@@ -0,0 +1,667 @@
import Foundation
import Alamofire
import CoreTelephony
import UIKit
import Darwin // utsname
import CommonCrypto
//
//enum AppConfig {
// static let baseURL = "https://api.example.com" // API URL
//}
//
enum NetworkStatus: Int {
case notReachable = 0
case reachableViaWWAN = 1
case reachableViaWiFi = 2
}
//
enum NetworkError: Error {
case invalidURL
case requestFailed(statusCode: Int, message: String?)
case invalidResponse
case decodingFailed
case networkUnavailable
case serverError(message: String)
case unauthorized
case rateLimited
var localizedDescription: String {
switch self {
case .invalidURL:
return "无效的 URL"
case .requestFailed(let statusCode, let message):
return "请求失败: \(statusCode), \(message ?? "未知错误")"
case .invalidResponse:
return "无效的响应"
case .decodingFailed:
return "数据解析失败"
case .networkUnavailable:
return "网络不可用"
case .serverError(let message):
return "服务器错误: \(message)"
case .unauthorized:
return "未授权访问"
case .rateLimited:
return "请求过于频繁"
}
}
}
// MARK: - MD5
extension String {
func md5() -> String {
let str = self.cString(using: .utf8)
let strLen = CUnsignedInt(self.lengthOfBytes(using: .utf8))
let digestLen = Int(CC_MD5_DIGEST_LENGTH)
let result = UnsafeMutablePointer<UInt8>.allocate(capacity: digestLen)
CC_MD5(str!, strLen, result)
let hash = NSMutableString()
for i in 0..<digestLen {
hash.appendFormat("%02x", result[i])
}
result.deallocate()
return hash as String
}
}
//
struct BaseParameters: Encodable {
let acceptLanguage: String
let os: String = "iOS"
let osVersion: String
let ispType: String
let channel: String
let model: String
let deviceId: String
let appVersion: String
let app: String
let mcc: String?
let pub_sign: String
enum CodingKeys: String, CodingKey {
case acceptLanguage = "Accept-Language"
case os, osVersion, ispType, channel, model, deviceId
case appVersion, app, mcc
case pub_sign
}
init() {
// 使
self.acceptLanguage = LanguageManager.getCurrentLanguage()
//
self.osVersion = UIDevice.current.systemVersion
//
let networkInfo = CTTelephonyNetworkInfo()
var ispType = "65535"
var mcc: String? = nil // mcc
if #available(iOS 12.0, *) {
// 使 API
if let carriers = networkInfo.serviceSubscriberCellularProviders,
let carrier = carriers.values.first {
ispType = (
carrier.mobileNetworkCode != nil ? carrier.mobileNetworkCode : ""
) ?? "65535"
mcc = carrier.mobileCountryCode
}
} else {
//
if let carrier = networkInfo.subscriberCellularProvider {
ispType = (
carrier.mobileNetworkCode != nil ? carrier.mobileNetworkCode : ""
) ?? "65535"
mcc = carrier.mobileCountryCode
}
}
self.ispType = ispType
self.mcc = mcc // mcc
//
self.channel = ChannelManager.getCurrentChannel()
//
self.model = DeviceManager.getDeviceModel()
//
self.deviceId = UIDevice.current.identifierForVendor?.uuidString ?? ""
//
self.appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
//
self.app = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? ""
// pub_sign
let key = "rpbs6us1m8r2j9g6u06ff2bo18orwaya"
let signString = "key=\(key)"
self.pub_sign = signString.md5().uppercased()
}
}
final class NetworkManager {
static let shared = NetworkManager()
//
struct NetworkResponse<T> {
let statusCode: Int
let data: T?
let headers: [AnyHashable: Any]
let metrics: URLSessionTaskMetrics?
var isSuccessful: Bool {
return (200...299).contains(statusCode)
}
}
private let reachability = NetworkReachabilityManager()
private var isNetworkReachable = false
private let baseURL = AppConfig.baseURL
private let session: Session
private let retryLimit = 2
//
var networkStatusChanged: ((NetworkStatus) -> Void)?
init() {
let configuration = URLSessionConfiguration.af.default
configuration.httpShouldSetCookies = true
configuration.httpCookieAcceptPolicy = .always
configuration.timeoutIntervalForRequest = 60
configuration.timeoutIntervalForResource = 60
configuration.httpMaximumConnectionsPerHost = 10
//
configuration.httpAdditionalHeaders = [
"Accept": "application/json, text/json, text/javascript, text/html, text/plain, image/jpeg, image/png",
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/json"
]
// TLS 1.2+ HTTP/1.1
configuration.tlsMinimumSupportedProtocolVersion = .TLSv12
configuration.httpShouldUsePipelining = true
//
let retrier = RetryPolicy(retryLimit: UInt(retryLimit))
session = Session(
configuration: configuration,
interceptor: retrier,
eventMonitors: [AlamofireLogger()]
)
//
setupReachability()
}
private func setupReachability() {
reachability?.startListening { [weak self] status in
guard let self = self else { return }
switch status {
case .reachable(.ethernetOrWiFi):
self.isNetworkReachable = true
self.networkStatusChanged?(.reachableViaWiFi)
case .reachable(.cellular):
self.isNetworkReachable = true
self.networkStatusChanged?(.reachableViaWWAN)
case .notReachable:
self.isNetworkReachable = false
self.networkStatusChanged?(.notReachable)
case .unknown:
self.isNetworkReachable = false
self.networkStatusChanged?(.notReachable)
case .reachable(_):
self.isNetworkReachable = true
self.networkStatusChanged?(.reachableViaWiFi)
@unknown default:
fatalError("未知的网络状态")
}
}
}
// MARK: - 便
/// GET
func get<T: Decodable>(
path: String,
queryItems: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
enhancedRequest(
path: path,
method: .get,
queryItems: queryItems,
responseType: T.self
) { result in
switch result {
case .success(let response):
if let data = response.data as? T {
completion(.success(data))
} else {
completion(.failure(.decodingFailed))
}
case .failure(let error):
completion(.failure(error))
}
}
}
/// POST
func post<T: Decodable, P: Encodable>(
path: String,
parameters: P,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
enhancedRequest(
path: path,
method: .post,
bodyParameters: parameters,
responseType: T.self
) { result in
switch result {
case .success(let response):
if let data = response.data as? T {
completion(.success(data))
} else {
completion(.failure(.decodingFailed))
}
case .failure(let error):
completion(.failure(error))
}
}
}
// MARK: -
func enhancedRequest<T>(
path: String,
method: HTTPMethod = .get,
queryItems: [String: String]? = nil,
bodyParameters: Encodable? = nil,
responseType: T.Type = Data.self,
completion: @escaping (Result<NetworkResponse<T>, NetworkError>) -> Void
) {
//
guard isNetworkReachable else {
completion(.failure(.networkUnavailable))
return
}
guard let baseURL = URL(string: baseURL) else {
completion(.failure(.invalidURL))
return
}
var urlComponents = URLComponents(url: baseURL.appendingPathComponent(path), resolvingAgainstBaseURL: true)
urlComponents?.queryItems = queryItems?.map { URLQueryItem(name: $0.key, value: $0.value) }
guard let finalURL = urlComponents?.url else {
completion(.failure(.invalidURL))
return
}
//
// TODO: pub_sign
let baseParams = BaseParameters()
var parameters: Parameters = baseParams.dictionary ?? [:]
if let customParams = bodyParameters {
if let dict = try? customParams.asDictionary() {
parameters.merge(dict) { (_, new) in new }
}
}
session.request(finalURL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: commonHeaders)
.validate()
.responseData { [weak self] response in
guard let self = self else { return }
let statusCode = response.response?.statusCode ?? -1
let headers = response.response?.allHeaderFields ?? [:]
let metrics = response.metrics
switch response.result {
case .success(let decodedData):
do {
let resultData: T
if T.self == Data.self {
resultData = decodedData as! T
} else if let decodableData = decodedData as? T {
resultData = decodableData
} else {
throw AFError.responseSerializationFailed(reason: .decodingFailed(error: NetworkError.decodingFailed))
}
let networkResponse = NetworkResponse(
statusCode: statusCode,
data: resultData,
headers: headers,
metrics: metrics
)
if networkResponse.isSuccessful {
completion(.success(networkResponse))
} else {
self.handleErrorResponse(statusCode: statusCode, completion: completion)
}
} catch {
completion(.failure(.decodingFailed))
}
case .failure(let error):
self.handleRequestError(error, statusCode: statusCode, completion: completion)
}
}
}
// MARK: -
private func handleErrorResponse<T>(
statusCode: Int,
completion: (Result<NetworkResponse<T>, NetworkError>) -> Void
) {
switch statusCode {
case 401:
completion(.failure(.unauthorized))
case 429:
completion(.failure(.rateLimited))
case 500...599:
completion(.failure(.serverError(message: "服务器错误 \(statusCode)")))
default:
completion(.failure(.requestFailed(statusCode: statusCode, message: "请求失败")))
}
}
private func handleRequestError<T>(
_ error: AFError,
statusCode: Int,
completion: (Result<NetworkResponse<T>, NetworkError>) -> Void
) {
if let underlyingError = error.underlyingError as? URLError {
switch underlyingError.code {
case .notConnectedToInternet:
completion(.failure(.networkUnavailable))
default:
completion(.failure(.requestFailed(
statusCode: statusCode,
message: underlyingError.localizedDescription
)))
}
} else {
completion(.failure(.invalidResponse))
}
}
// MARK: -
private var commonHeaders: HTTPHeaders {
var headers = HTTPHeaders()
//
if let language = Locale.preferredLanguages.first {
headers.add(name: "Accept-Language", value: language)
}
headers.add(name: "App-Version", value: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0")
//
let uid = "" //AccountInfoStorage.instance?.getUid() ?? ""
let ticket = "" //AccountInfoStorage.instance?.getTicket() ?? ""
headers.add(name: "pub_uid", value: uid)
headers.add(name: "pub_ticket", value: ticket)
return headers
}
// MARK: -
func request<T: Decodable, P: Encodable>(
_ path: String,
method: HTTPMethod = .get,
parameters: P? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
enhancedRequest(
path: path,
method: method,
bodyParameters: parameters,
responseType: T.self
) { result in
switch result {
case .success(let response):
if let data = response.data {
completion(.success(data))
} else {
completion(.failure(.decodingFailed))
}
case .failure(let error):
completion(.failure(error))
}
}
}
// 便
func request<T: Decodable>(
_ path: String,
method: HTTPMethod = .get,
parameters: [String: Any]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
// Data [String: String]
if let params = parameters {
do {
let jsonData = try JSONSerialization.data(withJSONObject: params)
let decoder = JSONDecoder()
let encodableParams = try decoder.decode([String: String].self, from: jsonData)
request(path, method: method, parameters: encodableParams, completion: completion)
} catch {
completion(.failure(.decodingFailed))
}
} else {
// 使
request(path, method: method, parameters: [String: String](), completion: completion)
}
}
// MARK: -
func getCurrentLanguage() -> String {
return LanguageManager.getCurrentLanguage()
}
func updateLanguage(_ language: String) {
LanguageManager.updateLanguage(language)
}
}
// MARK: - Logger
final class AlamofireLogger: EventMonitor {
func requestDidResume(_ request: Request) {
let allHeaders = request.request?.allHTTPHeaderFields ?? [:]
let relevantHeaders = allHeaders.filter { !$0.key.contains("Authorization") }
print("🚀 Request Started: \(request.description)")
print("📝 Headers: \(relevantHeaders)")
if let httpBody = request.request?.httpBody,
let parameters = try? JSONSerialization.jsonObject(with: httpBody) {
print("📦 Parameters: \(parameters)")
}
}
func request(_ request: DataRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, data: Data?) {
print("📥 Response Status: \(response.statusCode)")
if let data = data,
let json = try? JSONSerialization.jsonObject(with: data) {
print("📄 Response Data: \(json)")
}
}
}
// MARK: - Encodable Extension
private extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NetworkError.decodingFailed
}
return dictionary
}
}
// MARK: -
enum LanguageManager {
static let languageKey = "UserSelectedLanguage"
//
static func mapLanguage(_ language: String) -> String {
// "zh-Hans""zh-Hant""zh-HK"
if language.hasPrefix("zh-Hans") || language.hasPrefix("zh-CN") {
return "zh-Hant" //
} else if language.hasPrefix("zh") {
return "zh-Hant" //
} else if language.hasPrefix("ar") {
return "ar" //
} else if language.hasPrefix("tr") {
return "tr" //
} else {
return "en" //
}
}
//
static func getCurrentLanguage() -> String {
// UserDefaults
if let savedLanguage = UserDefaults.standard.string(forKey: languageKey) {
return savedLanguage
}
//
let preferredLanguages = Locale.preferredLanguages.first
// let systemLanguage = preferredLanguages.first ?? Locale.current.languageCode ?? "en"
//
let mappedLanguage = mapLanguage(preferredLanguages ?? "en")
UserDefaults.standard.set(mappedLanguage, forKey: languageKey)
UserDefaults.standard.synchronize()
return mappedLanguage
}
//
static func updateLanguage(_ language: String) {
let mappedLanguage = mapLanguage(language)
UserDefaults.standard.set(mappedLanguage, forKey: languageKey)
UserDefaults.standard.synchronize()
}
//
static func getSystemLanguageInfo() -> (preferred: [String], current: String?) {
return (
Bundle.main.preferredLocalizations,
Locale.current.languageCode
)
}
}
// MARK: -
enum DeviceManager {
//
static func getDeviceIdentifier() -> String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}
//
static func mapDeviceModel(_ identifier: String) -> String {
switch identifier {
// iPhone
case "iPhone13,1": return "iPhone 12 mini"
case "iPhone13,2": return "iPhone 12"
case "iPhone13,3": return "iPhone 12 Pro"
case "iPhone13,4": return "iPhone 12 Pro Max"
case "iPhone14,4": return "iPhone 13 mini"
case "iPhone14,5": return "iPhone 13"
case "iPhone14,2": return "iPhone 13 Pro"
case "iPhone14,3": return "iPhone 13 Pro Max"
case "iPhone14,7": return "iPhone 14"
case "iPhone14,8": return "iPhone 14 Plus"
case "iPhone15,2": return "iPhone 14 Pro"
case "iPhone15,3": return "iPhone 14 Pro Max"
case "iPhone15,4": return "iPhone 15"
case "iPhone15,5": return "iPhone 15 Plus"
case "iPhone16,1": return "iPhone 15 Pro"
case "iPhone16,2": return "iPhone 15 Pro Max"
// iPad
case "iPad13,1", "iPad13,2": return "iPad Air (4th generation)"
case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7": return "iPad Pro 12.9-inch (5th generation)"
case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11": return "iPad Pro 12.9-inch (6th generation)"
// iPod
case "iPod9,1": return "iPod touch (7th generation)"
//
case "i386", "x86_64", "arm64": return "Simulator \(mapDeviceModel(ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))"
default: return identifier //
}
}
//
static func getDeviceModel() -> String {
let identifier = getDeviceIdentifier()
return mapDeviceModel(identifier)
}
}
// MARK: -
enum ChannelManager {
static let enterpriseBundleId = "com.stupidmonkey.yana.yana"//"com.hflighting.yumi"
enum ChannelType: String {
case enterprise = "enterprise"
case testflight = "testflight"
case appstore = "appstore"
}
//
static func isEnterprise() -> Bool {
let bundleId = Bundle.main.bundleIdentifier ?? ""
return bundleId == enterpriseBundleId
}
// TestFlight
static func isTestFlight() -> Bool {
return Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
}
//
static func getCurrentChannel() -> String {
if isEnterprise() {
return ChannelType.enterprise.rawValue
} else if isTestFlight() {
return ChannelType.testflight.rawValue
} else {
return ChannelType.appstore.rawValue
}
}
}

View File

@@ -0,0 +1,4 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

View File

@@ -6,9 +6,12 @@
//
import SwiftUI
import NIMSDK
@main
struct yanaApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()