qingqiu chegngong

This commit is contained in:
fengshuo
2024-02-24 13:49:51 +08:00
parent 990dfebf8a
commit d0f0f68e2b
69 changed files with 4861 additions and 3864 deletions

View File

@@ -6,7 +6,7 @@
//
import UIKit
import DeviceKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
@@ -17,7 +17,11 @@ var window: UIWindow?
self.window = UIWindow.init(frame: UIScreen.main.bounds)
self.window?.backgroundColor = UIColor.white
self.window?.rootViewController = BaseNavigationViewController(rootViewController:AuthLoginVC())
return true
}
}

View File

@@ -0,0 +1,145 @@
//
// HUDTool.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/23.
//
import Foundation
import MBProgressHUD
class HUDTool: NSObject {
static var currentHud : MBProgressHUD?
private static let kDelayTime: TimeInterval = 2
/** HUD*/
class func hidden(_ time:TimeInterval = 0){
guard let HUD = currentHud else {
return
}
HUD.hide(animated: false)
currentHud = nil
}
/**
message
- Parameter message:
- Parameter view: view
- Parameter afterDelay:
- Parameter enabled: no: yes:
*/
class func showSuccess(with message: String, in view: UIView? = YMRequestX.topViewController()?.view, delay afterDelay: TimeInterval = kDelayTime, enabled: Bool = true, icon:String = "") {
if message.isEmpty { return }
DispatchQueue.main.async {
hidden(0)
var view = view
if view == nil {
view = YMRequestX.topViewController()?.view ?? YMRequestX.keyWindow()
}
if let view = view {
let hud = normalProgressHUD(in: view)
currentHud = hud
hud.isUserInteractionEnabled = enabled
hud.mode = .customView
hud.bezelView.style = .solidColor
hud.margin = 8
hud.backgroundColor = .clear
hud.removeFromSuperViewOnHide = true
let messageView = HudMessageView()
messageView.titleLb.text = message
let maxWidth = 242.0
let size = message.boundingRect(with: CGSize(width: maxWidth, height: CGFLOAT_MAX), font: UIFont.systemFont(ofSize: 14, weight: .medium), lineSpacing: 6)
var width = size.width + 5
var height = size.height
if icon.count > 0 {
messageView.logoImgView.image = UIImage(named: icon)
messageView.logoImgView.isHidden = false
width += 22.0
} else {
messageView.logoImgView.isHidden = true
}
hud.bezelView.backgroundColor = ThemeColor(hexStr: "#000000", alpha: 0.7)
hud.bezelView.layer.cornerRadius = 8
hud.bezelView.layer.masksToBounds = true
hud.bezelView.addSubview(messageView)
messageView.snp.makeConstraints { make in
make.height.equalTo(height)
make.width.equalTo(width)
make.center.equalTo(hud.bezelView)
}
hud.minSize = CGSize(width: width + 40, height: height + 24)
hud.show(animated: true)
hud.hide(animated: false, afterDelay: kDelayTime)
}
}
}
private class func normalProgressHUD(in view: UIView) -> MBProgressHUD {
let hud = MBProgressHUD.showAdded(to: view, animated: true)
hud.mode = .indeterminate
hud.bezelView.style = .solidColor
hud.margin = 8
//
hud.bezelView.color = UIColor.black.withAlphaComponent(0.8)
return hud
}
}
class HudMessageView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(stackView)
stackView.addArrangedSubview(logoImgView)
stackView.addArrangedSubview(titleLb)
stackView.snp.makeConstraints { make in
make.edges.equalTo(self)
}
logoImgView.snp.makeConstraints { make in
make.size.equalTo(CGSize(width: 20, height: 20))
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.distribution = .fill
stackView.axis = .horizontal
stackView.alignment = .top
stackView.spacing = 2
return stackView
}()
lazy var logoImgView: UIImageView = {
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
imageView.layer.masksToBounds = true
imageView.contentMode = .scaleAspectFill
return imageView
}()
lazy var titleLb: UILabel = {
let label = UILabel()
label.textColor = UIColor.white
label.font = UIFont.systemFont(ofSize: 14, weight: .medium)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
}

View File

@@ -0,0 +1,37 @@
//
// Deserialized.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/22.
//
import Foundation
@_exported import HandyJSON
/// Deserialized json converts to Model or Array.
public struct Deserialized<H> where H: HandyJSON {
public static func toModel(with element: Any?) -> H? {
if let string = element as? String, let model = H.deserialize(from: string) {
return model
}
if let dictionary = element as? Dictionary<String, Any>, let model = H.deserialize(from: dictionary) {
return model
}
if let dictionary = element as? [String : Any], let model = H.deserialize(from: dictionary) {
return model
}
return nil
}
public static func toArray(with element: Any?) -> [H]? {
if let string = element as? String, let array = [H].deserialize(from: string) as? [H] {
return array
}
if let array = [H].deserialize(from: element as? [Any]) as? [H] {
return array
}
return nil
}
}

View File

@@ -0,0 +1,134 @@
//
// YMNetworkAPI.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/24.
//
import Foundation
import Alamofire
import HandyJSON
import DeviceKit
typealias SessionCallSucceed = (Any) -> Void
typealias SessionCallFail = (Int,String) -> Void
class YMNetworkHelper: NSObject {
var baseParameters: [String: Any] {
var parameters: [String: Any] = Dictionary()
parameters["os"] = "iOS"
parameters["osVersion"] = Device.current.systemVersion
parameters["model"] = "iPhone14,7"
parameters["ispType"] = "1"
parameters["channel"] = APPUtils.currentChannle
parameters["netType"] = "2"
parameters["app"] = "yinmeng"
parameters["appVersion"] = APPUtils.appVersion
parameters["deviceId"] = APPUtils.currentDeveiceId
return parameters
}
var sessionNetMana :Session!
public static let share = YMNetworkHelper.init()
var headersSet: HTTPHeaders = [
"Content-Type": "application/json",
"Authorization": ""
]
func requestSend(type:HTTPMethod,path:String,params:Dictionary<String, Any>, succeed:SessionCallSucceed?,fail:SessionCallFail?) -> Void {
getHttpRequestHeaders()
requestSend(type: type,host: "http://beta.api.ymlive.fun/", path: path, params: params, encoding: URLEncoding.queryString, header: headersSet, succeed: succeed, fail: fail)
}
func requestSend(type:HTTPMethod,
host:String,
path:String,
params:[String: Any],
encoding:ParameterEncoding,
header:HTTPHeaders,
succeed:SessionCallSucceed?,
fail:SessionCallFail?) -> Void {
let encrypteChonParma = baseParameters.merging(params) {$1}
sessionNetMana.request(host+path, method: type, parameters: encrypteChonParma,encoding: encoding,headers: header)
.validate(contentType: ["application/json"])
.responseJSON { [weak self] (response) in
self?.analyzeThe(response1: response, succeed2: succeed, fail3: fail,uuid4:"")
}
}
override init() {
super.init()
let configCo = AF.sessionConfiguration
configCo.httpShouldSetCookies = false
configCo.timeoutIntervalForRequest = 30
sessionNetMana = Session(configuration: configCo)
}
private func getHttpRequestHeaders() {
headersSet["pub_uid"] = "\(AuthManager.userUid)"
headersSet["pub_ticket"] = AuthManager.ticket
}
func analyzeThe(response1:AFDataResponse<Any>,
succeed2:SessionCallSucceed?,
fail3: SessionCallFail?,uuid4:String) -> Void {
let maiUrlSss = response1.request?.url?.absoluteString ?? "unkown"
switch response1.result {
case .success:
let maiResponNk :Dictionary = response1.value as? Dictionary<String, Any> ?? Dictionary.init()
let maiResultMo = maiResponNk
if maiResultMo.keys.contains("code") {
let maicodeNum :Int = maiResultMo["code"] as? Int ?? 0
if maicodeNum == 200 {
if maiResultMo.keys.contains("data") {
let maiDDD = maiResultMo["data"] as Any
succeed2?(maiDDD)
}else{
succeed2?(Dictionary<String, Any>.init())
}
}else{
if maicodeNum == 401 && maiUrlSss.contains("auth-center/sso/logout") == false {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "MAISessionTickValid"), object: nil)
sessionNetMana.cancelAllRequests()
}
var messageIn = response1.error.debugDescription
if maiResultMo.keys.contains("message") { messageIn = maiResultMo["message"] as? String ?? "" }
fail3?(maicodeNum,messageIn)
}
} else if maiResultMo.keys.contains("errno") {
let maiCodeNum :Int = maiResultMo["errno"] as? Int ?? 0
if maiCodeNum == 0 {
if maiResultMo.keys.contains("data") {
let dataSc = maiResultMo["data"] as Any
succeed2?(dataSc)
}else{
succeed2?(Dictionary<String, Any>.init())
}
}else{
var majmessageStr = response1.error.debugDescription
if maiResultMo.keys.contains("errmsg") { majmessageStr = maiResultMo["errmsg"] as? String ?? ""}
fail3?(maiCodeNum,majmessageStr)
}
} else {
fail3?(10000,"请求失败")
}
case let .failure(error):
var maiErrorMssg = response1.error?.errorDescription ?? ""
var maicodeNum = response1.error?.responseCode ?? 0
fail3?(maicodeNum,maiErrorMssg)
}
}
}
struct ResponseModel: HandyJSON {
var code:Int? = 0
var message:String? = ""
var data:Any?
}

