
- 在DynamicsModels.swift中为动态响应结构和列表数据添加Sendable协议,提升并发安全性。 - 在FeedListFeature.swift中实现动态内容的加载逻辑,集成API请求以获取最新动态。 - 在FeedListView.swift中新增动态内容列表展示,优化用户交互体验。 - 在MeView.swift中添加设置按钮,支持弹出设置视图。 - 在SettingView.swift中新增COS上传测试功能,允许用户测试图片上传至腾讯云COS。 - 在OptimizedDynamicCardView.swift中实现优化的动态卡片组件,提升动态展示效果。
240 lines
8.2 KiB
Swift
240 lines
8.2 KiB
Swift
import SwiftUI
|
|
import ComposableArchitecture
|
|
import Foundation
|
|
|
|
// MARK: - 优化的动态卡片组件
|
|
struct OptimizedDynamicCardView: View {
|
|
let moment: MomentsInfo
|
|
let allMoments: [MomentsInfo]
|
|
let currentIndex: Int
|
|
|
|
init(moment: MomentsInfo, allMoments: [MomentsInfo], currentIndex: Int) {
|
|
self.moment = moment
|
|
self.allMoments = allMoments
|
|
self.currentIndex = currentIndex
|
|
}
|
|
|
|
public var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
// 用户信息
|
|
HStack {
|
|
// 头像
|
|
CachedAsyncImage(url: moment.avatar) { image in
|
|
image
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fill)
|
|
} placeholder: {
|
|
Circle()
|
|
.fill(Color.gray.opacity(0.3))
|
|
.overlay(
|
|
Text(String(moment.nick.prefix(1)))
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundColor(.white)
|
|
)
|
|
}
|
|
.frame(width: 40, height: 40)
|
|
.clipShape(Circle())
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(moment.nick)
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundColor(.white)
|
|
|
|
Text(formatTime(moment.publishTime))
|
|
.font(.system(size: 12))
|
|
.foregroundColor(.white.opacity(0.6))
|
|
}
|
|
|
|
Spacer()
|
|
|
|
// VIP 标识
|
|
if let vipInfo = moment.userVipInfoVO, let vipLevel = vipInfo.vipLevel {
|
|
Text("VIP\(vipLevel)")
|
|
.font(.system(size: 10, weight: .bold))
|
|
.foregroundColor(.yellow)
|
|
.padding(.horizontal, 6)
|
|
.padding(.vertical, 2)
|
|
.background(Color.yellow.opacity(0.2))
|
|
.cornerRadius(4)
|
|
}
|
|
}
|
|
|
|
// 动态内容
|
|
if !moment.content.isEmpty {
|
|
Text(moment.content)
|
|
.font(.system(size: 14))
|
|
.foregroundColor(.white.opacity(0.9))
|
|
.multilineTextAlignment(.leading)
|
|
}
|
|
|
|
// 优化的图片网格
|
|
if let images = moment.dynamicResList, !images.isEmpty {
|
|
OptimizedImageGrid(images: images)
|
|
}
|
|
|
|
// 互动按钮
|
|
HStack(spacing: 20) {
|
|
Button(action: {}) {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "message")
|
|
.font(.system(size: 16))
|
|
Text("\(moment.commentCount)")
|
|
.font(.system(size: 14))
|
|
}
|
|
.foregroundColor(.white.opacity(0.8))
|
|
}
|
|
|
|
Button(action: {}) {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: moment.isLike ? "heart.fill" : "heart")
|
|
.font(.system(size: 16))
|
|
Text("\(moment.likeCount)")
|
|
.font(.system(size: 14))
|
|
}
|
|
.foregroundColor(moment.isLike ? .red : .white.opacity(0.8))
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.top, 8)
|
|
}
|
|
.padding(16)
|
|
.background(
|
|
Color.white.opacity(0.1)
|
|
.cornerRadius(12)
|
|
)
|
|
.onAppear {
|
|
preloadNearbyImages()
|
|
}
|
|
}
|
|
|
|
private func formatTime(_ timestamp: Int) -> String {
|
|
let date = Date(timeIntervalSince1970: TimeInterval(timestamp) / 1000.0)
|
|
let formatter = DateFormatter()
|
|
formatter.locale = Locale(identifier: "zh_CN")
|
|
|
|
let now = Date()
|
|
let interval = now.timeIntervalSince(date)
|
|
|
|
if interval < 60 {
|
|
return "刚刚"
|
|
} else if interval < 3600 {
|
|
return "\(Int(interval / 60))分钟前"
|
|
} else if interval < 86400 {
|
|
return "\(Int(interval / 3600))小时前"
|
|
} else {
|
|
formatter.dateFormat = "MM-dd HH:mm"
|
|
return formatter.string(from: date)
|
|
}
|
|
}
|
|
|
|
private func preloadNearbyImages() {
|
|
var urlsToPreload: [String] = []
|
|
let preloadRange = max(0, currentIndex - 2)...min(allMoments.count - 1, currentIndex + 2)
|
|
for index in preloadRange {
|
|
let moment = allMoments[index]
|
|
urlsToPreload.append(moment.avatar)
|
|
if let images = moment.dynamicResList {
|
|
urlsToPreload.append(contentsOf: images.map { $0.resUrl })
|
|
}
|
|
}
|
|
ImageCacheManager.shared.preloadImages(urls: urlsToPreload)
|
|
}
|
|
}
|
|
|
|
// MARK: - 优化的图片网格
|
|
struct OptimizedImageGrid: View {
|
|
let images: [MomentsPicture]
|
|
|
|
init(images: [MomentsPicture]) {
|
|
self.images = images
|
|
}
|
|
|
|
public var body: some View {
|
|
GeometryReader { geometry in
|
|
let availableWidth = max(geometry.size.width, 1)
|
|
let spacing: CGFloat = 8
|
|
if availableWidth < 10 {
|
|
Color.clear.frame(height: 1)
|
|
} else {
|
|
switch images.count {
|
|
case 1:
|
|
let imageSize: CGFloat = min(availableWidth * 0.6, 200)
|
|
HStack {
|
|
Spacer()
|
|
SquareImageView(image: images[0], size: imageSize)
|
|
Spacer()
|
|
}
|
|
case 2:
|
|
let imageSize: CGFloat = (availableWidth - spacing) / 2
|
|
HStack(spacing: spacing) {
|
|
SquareImageView(image: images[0], size: imageSize)
|
|
SquareImageView(image: images[1], size: imageSize)
|
|
}
|
|
case 3:
|
|
let imageSize: CGFloat = (availableWidth - spacing * 2) / 3
|
|
HStack(spacing: spacing) {
|
|
ForEach(images.prefix(3), id: \ .id) { image in
|
|
SquareImageView(image: image, size: imageSize)
|
|
}
|
|
}
|
|
default:
|
|
let imageSize: CGFloat = (availableWidth - spacing * 2) / 3
|
|
let columns = Array(repeating: GridItem(.fixed(imageSize), spacing: spacing), count: 3)
|
|
LazyVGrid(columns: columns, spacing: spacing) {
|
|
ForEach(images.prefix(9), id: \ .id) { image in
|
|
SquareImageView(image: image, size: imageSize)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.frame(height: calculateGridHeight())
|
|
}
|
|
|
|
private func calculateGridHeight() -> CGFloat {
|
|
switch images.count {
|
|
case 1:
|
|
return 200
|
|
case 2:
|
|
return 120
|
|
case 3:
|
|
return 100
|
|
case 4...6:
|
|
return 216
|
|
default:
|
|
return 340
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 正方形图片视图组件
|
|
struct SquareImageView: View {
|
|
let image: MomentsPicture
|
|
let size: CGFloat
|
|
|
|
init(image: MomentsPicture, size: CGFloat) {
|
|
self.image = image
|
|
self.size = size
|
|
}
|
|
|
|
public var body: some View {
|
|
let safeSize = size.isFinite && size > 0 ? size : 100
|
|
CachedAsyncImage(url: image.resUrl) { imageView in
|
|
imageView
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fill)
|
|
} placeholder: {
|
|
Rectangle()
|
|
.fill(Color.gray.opacity(0.3))
|
|
.overlay(
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .white.opacity(0.6)))
|
|
.scaleEffect(0.8)
|
|
)
|
|
}
|
|
.frame(width: safeSize, height: safeSize)
|
|
.clipped()
|
|
.cornerRadius(8)
|
|
}
|
|
} |