feat: 更新视图组件以增强用户交互体验和图片处理功能

- 在AppSettingView中重构主视图逻辑,优化图片选择与预览功能。
- 在FeedListFeature中改进点赞状态管理,确保动态更新流畅。
- 在DetailView中添加卡片点击回调,提升用户交互体验。
- 在OptimizedDynamicCardView中新增卡片点击手势,支持非详情页模式下的交互。
- 在swift-assistant-style.mdc中更新功能要求,强调使用函数式编程。
This commit is contained in:
edwinQQQ
2025-07-28 17:20:25 +08:00
parent 2a607e246c
commit f9ff572a30
7 changed files with 170 additions and 140 deletions

View File

@@ -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

View File

@@ -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 {
// APIAPILoadingManager
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
}
}
}
}

View File

@@ -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: {
// actionset
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: {
// actionset
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
}
)
}
}

View File

@@ -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) {
//

View File

@@ -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
)

View File

@@ -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 {

View File

@@ -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)