View File

@@ -0,0 +1,99 @@
//
// YMRequestX.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
public struct YMRequestX {
/// Maps data received from the signal into a JSON object.
public static func mapJSON<T>(_ type: T.Type, named: String, forResource: String = "RxNetworks") -> T? {
guard let data = jsonData(named, forResource: forResource) else {
return nil
}
let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
return json as? T
}
/// Read json data
public static func jsonData(_ named: String, forResource: String = "RxNetworks") -> Data? {
let bundle: Bundle?
if let bundlePath = Bundle.main.path(forResource: forResource, ofType: "bundle") {
bundle = Bundle.init(path: bundlePath)
} else {
bundle = Bundle.main
}
guard let path = ["json", "JSON", "Json"].compactMap({
bundle?.path(forResource: named, ofType: $0)
}).first else {
return nil
}
let contentURL = URL(fileURLWithPath: path)
return try? Data(contentsOf: contentURL)
}
public static func toJSON(form value: Any, prettyPrint: Bool = false) -> String? {
guard JSONSerialization.isValidJSONObject(value) else {
return nil
}
var jsonData: Data? = nil
if prettyPrint {
jsonData = try? JSONSerialization.data(withJSONObject: value, options: [.prettyPrinted])
} else {
jsonData = try? JSONSerialization.data(withJSONObject: value, options: [])
}
guard let data = jsonData else { return nil }
return String(data: data ,encoding: .utf8)
}
public static func toDictionary(form json: String) -> [String : Any]? {
guard let jsonData = json.data(using: .utf8),
let object = try? JSONSerialization.jsonObject(with: jsonData, options: []),
let result = object as? [String : Any] else {
return nil
}
return result
}
public static func keyWindow() -> UIWindow? {
if #available(iOS 13.0, *) {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first(where: { $0 is UIWindowScene })
.flatMap({ $0 as? UIWindowScene })?.windows
.first(where: \.isKeyWindow)
} else {
return UIApplication.shared.keyWindow
}
}
public static func topViewController() -> UIViewController? {
let window = UIApplication.shared.delegate?.window
guard window != nil, let rootViewController = window?!.rootViewController else {
return nil
}
return self.getTopViewController(controller: rootViewController)
}
public static func getTopViewController(controller: UIViewController) -> UIViewController {
if let presentedViewController = controller.presentedViewController {
return self.getTopViewController(controller: presentedViewController)
} else if let navigationController = controller as? UINavigationController {
if let topViewController = navigationController.topViewController {
return self.getTopViewController(controller: topViewController)
}
return navigationController
} else if let tabbarController = controller as? UITabBarController {
if let selectedViewController = tabbarController.selectedViewController {
return self.getTopViewController(controller: selectedViewController)
}
return tabbarController
} else {
return controller
}
}
}

View File

@@ -0,0 +1,16 @@
//
// Base64.h
// BellFramework
//
// Created by 罗兴志 on 2017/5/4.
// Copyright © 2017年 罗兴志. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Base64 : NSObject
+(NSString *)encode:(NSData *)data;
+(NSData *)decode:(NSString *)dataString;
@end

View File

@@ -0,0 +1,133 @@
//
// Base64.m
// BellFramework
//
// Created by on 2017/5/4.
// Copyright © 2017 . All rights reserved.
//
#import "Base64.h"
@interface Base64()
+(int)char2Int:(char)c;
@end
@implementation Base64
static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+(NSString *)encode:(NSData *)data
{
if (data.length == 0)
return nil;
char *characters = malloc(data.length * 3 / 2);
if (characters == NULL)
return nil;
int end = data.length - 3;
int index = 0;
int charCount = 0;
int n = 0;
while (index <= end) {
int d = (((int)(((char *)[data bytes])[index]) & 0x0ff) << 16)
| (((int)(((char *)[data bytes])[index + 1]) & 0x0ff) << 8)
| ((int)(((char *)[data bytes])[index + 2]) & 0x0ff);
characters[charCount++] = encodingTable[(d >> 18) & 63];
characters[charCount++] = encodingTable[(d >> 12) & 63];
characters[charCount++] = encodingTable[(d >> 6) & 63];
characters[charCount++] = encodingTable[d & 63];
index += 3;
if(n++ >= 14)
{
n = 0;
characters[charCount++] = ' ';
}
}
if(index == data.length - 2)
{
int d = (((int)(((char *)[data bytes])[index]) & 0x0ff) << 16)
| (((int)(((char *)[data bytes])[index + 1]) & 255) << 8);
characters[charCount++] = encodingTable[(d >> 18) & 63];
characters[charCount++] = encodingTable[(d >> 12) & 63];
characters[charCount++] = encodingTable[(d >> 6) & 63];
characters[charCount++] = '=';
}
else if(index == data.length - 1)
{
int d = ((int)(((char *)[data bytes])[index]) & 0x0ff) << 16;
characters[charCount++] = encodingTable[(d >> 18) & 63];
characters[charCount++] = encodingTable[(d >> 12) & 63];
characters[charCount++] = '=';
characters[charCount++] = '=';
}
NSString * rtnStr = [[NSString alloc] initWithBytesNoCopy:characters length:charCount encoding:NSUTF8StringEncoding freeWhenDone:YES];
return rtnStr;
}
+(NSData *)decode:(NSString *)data
{
if(data == nil || data.length <= 0) {
return nil;
}
NSMutableData *rtnData = [[NSMutableData alloc]init];
int slen = data.length;
int index = 0;
while (true) {
while (index < slen && [data characterAtIndex:index] <= ' ') {
index++;
}
if (index >= slen || index + 3 >= slen) {
break;
}
int byte = ([self char2Int:[data characterAtIndex:index]] << 18) + ([self char2Int:[data characterAtIndex:index + 1]] << 12) + ([self char2Int:[data characterAtIndex:index + 2]] << 6) + [self char2Int:[data characterAtIndex:index + 3]];
Byte temp1 = (byte >> 16) & 255;
[rtnData appendBytes:&temp1 length:1];
if([data characterAtIndex:index + 2] == '=') {
break;
}
Byte temp2 = (byte >> 8) & 255;
[rtnData appendBytes:&temp2 length:1];
if([data characterAtIndex:index + 3] == '=') {
break;
}
Byte temp3 = byte & 255;
[rtnData appendBytes:&temp3 length:1];
index += 4;
}
return rtnData;
}
+(int)char2Int:(char)c
{
if (c >= 'A' && c <= 'Z') {
return c - 65;
} else if (c >= 'a' && c <= 'z') {
return c - 97 + 26;
} else if (c >= '0' && c <= '9') {
return c - 48 + 26 + 26;
} else {
switch(c) {
case '+':
return 62;
case '/':
return 63;
case '=':
return 0;
default:
return -1;
}
}
}
@end

