Files
e-party-iOS/yana/Utils/TCCos/Views/COSView.swift
edwinQQQ b966e24532 feat: 更新COSManager和相关视图以增强图片上传功能
- 修改COSManagerAdapter以支持新的TCCos组件,确保与腾讯云COS的兼容性。
- 在CreateFeedFeature中新增图片上传相关状态和Action,优化图片选择与上传逻辑。
- 更新CreateFeedView以整合图片上传功能,提升用户体验。
- 在多个视图中添加键盘状态管理,改善用户交互体验。
- 新增COS相关的测试文件,确保功能的正确性和稳定性。
2025-07-31 11:41:56 +08:00

359 lines
10 KiB
Swift

import SwiftUI
import ComposableArchitecture
// MARK: - COS
/// COS
/// Token
public struct COSView: View {
// MARK: - Properties
let store: StoreOf<COSFeature>
// MARK: - Initialization
public init(store: StoreOf<COSFeature>) {
self.store = store
}
// MARK: - Body
public var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack(spacing: 16) {
// Token
if let tokenState = viewStore.tokenState {
TokenStatusView(tokenState: tokenState)
}
//
if let configState = viewStore.configurationState {
ConfigurationStatusView(configState: configState)
}
//
if let uploadState = viewStore.uploadState {
UploadProgressView(uploadState: uploadState)
}
//
COSActionButtonsView(store: store)
Spacer()
}
.padding()
.onAppear {
viewStore.send(.onAppear)
}
}
}
}
// MARK: - Token
/// Token
private struct TokenStatusView: View {
let tokenState: TokenState
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: tokenIcon)
.foregroundColor(tokenColor)
Text("Token 状态")
.font(.headline)
Spacer()
if tokenState.isLoading {
ProgressView()
.scaleEffect(0.8)
}
}
if let token = tokenState.currentToken {
VStack(alignment: .leading, spacing: 4) {
Text("存储桶: \(token.bucket)")
.font(.caption)
.foregroundColor(.secondary)
Text("地域: \(token.region)")
.font(.caption)
.foregroundColor(.secondary)
Text("过期时间: \(formatRelativeTime(token.expirationDate))")
.font(.caption)
.foregroundColor(token.isExpired ? .red : .green)
}
} else {
Text("未获取 Token")
.font(.caption)
.foregroundColor(.secondary)
}
if let error = tokenState.error {
Text("错误: \(error)")
.font(.caption)
.foregroundColor(.red)
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
}
private var tokenIcon: String {
if tokenState.isLoading {
return "arrow.clockwise"
} else if let token = tokenState.currentToken {
return token.isExpired ? "exclamationmark.triangle" : "checkmark.circle"
} else {
return "questionmark.circle"
}
}
private var tokenColor: Color {
if tokenState.isLoading {
return .blue
} else if let token = tokenState.currentToken {
return token.isExpired ? .red : .green
} else {
return .orange
}
}
private func formatRelativeTime(_ date: Date) -> String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter.localizedString(for: date, relativeTo: Date())
}
}
// MARK: -
///
private struct ConfigurationStatusView: View {
let configState: ConfigurationState
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: configIcon)
.foregroundColor(configColor)
Text("服务状态")
.font(.headline)
Spacer()
}
Text(statusMessage)
.font(.caption)
.foregroundColor(.secondary)
if let error = configState.error {
Text("错误: \(error)")
.font(.caption)
.foregroundColor(.red)
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
}
private var configIcon: String {
switch configState.serviceStatus {
case .notInitialized:
return "xmark.circle"
case .initializing:
return "arrow.clockwise"
case .initialized:
return "checkmark.circle"
case .failed:
return "exclamationmark.triangle"
}
}
private var configColor: Color {
switch configState.serviceStatus {
case .notInitialized:
return .orange
case .initializing:
return .blue
case .initialized:
return .green
case .failed:
return .red
}
}
private var statusMessage: String {
switch configState.serviceStatus {
case .notInitialized:
return "服务未初始化"
case .initializing:
return "正在初始化服务..."
case .initialized(let config):
return "服务已初始化 - \(config.bucket)"
case .failed(let error):
return "初始化失败: \(error)"
}
}
}
// MARK: -
///
private struct UploadProgressView: View {
let uploadState: UploadState
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: uploadIcon)
.foregroundColor(uploadColor)
Text("上传状态")
.font(.headline)
Spacer()
if uploadState.isUploading {
Button("取消") {
// TODO:
}
.font(.caption)
.foregroundColor(.red)
}
}
if let task = uploadState.currentTask {
VStack(alignment: .leading, spacing: 4) {
Text("文件: \(task.fileName)")
.font(.caption)
.foregroundColor(.secondary)
Text("大小: \(ByteCountFormatter.string(fromByteCount: Int64(task.imageData.count), countStyle: .file))")
.font(.caption)
.foregroundColor(.secondary)
}
}
if uploadState.isUploading {
ProgressView(value: uploadState.progress)
.progressViewStyle(LinearProgressViewStyle())
Text("\(Int(uploadState.progress * 100))%")
.font(.caption)
.foregroundColor(.secondary)
}
if let result = uploadState.result {
VStack(alignment: .leading, spacing: 4) {
Text("上传成功")
.font(.caption)
.foregroundColor(.green)
Text(result)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(2)
}
}
if let error = uploadState.error {
Text("上传失败: \(error)")
.font(.caption)
.foregroundColor(.red)
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
}
private var uploadIcon: String {
if uploadState.isUploading {
return "arrow.up.circle"
} else if uploadState.result != nil {
return "checkmark.circle"
} else if uploadState.error != nil {
return "xmark.circle"
} else {
return "arrow.up.circle"
}
}
private var uploadColor: Color {
if uploadState.isUploading {
return .blue
} else if uploadState.result != nil {
return .green
} else if uploadState.error != nil {
return .red
} else {
return .gray
}
}
}
// MARK: -
///
private struct COSActionButtonsView: View {
let store: StoreOf<COSFeature>
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack(spacing: 12) {
//
HStack(spacing: 12) {
Button("获取 Token") {
viewStore.send(.token(.getToken))
}
.buttonStyle(.borderedProminent)
.disabled(viewStore.tokenState?.isLoading == true)
Button("刷新 Token") {
viewStore.send(.token(.refreshToken))
}
.buttonStyle(.bordered)
.disabled(viewStore.tokenState?.isLoading == true)
}
HStack(spacing: 12) {
Button("重试") {
viewStore.send(.retry)
}
.buttonStyle(.bordered)
Button("重置") {
viewStore.send(.resetAll)
}
.buttonStyle(.bordered)
.foregroundColor(.red)
}
Button("健康检查") {
viewStore.send(.checkHealth)
}
.buttonStyle(.bordered)
.font(.caption)
}
}
}
}
// MARK: -
#Preview {
COSView(
store: Store(
initialState: COSFeature.State(),
reducer: { COSFeature() }
)
)
}