feat: 更新视图组件以增强用户交互体验和图片处理功能
- 在AppSettingView中重构主视图逻辑,优化图片选择与预览功能。 - 在FeedListFeature中改进点赞状态管理,确保动态更新流畅。 - 在DetailView中添加卡片点击回调,提升用户交互体验。 - 在OptimizedDynamicCardView中新增卡片点击手势,支持非详情页模式下的交互。 - 在swift-assistant-style.mdc中更新功能要求,强调使用函数式编程。
This commit is contained in:
@@ -14,14 +14,11 @@ I would like advice on using the latest tools and seek step-by-step guidance to
|
||||
As a professional AI programming assistant, your task is to provide me with clear, readable, and efficient code. You should:
|
||||
|
||||
- Use the latest versions of SwiftUI, Swift(6), and TCA(1.20.2), and be familiar with the latest features and best practices.
|
||||
|
||||
- Use Functional Programming.
|
||||
- Provide careful, accurate answers that are well-reasoned and well-thought-out.
|
||||
|
||||
- **Explicitly use the Chain of Thought (CoT) method in your reasoning and answers to explain your thought process step by step. **
|
||||
- **Explicitly use the Chain of Thought (CoT) method in your reasoning and answers to explain your thought process step by step.**
|
||||
- Follow my instructions and complete the task meticulously.
|
||||
|
||||
- Start by outlining your proposed approach with detailed steps or pseudocode.
|
||||
|
||||
- Once you have confirmed your plan, start writing code.
|
||||
- After coding is done, no compilation check is required, remind me to check
|
||||
|
||||
|
@@ -181,14 +181,11 @@ struct FeedListFeature {
|
||||
}
|
||||
|
||||
case let .likeResponse(.success(response), dynamicId):
|
||||
state.likeLoadingDynamicIds.remove(dynamicId)
|
||||
if let data = response.data, let success = data.success, success {
|
||||
// 根据API响应更新点赞状态
|
||||
if let index = state.moments.firstIndex(where: { $0.dynamicId == dynamicId }) {
|
||||
// 根据当前状态推断新的点赞状态
|
||||
let currentMoment = state.moments[index]
|
||||
let newLikeState = !currentMoment.isLike // 切换点赞状态
|
||||
|
||||
// 创建更新后的动态对象
|
||||
let newLikeState = !currentMoment.isLike
|
||||
let updatedMoment = MomentsInfo(
|
||||
dynamicId: currentMoment.dynamicId,
|
||||
uid: currentMoment.uid,
|
||||
@@ -223,22 +220,14 @@ struct FeedListFeature {
|
||||
)
|
||||
state.moments[index] = updatedMoment
|
||||
}
|
||||
// 移除loading状态
|
||||
state.likeLoadingDynamicIds.removeAll()
|
||||
} else {
|
||||
// API返回失败,通过APILoadingManager显示错误信息
|
||||
let errorMessage = response.message.isEmpty ? "点赞失败,请重试" : response.message
|
||||
setAPILoadingErrorSync(UUID(), errorMessage: errorMessage)
|
||||
}
|
||||
|
||||
// 移除所有loading状态
|
||||
state.likeLoadingDynamicIds.removeAll()
|
||||
return .none
|
||||
|
||||
case let .likeResponse(.failure(error), dynamicId):
|
||||
// 移除loading状态
|
||||
state.likeLoadingDynamicIds.removeAll()
|
||||
// 通过APILoadingManager显示错误信息
|
||||
state.likeLoadingDynamicIds.remove(dynamicId)
|
||||
setAPILoadingErrorSync(UUID(), errorMessage: error.localizedDescription)
|
||||
return .none
|
||||
}
|
||||
@@ -253,4 +242,4 @@ enum Feed: Equatable, Identifiable {
|
||||
case .placeholder(let id): return id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -33,134 +33,144 @@ struct AppSettingView: View {
|
||||
var body: some View {
|
||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
WithPerceptionTracking {
|
||||
ZStack {
|
||||
mainContent(viewStore: viewStore)
|
||||
}
|
||||
.confirmationDialog(
|
||||
"请选择图片来源",
|
||||
isPresented: $showActionSheet,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("拍照") { showCamera = true }
|
||||
Button("从相册选择") { showPhotoPicker = true }
|
||||
Button("取消", role: .cancel) {}
|
||||
}
|
||||
.photosPicker(
|
||||
isPresented: $showPhotoPicker,
|
||||
selection: $selectedPhotoItems,
|
||||
maxSelectionCount: 1,
|
||||
matching: .images
|
||||
)
|
||||
.sheet(isPresented: $showCamera) {
|
||||
CameraPicker { image in
|
||||
print("[LOG] CameraPicker回调,image: \(image != nil)")
|
||||
if let image = image {
|
||||
print("[LOG] CameraPicker获得图片,直接上传头像")
|
||||
if let data = image.jpegData(compressionQuality: 0.8) {
|
||||
viewStore.send(AppSettingFeature.Action.avatarSelected(data))
|
||||
mainView(viewStore: viewStore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func mainView(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||
contentWithImagePickers(viewStore: viewStore)
|
||||
.onChange(of: selectedPhotoItems) { items in
|
||||
print("[LOG] PhotosPicker选中items: \(items.count)")
|
||||
guard !items.isEmpty else { return }
|
||||
isLoading = true
|
||||
selectedImages = []
|
||||
let group = DispatchGroup()
|
||||
var tempImages: [UIImage] = []
|
||||
for item in items {
|
||||
group.enter()
|
||||
item.loadTransferable(type: Data.self) { result in
|
||||
defer { group.leave() }
|
||||
if let data = try? result.get(), let uiImage = UIImage(data: data) {
|
||||
DispatchQueue.main.async {
|
||||
tempImages.append(uiImage)
|
||||
print("[LOG] 成功加载图片,当前tempImages数量: \(tempImages.count)")
|
||||
}
|
||||
} else {
|
||||
errorMessage = "拍照失败,请重试"
|
||||
print("[LOG] 图片加载失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
DispatchQueue.global().async {
|
||||
group.wait()
|
||||
DispatchQueue.main.async {
|
||||
isLoading = false
|
||||
print("[LOG] 所有图片加载完成,tempImages数量: \(tempImages.count)")
|
||||
if tempImages.isEmpty {
|
||||
errorMessage = "图片加载失败,请重试"
|
||||
// 强制关闭所有弹窗
|
||||
showPreview = false
|
||||
showCamera = false
|
||||
showPhotoPicker = false
|
||||
showActionSheet = false
|
||||
print("[LOG] CameraPicker无图片,弹出错误提示")
|
||||
}
|
||||
showCamera = false
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showPreview) {
|
||||
ImagePreviewView(
|
||||
images: $selectedImages,
|
||||
currentIndex: .constant(0),
|
||||
onConfirm: {
|
||||
print("[LOG] 预览确认,准备上传头像")
|
||||
if let image = selectedImages.first, let data = image.jpegData(compressionQuality: 0.8) {
|
||||
viewStore.send(AppSettingFeature.Action.avatarSelected(data))
|
||||
}
|
||||
showPreview = false
|
||||
},
|
||||
onCancel: {
|
||||
print("[LOG] 预览取消")
|
||||
showPreview = false
|
||||
}
|
||||
)
|
||||
}
|
||||
.onChange(of: selectedPhotoItems) { items in
|
||||
print("[LOG] PhotosPicker选中items: \(items.count)")
|
||||
guard !items.isEmpty else { return }
|
||||
isLoading = true
|
||||
selectedImages = []
|
||||
let group = DispatchGroup()
|
||||
var tempImages: [UIImage] = []
|
||||
for item in items {
|
||||
group.enter()
|
||||
item.loadTransferable(type: Data.self) { result in
|
||||
defer { group.leave() }
|
||||
if let data = try? result.get(), let uiImage = UIImage(data: data) {
|
||||
DispatchQueue.main.async {
|
||||
tempImages.append(uiImage)
|
||||
print("[LOG] 成功加载图片,当前tempImages数量: \(tempImages.count)")
|
||||
}
|
||||
} else {
|
||||
print("[LOG] 图片加载失败")
|
||||
print("[LOG] PhotosPicker图片加载失败,弹出错误提示")
|
||||
} else {
|
||||
// 先设置selectedImages,确保预览组件能接收到图片
|
||||
selectedImages = tempImages
|
||||
print("[LOG] selectedImages已设置,数量: \(selectedImages.count)")
|
||||
// 确保在主线程上设置showPreview
|
||||
DispatchQueue.main.async {
|
||||
showPreview = true
|
||||
print("[LOG] showPreview已设置为true")
|
||||
}
|
||||
}
|
||||
}
|
||||
DispatchQueue.global().async {
|
||||
group.wait()
|
||||
DispatchQueue.main.async {
|
||||
isLoading = false
|
||||
print("[LOG] 所有图片加载完成,tempImages数量: \(tempImages.count)")
|
||||
if tempImages.isEmpty {
|
||||
errorMessage = "图片加载失败,请重试"
|
||||
// 强制关闭所有弹窗
|
||||
showPreview = false
|
||||
showCamera = false
|
||||
showPhotoPicker = false
|
||||
showActionSheet = false
|
||||
print("[LOG] PhotosPicker图片加载失败,弹出错误提示")
|
||||
} else {
|
||||
// 先设置selectedImages,确保预览组件能接收到图片
|
||||
selectedImages = tempImages
|
||||
print("[LOG] selectedImages已设置,数量: \(selectedImages.count)")
|
||||
// 确保在主线程上设置showPreview
|
||||
DispatchQueue.main.async {
|
||||
showPreview = true
|
||||
print("[LOG] showPreview已设置为true")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(isPresented: Binding<Bool>(
|
||||
get: { errorMessage != nil },
|
||||
set: { if !$0 { errorMessage = nil } }
|
||||
)) {
|
||||
print("[LOG] 错误弹窗弹出: \(errorMessage ?? "")")
|
||||
return Alert(title: Text("错误"), message: Text(errorMessage ?? ""), dismissButton: .default(Text("确定"), action: {
|
||||
// 强制关闭所有弹窗,放到action中,避免在视图更新周期set状态
|
||||
showPreview = false
|
||||
showCamera = false
|
||||
showPhotoPicker = false
|
||||
showActionSheet = false
|
||||
}))
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
.alert("修改昵称", isPresented: $showNicknameAlert) {
|
||||
nicknameAlertContent(viewStore: viewStore)
|
||||
} message: {
|
||||
Text("昵称最长15个字符")
|
||||
}
|
||||
.sheet(isPresented: userAgreementBinding(viewStore: viewStore)) {
|
||||
WebView(url: URL(string: "https://www.yana.com/user-agreement")!)
|
||||
}
|
||||
.sheet(isPresented: privacyPolicyBinding(viewStore: viewStore)) {
|
||||
WebView(url: URL(string: "https://www.yana.com/privacy-policy")!)
|
||||
}
|
||||
}
|
||||
.alert(isPresented: Binding<Bool>(
|
||||
get: { errorMessage != nil },
|
||||
set: { if !$0 { errorMessage = nil } }
|
||||
)) {
|
||||
print("[LOG] 错误弹窗弹出: \(errorMessage ?? "")")
|
||||
return Alert(title: Text("错误"), message: Text(errorMessage ?? ""), dismissButton: .default(Text("确定"), action: {
|
||||
// 强制关闭所有弹窗,放到action中,避免在视图更新周期set状态
|
||||
showPreview = false
|
||||
showCamera = false
|
||||
showPhotoPicker = false
|
||||
showActionSheet = false
|
||||
}))
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
.alert("修改昵称", isPresented: $showNicknameAlert) {
|
||||
nicknameAlertContent(viewStore: viewStore)
|
||||
} message: {
|
||||
Text("昵称最长15个字符")
|
||||
}
|
||||
.sheet(isPresented: userAgreementBinding(viewStore: viewStore)) {
|
||||
WebView(url: APIConfiguration.webURL(for: .userAgreement)!)
|
||||
}
|
||||
.sheet(isPresented: privacyPolicyBinding(viewStore: viewStore)) {
|
||||
WebView(url: APIConfiguration.webURL(for: .privacyPolicy)!)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func contentWithImagePickers(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||
ZStack {
|
||||
mainContent(viewStore: viewStore)
|
||||
}
|
||||
.confirmationDialog(
|
||||
"请选择图片来源",
|
||||
isPresented: $showActionSheet,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("拍照") { showCamera = true }
|
||||
Button("从相册选择") { showPhotoPicker = true }
|
||||
Button("取消", role: .cancel) {}
|
||||
}
|
||||
.photosPicker(
|
||||
isPresented: $showPhotoPicker,
|
||||
selection: $selectedPhotoItems,
|
||||
maxSelectionCount: 1,
|
||||
matching: .images
|
||||
)
|
||||
.sheet(isPresented: $showCamera) {
|
||||
CameraPicker { image in
|
||||
print("[LOG] CameraPicker回调,image: \(image != nil)")
|
||||
if let image = image {
|
||||
print("[LOG] CameraPicker获得图片,直接上传头像")
|
||||
if let data = image.jpegData(compressionQuality: 0.8) {
|
||||
viewStore.send(AppSettingFeature.Action.avatarSelected(data))
|
||||
}
|
||||
} else {
|
||||
errorMessage = "拍照失败,请重试"
|
||||
// 强制关闭所有弹窗
|
||||
showPreview = false
|
||||
showCamera = false
|
||||
showPhotoPicker = false
|
||||
showActionSheet = false
|
||||
print("[LOG] CameraPicker无图片,弹出错误提示")
|
||||
}
|
||||
showCamera = false
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showPreview) {
|
||||
ImagePreviewView(
|
||||
images: $selectedImages,
|
||||
currentIndex: .constant(0),
|
||||
onConfirm: {
|
||||
print("[LOG] 预览确认,准备上传头像")
|
||||
if let image = selectedImages.first, let data = image.jpegData(compressionQuality: 0.8) {
|
||||
viewStore.send(AppSettingFeature.Action.avatarSelected(data))
|
||||
}
|
||||
showPreview = false
|
||||
},
|
||||
onCancel: {
|
||||
print("[LOG] 预览取消")
|
||||
showPreview = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,17 +11,20 @@ struct OptimizedDynamicCardView: View {
|
||||
let onImageTap: (_ images: [String], _ index: Int) -> Void
|
||||
// 新增:点赞回调
|
||||
let onLikeTap: (_ dynamicId: Int, _ uid: Int, _ likedUid: Int, _ worldId: Int) -> Void
|
||||
// 新增:卡片点击回调
|
||||
let onCardTap: (() -> 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, isDetailMode: Bool = false, isLikeLoading: Bool = false) {
|
||||
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, 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.isDetailMode = isDetailMode
|
||||
self.isLikeLoading = isLikeLoading
|
||||
}
|
||||
@@ -39,6 +42,15 @@ struct OptimizedDynamicCardView: View {
|
||||
.shadow(color: Color(red: 0.43, green: 0.43, blue: 0.43, opacity: 0.34), radius: 10.7, x: 0, y: 1.9)
|
||||
}
|
||||
|
||||
// 卡片点击手势 - 仅在非详情页模式且有回调时显示
|
||||
if !isDetailMode, let onCardTap = onCardTap {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
onCardTap()
|
||||
}
|
||||
}
|
||||
|
||||
// 内容层
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
// 用户信息
|
||||
|
@@ -52,6 +52,7 @@ struct DetailView: View {
|
||||
onLikeTap: { dynamicId, uid, likedUid, worldId in
|
||||
store.send(.likeDynamic(dynamicId, uid, likedUid, worldId))
|
||||
},
|
||||
onCardTap: nil, // 详情页不需要卡片点击
|
||||
isDetailMode: true, // 详情页模式,点击卡片不跳转
|
||||
isLikeLoading: store.isLikeLoading
|
||||
)
|
||||
|
@@ -95,12 +95,10 @@ struct MomentCardView: View {
|
||||
currentIndex: index,
|
||||
onImageTap: onImageTap,
|
||||
onLikeTap: onLikeTap,
|
||||
onCardTap: onTap,
|
||||
isDetailMode: false,
|
||||
isLikeLoading: isLikeLoading
|
||||
)
|
||||
.onTapGesture {
|
||||
onTap()
|
||||
}
|
||||
|
||||
// 上拉加载更多触发点
|
||||
if isLastItem && hasMore && !isLoadingMore {
|
||||
|
@@ -6,6 +6,9 @@ struct MeView: View {
|
||||
// 新增:图片预览状态
|
||||
@State private var previewItem: PreviewItem? = nil
|
||||
@State private var previewCurrentIndex: Int = 0
|
||||
// 新增:详情页状态
|
||||
@State private var showDetail: Bool = false
|
||||
@State private var selectedMoment: MomentsInfo? = nil
|
||||
|
||||
var body: some View {
|
||||
WithViewStore(self.store, observe: { $0 }) { viewStore in
|
||||
@@ -60,6 +63,22 @@ struct MeView: View {
|
||||
previewItem = nil
|
||||
}
|
||||
}
|
||||
// 新增:详情页导航
|
||||
.navigationDestination(isPresented: $showDetail) {
|
||||
if let selectedMoment = selectedMoment {
|
||||
DetailView(
|
||||
store: Store(
|
||||
initialState: DetailFeature.State(moment: selectedMoment)
|
||||
) {
|
||||
DetailFeature()
|
||||
},
|
||||
onDismiss: {
|
||||
showDetail = false
|
||||
selectedMoment = nil
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +164,10 @@ struct MeView: View {
|
||||
},
|
||||
onLikeTap: { _, _, _, _ in
|
||||
// 暂时不处理点赞,后续可以添加点赞功能
|
||||
},
|
||||
onCardTap: {
|
||||
self.selectedMoment = moment
|
||||
self.showDetail = true
|
||||
}
|
||||
)
|
||||
.padding(.horizontal, 12)
|
||||
|
Reference in New Issue
Block a user