
- 在DetailView中添加头像点击功能,支持展示非当前用户的主页。 - 更新OptimizedDynamicCardView以支持头像点击回调。 - 修改DetailFeature以管理用户主页显示状态。 - 在MeView中添加关闭按钮支持,优化用户体验。 - 确保其他页面的兼容性,未影响现有功能。
339 lines
13 KiB
Swift
339 lines
13 KiB
Swift
import SwiftUI
|
||
import ComposableArchitecture
|
||
import Foundation
|
||
|
||
// MARK: - 优化的动态卡片组件
|
||
struct OptimizedDynamicCardView: View {
|
||
let moment: MomentsInfo
|
||
let allMoments: [MomentsInfo]
|
||
let currentIndex: Int
|
||
// 新增:图片点击回调
|
||
let onImageTap: (_ images: [String], _ index: Int) -> Void
|
||
// 新增:点赞回调
|
||
let onLikeTap: (_ dynamicId: Int, _ uid: Int, _ likedUid: Int, _ worldId: Int) -> Void
|
||
// 新增:卡片点击回调
|
||
let onCardTap: (() -> Void)?
|
||
// 新增:头像点击回调
|
||
let onAvatarTap: (() -> Void)?
|
||
// 新增:详情页模式,点击卡片不跳转
|
||
let isDetailMode: Bool
|
||
// 新增:点赞loading状态
|
||
let isLikeLoading: Bool
|
||
|
||
init(moment: MomentsInfo, allMoments: [MomentsInfo], currentIndex: Int, onImageTap: @escaping (_ images: [String], _ index: Int) -> Void, onLikeTap: @escaping (_ dynamicId: Int, _ uid: Int, _ likedUid: Int, _ worldId: Int) -> Void, onCardTap: (() -> Void)? = nil, onAvatarTap: (() -> Void)? = nil, isDetailMode: Bool = false, isLikeLoading: Bool = false) {
|
||
self.moment = moment
|
||
self.allMoments = allMoments
|
||
self.currentIndex = currentIndex
|
||
self.onImageTap = onImageTap
|
||
self.onLikeTap = onLikeTap
|
||
self.onCardTap = onCardTap
|
||
self.onAvatarTap = onAvatarTap
|
||
self.isDetailMode = isDetailMode
|
||
self.isLikeLoading = isLikeLoading
|
||
}
|
||
|
||
public var body: some View {
|
||
ZStack {
|
||
// 背景层 - 仅在非详情页模式下显示
|
||
if !isDetailMode {
|
||
RoundedRectangle(cornerRadius: 12)
|
||
.fill(Color.clear)
|
||
.overlay(
|
||
RoundedRectangle(cornerRadius: 12)
|
||
.stroke(Color.white.opacity(0.1), lineWidth: 1)
|
||
)
|
||
.shadow(color: Color(red: 0.43, green: 0.43, blue: 0.43, opacity: 0.34), radius: 10.7, x: 0, y: 1.9)
|
||
}
|
||
|
||
// 内容层
|
||
VStack(alignment: .leading, spacing: 10) {
|
||
// 用户信息
|
||
HStack(alignment: .top) {
|
||
// 头像
|
||
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())
|
||
.onTapGesture {
|
||
if let onAvatarTap = onAvatarTap {
|
||
onAvatarTap()
|
||
}
|
||
}
|
||
|
||
VStack(alignment: .leading, spacing: 2) {
|
||
Text(moment.nick)
|
||
.font(.system(size: 16, weight: .medium))
|
||
.foregroundColor(.white)
|
||
.allowsHitTesting(false) // 不拦截点击事件
|
||
UserIDDisplay(uid: moment.uid, fontSize: 12, textColor: .white.opacity(0.6))
|
||
.allowsHitTesting(false) // 不拦截点击事件
|
||
}
|
||
Spacer()
|
||
// 时间(原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)
|
||
.allowsHitTesting(false) // 不拦截点击事件
|
||
}
|
||
|
||
// 动态内容
|
||
if !moment.content.isEmpty {
|
||
Text(moment.content)
|
||
.font(.system(size: 14))
|
||
.foregroundColor(.white.opacity(0.9))
|
||
.multilineTextAlignment(.leading)
|
||
.padding(.leading, 40 + 8) // 与用户名左边对齐
|
||
.allowsHitTesting(false) // 不拦截点击事件
|
||
}
|
||
|
||
// 优化的图片网格
|
||
if let images = moment.dynamicResList, !images.isEmpty {
|
||
OptimizedImageGrid(images: images) { tappedIndex in
|
||
let urls = images.map { $0.resUrl ?? "" }
|
||
onImageTap(urls, tappedIndex)
|
||
}
|
||
.padding(.leading, 40 + 8)
|
||
.padding(.bottom, images.count == 2 ? 30 : 0) // 两张图片时增加底部间距
|
||
.allowsHitTesting(true) // 图片网格需要响应点击事件
|
||
}
|
||
|
||
// 互动按钮
|
||
HStack(spacing: 20) {
|
||
// Like 按钮与用户名左侧对齐
|
||
Button(action: {
|
||
if !isLikeLoading {
|
||
onLikeTap(moment.dynamicId, moment.uid, moment.uid, moment.worldId)
|
||
}
|
||
}) {
|
||
HStack(spacing: 4) {
|
||
if isLikeLoading {
|
||
ProgressView()
|
||
.progressViewStyle(CircularProgressViewStyle(tint: moment.isLike ? .red : .white.opacity(0.8)))
|
||
.scaleEffect(0.8)
|
||
} else {
|
||
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))
|
||
}
|
||
.disabled(isLikeLoading)
|
||
.padding(.leading, 40 + 8) // 与用户名左侧对齐(头像宽度 + 间距)
|
||
.allowsHitTesting(true) // Like 按钮需要响应点击事件
|
||
Spacer()
|
||
}
|
||
.padding(.top, 8)
|
||
}
|
||
.padding(16)
|
||
// 卡片点击手势 - 仅在非详情页模式且有回调时显示
|
||
.contentShape(Rectangle())
|
||
.onTapGesture {
|
||
if !isDetailMode, let onCardTap = onCardTap {
|
||
onCardTap()
|
||
}
|
||
}
|
||
}
|
||
.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 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)
|
||
for index in preloadRange {
|
||
let moment = allMoments[index]
|
||
urlsToPreload.append(moment.avatar)
|
||
if let images = moment.dynamicResList {
|
||
urlsToPreload.append(contentsOf: images.compactMap { $0.resUrl })
|
||
}
|
||
}
|
||
ImageCacheManager.shared.preloadImages(urls: urlsToPreload)
|
||
}
|
||
|
||
// 移除批量下载UIImage逻辑,直接用URL数组
|
||
}
|
||
|
||
// MARK: - 优化的图片网格
|
||
struct OptimizedImageGrid: View {
|
||
let images: [MomentsPicture]
|
||
let onImageTap: (Int) -> Void
|
||
|
||
init(images: [MomentsPicture], onImageTap: @escaping (Int) -> Void) {
|
||
self.images = images
|
||
self.onImageTap = onImageTap
|
||
}
|
||
|
||
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) {
|
||
onImageTap(0)
|
||
}
|
||
Spacer()
|
||
}
|
||
case 2:
|
||
let imageSize: CGFloat = (availableWidth - spacing) / 2
|
||
HStack(spacing: spacing) {
|
||
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(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(Array(images.prefix(9).enumerated()), id: \ .element.id) { idx, image in
|
||
SquareImageView(image: image, size: imageSize) {
|
||
onImageTap(idx)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.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
|
||
let onTap: (() -> Void)?
|
||
|
||
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()
|
||
.aspectRatio(contentMode: .fill)
|
||
} placeholder: {
|
||
Rectangle()
|
||
.fill(Color.gray.opacity(0.3))
|
||
.overlay(
|
||
ProgressView()
|
||
.progressViewStyle(CircularProgressViewStyle(tint: .white.opacity(0.6)))
|
||
.scaleEffect(0.8)
|
||
)
|
||
}
|
||
}
|
||
}
|