
- 在Podfile中添加Alamofire依赖,并更新Podfile.lock以反映更改。 - 新增动态内容API文档,详细描述`dynamic/square/latestDynamics`接口的请求参数、响应数据结构及示例。 - 实现动态内容的模型和API请求结构,支持获取最新动态列表。 - 更新FeedView和HomeView以集成动态内容展示,增强用户体验。 - 添加动态卡片组件,展示用户动态信息及互动功能。
329 lines
13 KiB
Swift
329 lines
13 KiB
Swift
import SwiftUI
|
||
import ComposableArchitecture
|
||
|
||
struct FeedView: View {
|
||
let store: StoreOf<FeedFeature>
|
||
|
||
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(viewStore.moments, id: \.dynamicId) { moment in
|
||
RealDynamicCardView(moment: moment)
|
||
}
|
||
}
|
||
}
|
||
.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 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()
|
||
}
|
||
)
|
||
}
|