View File

@@ -0,0 +1,16 @@
//
// MAIDESEncryptTool.h
// BellFramework
//
// Created by 罗兴志 on 2017/5/4.
// Copyright © 2017年 罗兴志. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MAIDESEncryptTool : NSObject
//加密方法
+(NSString *)encryptUseDES:(NSString *)plainText key:(NSString *)key;
//解密方法
+(NSString *)decryptUseDES:(NSString *)cipherText key:(NSString *)key;
@end

View File

@@ -0,0 +1,63 @@
//
// MAIDESEncryptTool.m
// BellFramework
//
// Created by on 2017/5/4.
// Copyright © 2017 . All rights reserved.
//
#import "MAIDESEncryptTool.h"
#import <CommonCrypto/CommonCrypto.h>
#import "Base64.h"
@implementation MAIDESEncryptTool : NSObject
const Byte iv[] = {1,2,3,4,5,6,7,8};
#pragma mark-
+(NSString *) encryptUseDES:(NSString *)plainText key:(NSString *)key
{
NSString *ciphertext = nil;
NSData *textData = [plainText dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger dataLength = [textData length];
unsigned char buffer[20000];
memset(buffer, 0, sizeof(char));
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmDES,
kCCOptionPKCS7Padding|kCCOptionECBMode,
[key UTF8String], kCCKeySizeDES,
iv,
[textData bytes], dataLength,
buffer, 20000,
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
ciphertext = [Base64 encode:data];
}
return ciphertext;
}
#pragma mark-
+(NSString *)decryptUseDES:(NSString *)cipherText key:(NSString *)key
{
NSString *plaintext = nil;
NSData *cipherdata = [Base64 decode:cipherText];
unsigned char buffer[20000];
memset(buffer, 0, sizeof(char));
size_t numBytesDecrypted = 0;
// kCCOptionPKCS7Padding|kCCOptionECBMode
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmDES,
kCCOptionPKCS7Padding|kCCOptionECBMode,
[key UTF8String], kCCKeySizeDES,
iv,
[cipherdata bytes], [cipherdata length],
buffer, 20000,
&numBytesDecrypted);
if(cryptStatus == kCCSuccess) {
NSData *plaindata = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesDecrypted];
plaintext = [[NSString alloc]initWithData:plaindata encoding:NSUTF8StringEncoding];
}
return plaintext;
}
@end

View File

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

View File

@@ -0,0 +1,47 @@
//
// APPUtils.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/22.
//
import Foundation
import CoreTelephony
import DeviceKit
enum TelephonyType: String{
case Unknow = "0"
case Mobile = "1"
case Unicom = "2"
case Telecom = "3"
}
public struct APPUtils {
static var currentCarrierName: String {
getCurrentCarrierName()
}
static var currentChannle: String {
getCurrentChannle()
}
static var appVersion: String {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
}
static var currentDeveiceId: String {
var udid = UIDevice.current.identifierForVendor?.uuidString
udid = udid?.replacingOccurrences(of: "-", with: "")
udid = udid?.lowercased()
return udid ?? ""
}
}
private extension APPUtils {
static func getCurrentCarrierName() -> String {
return TelephonyType.Unicom.rawValue
}
static func getCurrentChannle() -> String { return "yinmeng_appstore"}
}

View File

@@ -27,3 +27,6 @@ let SafeAraeBottomHeight = isSafeScreen ? 34.0 : 0.0
let SafeAraeTopmHeight = isSafeScreen ? 24.0 : 0.0
let NavHeight = (StatusBarHeight + 44)
let TabHeight = (49)
let DesKey = "1ea53d260ecf11e7b56e00163e046a26"

View File

@@ -6,7 +6,6 @@
//
import UIKit
///
protocol HiddenNavigationBarProtocol where Self: UIViewController {}

View File

@@ -0,0 +1,49 @@
//
// List+.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/24.
//
import Foundation
extension Array where Element: Equatable {
mutating func remove(_ element: Element) {
if let index = firstIndex(of: element) {
remove(at: index)
}
}
mutating func addObjects(_ elements: [Element]) {
for value in elements {
self.append(value)
}
}
mutating func replaceObject(_ element: Element, index: Int) {
if self.count == index {
self.append(element)
}
else if self.count > index {
self.insert(element, at: index)
self.remove(at: index + 1)
}
}
}
public extension Collection {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
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] }
}
}

View File

@@ -0,0 +1,68 @@
//
// String+.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/23.
//
import Foundation
import CommonCrypto
extension String {
func encrypt() -> String? {
let str = MAIDESEncryptTool.encryptUseDES(self, key: DesKey)
return str
}
func decrypt() -> String? {
let str = MAIDESEncryptTool.decryptUseDES(self, key: DesKey)
return str
}
func decrypt(key:String) -> String? {
let str = MAIDESEncryptTool.decryptUseDES(self, key: key)
return str
}
}
extension String {
///
/// - Parameters:
/// - begin:
/// - count:
/// - Returns:
func substring(start: Int, _ count: Int) -> String {
let begin = index(startIndex, offsetBy: max(0, start))
let end = index(startIndex, offsetBy: min(count, start + count))
return String(self[begin..<end])
}
///
var isBlank: Bool {
let trimmedStr = self.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmedStr.isEmpty
}
func boundingRect(with constrainedSize: CGSize, font: UIFont, lineSpacing: CGFloat? = nil) -> CGSize {
let attritube = NSMutableAttributedString(string: self)
let range = NSRange(location: 0, length: attritube.length)
attritube.addAttributes([NSAttributedString.Key.font: font], range: range)
if lineSpacing != nil {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing ?? 0
attritube.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: range)
}
let rect = attritube.boundingRect(with: constrainedSize, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
var size = rect.size
if let currentLineSpacing = lineSpacing {
let spacing = size.height - font.lineHeight
if spacing <= currentLineSpacing && spacing > 0 {
size = CGSize(width: size.width, height: font.lineHeight)
}
}
return size
}
}

View File

@@ -1,38 +0,0 @@
//
// HeadstreamRequest.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
public final class HeadstreamRequest {
/// Empty data, convenient for subsequent plugin operations.
public var result: Result<Moya.Response, MoyaError>?
public var session: Moya.Session?
///
public var endRequest: Bool = false
public init() { }
}
extension HeadstreamRequest {
func toJSON() throws -> Any {
guard let result = result else {
let userInfo = [
NSLocalizedDescriptionKey: "The result is empty."
]
let error = NSError(domain: "com.condy.rx.network", code: 2004, userInfo: userInfo)
throw error
}
switch result {
case .success(let response):
return try YMRequestX.toJSON(with: response)
case .failure(let error):
throw error
}
}
}

View File

