diff --git a/yana/Views/Components/OptimizedDynamicCardView.swift b/yana/Views/Components/OptimizedDynamicCardView.swift index 2550675..d7a2fb9 100644 --- a/yana/Views/Components/OptimizedDynamicCardView.swift +++ b/yana/Views/Components/OptimizedDynamicCardView.swift @@ -8,6 +8,11 @@ struct OptimizedDynamicCardView: View { let allMoments: [MomentsInfo] let currentIndex: Int + // 预览相关状态 + @State private var showPreview = false + @State private var previewImageUrls: [String] = [] + @State private var previewIndex: Int = 0 + init(moment: MomentsInfo, allMoments: [MomentsInfo], currentIndex: Int) { self.moment = moment self.allMoments = allMoments @@ -17,7 +22,7 @@ struct OptimizedDynamicCardView: View { public var body: some View { VStack(alignment: .leading, spacing: 12) { // 用户信息 - HStack { + HStack(alignment: .top) { // 头像 CachedAsyncImage(url: moment.avatar) { image in image @@ -34,56 +39,48 @@ struct OptimizedDynamicCardView: View { } .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)) + Text("ID: \(moment.uid)") .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) - } + // 时间(原VIP位置) + Text(formatDisplayTime(moment.publishTime)) + .font(.system(size: 12, weight: .bold)) + .foregroundColor(.white.opacity(0.8)) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color.white.opacity(0.15)) + .cornerRadius(4) } - + // 动态内容 if !moment.content.isEmpty { Text(moment.content) .font(.system(size: 14)) .foregroundColor(.white.opacity(0.9)) .multilineTextAlignment(.leading) + .padding(.leading, 40 + 8) // 与用户名左边对齐 } - + // 优化的图片网格 if let images = moment.dynamicResList, !images.isEmpty { - OptimizedImageGrid(images: images) + OptimizedImageGrid(images: images) { tappedIndex in + previewImageUrls = images.map { $0.resUrl } + previewIndex = tappedIndex + showPreview = true + } + .padding(.bottom, images.count == 2 ? 16 : 0) // 两张图片时增加底部间距 } - + // 互动按钮 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)) - } - + // Like 按钮左对齐 Button(action: {}) { HStack(spacing: 4) { Image(systemName: moment.isLike ? "heart.fill" : "heart") @@ -93,7 +90,6 @@ struct OptimizedDynamicCardView: View { } .foregroundColor(moment.isLike ? .red : .white.opacity(0.8)) } - Spacer() } .padding(.top, 8) @@ -106,6 +102,13 @@ struct OptimizedDynamicCardView: View { .onAppear { preloadNearbyImages() } + // 图片预览弹窗 + .fullScreenCover(isPresented: $showPreview) { + ImagePreviewPager(images: previewImageUrls, currentIndex: $previewIndex) { + showPreview = false + previewImageUrls = [] + } + } } private func formatTime(_ timestamp: Int) -> String { @@ -128,6 +131,28 @@ struct OptimizedDynamicCardView: View { } } + // 新增:时间显示逻辑 + private func formatDisplayTime(_ 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) + let calendar = Calendar.current + if calendar.isDateInToday(date) { + if interval < 60 { + return "刚刚" + } else if interval < 3600 { + return "\(Int(interval / 60))分钟前" + } else { + return "\(Int(interval / 3600))小时前" + } + } else { + formatter.dateFormat = "MM/dd" + return formatter.string(from: date) + } + } + private func preloadNearbyImages() { var urlsToPreload: [String] = [] let preloadRange = max(0, currentIndex - 2)...min(allMoments.count - 1, currentIndex + 2) @@ -140,14 +165,18 @@ struct OptimizedDynamicCardView: View { } ImageCacheManager.shared.preloadImages(urls: urlsToPreload) } + + // 移除批量下载UIImage逻辑,直接用URL数组 } // MARK: - 优化的图片网格 struct OptimizedImageGrid: View { let images: [MomentsPicture] + let onImageTap: (Int) -> Void - init(images: [MomentsPicture]) { + init(images: [MomentsPicture], onImageTap: @escaping (Int) -> Void) { self.images = images + self.onImageTap = onImageTap } public var body: some View { @@ -162,28 +191,38 @@ struct OptimizedImageGrid: View { let imageSize: CGFloat = min(availableWidth * 0.6, 200) HStack { Spacer() - SquareImageView(image: images[0], size: imageSize) + SquareImageView(image: images[0], size: imageSize) { + onImageTap(0) + } Spacer() } case 2: let imageSize: CGFloat = (availableWidth - spacing) / 2 HStack(spacing: spacing) { - SquareImageView(image: images[0], size: imageSize) - SquareImageView(image: images[1], size: imageSize) + SquareImageView(image: images[0], size: imageSize) { + onImageTap(0) + } + SquareImageView(image: images[1], size: imageSize) { + onImageTap(1) + } } 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) + ForEach(Array(images.prefix(3).enumerated()), id: \ .element.id) { idx, image in + SquareImageView(image: image, size: imageSize) { + onImageTap(idx) + } } } 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) + ForEach(Array(images.prefix(9).enumerated()), id: \ .element.id) { idx, image in + SquareImageView(image: image, size: imageSize) { + onImageTap(idx) + } } } } @@ -212,14 +251,32 @@ struct OptimizedImageGrid: View { struct SquareImageView: View { let image: MomentsPicture let size: CGFloat + let onTap: (() -> Void)? - init(image: MomentsPicture, size: CGFloat) { + init(image: MomentsPicture, size: CGFloat, onTap: (() -> Void)? = nil) { self.image = image self.size = size + self.onTap = onTap } public var body: some View { let safeSize = size.isFinite && size > 0 ? size : 100 + Group { + if let onTap = onTap { + Button(action: onTap) { + imageContent + } + .buttonStyle(PlainButtonStyle()) + } else { + imageContent + } + } + .frame(width: safeSize, height: safeSize) + .clipped() + .cornerRadius(8) + } + + private var imageContent: some View { CachedAsyncImage(url: image.resUrl) { imageView in imageView .resizable() @@ -233,8 +290,5 @@ struct SquareImageView: View { .scaleEffect(0.8) ) } - .frame(width: safeSize, height: safeSize) - .clipped() - .cornerRadius(8) } -} \ No newline at end of file +} diff --git a/yana/Views/ImagePreviewPager.swift b/yana/Views/ImagePreviewPager.swift index 88aa86f..63c4839 100644 --- a/yana/Views/ImagePreviewPager.swift +++ b/yana/Views/ImagePreviewPager.swift @@ -1,20 +1,78 @@ import SwiftUI +enum ImagePreviewSource: Identifiable, Equatable { + case local(UIImage) + case remote(String) + var id: String { + switch self { + case .local(let img): + return String(describing: img.hashValue) + case .remote(let url): + return url + } + } + static func == (lhs: ImagePreviewSource, rhs: ImagePreviewSource) -> Bool { + switch (lhs, rhs) { + case let (.local(l), .local(r)): + return l.pngData() == r.pngData() + case let (.remote(l), .remote(r)): + return l == r + default: + return false + } + } +} + struct ImagePreviewPager: View { - let images: [UIImage] + let images: [ImagePreviewSource] @Binding var currentIndex: Int let onClose: () -> Void + + // 本地图片构造器 + init(images: [UIImage], currentIndex: Binding, onClose: @escaping () -> Void) { + self.images = images.map { .local($0) } + self._currentIndex = currentIndex + self.onClose = onClose + } + // 远程图片构造器 + init(images: [String], currentIndex: Binding, onClose: @escaping () -> Void) { + self.images = images.map { .remote($0) } + self._currentIndex = currentIndex + self.onClose = onClose + } + var body: some View { ZStack(alignment: .topTrailing) { Color.black.ignoresSafeArea() TabView(selection: $currentIndex) { - ForEach(images.indices, id: \Int.self) { idx in - Image(uiImage: images[idx]) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.black) - .tag(idx) + ForEach(Array(images.enumerated()), id: \ .element.id) { idx, source in + Group { + switch source { + case .local(let img): + Image(uiImage: img) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.black) + case .remote(let urlStr): + CachedAsyncImage(url: urlStr) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.black) + } placeholder: { + Rectangle() + .fill(Color.gray.opacity(0.3)) + .overlay( + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .white.opacity(0.6))) + .scaleEffect(0.8) + ) + } + } + } + .tag(idx) } } .tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))