feat: 添加项目基础文件和依赖管理
新增.gitignore、Podfile和Podfile.lock文件以管理项目依赖,添加README.md文件提供项目简介和安装步骤,创建NIMSessionManager、ClientConfig、LogManager和NetworkManager等管理类以支持网络请求和日志记录功能,更新AppDelegate和ContentView以集成NIM SDK和实现用户登录功能。
This commit is contained in:
69
.cursor/rules/swift-assistant-style.mdc
Normal file
69
.cursor/rules/swift-assistant-style.mdc
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# CONTEXT
|
||||||
|
|
||||||
|
I am a native Chinese speaker who has just begun learning Swift 6 and Xcode 16, and I am enthusiastic about exploring new technologies. I wish to receive advice using the latest tools and
|
||||||
|
seek step-by-step guidance to fully understand the implementation process. Since many excellent code resources are in English, I hope my questions can be thoroughly understood. Therefore,
|
||||||
|
I would like the AI assistant to think and reason in English, then translate the English responses into Chinese for me.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# OBJECTIVE
|
||||||
|
|
||||||
|
As an expert AI programming assistant, your task is to provide me with clear and readable SwiftUI code. You should:
|
||||||
|
|
||||||
|
- Utilize the latest versions of SwiftUI and Swift, being familiar with the newest features and best practices.
|
||||||
|
- Provide careful and accurate answers that are well-founded and thoughtfully considered.
|
||||||
|
- **Explicitly use the Chain-of-Thought (CoT) method in your reasoning and answers, explaining your thought process step by step.**
|
||||||
|
- Strictly adhere to my requirements and meticulously complete the tasks.
|
||||||
|
- Begin by outlining your proposed approach with detailed steps or pseudocode.
|
||||||
|
- Upon confirming the plan, proceed to write the code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- Keep answers concise and direct, minimizing unnecessary wording.
|
||||||
|
- Emphasize code readability over performance optimization.
|
||||||
|
- Maintain a professional and supportive tone, ensuring clarity of content.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TONE
|
||||||
|
|
||||||
|
- Be positive and encouraging, helping me improve my programming skills.
|
||||||
|
- Be professional and patient, assisting me in understanding each step.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# AUDIENCE
|
||||||
|
|
||||||
|
The target audience is me—a native Chinese developer eager to learn Swift 6 and Xcode 16, seeking guidance and advice on utilizing the latest technologies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# RESPONSE FORMAT
|
||||||
|
|
||||||
|
- **Utilize the Chain-of-Thought (CoT) method to reason and respond, explaining your thought process step by step.**
|
||||||
|
- Conduct reasoning, thinking, and code writing in English.
|
||||||
|
- The final reply should translate the English into Chinese for me.
|
||||||
|
- The reply should include:
|
||||||
|
|
||||||
|
1. **Step-by-Step Plan**: Describe the implementation process with detailed pseudocode or step-by-step explanations, showcasing your thought process.
|
||||||
|
2. **Code Implementation**: Provide correct, up-to-date, error-free, fully functional, runnable, secure, and efficient code. The code should:
|
||||||
|
- Include all necessary imports and properly name key components.
|
||||||
|
- Fully implement all requested features, leaving no to-dos, placeholders, or omissions.
|
||||||
|
3. **Concise Response**: Minimize unnecessary verbosity, focusing only on essential information.
|
||||||
|
|
||||||
|
- If a correct answer may not exist, please point it out. If you do not know the answer, please honestly inform me rather than guessing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# START ANALYSIS
|
||||||
|
|
||||||
|
If you understand, please prepare to assist me and await my question.
|
||||||
|
|
115
.cursor/rules/swift-swiftui-dev-rules.mdc
Normal file
115
.cursor/rules/swift-swiftui-dev-rules.mdc
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
You are an expert iOS developer using Swift and SwiftUI. Follow these guidelines:
|
||||||
|
|
||||||
|
# Architechture
|
||||||
|
- Use TCA(The Composable Architecture) architecture with SwiftUI & Swift
|
||||||
|
|
||||||
|
# Code Structure
|
||||||
|
- Use Swift's latest features and protocol-oriented programming
|
||||||
|
- Prefer value types (structs) over classes
|
||||||
|
- Follow Apple's Human Interface Guidelines
|
||||||
|
|
||||||
|
|
||||||
|
# Naming
|
||||||
|
- camelCase for vars/funcs, PascalCase for types
|
||||||
|
- Verbs for methods (fetchData)
|
||||||
|
- Boolean: use is/has/should prefixes
|
||||||
|
- Clear, descriptive names following Apple style
|
||||||
|
|
||||||
|
|
||||||
|
# Swift Best Practices
|
||||||
|
|
||||||
|
- Strong type system, proper optionals
|
||||||
|
- async/await for concurrency
|
||||||
|
- Result type for errors
|
||||||
|
- @Published, @StateObject for state
|
||||||
|
- Prefer let over var
|
||||||
|
- Protocol extensions for shared code
|
||||||
|
|
||||||
|
|
||||||
|
# UI Development
|
||||||
|
|
||||||
|
- SwiftUI first, UIKit when needed
|
||||||
|
- SF Symbols for icons
|
||||||
|
- Support dark mode, dynamic type
|
||||||
|
- SafeArea and GeometryReader for layout
|
||||||
|
- Handle all screen sizes and orientations
|
||||||
|
- Implement proper keyboard handling
|
||||||
|
|
||||||
|
|
||||||
|
# Performance
|
||||||
|
|
||||||
|
- Profile with Instruments
|
||||||
|
- Lazy load views and images
|
||||||
|
- Optimize network requests
|
||||||
|
- Background task handling
|
||||||
|
- Proper state management
|
||||||
|
- Memory management
|
||||||
|
|
||||||
|
|
||||||
|
# Data & State
|
||||||
|
|
||||||
|
- CoreData for complex models
|
||||||
|
- UserDefaults for preferences
|
||||||
|
- Combine for reactive code
|
||||||
|
- Clean data flow architecture
|
||||||
|
- Proper dependency injection
|
||||||
|
- Handle state restoration
|
||||||
|
|
||||||
|
|
||||||
|
# Security
|
||||||
|
|
||||||
|
- Encrypt sensitive data
|
||||||
|
- Use Keychain securely
|
||||||
|
- Certificate pinning
|
||||||
|
- Biometric auth when needed
|
||||||
|
- App Transport Security
|
||||||
|
- Input validation
|
||||||
|
|
||||||
|
|
||||||
|
# Testing & Quality
|
||||||
|
|
||||||
|
- XCTest for unit tests
|
||||||
|
- XCUITest for UI tests
|
||||||
|
- Test common user flows
|
||||||
|
- Performance testing
|
||||||
|
- Error scenarios
|
||||||
|
- Accessibility testing
|
||||||
|
|
||||||
|
|
||||||
|
# Essential Features
|
||||||
|
|
||||||
|
- Deep linking support
|
||||||
|
- Push notifications
|
||||||
|
- Background tasks
|
||||||
|
- Localization
|
||||||
|
- Error handling
|
||||||
|
- Analytics/logging
|
||||||
|
|
||||||
|
|
||||||
|
# Development Process
|
||||||
|
|
||||||
|
- Use SwiftUI previews
|
||||||
|
- Git branching strategy
|
||||||
|
- Code review process
|
||||||
|
- CI/CD pipeline
|
||||||
|
- Documentation
|
||||||
|
- Unit test coverage
|
||||||
|
|
||||||
|
|
||||||
|
# App Store Guidelines
|
||||||
|
|
||||||
|
- Privacy descriptions
|
||||||
|
- App capabilities
|
||||||
|
- In-app purchases
|
||||||
|
- Review guidelines
|
||||||
|
- App thinning
|
||||||
|
- Proper signing
|
||||||
|
|
||||||
|
|
||||||
|
Follow Apple's documentation for detailed implementation guidance.
|
||||||
|
|
52
.cursor/rules/swift-tca-architecture-guidelines.mdc
Normal file
52
.cursor/rules/swift-tca-architecture-guidelines.mdc
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
# TCA Architecture Guidelines
|
||||||
|
- Use The Composable Architecture (TCA) for state management and side effect handling.
|
||||||
|
- Define reducers for each feature and use @Reducer annotation.
|
||||||
|
- Use stores to manage state.
|
||||||
|
- Follow unidirectional data flow (state -> view -> action -> reducer -> state).
|
||||||
|
- Use @State to manage local state and @ObservedObject or @EnvironmentObject to manage shared state.
|
||||||
|
- Make sure all side effects (such as network requests) are handled in reducers using Effects.
|
||||||
|
- Divide reducers by functionality and define a root reducer.
|
||||||
|
- Use Dependency Injection to manage external dependencies (such as network, database).
|
||||||
|
- Write tests for reducers to ensure correct state transitions.
|
||||||
|
|
||||||
|
## Feature Structure
|
||||||
|
Each feature must include:
|
||||||
|
1. A `State` struct
|
||||||
|
2. An `Action` enum
|
||||||
|
3. A `Reducer`
|
||||||
|
4. A `View` (if applicable)
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
- Feature: `{{FeatureName}}Feature`
|
||||||
|
- State: `{{FeatureName}}Feature.State`
|
||||||
|
- Action: `{{FeatureName}}Feature.Action`
|
||||||
|
- Reducer: `{{FeatureName}}Feature.reducer`
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```swift
|
||||||
|
struct CounterFeature {
|
||||||
|
struct State: Equatable {
|
||||||
|
var count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Action: Equatable {
|
||||||
|
case increment
|
||||||
|
case decrement
|
||||||
|
}
|
||||||
|
|
||||||
|
static let reducer = Reducer<State, Action, Void> { state, action, _ in
|
||||||
|
switch action {
|
||||||
|
case .increment:
|
||||||
|
state.count += 1
|
||||||
|
return .none
|
||||||
|
case .decrement:
|
||||||
|
state.count -= 1
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Pods
|
||||||
|
.vscode
|
||||||
|
yana.xcworkspace/xcuserdata/edwinqqq.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
|
||||||
|
*.xcbkptlist
|
40
Podfile
Normal file
40
Podfile
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Uncomment the next line to define a global platform for your project
|
||||||
|
platform :ios, '15.6'
|
||||||
|
|
||||||
|
target 'yana' do
|
||||||
|
# Comment the next line if you don't want to use dynamic frameworks
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
# Pods for yana
|
||||||
|
|
||||||
|
# IM 即时通讯
|
||||||
|
pod 'NIMSDK_LITE'
|
||||||
|
# 基础库
|
||||||
|
pod 'NEChatKit', '10.6.1'
|
||||||
|
pod 'NEChatUIKit', '10.6.1' # 会话(聊天)组件
|
||||||
|
pod 'NEContactUIKit', '10.6.1' # 通讯录组件
|
||||||
|
pod 'NELocalConversationUIKit', '10.6.1' # 本地会话列表组件。
|
||||||
|
|
||||||
|
# Networks
|
||||||
|
pod 'Alamofire'
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# 新增冲突处理脚本
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
target.build_phases.each do |phase|
|
||||||
|
if phase.respond_to?(:name) && phase.name == 'Embed Frameworks'
|
||||||
|
phase.input_paths.delete_if { |path|
|
||||||
|
path.end_with?('nimsdk.xcframework', 'nimsocketrocket.xcframework', 'nimquic.xcframework', 'nimnos.xcframework')
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
127
Podfile.lock
Normal file
127
Podfile.lock
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
PODS:
|
||||||
|
- Alamofire (5.10.2)
|
||||||
|
- CocoaLumberjack (3.8.5):
|
||||||
|
- CocoaLumberjack/Core (= 3.8.5)
|
||||||
|
- CocoaLumberjack/Core (3.8.5)
|
||||||
|
- libwebp (1.5.0):
|
||||||
|
- libwebp/demux (= 1.5.0)
|
||||||
|
- libwebp/mux (= 1.5.0)
|
||||||
|
- libwebp/sharpyuv (= 1.5.0)
|
||||||
|
- libwebp/webp (= 1.5.0)
|
||||||
|
- libwebp/demux (1.5.0):
|
||||||
|
- libwebp/webp
|
||||||
|
- libwebp/mux (1.5.0):
|
||||||
|
- libwebp/demux
|
||||||
|
- libwebp/sharpyuv (1.5.0)
|
||||||
|
- libwebp/webp (1.5.0):
|
||||||
|
- libwebp/sharpyuv
|
||||||
|
- MJRefresh (3.7.5)
|
||||||
|
- NEChatKit (10.6.1):
|
||||||
|
- NEChatKit/NOS (= 10.6.1)
|
||||||
|
- NEChatKit/NOS (10.6.1):
|
||||||
|
- NECommonKit (= 9.7.2)
|
||||||
|
- NECoreIM2Kit/NOS (= 1.0.9)
|
||||||
|
- NEChatUIKit (10.6.1):
|
||||||
|
- NEChatUIKit/NOS (= 10.6.1)
|
||||||
|
- NEChatUIKit/NOS (10.6.1):
|
||||||
|
- MJRefresh (= 3.7.5)
|
||||||
|
- NEChatKit/NOS
|
||||||
|
- NECommonUIKit (= 9.7.6)
|
||||||
|
- SDWebImageSVGKitPlugin
|
||||||
|
- SDWebImageWebPCoder
|
||||||
|
- NECommonKit (9.7.2):
|
||||||
|
- YXAlog
|
||||||
|
- NECommonUIKit (9.7.6):
|
||||||
|
- NECommonKit
|
||||||
|
- SDWebImage
|
||||||
|
- NEContactUIKit (10.6.1):
|
||||||
|
- NEContactUIKit/NOS (= 10.6.1)
|
||||||
|
- NEContactUIKit/NOS (10.6.1):
|
||||||
|
- MJRefresh (= 3.7.5)
|
||||||
|
- NEChatKit/NOS
|
||||||
|
- NECommonUIKit (= 9.7.6)
|
||||||
|
- NECoreIM2Kit/NOS (1.0.9):
|
||||||
|
- NECoreKit (= 9.7.5)
|
||||||
|
- NIMSDK_LITE (= 10.8.20)
|
||||||
|
- YXAlog (= 1.0.9)
|
||||||
|
- NECoreKit (9.7.5):
|
||||||
|
- YXAlog
|
||||||
|
- NELocalConversationUIKit (10.6.1):
|
||||||
|
- NELocalConversationUIKit/NOS (= 10.6.1)
|
||||||
|
- NELocalConversationUIKit/NOS (10.6.1):
|
||||||
|
- MJRefresh (= 3.7.5)
|
||||||
|
- NEChatKit/NOS
|
||||||
|
- NECommonUIKit (= 9.7.6)
|
||||||
|
- NIMSDK_LITE (10.8.20):
|
||||||
|
- NIMSDK_LITE/NOS (= 10.8.20)
|
||||||
|
- YXArtemis_XCFramework
|
||||||
|
- NIMSDK_LITE/NOS (10.8.20):
|
||||||
|
- YXArtemis_XCFramework
|
||||||
|
- SDWebImage (5.21.0):
|
||||||
|
- SDWebImage/Core (= 5.21.0)
|
||||||
|
- SDWebImage/Core (5.21.0)
|
||||||
|
- SDWebImageSVGKitPlugin (1.4.0):
|
||||||
|
- SDWebImage/Core (~> 5.10)
|
||||||
|
- SVGKit (~> 3.0)
|
||||||
|
- SDWebImageWebPCoder (0.14.6):
|
||||||
|
- libwebp (~> 1.0)
|
||||||
|
- SDWebImage/Core (~> 5.17)
|
||||||
|
- SVGKit (3.0.0):
|
||||||
|
- CocoaLumberjack (~> 3.0)
|
||||||
|
- YXAlog (1.0.9)
|
||||||
|
- YXArtemis_XCFramework (1.1.4)
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- Alamofire
|
||||||
|
- NEChatKit (= 10.6.1)
|
||||||
|
- NEChatUIKit (= 10.6.1)
|
||||||
|
- NEContactUIKit (= 10.6.1)
|
||||||
|
- NELocalConversationUIKit (= 10.6.1)
|
||||||
|
- NIMSDK_LITE
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- Alamofire
|
||||||
|
- CocoaLumberjack
|
||||||
|
- libwebp
|
||||||
|
- MJRefresh
|
||||||
|
- NEChatKit
|
||||||
|
- NEChatUIKit
|
||||||
|
- NECommonKit
|
||||||
|
- NECommonUIKit
|
||||||
|
- NEContactUIKit
|
||||||
|
- NECoreIM2Kit
|
||||||
|
- NECoreKit
|
||||||
|
- NELocalConversationUIKit
|
||||||
|
- NIMSDK_LITE
|
||||||
|
- SDWebImage
|
||||||
|
- SDWebImageSVGKitPlugin
|
||||||
|
- SDWebImageWebPCoder
|
||||||
|
- SVGKit
|
||||||
|
- YXAlog
|
||||||
|
- YXArtemis_XCFramework
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||||
|
CocoaLumberjack: 6a459bc897d6d80bd1b8c78482ec7ad05dffc3f0
|
||||||
|
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||||
|
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
|
||||||
|
NEChatKit: c36d5824242fcbff0790bfa76316faabf09df8df
|
||||||
|
NEChatUIKit: 8b431a7d1ec5fbe7c4d079b9ae0dc5062cd5e146
|
||||||
|
NECommonKit: f2359393571fcc105a7fc2fb0367a71319606042
|
||||||
|
NECommonUIKit: b5373164800ff138dd075abac90e95379603bb60
|
||||||
|
NEContactUIKit: 532609b8da3d2a7f274489e6e6109c6f8b774505
|
||||||
|
NECoreIM2Kit: 0faffb84b4a2ac0fcc3705dbf4e72f022c01320f
|
||||||
|
NECoreKit: 0ccc64f01c8fdc7266f5a4df41de67447db18503
|
||||||
|
NELocalConversationUIKit: 2f9208763b4f855d3cb3e3e105e733b020594f19
|
||||||
|
NIMSDK_LITE: 22740bf6e2660cb7bafc40f8293fa04d3a77948e
|
||||||
|
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
|
||||||
|
SDWebImageSVGKitPlugin: 7542dd07c344ec3415ded0461a1161a6f087e0c9
|
||||||
|
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||||
|
SVGKit: 1ad7513f8c74d9652f94ed64ddecda1a23864dea
|
||||||
|
YXAlog: 6fdd73102ba0a16933dd7bef426d6011d913c041
|
||||||
|
YXArtemis_XCFramework: d298161285aa9cf0c99800b17847dc99aef60617
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 4034a059527d37196c5dca32d338b37b71e31488
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
78
README.md
Normal file
78
README.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Yana iOS 项目
|
||||||
|
|
||||||
|
## 项目简介
|
||||||
|
|
||||||
|
Yana 是一个基于 iOS 平台的即时通讯应用,使用 Swift 语言开发,集成了网易云信 SDK 实现即时通讯功能。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- 开发语言:Swift
|
||||||
|
- 最低支持版本:iOS 15.6
|
||||||
|
- 主要框架:
|
||||||
|
- NIMSDK_LITE:网易云信即时通讯 SDK
|
||||||
|
- NEChatKit:聊天核心组件
|
||||||
|
- NEChatUIKit:会话(聊天)UI 组件
|
||||||
|
- NEContactUIKit:通讯录 UI 组件
|
||||||
|
- NELocalConversationUIKit:本地会话列表 UI 组件
|
||||||
|
- Alamofire:网络请求框架
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
yana/
|
||||||
|
├── AppDelegate.swift # 应用程序代理
|
||||||
|
├── yanaApp.swift # SwiftUI 应用入口
|
||||||
|
├── ContentView.swift # 主视图
|
||||||
|
├── Managers/ # 管理器类
|
||||||
|
├── Models/ # 数据模型
|
||||||
|
├── Configs/ # 配置文件
|
||||||
|
└── Assets.xcassets/ # 资源文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
- Xcode 13.0 或更高版本
|
||||||
|
- iOS 15.6 或更高版本
|
||||||
|
- CocoaPods 包管理器
|
||||||
|
|
||||||
|
## 安装步骤
|
||||||
|
|
||||||
|
1. 克隆项目到本地
|
||||||
|
2. 在项目根目录执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pod install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 打开 `yana.xcworkspace` 文件
|
||||||
|
4. 编译运行项目
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
- 即时通讯
|
||||||
|
- 会话管理
|
||||||
|
- 通讯录管理
|
||||||
|
- 本地会话列表
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 项目使用 CocoaPods 管理依赖
|
||||||
|
- 需要配置网易云信相关密钥
|
||||||
|
- 最低支持 iOS 15.6 版本
|
||||||
|
|
||||||
|
## 开发规范
|
||||||
|
|
||||||
|
- 遵循 Swift 官方编码规范
|
||||||
|
- 使用 SwiftUI 构建用户界面
|
||||||
|
- 采用 MVVM 架构模式
|
||||||
|
|
||||||
|
## 依赖版本
|
||||||
|
|
||||||
|
- NIMSDK 相关组件版本:10.6.1
|
||||||
|
- Alamofire:最新版本
|
||||||
|
|
||||||
|
## 构建配置
|
||||||
|
|
||||||
|
- 项目使用动态框架
|
||||||
|
- 支持 iOS 13.0 及以上版本
|
||||||
|
- 已配置框架冲突处理脚本
|
21
tools/YYUtility/LICENSE
Normal file
21
tools/YYUtility/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 YY Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@@ -6,13 +6,50 @@
|
|||||||
objectVersion = 77;
|
objectVersion = 77;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
856EF8A28776CEF6CE595B76 /* Pods_yana.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8529F57AF9337F626C670ED /* Pods_yana.framework */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
4C4C8FC12DE5AF9200384527 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 4C3E65172DB61F7A00E5A455 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 4C3E651E2DB61F7A00E5A455;
|
||||||
|
remoteInfo = yana;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0977E1E6E883533DD125CAD4 /* Pods-yana.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-yana.debug.xcconfig"; path = "Target Support Files/Pods-yana/Pods-yana.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
4C3E651F2DB61F7A00E5A455 /* yana.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = yana.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
4C3E651F2DB61F7A00E5A455 /* yana.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = yana.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
4C4C8FBD2DE5AF9200384527 /* yanaAPITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = yanaAPITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
977CD030E95CB064179F3A1B /* Pods-yana.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-yana.release.xcconfig"; path = "Target Support Files/Pods-yana/Pods-yana.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
D8529F57AF9337F626C670ED /* Pods_yana.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_yana.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
4CD4A30C2DBA2D6A00F49950 /* Exceptions for "yana" folder in "yana" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
"yana-Bridging-Header.h",
|
||||||
|
);
|
||||||
|
target = 4C3E651E2DB61F7A00E5A455 /* yana */;
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
4C3E65212DB61F7A00E5A455 /* yana */ = {
|
4C4C8FBE2DE5AF9200384527 /* yanaAPITests */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
path = yanaAPITests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
4C55BD992DB64C3C0021505D /* yana */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
4CD4A30C2DBA2D6A00F49950 /* Exceptions for "yana" folder in "yana" target */,
|
||||||
|
);
|
||||||
path = yana;
|
path = yana;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -20,6 +57,14 @@
|
|||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
4C3E651C2DB61F7A00E5A455 /* Frameworks */ = {
|
4C3E651C2DB61F7A00E5A455 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
856EF8A28776CEF6CE595B76 /* Pods_yana.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
4C4C8FBA2DE5AF9200384527 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
@@ -32,8 +77,12 @@
|
|||||||
4C3E65162DB61F7A00E5A455 = {
|
4C3E65162DB61F7A00E5A455 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4C3E65212DB61F7A00E5A455 /* yana */,
|
4C4C8FE72DE6F05300384527 /* tools */,
|
||||||
|
4C55BD992DB64C3C0021505D /* yana */,
|
||||||
|
4C4C8FBE2DE5AF9200384527 /* yanaAPITests */,
|
||||||
4C3E65202DB61F7A00E5A455 /* Products */,
|
4C3E65202DB61F7A00E5A455 /* Products */,
|
||||||
|
87A8B7A8B4E2D53BA55B66D1 /* Pods */,
|
||||||
|
556C2003CCDA5AC2C56882D0 /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -41,35 +90,92 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4C3E651F2DB61F7A00E5A455 /* yana.app */,
|
4C3E651F2DB61F7A00E5A455 /* yana.app */,
|
||||||
|
4C4C8FBD2DE5AF9200384527 /* yanaAPITests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4C4C8FE72DE6F05300384527 /* tools */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
path = tools;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
556C2003CCDA5AC2C56882D0 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D8529F57AF9337F626C670ED /* Pods_yana.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
87A8B7A8B4E2D53BA55B66D1 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0977E1E6E883533DD125CAD4 /* Pods-yana.debug.xcconfig */,
|
||||||
|
977CD030E95CB064179F3A1B /* Pods-yana.release.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXHeadersBuildPhase section */
|
||||||
|
4C4C90522DE6FCF700384527 /* Headers */ = {
|
||||||
|
isa = PBXHeadersBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXHeadersBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
4C3E651E2DB61F7A00E5A455 /* yana */ = {
|
4C3E651E2DB61F7A00E5A455 /* yana */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 4C3E652A2DB61F7B00E5A455 /* Build configuration list for PBXNativeTarget "yana" */;
|
buildConfigurationList = 4C3E652A2DB61F7B00E5A455 /* Build configuration list for PBXNativeTarget "yana" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
E0BDB6E67FEFE696E7D48CE4 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
4C4C90522DE6FCF700384527 /* Headers */,
|
||||||
4C3E651B2DB61F7A00E5A455 /* Sources */,
|
4C3E651B2DB61F7A00E5A455 /* Sources */,
|
||||||
4C3E651C2DB61F7A00E5A455 /* Frameworks */,
|
4C3E651C2DB61F7A00E5A455 /* Frameworks */,
|
||||||
4C3E651D2DB61F7A00E5A455 /* Resources */,
|
4C3E651D2DB61F7A00E5A455 /* Resources */,
|
||||||
|
80E012C82442392F08611AA3 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
4C3E65212DB61F7A00E5A455 /* yana */,
|
4C55BD992DB64C3C0021505D /* yana */,
|
||||||
);
|
);
|
||||||
name = yana;
|
name = yana;
|
||||||
packageProductDependencies = (
|
|
||||||
);
|
|
||||||
productName = yana;
|
productName = yana;
|
||||||
productReference = 4C3E651F2DB61F7A00E5A455 /* yana.app */;
|
productReference = 4C3E651F2DB61F7A00E5A455 /* yana.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
|
4C4C8FBC2DE5AF9200384527 /* yanaAPITests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 4C4C8FC32DE5AF9200384527 /* Build configuration list for PBXNativeTarget "yanaAPITests" */;
|
||||||
|
buildPhases = (
|
||||||
|
4C4C8FB92DE5AF9200384527 /* Sources */,
|
||||||
|
4C4C8FBA2DE5AF9200384527 /* Frameworks */,
|
||||||
|
4C4C8FBB2DE5AF9200384527 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
4C4C8FC22DE5AF9200384527 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
4C4C8FBE2DE5AF9200384527 /* yanaAPITests */,
|
||||||
|
);
|
||||||
|
name = yanaAPITests;
|
||||||
|
productName = yanaAPITests;
|
||||||
|
productReference = 4C4C8FBD2DE5AF9200384527 /* yanaAPITests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
@@ -83,6 +189,10 @@
|
|||||||
4C3E651E2DB61F7A00E5A455 = {
|
4C3E651E2DB61F7A00E5A455 = {
|
||||||
CreatedOnToolsVersion = 16.3;
|
CreatedOnToolsVersion = 16.3;
|
||||||
};
|
};
|
||||||
|
4C4C8FBC2DE5AF9200384527 = {
|
||||||
|
CreatedOnToolsVersion = 16.3;
|
||||||
|
TestTargetID = 4C3E651E2DB61F7A00E5A455;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 4C3E651A2DB61F7A00E5A455 /* Build configuration list for PBXProject "yana" */;
|
buildConfigurationList = 4C3E651A2DB61F7A00E5A455 /* Build configuration list for PBXProject "yana" */;
|
||||||
@@ -100,6 +210,7 @@
|
|||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
4C3E651E2DB61F7A00E5A455 /* yana */,
|
4C3E651E2DB61F7A00E5A455 /* yana */,
|
||||||
|
4C4C8FBC2DE5AF9200384527 /* yanaAPITests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@@ -112,8 +223,61 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
4C4C8FBB2DE5AF9200384527 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
80E012C82442392F08611AA3 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
E0BDB6E67FEFE696E7D48CE4 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-yana-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
4C3E651B2DB61F7A00E5A455 /* Sources */ = {
|
4C3E651B2DB61F7A00E5A455 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
@@ -122,8 +286,23 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
4C4C8FB92DE5AF9200384527 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
4C4C8FC22DE5AF9200384527 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 4C3E651E2DB61F7A00E5A455 /* yana */;
|
||||||
|
targetProxy = 4C4C8FC12DE5AF9200384527 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
4C3E65282DB61F7B00E5A455 /* Debug */ = {
|
4C3E65282DB61F7B00E5A455 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
@@ -248,6 +427,7 @@
|
|||||||
};
|
};
|
||||||
4C3E652B2DB61F7B00E5A455 /* Debug */ = {
|
4C3E652B2DB61F7B00E5A455 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 0977E1E6E883533DD125CAD4 /* Pods-yana.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
@@ -255,27 +435,50 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = EKM7RAGNA6;
|
DEVELOPMENT_TEAM = EKM7RAGNA6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
HEADER_SEARCH_PATHS = (
|
||||||
|
"$(SRCROOT)",
|
||||||
|
"$(inherited)",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers\"",
|
||||||
|
);
|
||||||
|
INFOPLIST_FILE = yana/Info.plist;
|
||||||
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "此App将可发现和连接到您所用网络上的设备。";
|
||||||
|
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "“MoliStar”需要您的同意,才可以进行定位服务,访问网络状态";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 20.20.61;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yana.yana;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yana.yana;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "yana/yana-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
4C3E652C2DB61F7B00E5A455 /* Release */ = {
|
4C3E652C2DB61F7B00E5A455 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 977CD030E95CB064179F3A1B /* Pods-yana.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
@@ -283,22 +486,90 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = EKM7RAGNA6;
|
DEVELOPMENT_TEAM = EKM7RAGNA6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
HEADER_SEARCH_PATHS = (
|
||||||
|
"$(SRCROOT)",
|
||||||
|
"$(inherited)",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers\"",
|
||||||
|
"\"${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers\"",
|
||||||
|
);
|
||||||
|
INFOPLIST_FILE = yana/Info.plist;
|
||||||
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "此App将可发现和连接到您所用网络上的设备。";
|
||||||
|
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "“MoliStar”需要您的同意,才可以进行定位服务,访问网络状态";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 20.20.61;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yana.yana;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yana.yana;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "yana/yana-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
4C4C8FC42DE5AF9200384527 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = EKM7RAGNA6;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yanaAPITests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/yana.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/yana";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
4C4C8FC52DE5AF9200384527 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = EKM7RAGNA6;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yanaAPITests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/yana.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/yana";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -323,6 +594,15 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
4C4C8FC32DE5AF9200384527 /* Build configuration list for PBXNativeTarget "yanaAPITests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
4C4C8FC42DE5AF9200384527 /* Debug */,
|
||||||
|
4C4C8FC52DE5AF9200384527 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = 4C3E65172DB61F7A00E5A455 /* Project object */;
|
rootObject = 4C3E65172DB61F7A00E5A455 /* Project object */;
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<key>yana.xcscheme_^#shared#^_</key>
|
<key>yana.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>23</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
10
yana.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
yana.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:yana.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
@@ -0,0 +1,152 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Bucket
|
||||||
|
uuid = "A60FAB2A-3184-45B2-920F-A3D7A086CF95"
|
||||||
|
type = "0"
|
||||||
|
version = "2.0">
|
||||||
|
<Breakpoints>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "BF83E194-5D1D-4B84-AD21-2D4CDCC124DE"
|
||||||
|
shouldBeEnabled = "Yes"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Managers/NIMSessionManager.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "97"
|
||||||
|
endingLineNumber = "97"
|
||||||
|
landmarkName = "onLoginStatus(_:)"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "5E054207-7C17-4F34-A910-1C9F814EC837"
|
||||||
|
shouldBeEnabled = "Yes"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Managers/NIMSessionManager.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "101"
|
||||||
|
endingLineNumber = "101"
|
||||||
|
landmarkName = "onLoginFailed(_:)"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "164971C8-E03E-4FAD-891E-C07DFA41444D"
|
||||||
|
shouldBeEnabled = "Yes"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Managers/NIMSessionManager.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "105"
|
||||||
|
endingLineNumber = "105"
|
||||||
|
landmarkName = "onKickedOffline(_:)"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "9A59F819-E987-4891-AEDD-AE98333E1722"
|
||||||
|
shouldBeEnabled = "Yes"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Managers/NIMSessionManager.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "112"
|
||||||
|
endingLineNumber = "112"
|
||||||
|
landmarkName = "onLoginClientChanged(_:clients:)"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "1268A929-970C-4C74-B3E5-09976D796C5E"
|
||||||
|
shouldBeEnabled = "No"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Managers/NetworkManager.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "54"
|
||||||
|
endingLineNumber = "54"
|
||||||
|
landmarkName = "String"
|
||||||
|
landmarkType = "21">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "DB8B4E7E-87FD-4E45-86F7-D21CF24F3B08"
|
||||||
|
shouldBeEnabled = "No"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Configs/ClientConfig.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "14"
|
||||||
|
endingLineNumber = "14"
|
||||||
|
landmarkName = "initializeClient()"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "ADC3C5EC-46AE-4FDA-9FD6-D685B5C36044"
|
||||||
|
shouldBeEnabled = "No"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Managers/NetworkManager.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "519"
|
||||||
|
endingLineNumber = "519"
|
||||||
|
landmarkName = "request(_:didValidateRequest:response:data:)"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "492235D2-D281-4F70-B43C-C09990DC22EC"
|
||||||
|
shouldBeEnabled = "Yes"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Managers/NetworkManager.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "326"
|
||||||
|
endingLineNumber = "326"
|
||||||
|
landmarkName = "enhancedRequest(path:method:queryItems:bodyParameters:responseType:completion:)"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "198A1AE8-A7A4-4A66-A4D3-DF86D873E2AE"
|
||||||
|
shouldBeEnabled = "Yes"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "yana/Managers/NetworkManager.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "361"
|
||||||
|
endingLineNumber = "361"
|
||||||
|
landmarkName = "enhancedRequest(path:method:queryItems:bodyParameters:responseType:completion:)"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
</Breakpoints>
|
||||||
|
</Bucket>
|
59
yana/APIs/API.swift
Normal file
59
yana/APIs/API.swift
Normal 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
9
yana/AppDelegate.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@@ -15,9 +15,9 @@ struct AppConfig {
|
|||||||
static var baseURL: String {
|
static var baseURL: String {
|
||||||
switch current {
|
switch current {
|
||||||
case .development:
|
case .development:
|
||||||
return "https://dev-api.yourdomain.com/v1"
|
return "http://beta.api.molistar.xyz"
|
||||||
case .production:
|
case .production:
|
||||||
return "https://api.yourdomain.com/v1"
|
return "https://api.hfighting.com"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
32
yana/Configs/ClientConfig.swift
Normal file
32
yana/Configs/ClientConfig.swift
Normal 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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -8,15 +8,71 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
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 {
|
var body: some View {
|
||||||
VStack {
|
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")
|
Image(systemName: "globe")
|
||||||
.imageScale(.large)
|
.imageScale(.large)
|
||||||
.foregroundStyle(.tint)
|
.foregroundStyle(.tint)
|
||||||
Text("Hello, world!")
|
Text("Hello, yana!")
|
||||||
}
|
}
|
||||||
.padding()
|
.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 {
|
#Preview {
|
||||||
|
11
yana/Info.plist
Normal file
11
yana/Info.plist
Normal 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>
|
51
yana/Managers/LogManager.swift
Normal file
51
yana/Managers/LogManager.swift
Normal 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 环境输出(默认 false,Debug 全部输出)
|
||||||
|
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)
|
||||||
|
}
|
@@ -1,10 +1,33 @@
|
|||||||
import NIMSDK
|
import NIMSDK
|
||||||
|
import NECoreKit
|
||||||
|
import NECoreIM2Kit
|
||||||
|
import NEChatKit
|
||||||
|
import NEChatUIKit
|
||||||
|
|
||||||
struct NIMConfigurationManager {
|
struct NIMConfigurationManager {
|
||||||
static func setupSDK() {
|
|
||||||
NIMSDK.shared().register(
|
static func setupNimSDK() {
|
||||||
withAppID: "79bc37000f4018a2a24ea9dc6ca08d32",
|
let option = configureNIMSDKOption()
|
||||||
cerName: "pikoDevelopPush"
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
127
yana/Managers/NIMSessionManager.swift
Normal file
127
yana/Managers/NIMSessionManager.swift
Normal 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
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
667
yana/Managers/NetworkManager.swift
Normal file
667
yana/Managers/NetworkManager.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
yana/yana-Bridging-Header.h
Normal file
4
yana/yana-Bridging-Header.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
//
|
||||||
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
|
//
|
||||||
|
|
@@ -6,9 +6,12 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import NIMSDK
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct yanaApp: App {
|
struct yanaApp: App {
|
||||||
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
68
yanaAPITests/yanaAPITests.swift
Normal file
68
yanaAPITests/yanaAPITests.swift
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// yanaAPITests.swift
|
||||||
|
// yanaAPITests
|
||||||
|
//
|
||||||
|
// Created by P on 2025/5/27.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import yana
|
||||||
|
|
||||||
|
final class yanaAPITests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() throws {
|
||||||
|
// This is an example of a functional test case.
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
|
// Any test you write for XCTest can be annotated as throws and async.
|
||||||
|
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||||
|
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPerformanceExample() throws {
|
||||||
|
// This is an example of a performance test case.
|
||||||
|
measure {
|
||||||
|
// Put the code you want to measure the time of here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testClientInit_Success() {
|
||||||
|
let expectation = self.expectation(description: "clientInit success")
|
||||||
|
API.clientInit { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let data):
|
||||||
|
XCTAssertNotNil(data)
|
||||||
|
// 可根据实际返回内容进一步断言
|
||||||
|
case .failure(let error):
|
||||||
|
XCTFail("Expected success, got error: \(error)")
|
||||||
|
}
|
||||||
|
expectation.fulfill()
|
||||||
|
}
|
||||||
|
waitForExpectations(timeout: 5, handler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testClientInit_Failure() {
|
||||||
|
// 可通过mock或断网等方式测试失败场景
|
||||||
|
// 这里只做结构示例
|
||||||
|
let expectation = self.expectation(description: "clientInit failure")
|
||||||
|
// 假设API支持注入baseURL或mock
|
||||||
|
API.clientInit { result in
|
||||||
|
switch result {
|
||||||
|
case .success(_):
|
||||||
|
// 若期望失败则此处应fail
|
||||||
|
XCTFail("Expected failure, got success")
|
||||||
|
case .failure(let error):
|
||||||
|
XCTAssertNotNil(error)
|
||||||
|
}
|
||||||
|
expectation.fulfill()
|
||||||
|
}
|
||||||
|
waitForExpectations(timeout: 5, handler: nil)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user