@@ -1,190 +0,0 @@
//
// LevelStatusBarWindowController.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
public protocol LevelStatusBarWindowShowUpable {
///
/// - Parameter superview:
func makeOpenedStatusConstraint(superview: UIView)
///
func refreshBeforeShow()
///
/// - Parameters:
/// - animated:
/// - animation:
/// - completion:
func show(animated: Bool, animation: (() -> Void)?, completion: ((Bool) -> Void)?)
///
/// - Parameters:
/// - animated:
/// - animation:
/// - completion:
func close(animated: Bool, animation: (() -> Void)?, completion: ((Bool) -> Void)?)
}
///
open class LevelStatusBarWindowController: UIViewController {
private var isCalledClose = false
private var canNotBeCanceled = false
private var loadingCount: Int = 0
private lazy var lock = NSLock()
private lazy var overlay: UIView = {
let view = UIView(frame: self.view.bounds)
view.backgroundColor = overlayBackgroundColor
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(overlayTap)))
view.isUserInteractionEnabled = true
return view
}()
public var key: String?
public var showUpView: LevelStatusBarWindowShowUpable?
///
public var canCloseWhenTapOutSize: Bool = true
/// `showUpView`
public var addedShowUpView: Bool = false
public var overlayBackgroundColor: UIColor = UIColor.black.withAlphaComponent(0.2) {
didSet {
self.overlay.backgroundColor = overlayBackgroundColor
}
}
public func addedLoadingCount() {
self.lock.lock()
self.loadingCount += 1
self.lock.unlock()
}
public func subtractLoadingCount() -> Int {
self.lock.lock()
defer { self.lock.unlock() }
self.loadingCount -= 1
return self.loadingCount
}
open override var prefersStatusBarHidden: Bool {
if let controller = YMRequestX.topViewController() {
return controller.prefersStatusBarHidden
}
return true
}
open override var preferredStatusBarStyle: UIStatusBarStyle {
if let controller = YMRequestX.topViewController() {
return controller.preferredStatusBarStyle
}
return .default
}
open override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.clear
self.view.addSubview(self.overlay)
if self.addedShowUpView {
if let alertView = self.showUpView as? UIView {
self.view.bringSubviewToFront(alertView)
}
} else if let alertView = self.showUpView as? UIView {
self.view.addSubview(alertView)
}
self.showUpView?.makeOpenedStatusConstraint(superview: self.view)
}
private var overlayTapCloseBlock: ((LevelStatusBarWindowController) -> Void)?
public func setOverlayTapCloseBlock(block: @escaping (LevelStatusBarWindowController) -> Void) {
self.overlayTapCloseBlock = block
}
public func show(completion: ((Bool) -> Void)? = nil) {
Self.controllers.removeAll { $0 == self }
if let rootViewController = Self.window.rootViewController as? Self, !rootViewController.isCalledClose {
Self.controllers.append(rootViewController)
Self.window.rootViewController = nil
}
self.showUpView?.refreshBeforeShow()
if Self.lastKeyWindow != Self.window {
Self.lastKeyWindow = YMRequestX.keyWindow()
}
Self.window.isHidden = false
Self.window.windowLevel = UIWindow.Level.statusBar
Self.window.rootViewController = self
Self.window.makeKeyAndVisible()
self.overlay.alpha = 0
self.showUpView?.show(animated: true, animation: { [weak self] in
self?.overlay.alpha = 1.0
self?.overlay.backgroundColor = self?.overlayBackgroundColor
}, completion: completion)
}
public func close(animated: Bool = true) {
self.isCalledClose = true
self.showUpView?.close(animated: animated, animation: { [weak self] in
self?.overlay.alpha = 0
}, completion: self.closeCompleted)
}
private func closeCompleted(_: Bool) {
guard Self.window.rootViewController == self else {
return
}
if let lastKeyWindow = Self.lastKeyWindow {
if lastKeyWindow.rootViewController != nil {
lastKeyWindow.makeKeyAndVisible()
}
Self.lastKeyWindow = nil
} else if let window = UIApplication.shared.delegate?.window, window != nil {
window?.makeKeyAndVisible()
}
Self.window.rootViewController = nil
Self.window.isHidden = true
if Self.controllers.count < 10 {
while let rootViewController = Self.controllers.last {
if rootViewController.isCalledClose {
Self.controllers.removeLast()
continue
}
rootViewController.show()
break
}
} else {
Self.controllers.removeAll()
}
}
@objc private func overlayTap() {
if canCloseWhenTapOutSize {
close()
overlayTapCloseBlock?(self)
}
}
}
extension LevelStatusBarWindowController {
private static let window = UIWindow(frame: UIScreen.main.bounds)
private static var lastKeyWindow: UIWindow?
private static var controllers = [LevelStatusBarWindowController]()
public static func cancelAllBackgroundControllersShow() {
Self.controllers = Self.controllers.filter({ $0.canNotBeCanceled })
}
public static func forcecancelAllControllers() {
if let controller = Self.window.rootViewController as? LevelStatusBarWindowController, !controller.canNotBeCanceled {
Self.window.rootViewController = nil
Self.window.isHidden = true
}
cancelAllBackgroundControllersShow()
}
}

View File

@@ -1,139 +0,0 @@
//
// NewworkRequest.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
import Alamofire
import Moya
public extension YMNetworkAPI {
@discardableResult func HTTPRequest(
success: @escaping APISuccess,
failure: @escaping APIFailure,
progress: ProgressBlock? = nil,
queue: DispatchQueue? = nil,
plugins: APIPlugins = []
) -> Cancellable? {
let key = self.keyPrefix
let plugins__ = YMRequestX.setupPluginsAndKey(key, plugins: self.plugins + plugins)
SharedDriver.shared.addedRequestingAPI(self, key: key, plugins: plugins__)
let request = self.setupConfiguration(plugins: plugins__)
if request.endRequest, let result = request.result {
let lastResult = LastNeverResult(result: result, plugins: plugins__)
lastResult.mapResult(success: { json in
SharedDriver.shared.removeRequestingAPI(key)
DispatchQueue.main.async { success(json) }
}, failure: { error in
SharedDriver.shared.removeRequestingAPI(key)
DispatchQueue.main.async { failure(error) }
}, progress: progress)
return nil
}
let session = request.session ?? {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = YMRequestConfig.timeoutIntervalForRequest
return Moya.Session(
configuration: configuration,
startRequestsImmediately: false,
interceptor: YMRequestConfig.interceptor
)
}()
let queue = queue ?? {
DispatchQueue(label: "condy.request.network.queue", attributes: [.concurrent])
}()
let target = MultiTarget.target(self)
let endpointTask = self.task
var endpointHeaders = YMRequestX.hasNetworkHttpHeaderPlugin(key) ?? YMRequestConfig.baseHeaders
if let dict = self.headers {
// Merge the dictionaries and take the second value.
endpointHeaders = endpointHeaders.merging(dict) { $1 }
}
let provider = MoyaProvider<MultiTarget>.init(endpointClosure: { _ in
Endpoint(url: URL(target: target).absoluteString,
sampleResponseClosure: { .networkResponse(200, self.sampleData) },
method: self.method,
task: endpointTask,
httpHeaderFields: endpointHeaders)
}, stubClosure: { _ in
stubBehavior
}, callbackQueue: queue, session: session, plugins: plugins__)
//
if let json = try? request.toJSON() {
DispatchQueue.main.async { success(json) }
}
//
return self.request(plugins__, provider: provider, success: { json in
SharedDriver.shared.removeRequestingAPI(key)
DispatchQueue.main.async { success(json) }
}, failure: { error in
SharedDriver.shared.removeRequestingAPI(key)
DispatchQueue.main.async { failure(error) }
}, progress: progress)
}
@discardableResult
func request(plugins: APIPlugins = [], complete: @escaping APIComplete) -> Cancellable? {
HTTPRequest(success: { json in
complete(.success(json))
}, failure: { error in
complete(.failure(error))
}, plugins: plugins)
}
}
extension YMNetworkAPI {
///
private func setupConfiguration(plugins: APIPlugins) -> HeadstreamRequest {
var request = HeadstreamRequest()
plugins.forEach {
request = $0.configuration(request, target: self)
}
return request
}
///
private func setupOutputResult(plugins: APIPlugins, result: APIResponseResult, onNext: @escaping LastNeverCallback) {
var lastResult = LastNeverResult(result: result, plugins: plugins)
var iterator = plugins.makeIterator()
func handleLastNever(_ plugin: PluginSubType?) {
guard let plugin = plugin else {
onNext(lastResult)
return
}
plugin.lastNever(lastResult, target: self) {
lastResult = $0
handleLastNever(iterator.next())
}
}
handleLastNever(iterator.next())
}
private func request(_ plugins: APIPlugins,
provider: MoyaProvider<MultiTarget>,
success: @escaping APISuccess,
failure: @escaping APIFailure,
progress: ProgressBlock? = nil) -> Cancellable {
let target = MultiTarget.target(self)
return provider.request(target, progress: progress, completion: { result in
setupOutputResult(plugins: plugins, result: result) { lastResult in
if lastResult.againRequest {
_ = request(plugins, provider: provider, success: success, failure: failure, progress: progress)
return
}
lastResult.mapResult(success: success, failure: failure)
}
})
}
}

