feat: 新增用户信息更新功能

- 在APIEndpoints中新增用户信息更新端点。
- 实现UpdateUserRequest和UpdateUserResponse结构体,支持用户信息更新请求和响应。
- 在APIService中添加updateUser方法,处理用户信息更新请求。
- 更新AppSettingFeature以支持头像和昵称的修改,整合用户信息更新逻辑。
- 在AppSettingView中实现头像选择和昵称编辑功能,提升用户体验。
This commit is contained in:
edwinQQQ
2025-07-24 16:38:27 +08:00
parent c072a7e73d
commit 343fd9e2df
7 changed files with 307 additions and 88 deletions

View File

@@ -1,31 +1,51 @@
import SwiftUI
import ComposableArchitecture
import PhotosUI
struct AppSettingView: View {
let store: StoreOf<AppSettingFeature>
@State private var showPhotoPicker = false
@State private var selectedPhotoItem: PhotosPickerItem? = nil
@State private var showNicknameAlert = false
@State private var nicknameInput: String = ""
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
ZStack {
Color(red: 22/255, green: 17/255, blue: 44/255).ignoresSafeArea()
// VStack(spacing: 0) {
VStack(spacing: 0) {
//
VStack(spacing: 0) {
//
avatarSection(viewStore: viewStore)
//
nicknameSection(viewStore: viewStore)
//
settingsSection(viewStore: viewStore)
Spacer()
//
logoutButton(viewStore: viewStore)
}
// }
}
// Loading &
if viewStore.isUploadingAvatar || viewStore.isUpdatingUser {
Color.black.opacity(0.3).ignoresSafeArea()
ProgressView("正在上传/更新...")
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.foregroundColor(.white)
.scaleEffect(1.2)
}
if let error = viewStore.avatarUploadError ?? viewStore.updateUserError {
VStack {
Spacer()
Text(error)
.foregroundColor(.red)
.padding()
.background(Color.white.opacity(0.9))
.cornerRadius(10)
Spacer()
}
}
}
.navigationTitle(NSLocalizedString("appSetting.title", comment: "Edit"))
.navigationBarTitleDisplayMode(.inline)
@@ -51,6 +71,35 @@ struct AppSettingView: View {
isPresented: privacyPolicyBinding(viewStore: viewStore),
url: APIConfiguration.webURL(for: .privacyPolicy)
)
//
.photosPicker(isPresented: $showPhotoPicker, selection: $selectedPhotoItem, matching: .images, photoLibrary: .shared())
.onChange(of: selectedPhotoItem) { newItem in
if let item = newItem {
loadAndProcessImage(item: item) { data in
if let data = data {
viewStore.send(.avatarSelected(data))
}
}
}
}
// Alert
.alert("修改昵称", isPresented: $showNicknameAlert, actions: {
TextField("请输入昵称", text: $nicknameInput)
.onChange(of: nicknameInput) { newValue in
if newValue.count > 15 {
nicknameInput = String(newValue.prefix(15))
}
}
Button("确定") {
let trimmed = nicknameInput.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty && trimmed != viewStore.nickname {
viewStore.send(.nicknameEditConfirmed(trimmed))
}
}
Button("取消", role: .cancel) {}
}, message: {
Text("昵称最长15个字符")
})
}
}
@@ -60,7 +109,13 @@ struct AppSettingView: View {
private func avatarSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
ZStack(alignment: .bottomTrailing) {
avatarImageView(viewStore: viewStore)
.onTapGesture {
showPhotoPicker = true
}
cameraButton
.onTapGesture {
showPhotoPicker = true
}
}
.padding(.top, 24)
}
@@ -141,7 +196,8 @@ struct AppSettingView: View {
.padding(.horizontal, 32)
.padding(.vertical, 18)
.onTapGesture {
viewStore.send(.editNicknameTapped)
nicknameInput = viewStore.nickname
showNicknameAlert = true
}
Divider().background(Color.gray.opacity(0.3))
@@ -255,4 +311,34 @@ struct AppSettingView: View {
send: AppSettingFeature.Action.privacyPolicyDismissed
)
}
// MARK: -
private func loadAndProcessImage(item: PhotosPickerItem, completion: @escaping (Data?) -> Void) {
item.loadTransferable(type: Data.self) { result in
guard let data = try? result.get(), let uiImage = UIImage(data: data) else {
completion(nil)
return
}
let square = cropToSquare(image: uiImage)
let resized = resizeImage(image: square, targetSize: CGSize(width: 180, height: 180))
let jpegData = resized.jpegData(compressionQuality: 0.8)
completion(jpegData)
}
}
}
// MARK: -
private func cropToSquare(image: UIImage) -> UIImage {
let size = min(image.size.width, image.size.height)
let x = (image.size.width - size) / 2
let y = (image.size.height - size) / 2
let cropRect = CGRect(x: x, y: y, width: size, height: size)
guard let cgImage = image.cgImage?.cropping(to: cropRect) else { return image }
return UIImage(cgImage: cgImage, scale: image.scale, orientation: image.imageOrientation)
}
private func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
image.draw(in: CGRect(origin: .zero, size: targetSize))
}
}