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;
|
||||
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 */
|
||||
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; };
|
||||
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 */
|
||||
|
||||
/* 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 */
|
||||
4C3E65212DB61F7A00E5A455 /* yana */ = {
|
||||
4C4C8FBE2DE5AF9200384527 /* yanaAPITests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = yanaAPITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C55BD992DB64C3C0021505D /* yana */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
4CD4A30C2DBA2D6A00F49950 /* Exceptions for "yana" folder in "yana" target */,
|
||||
);
|
||||
path = yana;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -20,6 +57,14 @@
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
4C3E651C2DB61F7A00E5A455 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
856EF8A28776CEF6CE595B76 /* Pods_yana.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4C4C8FBA2DE5AF9200384527 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@@ -32,8 +77,12 @@
|
||||
4C3E65162DB61F7A00E5A455 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C3E65212DB61F7A00E5A455 /* yana */,
|
||||
4C4C8FE72DE6F05300384527 /* tools */,
|
||||
4C55BD992DB64C3C0021505D /* yana */,
|
||||
4C4C8FBE2DE5AF9200384527 /* yanaAPITests */,
|
||||
4C3E65202DB61F7A00E5A455 /* Products */,
|
||||
87A8B7A8B4E2D53BA55B66D1 /* Pods */,
|
||||
556C2003CCDA5AC2C56882D0 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -41,35 +90,92 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C3E651F2DB61F7A00E5A455 /* yana.app */,
|
||||
4C4C8FBD2DE5AF9200384527 /* yanaAPITests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
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 */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
4C4C90522DE6FCF700384527 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
4C3E651E2DB61F7A00E5A455 /* yana */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4C3E652A2DB61F7B00E5A455 /* Build configuration list for PBXNativeTarget "yana" */;
|
||||
buildPhases = (
|
||||
E0BDB6E67FEFE696E7D48CE4 /* [CP] Check Pods Manifest.lock */,
|
||||
4C4C90522DE6FCF700384527 /* Headers */,
|
||||
4C3E651B2DB61F7A00E5A455 /* Sources */,
|
||||
4C3E651C2DB61F7A00E5A455 /* Frameworks */,
|
||||
4C3E651D2DB61F7A00E5A455 /* Resources */,
|
||||
80E012C82442392F08611AA3 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
4C3E65212DB61F7A00E5A455 /* yana */,
|
||||
4C55BD992DB64C3C0021505D /* yana */,
|
||||
);
|
||||
name = yana;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = yana;
|
||||
productReference = 4C3E651F2DB61F7A00E5A455 /* yana.app */;
|
||||
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 */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -83,6 +189,10 @@
|
||||
4C3E651E2DB61F7A00E5A455 = {
|
||||
CreatedOnToolsVersion = 16.3;
|
||||
};
|
||||
4C4C8FBC2DE5AF9200384527 = {
|
||||
CreatedOnToolsVersion = 16.3;
|
||||
TestTargetID = 4C3E651E2DB61F7A00E5A455;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 4C3E651A2DB61F7A00E5A455 /* Build configuration list for PBXProject "yana" */;
|
||||
@@ -100,6 +210,7 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
4C3E651E2DB61F7A00E5A455 /* yana */,
|
||||
4C4C8FBC2DE5AF9200384527 /* yanaAPITests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -112,8 +223,61 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4C4C8FBB2DE5AF9200384527 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* 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 */
|
||||
4C3E651B2DB61F7A00E5A455 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
@@ -122,8 +286,23 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4C4C8FB92DE5AF9200384527 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
4C4C8FC22DE5AF9200384527 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 4C3E651E2DB61F7A00E5A455 /* yana */;
|
||||
targetProxy = 4C4C8FC12DE5AF9200384527 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
4C3E65282DB61F7B00E5A455 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
@@ -248,6 +427,7 @@
|
||||
};
|
||||
4C3E652B2DB61F7B00E5A455 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 0977E1E6E883533DD125CAD4 /* Pods-yana.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
@@ -255,27 +435,50 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = EKM7RAGNA6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
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_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 20.20.61;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yana.yana;
|
||||
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_OBJC_BRIDGING_HEADER = "yana/yana-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4C3E652C2DB61F7B00E5A455 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 977CD030E95CB064179F3A1B /* Pods-yana.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
@@ -283,22 +486,90 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = EKM7RAGNA6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
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_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 20.20.61;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yana.yana;
|
||||
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_OBJC_BRIDGING_HEADER = "yana/yana-Bridging-Header.h";
|
||||
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;
|
||||
};
|
||||
@@ -323,6 +594,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
4C4C8FC32DE5AF9200384527 /* Build configuration list for PBXNativeTarget "yanaAPITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4C4C8FC42DE5AF9200384527 /* Debug */,
|
||||
4C4C8FC52DE5AF9200384527 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 4C3E65172DB61F7A00E5A455 /* Project object */;
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<key>yana.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>23</integer>
|
||||
</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 {
|
||||
switch current {
|
||||
case .development:
|
||||
return "https://dev-api.yourdomain.com/v1"
|
||||
return "http://beta.api.molistar.xyz"
|
||||
case .production:
|
||||
return "https://api.yourdomain.com/v1"
|
||||
return "https://api.hfighting.com"
|
||||
}
|
||||
}
|
||||
|
||||
|
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
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var account = ""
|
||||
@State private var password = ""
|
||||
|
||||
#if DEBUG
|
||||
init() {
|
||||
_account = State(initialValue: "3184")
|
||||
_password = State(initialValue: "a0d5da073d14731cc7a01ecaa17b9174")
|
||||
}
|
||||
#endif
|
||||
@State private var isLoading = false
|
||||
@State private var loginError: String?
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
// 新增测试按钮
|
||||
Button("测试初始化") {
|
||||
ClientConfig.shared.initializeClient()
|
||||
}
|
||||
.padding(.top, 20)
|
||||
|
||||
TextField("账号", text: $account)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding()
|
||||
|
||||
SecureField("密码", text: $password)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding()
|
||||
|
||||
Button(action: handleLogin) {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text("登录")
|
||||
}
|
||||
}
|
||||
.disabled(isLoading)
|
||||
.alert("登录错误", isPresented: .constant(loginError != nil)) {
|
||||
Button("确定") { loginError = nil }
|
||||
} message: {
|
||||
Text(loginError ?? "")
|
||||
}
|
||||
|
||||
Image(systemName: "globe")
|
||||
.imageScale(.large)
|
||||
.foregroundStyle(.tint)
|
||||
Text("Hello, world!")
|
||||
Text("Hello, yana!")
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func handleLogin() {
|
||||
isLoading = true
|
||||
NIMSessionManager.shared
|
||||
.autoLogin(account: account, token: password) { error in
|
||||
if let error = error {
|
||||
loginError = error.localizedDescription
|
||||
} else {
|
||||
// 登录成功处理
|
||||
}
|
||||
}
|
||||
// NIMSessionManager.shared.login(account: account, token: password) { error in
|
||||
// isLoading = false
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
11
yana/Info.plist
Normal file
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 NECoreKit
|
||||
import NECoreIM2Kit
|
||||
import NEChatKit
|
||||
import NEChatUIKit
|
||||
|
||||
struct NIMConfigurationManager {
|
||||
static func setupSDK() {
|
||||
NIMSDK.shared().register(
|
||||
withAppID: "79bc37000f4018a2a24ea9dc6ca08d32",
|
||||
cerName: "pikoDevelopPush"
|
||||
)
|
||||
|
||||
static func setupNimSDK() {
|
||||
let option = configureNIMSDKOption()
|
||||
setupSDK(with: option)
|
||||
setupChatSDK(with: option)
|
||||
}
|
||||
}
|
||||
|
||||
static func setupSDK(with option: NIMSDKOption) {
|
||||
NIMSDK.shared().register(with: option)
|
||||
NIMSDKConfig.shared().shouldConsiderRevokedMessageUnreadCount = true
|
||||
NIMSDKConfig.shared().shouldSyncStickTopSessionInfos = true
|
||||
}
|
||||
|
||||
static func setupChatSDK(with option: NIMSDKOption) {
|
||||
let v2Option = V2NIMSDKOption()
|
||||
v2Option.enableV2CloudConversation = false
|
||||
IMKitClient.instance.setupIM2(option, v2Option)
|
||||
}
|
||||
|
||||
static func configureNIMSDKOption() -> NIMSDKOption {
|
||||
let option = NIMSDKOption()
|
||||
option.appKey = "79bc37000f4018a2a24ea9dc6ca08d32"
|
||||
option.apnsCername = "pikoDevelopPush"
|
||||
return option
|
||||
}
|
||||
}
|
||||
|
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 NIMSDK
|
||||
|
||||
@main
|
||||
struct yanaApp: App {
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
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