View File

@@ -1,167 +0,0 @@
//
// YMRequestLoadingPlugin.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
import Moya
import MBProgressHUD
/// MBProgressHUD
/// Loading plugin, based on MBProgressHUD package
public struct YMRequestLoadingPlugin: PluginPropertiesable {
public var plugins: APIPlugins = []
public var key: String?
public var delay: Double {
options.delayHideHUD
}
public var options: Options
public init(options: Options = .init()) {
self.options = options
}
/// Hide the loading hud.
public func hideMBProgressHUD() {
let vc = YMRequestX.removeHUD(key: key)
vc?.close()
}
}
extension YMRequestLoadingPlugin {
public struct Options {
/// Loading will not be automatically hidden and display window.
public static let dontAutoHide: Options = .init(autoHide: false)
/// Do you need to display an error message, the default is empty
let displayLoadText: String
/// Delay hidden, the default is zero seconds
let delayHideHUD: Double
/// Do you need to automatically hide the loading hud.
let autoHideLoading: Bool
public init(text: String = "", delay: Double = 0.0, autoHide: Bool = true) {
self.displayLoadText = text
self.delayHideHUD = delay
self.autoHideLoading = autoHide
}
var hudCallback: ((_ hud: MBProgressHUD) -> Void)?
/// Change hud related configuration closures.
public mutating func setChangeHudParameters(block: @escaping (_ hud: MBProgressHUD) -> Void) {
self.hudCallback = block
}
}
}
extension YMRequestLoadingPlugin: PluginSubType {
public var pluginName: String {
return "Loading"
}
public func willSend(_ request: RequestType, target: TargetType) {
DispatchQueue.main.async {
self.showText(options.displayLoadText)
}
}
public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
if options.autoHideLoading == false, case .success = result {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + options.delayHideHUD) {
self.hideMBProgressHUD()
}
}
}
extension YMRequestLoadingPlugin {
/// Display the prompt text
private func showText(_ text: String) {
guard let key = self.key else {
return
}
if let vc = YMRequestX.readHUD(key: key) {
if let _ = MBProgressHUD.forView(vc.view) {
return
}
vc.show()
} else {
let vc = LevelStatusBarWindowController()
// Set Activity Indicator View to white for hud loading.
let indicatorView = UIActivityIndicatorView.appearance(whenContainedInInstancesOf: [MBProgressHUD.self])
indicatorView.color = UIColor.white
let hud = MBProgressHUD.showAdded(to: vc.view, animated: true)
hud.mode = MBProgressHUDMode.indeterminate
hud.animationType = MBProgressHUDAnimation.zoom
hud.removeFromSuperViewOnHide = true
hud.bezelView.style = MBProgressHUDBackgroundStyle.solidColor
hud.bezelView.color = UIColor.black.withAlphaComponent(0.7)
hud.bezelView.layer.cornerRadius = 14
hud.detailsLabel.text = text
hud.detailsLabel.font = UIFont.systemFont(ofSize: 16)
hud.detailsLabel.numberOfLines = 0
hud.detailsLabel.textColor = UIColor.white
// User defined the hud configuration.
self.options.hudCallback?(hud)
vc.key = key
vc.showUpView = hud
vc.addedShowUpView = true
vc.show()
YMRequestX.saveHUD(key: key, window: vc)
}
}
}
extension MBProgressHUD: LevelStatusBarWindowShowUpable {
public func makeOpenedStatusConstraint(superview: UIView) {
}
public func refreshBeforeShow() {
}
public func show(animated: Bool, animation: (() -> Void)?, completion: ((Bool) -> Void)?) {
DispatchQueue.main.async {
self.show(animated: animated)
if animated {
UIView.animate(withDuration: 0.2, animations: {
animation?()
}, completion: completion)
} else {
animation?()
completion?(true)
}
}
}
public func close(animated: Bool, animation: (() -> Void)?, completion: ((Bool) -> Void)?) {
DispatchQueue.main.async {
self.hide(animated: animated)
if animated {
UIView.animate(withDuration: 0.2, animations: {
animation?()
}, completion: completion)
} else {
animation?()
completion?(true)
}
}
}
}

View File

