import SwiftUI import ComposableArchitecture struct FeedView: View { let store: StoreOf var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in GeometryReader { geometry in ScrollView { VStack(spacing: 20) { // 顶部区域 - 标题和加号按钮 HStack { Spacer() // 标题 Text("Enjoy your Life Time") .font(.system(size: 22, weight: .semibold)) .foregroundColor(.white) Spacer() // 右侧加号按钮 Button(action: { // 加号按钮操作 }) { Image("add icon") .frame(width: 36, height: 36) } } .padding(.horizontal, 20) // 心脏图标 Image(systemName: "heart.fill") .font(.system(size: 60)) .foregroundColor(.red) .padding(.top, 40) // 励志文字 Text("The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable.") .font(.system(size: 16)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.9)) .padding(.horizontal, 30) .padding(.top, 20) // 真实动态数据 LazyVStack(spacing: 16) { if viewStore.moments.isEmpty { // 空状态 VStack(spacing: 12) { Image(systemName: "heart.text.square") .font(.system(size: 40)) .foregroundColor(.white.opacity(0.6)) Text("暂无动态内容") .font(.system(size: 16)) .foregroundColor(.white.opacity(0.8)) if let error = viewStore.error { Text("错误: \(error)") .font(.system(size: 12)) .foregroundColor(.red.opacity(0.8)) .multilineTextAlignment(.center) .padding(.horizontal, 20) } } .padding(.top, 40) } else { // 显示真实动态数据 ForEach(Array(viewStore.moments.enumerated()), id: \.element.dynamicId) { index, moment in OptimizedDynamicCardView( moment: moment, allMoments: viewStore.moments, currentIndex: index ) } } } .padding(.horizontal, 16) .padding(.top, 30) // 加载状态 if viewStore.isLoading { HStack { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) Text("加载中...") .font(.system(size: 14)) .foregroundColor(.white.opacity(0.8)) } .padding(.top, 20) } // 底部安全区域 Color.clear.frame(height: geometry.safeAreaInsets.bottom + 100) } } .refreshable { viewStore.send(.loadLatestMoments) } } .onAppear { viewStore.send(.onAppear) } } } } // MARK: - 优化的动态卡片组件 struct OptimizedDynamicCardView: View { let moment: MomentsInfo let allMoments: [MomentsInfo] let currentIndex: Int 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] = [] // 预加载前后2个动态的图片 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] var body: some View { GeometryReader { geometry in let availableWidth = geometry.size.width let spacing: CGFloat = 8 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: // 四张及以上:九宫格布局(最多9张) 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 // 九宫格2行的高度 (实际图片大小 * 2 + 间距) default: return 340 // 九宫格3行的高度 (实际图片大小 * 3 + 间距 + 额外空间) } } } // MARK: - 正方形图片视图组件 struct SquareImageView: View { let image: MomentsPicture let size: CGFloat var body: some View { 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: size, height: size) .clipped() .cornerRadius(8) } } // MARK: - 旧的真实动态卡片组件(保留备用) struct RealDynamicCardView: View { let moment: MomentsInfo var body: some View { VStack(alignment: .leading, spacing: 12) { // 用户信息 HStack { AsyncImage(url: URL(string: 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 { LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: min(images.count, 3)), spacing: 8) { ForEach(images.prefix(9), id: \.id) { image in AsyncImage(url: URL(string: 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))) ) } .frame(height: 100) .clipped() .cornerRadius(8) } } } // 互动按钮 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) ) } 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) } } } // MARK: - 旧的模拟卡片组件(保留备用) struct DynamicCardView: View { let index: Int var body: some View { VStack(alignment: .leading, spacing: 12) { // 用户信息 HStack { Circle() .fill(Color.gray.opacity(0.3)) .frame(width: 40, height: 40) .overlay( Text("U\(index + 1)") .font(.system(size: 16, weight: .medium)) .foregroundColor(.white) ) VStack(alignment: .leading, spacing: 2) { Text("用户\(index + 1)") .font(.system(size: 16, weight: .medium)) .foregroundColor(.white) Text("2小时前") .font(.system(size: 12)) .foregroundColor(.white.opacity(0.6)) } Spacer() } // 动态内容 Text("今天是美好的一天,分享一些生活中的小确幸。希望大家都能珍惜每一个当下的时刻。") .font(.system(size: 14)) .foregroundColor(.white.opacity(0.9)) .multilineTextAlignment(.leading) // 图片网格 LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), spacing: 8) { ForEach(0..<3) { imageIndex in Rectangle() .fill(Color.gray.opacity(0.3)) .aspectRatio(1, contentMode: .fit) .overlay( Image(systemName: "photo") .foregroundColor(.white.opacity(0.6)) ) } } // 互动按钮 HStack(spacing: 20) { Button(action: {}) { HStack(spacing: 4) { Image(systemName: "message") .font(.system(size: 16)) Text("354") .font(.system(size: 14)) } .foregroundColor(.white.opacity(0.8)) } Button(action: {}) { HStack(spacing: 4) { Image(systemName: "heart") .font(.system(size: 16)) Text("354") .font(.system(size: 14)) } .foregroundColor(.white.opacity(0.8)) } Spacer() } .padding(.top, 8) } .padding(16) .background( Color.white.opacity(0.1) .cornerRadius(12) ) } } #Preview { FeedView( store: Store(initialState: FeedFeature.State()) { FeedFeature() } ) }