@@ -1,169 +0,0 @@
//
// SharedDriver.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
import Moya
///
struct SharedDriver {
typealias Key = String
static var shared = SharedDriver()
private let lock = NSLock()
private let tasklock = NSLock()
private let HUDsLock = NSLock()
private var requestingAPIs = [Key: (api: YMNetworkAPI, plugins: APIPlugins)]()
private var tasks = [Key: Moya.Cancellable]()
private var cacheBlocks = [(key: Key, success: APISuccess, failure: APIFailure)]()
private var cacheHUDs = [Key: LevelStatusBarWindowController]()
}
// MARK: - api
extension SharedDriver {
func readRequestAPI(_ key: Key) -> YMNetworkAPI? {
self.lock.lock()
defer { lock.unlock() }
return self.requestingAPIs[key]?.api
}
func readRequestPlugins(_ key: Key) -> APIPlugins {
self.lock.lock()
defer { lock.unlock() }
return self.requestingAPIs[key]?.plugins ?? []
}
mutating func removeRequestingAPI(_ key: Key) {
self.lock.lock()
let plugins = self.requestingAPIs[key]?.plugins
self.requestingAPIs.removeValue(forKey: key)
// Loading
if YMRequestConfig.lastCompleteAndCloseLoadingHUDs, self.requestingAPIs.isEmpty, let p = plugins {
let maxTime = YMRequestX.maxDelayTime(with: p)
DispatchQueue.main.asyncAfter(deadline: .now() + maxTime) {
SharedDriver.shared.removeLoadingHUDs()
}
}
self.lock.unlock()
}
mutating func addedRequestingAPI(_ api: YMNetworkAPI, key: Key, plugins: APIPlugins) {
self.lock.lock()
self.requestingAPIs[key] = (api, plugins)
self.lock.unlock()
}
}
// MARK: - task and blocks
extension SharedDriver {
func readTask(key: Key) -> Cancellable? {
self.tasklock.lock()
defer { tasklock.unlock() }
return self.tasks[key]
}
mutating func cacheBlocks(key: Key, success: @escaping APISuccess, failure: @escaping APIFailure) {
self.tasklock.lock()
defer { tasklock.unlock() }
self.cacheBlocks.append((key, success, failure))
}
mutating func cacheTask(key: Key, task: Cancellable) {
self.tasklock.lock()
defer { tasklock.unlock() }
self.tasks[key] = task
}
mutating func result(_ type: Result<APISuccessJSON, APIFailureError>, key: Key) {
self.tasklock.lock()
defer { tasklock.unlock() }
switch type {
case .success(let json):
self.cacheBlocks.forEach {
$0.key == key ? $0.success(json) : nil
}
case .failure(let error):
self.cacheBlocks.forEach {
$0.key == key ? $0.failure(error) : nil
}
}
self.tasks.removeValue(forKey: key)
self.cacheBlocks.removeAll { $0.key == key }
}
}
// MARK: - hud
extension SharedDriver {
func readHUD(key: String) -> LevelStatusBarWindowController? {
self.HUDsLock.lock()
defer { HUDsLock.unlock() }
return self.cacheHUDs[key]
}
func readHUD(prefix: String) -> [LevelStatusBarWindowController] {
self.HUDsLock.lock()
defer { HUDsLock.unlock() }
return self.cacheHUDs.compactMap {
if let prefix_ = $0.key.components(separatedBy: "_").first, prefix_ == prefix {
return $0.value
}
return nil
}
}
func readHUD(suffix: String) -> [LevelStatusBarWindowController] {
self.HUDsLock.lock()
defer { HUDsLock.unlock() }
return self.cacheHUDs.compactMap {
if let suffix_ = $0.key.components(separatedBy: "_").last, suffix_ == suffix {
return $0.value
}
return nil
}
}
mutating func saveHUD(key: Key, window: LevelStatusBarWindowController) {
self.HUDsLock.lock()
self.cacheHUDs[key] = window
self.HUDsLock.unlock()
}
@discardableResult mutating func removeHUD(key: Key?) -> LevelStatusBarWindowController? {
guard let key = key else {
return nil
}
self.HUDsLock.lock()
let window = self.cacheHUDs[key]
self.cacheHUDs.removeValue(forKey: key)
self.HUDsLock.unlock()
return window
}
mutating func removeAllAtLevelStatusBarWindow() {
self.HUDsLock.lock()
self.cacheHUDs.forEach {
$0.value.close()
}
self.cacheHUDs.removeAll()
self.HUDsLock.unlock()
}
mutating func removeLoadingHUDs() {
self.HUDsLock.lock()
for (key, hud) in self.cacheHUDs where YMRequestX.loadingSuffix(key: key) {
self.cacheHUDs.removeValue(forKey: key)
hud.close()
}
self.HUDsLock.unlock()
}
}

View File

@@ -1,69 +0,0 @@
//
// YMLastNeverResult.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
public typealias LastNeverCallback = ((_ lastResult: LastNeverResult) -> Void)
/// Containing the data source and whether auto last network request.
public final class LastNeverResult {
public var result: Result<Moya.Response, MoyaError>
/// Any
/// Solve the problem of repeated parsing, if a plugin has parsed the data into `Any`
public var mapResult: Result<Any, MoyaError>?
///
public var againRequest: Bool = false
private let plugins: APIPlugins
public init(result: Result<Moya.Response, MoyaError>, plugins: APIPlugins) {
self.result = result
self.plugins = plugins
}
}
extension LastNeverResult {
func mapResult(success: APISuccess? = nil, failure: APIFailure? = nil, progress: ProgressBlock? = nil) {
if let mapResult = mapResult {
switch mapResult {
case let .success(json):
success?(json)
case let .failure(error):
failure?(error)
}
return
}
switch result {
case let .success(response):
do {
let json = try YMRequestX.toJSON(with: response)
self.mapResult = .success(json)
success?(json)
progress?(ProgressResponse(response: response))
} catch MoyaError.statusCode(let response) {
let error = MoyaError.statusCode(response)
self.mapResult = .failure(error)
failure?(error)
} catch MoyaError.jsonMapping(let response) {
let error = MoyaError.jsonMapping(response)
self.mapResult = .failure(error)
failure?(error)
} catch {
if let error = error as? MoyaError {
self.mapResult = .failure(error)
}
failure?(error)
}
case let .failure(error):
self.mapResult = .failure(error)
failure?(error)
}
}
}

View File

@@ -1,34 +0,0 @@
//
// YMNetworkAPI.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
@_exported import Alamofire
@_exported import Moya
public typealias APIHost = String
public typealias APIPath = String
public typealias APINumber = Int
public typealias APIMethod = Moya.Method
public typealias APIParameters = Alamofire.Parameters
public typealias APIPlugins = [PluginSubType]
public typealias APIStubBehavior = Moya.StubBehavior
public typealias APISuccessJSON = Any
public typealias APIFailureError = Swift.Error
public typealias APIResponseResult = Result<Moya.Response, MoyaError>
public typealias APISuccess = (_ json: APISuccessJSON) -> Void
public typealias APIFailure = (_ error: APIFailureError) -> Void
public typealias APIComplete = (_ result: Result<APISuccessJSON, APIFailureError>) -> Void
public protocol YMNetworkAPI: Moya.TargetType {
var hostUrl:APIHost {get}
var pararms:APIParameters? {get}
var plugins:APIPlugins {get}
var stubBehavior: APIStubBehavior {get}
var retry:APINumber {get}
var keyPrefix: String { get }
func removeHUD()
func removeLoading()
}

View File

@@ -1,73 +0,0 @@
//
// YMPluginSubType.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
import Moya
public protocol PluginPropertiesable: PluginSubType {
var plugins: APIPlugins { get set }
var key: String? { get set }
/// Loading HUD delay hide time.
var delay: Double { get }
}
extension PluginPropertiesable {
public var delay: Double {
return 0
}
}
/// Moya便
/// Inherit the Moya plug-in protocol, which is convenient for subsequent expansion. All plug-in methods must implement this protocol
public protocol PluginSubType: Moya.PluginType {
///
var pluginName: String { get }
///
///
/// - Parameters:
/// - request:
/// - target:
/// - Returns:
///
/// After setting the network configuration information, before starting to prepare the request,
/// This method can be used in scenarios such as throwing data directly when the local cache exists without executing subsequent network requests.
/// - Parameters:
/// - request: Configuration information, which contains the data source and whether to end the subsequent network request.
/// - target: The protocol used to define the specifications necessary for a `MoyaProvider`.
/// - Returns: Containing the data source and whether to end the subsequent network request.
func configuration(_ request: HeadstreamRequest, target: TargetType) -> HeadstreamRequest
///
///
/// - Parameters:
/// - result:
/// - target:
/// - onNext:
///
/// The last time the last network response is returned,
/// This method can be used in scenarios such as key invalidation to obtain the key again and then automatically request the network again.
/// - Parameters:
/// - result: Containing the data source and whether auto-last network request.
/// - target: The protocol used to define the specifications necessary for a `MoyaProvider`.
/// - onNext: Provide callbacks for the plug-in to process tasks asynchronously.
func lastNever(_ result: LastNeverResult, target: TargetType, onNext: @escaping LastNeverCallback)
}
extension PluginSubType {
public func configuration(_ request: HeadstreamRequest, target: TargetType) -> HeadstreamRequest {
return request
}
public func lastNever(_ result: LastNeverResult, target: TargetType, onNext: @escaping LastNeverCallback) {
onNext(result)
}
}

View File

@@ -1,56 +0,0 @@
//
// YMRequestConfig.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
import Moya
///
/// Network configuration information, only need to be configured once when the program is started
public struct YMRequestConfig {
/// Whether to add the Debugging plugin by default
public static var addDebugging: Bool = false
/// Whether to add the Indicator plugin by default
public static var addIndicator: Bool = false
/// Set the request timeout, the default is 30 seconds
public static var timeoutIntervalForRequest: Double = 30
public static var interceptor: RequestInterceptor? = nil
/// Root path address
public static var baseURL: APIHost = ""
/// Default request type, default `post`
public static var baseMethod: APIMethod = APIMethod.post
/// Default basic parameters, similar to: userID, token, etc.
public static var baseParameters: APIParameters = [:]
/// Default Header argument, `NetworkHttpHeaderPlugin`.
public static var baseHeaders: [String: String] = [:]
/// Plugins that require default injection, generally not recommended
/// However, you can inject this kind of global unified general plugin, such as secret key plugin, certificate plugin, etc.
public static var basePlugins: [PluginSubType]?
/// Loading animation JSON, for `AnimatedLoadingPlugin` used.
public static var animatedJSON: String?
/// Loading the plugin name, to remove the loading plugin from level status bar window.
public static var loadingPluginNames: [String] = ["Loading", "AnimatedLoading"]
/// Auto close all loading after the end of the last network requesting.
public static var lastCompleteAndCloseLoadingHUDs: Bool = true
/// Update the default basic parameter data, which is generally used for what operation the user has switched.
/// - Parameters:
/// - value: Update value
/// - key: Update key
public static func updateBaseParametersWithValue(_ value: AnyObject?, key: String) {
var dict = YMRequestConfig.baseParameters
if let value = value {
dict.updateValue(value, forKey: key)
} else {
dict.removeValue(forKey: key)
}
YMRequestConfig.baseParameters = dict
}
}

View File

@@ -1,228 +0,0 @@
//
// YMRequestX.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/2.
//
import Foundation
public struct YMRequestX {
/// Maps data received from the signal into a JSON object.
public static func mapJSON<T>(_ type: T.Type, named: String, forResource: String = "RxNetworks") -> T? {
guard let data = jsonData(named, forResource: forResource) else {
return nil
}
let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
return json as? T
}
/// Read json data
public static func jsonData(_ named: String, forResource: String = "RxNetworks") -> Data? {
let bundle: Bundle?
if let bundlePath = Bundle.main.path(forResource: forResource, ofType: "bundle") {
bundle = Bundle.init(path: bundlePath)
} else {
bundle = Bundle.main
}
guard let path = ["json", "JSON", "Json"].compactMap({
bundle?.path(forResource: named, ofType: $0)
}).first else {
return nil
}
let contentURL = URL(fileURLWithPath: path)
return try? Data(contentsOf: contentURL)
}
public static func toJSON(form value: Any, prettyPrint: Bool = false) -> String? {
guard JSONSerialization.isValidJSONObject(value) else {
return nil
}
var jsonData: Data? = nil
if prettyPrint {
jsonData = try? JSONSerialization.data(withJSONObject: value, options: [.prettyPrinted])
} else {
jsonData = try? JSONSerialization.data(withJSONObject: value, options: [])
}
guard let data = jsonData else { return nil }
return String(data: data ,encoding: .utf8)
}
public static func toDictionary(form json: String) -> [String : Any]? {
guard let jsonData = json.data(using: .utf8),
let object = try? JSONSerialization.jsonObject(with: jsonData, options: []),
let result = object as? [String : Any] else {
return nil
}
return result
}
public static func keyWindow() -> UIWindow? {
if #available(iOS 13.0, *) {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first(where: { $0 is UIWindowScene })
.flatMap({ $0 as? UIWindowScene })?.windows
.first(where: \.isKeyWindow)
} else {
return UIApplication.shared.keyWindow
}
}
public static func topViewController() -> UIViewController? {
let window = UIApplication.shared.delegate?.window
guard window != nil, let rootViewController = window?!.rootViewController else {
return nil
}
return self.getTopViewController(controller: rootViewController)
}
public static func getTopViewController(controller: UIViewController) -> UIViewController {
if let presentedViewController = controller.presentedViewController {
return self.getTopViewController(controller: presentedViewController)
} else if let navigationController = controller as? UINavigationController {
if let topViewController = navigationController.topViewController {
return self.getTopViewController(controller: topViewController)
}
return navigationController
} else if let tabbarController = controller as? UITabBarController {
if let selectedViewController = tabbarController.selectedViewController {
return self.getTopViewController(controller: selectedViewController)
}
return tabbarController
} else {
return controller
}
}
}
// MARK: - HUD
extension YMRequestX {
/// HUD
public static func removeAllAtLevelStatusBarWindow() {
SharedDriver.shared.removeAllAtLevelStatusBarWindow()
}
/// HUD
public static func removeLoadingHUDs() {
SharedDriver.shared.removeLoadingHUDs()
}
public static func readHUD(key: String) -> LevelStatusBarWindowController? {
SharedDriver.shared.readHUD(key: key)
}
public static func saveHUD(key: String, window vc: LevelStatusBarWindowController) {
SharedDriver.shared.saveHUD(key: key, window: vc)
}
@discardableResult
public static func removeHUD(key: String?) -> LevelStatusBarWindowController? {
SharedDriver.shared.removeHUD(key: key)
}
}
// MARK: -
extension YMRequestX {
static func maxDelayTime(with plugins: APIPlugins) -> Double {
let times: [Double] = plugins.compactMap {
if let p = $0 as? PluginPropertiesable {
return p.delay
}
return 0.0
}
let maxTime = times.max() ?? 0.0
return maxTime
}
static func sortParametersToString(_ parameters: APIParameters?) -> String {
guard let params = parameters, !params.isEmpty else {
return ""
}
var paramString = "?"
let sorteds = params.sorted(by: { $0.key > $1.key })
for index in sorteds.indices {
paramString.append("\(sorteds[index].key)=\(sorteds[index].value)")
if index != sorteds.count - 1 { paramString.append("&") }
}
return paramString
}
static func requestLink(with target: TargetType, parameters: APIParameters? = nil) -> String {
let parameters: APIParameters? = parameters ?? {
if case .requestParameters(let parame, _) = target.task {
return parame
}
return nil
}()
let paramString = sortParametersToString(parameters)
return target.baseURL.absoluteString + target.path + paramString
}
static func toJSON(with response: Moya.Response) throws -> APISuccessJSON {
let response = try response.filterSuccessfulStatusCodes()
return try response.mapJSON()
}
static func loadingSuffix(key: SharedDriver.Key?) -> Bool {
guard let key = key else { return false }
if let suffix = key.components(separatedBy: "_").last, YMRequestConfig.loadingPluginNames.contains(suffix) {
return true
}
return false
}
static func setupPluginsAndKey(_ key: String, plugins: APIPlugins) -> APIPlugins {
var plugins = plugins
YMRequestX.setupBasePlugins(&plugins)
return plugins.map({
if var plugin = $0 as? PluginPropertiesable {
plugin.plugins = plugins
plugin.key = key + "_" + plugin.pluginName
return plugin
}
return $0
})
}
}
// MARK: -
extension YMRequestX {
///
static func setupBasePlugins(_ plugins: inout APIPlugins) {
var plugins_ = plugins
if let others = YMRequestConfig.basePlugins {
plugins_ += others
}
#if RXNETWORKS_PLUGINGS_INDICATOR
if NetworkConfig.addIndicator, !plugins_.contains(where: { $0 is NetworkIndicatorPlugin}) {
let Indicator = NetworkIndicatorPlugin.shared
plugins_.insert(Indicator, at: 0)
}
#endif
#if DEBUG && RXNETWORKS_PLUGINGS_DEBUGGING
if NetworkConfig.addDebugging, !plugins_.contains(where: { $0 is NetworkDebuggingPlugin}) {
let Debugging = NetworkDebuggingPlugin.init()
plugins_.append(Debugging)
}
#endif
plugins = plugins_
}
///
static func hasNetworkHttpHeaderPlugin(_ key: String) -> [String: String]? {
#if RXNETWORKS_PLUGINGS_HTTPHEADER
let plugins = SharedDriver.shared.readRequestPlugins(key)
if let p = plugins.first(where: { $0 is NetworkHttpHeaderPlugin }) {
return (p as? NetworkHttpHeaderPlugin)?.dictionary
}
#endif
return nil
}
}

View File

@@ -2,6 +2,11 @@
<!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>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>

View File

@@ -0,0 +1,15 @@
//
// AppConfigObject.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/22.
//
import Foundation
struct AppConfigObject : HandyJSON {
var certificationType:Bool? = false
var reportSwitch:Bool? = false
var teenagerMode:Bool? = false
var homeTabList:[String]? = []
}

View File

@@ -7,12 +7,23 @@
import UIKit
import SnapKit
import NSObject_Rx
class AuthLoginVC: BaseViewController, HiddenNavigationBarProtocol {
var countdownSeconds = 60
var timer: Timer?
var viewModel:AuthViewModel = AuthViewModel()
var phone:String = ""
var code:String = ""
override func viewDidLoad() {
super.viewDidLoad()
loadSubViews()
viewModel.data.subscribe(onNext: { [weak self] success in
if success {
self?.startCountdown()
}
}).disposed(by: rx.disposeBag)
}
func loadSubViews() {
@@ -180,6 +191,7 @@ class AuthLoginVC: BaseViewController, HiddenNavigationBarProtocol {
button.setTitleColor(UIColor.white, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
button.addTarget(self, action: #selector(phoneLoginBtnAction), for: .touchUpInside)
button.isSelected = true
return button
}()
@@ -374,11 +386,19 @@ extension AuthLoginVC {
}
@objc func getCodeBtnAction() {
if phone.count > 0 {
viewModel.getSmsCode(phone: phone, type: 1)
} else {
HUDTool.showSuccess(with: "请输入正确的手机号码")
}
}
@objc func confirmBtnAction() {
if self.phoneLoginBtn.isSelected == true {
viewModel.authPhoneCode(phone: phone, code: code)
} else {
//TODO: id
}
}
@objc func forgetBtnAction() {
@@ -386,11 +406,21 @@ extension AuthLoginVC {
}
@objc func phoneTextFiledDidChange(_ textField: UITextField) {
if let text = textField.text {
if text.count > 11 {
textField.text = text.substring(start: 0, 11)
}
}
phone = textField.text ?? ""
}
@objc func codeTextFiledDidChange(_ textField: UITextField) {
if let text = textField.text {
if text.count > 11 {
textField.text = text.substring(start: 0, 11)
}
}
code = textField.text ?? ""
}
@objc func idTextFiledDidChange(_ textField: UITextField) {
@@ -401,3 +431,38 @@ extension AuthLoginVC {
}
}
extension AuthLoginVC {
func startCountdown() {
if timer != nil {
timer?.invalidate()
timer = nil
}
countdownSeconds = 60
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
timer!.fire()
}
@objc func updateCountdown() {
countdownSeconds -= 1
if countdownSeconds <= 0 {
getCodeBtn.setTitle("重新获取验证码", for: .normal)
getCodeBtn.isEnabled = true
stopCountdown()
return
}
getCodeBtn.isHidden = false
let seconds = countdownSeconds % 60
getCodeBtn.setTitle("重新获取验证码(\(seconds)s", for: .disabled)
getCodeBtn.isEnabled = false
}
func stopCountdown() {
timer?.invalidate()
timer = nil
}
}

View File

@@ -5,8 +5,50 @@
// Created by MaiMang on 2024/2/21.
//
import Foundation
//import Foundation
//import Moya
//enum AuthAPI {
//
// case appConfig
// case getCode(String, String)
// case authToken(String, String)
//}
//
//extension AuthAPI: YMNetworkAPI {
// var hostUrl: APIHost {
// return YMRequestConfig.baseURL
// }
//
// var pararms: APIParameters? {
// switch self {
// case .getCode(let phone, let type):
// return ["mobile":phone, "type":type]
// case .authToken(let phone, let code):
// return ["phone":phone, "code":code, "client_secret":"uyzjdhds", "version":"1", "client_id":"erban-client", "grant_type":"password"]
// default:
// return [:]
// }
// }
//
// var path: String {
// switch self {
// case .appConfig:
// return "/client/init"
// case .getCode:
// return "/sms/getCode"
// case .authToken:
// return "/oauth/token"
// }
//
// }
//
// var method: Moya.Method {
// switch self {
// case .appConfig:
// return .get
// case .getCode:
// return .post
// case .authToken:
// return .post
// }
// }
//}

View File

@@ -0,0 +1,67 @@
//
// AuthManager.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/24.
//
import Foundation
private let UserTokenKey = "UserTokenKey"
private let UserTicketKey = "UserTicketKey"
class AuthManager: NSObject {
var tokenInfo:UserTokenObject?
static var userUid:Int {
return LoginTokenConfig.config.getAccountInfo()?.uid ?? 0
}
static var ticket:String{
return LoginTokenConfig.config.getTicket()
}
}
class LoginTokenConfig: NSObject {
public static let config = LoginTokenConfig.init()
var tokenInfo: UserTokenObject?
var ticket:String?
func saveTokenToLocaltion(token: UserTokenObject) {
self.tokenInfo = token
if let dic = token.dictionary {
UserDefaults.standard.setValue(dic, forKey: UserTokenKey)
UserDefaults.standard.synchronize()
}
}
func saveTicketToLoaction(ticket:String) {
self.ticket = ticket
UserDefaults.standard.setValue(ticket, forKey: UserTicketKey)
UserDefaults.standard.synchronize()
}
func getTicket() -> String {
if let ticket = self.ticket {
return ticket
}
if let ticket = UserDefaults.standard.value(forKey: UserTicketKey) as? String{
self.ticket = ticket
}
return self.ticket ?? ""
}
func getAccountInfo() -> UserTokenObject? {
if let accountModel = self.tokenInfo {
return accountModel
}
if let dic = UserDefaults.standard.value(forKey: UserTokenKey), let model = Deserialized<UserTokenObject>.toModel(with: dic){
self.tokenInfo = model
}
return self.tokenInfo
}
}

View File

@@ -0,0 +1,28 @@
//
// AuthViewModel.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/22.
//
import Foundation
import RxSwift
class AuthViewModel: NSObject {
let data = PublishSubject<Bool>()
func getSmsCode(phone:String, type:Int) {
if let phoneDes = phone.encrypt() {
let params:[String: Any] = ["mobile":phoneDes, "type":type]
YMNetworkHelper.share.requestSend(type: .post, path: "sms/getCode", params: params, succeed: { data in
self.data.onNext(true)
}, fail: { code, msg in
self.data.onNext(false)
})
}
}
func authPhoneCode(phone:String, code:String) {
}
}

View File

@@ -0,0 +1,15 @@
//
// UserTokenObject.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/24.
//
import Foundation
import HandyJSON
struct UserTokenObject: HandyJSON, Codable {
var uid:Int? = 0
var netEaseToken:String? = ""
var access_token:String? = ""
}