Files
suixinkan_uikit/suixinkan/UI/Profile/ProfileEditViewController.swift
汉秋 3c4ca7eefb Add submit-task flow aligned with Android and extract profile edit page.
Implement task creation with cloud/local media, order linking, and OSS upload; wire home entry and move profile nickname/avatar editing to ProfileEditViewController.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 10:12:20 +08:00

196 lines
6.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ProfileEditViewController.swift
// suixinkan
//
import PhotosUI
import SnapKit
import UIKit
///
final class ProfileEditViewController: BaseViewController {
private let viewModel: ProfileEditViewModel
private let profileAPI = NetworkServices.shared.profileAPI
private let ossUploadService = NetworkServices.shared.ossUploadService
private let avatarContainer = UIView()
private let avatarImageView = UIImageView()
private let avatarHintLabel = UILabel()
private let nicknameCard = UIView()
private let nicknameTitleLabel = UILabel()
private let nicknameField = UITextField()
private let saveButton = AppButton(title: "保存", style: .primary)
/// 使
init(nickname: String, avatarURL: String) {
viewModel = ProfileEditViewModel(nickname: nickname, avatarURL: avatarURL)
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "编辑资料"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackgroundSoft
avatarImageView.layer.cornerRadius = 48
avatarImageView.clipsToBounds = true
avatarImageView.contentMode = .scaleAspectFill
avatarImageView.isUserInteractionEnabled = true
avatarHintLabel.text = "点击更换头像"
avatarHintLabel.font = .systemFont(ofSize: 14)
avatarHintLabel.textColor = AppColor.textTertiary
avatarHintLabel.textAlignment = .center
avatarContainer.addSubview(avatarImageView)
avatarContainer.addSubview(avatarHintLabel)
view.addSubview(avatarContainer)
nicknameCard.backgroundColor = .white
nicknameCard.layer.cornerRadius = AppRadius.sm
view.addSubview(nicknameCard)
nicknameTitleLabel.text = "昵称"
nicknameTitleLabel.font = .systemFont(ofSize: 14)
nicknameTitleLabel.textColor = AppColor.textTertiary
nicknameField.font = .systemFont(ofSize: 16)
nicknameField.textColor = AppColor.text333
nicknameField.placeholder = "请输入昵称"
nicknameField.clearButtonMode = .whileEditing
nicknameField.returnKeyType = .done
nicknameCard.addSubview(nicknameTitleLabel)
nicknameCard.addSubview(nicknameField)
view.addSubview(saveButton)
}
override func setupConstraints() {
avatarContainer.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(32)
make.centerX.equalToSuperview()
}
avatarImageView.snp.makeConstraints { make in
make.top.centerX.equalToSuperview()
make.width.height.equalTo(96)
}
avatarHintLabel.snp.makeConstraints { make in
make.top.equalTo(avatarImageView.snp.bottom).offset(12)
make.leading.trailing.bottom.equalToSuperview()
}
nicknameCard.snp.makeConstraints { make in
make.top.equalTo(avatarContainer.snp.bottom).offset(32)
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
}
nicknameTitleLabel.snp.makeConstraints { make in
make.top.leading.equalToSuperview().inset(16)
}
nicknameField.snp.makeConstraints { make in
make.top.equalTo(nicknameTitleLabel.snp.bottom).offset(8)
make.leading.trailing.bottom.equalToSuperview().inset(16)
make.height.equalTo(44)
}
saveButton.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
make.height.equalTo(48)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
avatarImageView.addGestureRecognizer(
UITapGestureRecognizer(target: self, action: #selector(pickAvatar))
)
nicknameField.addTarget(self, action: #selector(nicknameChanged), for: .editingChanged)
saveButton.addTarget(self, action: #selector(saveTapped), for: .touchUpInside)
applyViewModel()
}
private func applyViewModel() {
nicknameField.text = viewModel.editingNickname
if let pendingData = viewModel.pendingAvatarData, let image = UIImage(data: pendingData) {
avatarImageView.image = image
} else {
avatarImageView.loadRemoteImage(urlString: viewModel.displayAvatarURL)
}
saveButton.isEnabled = !viewModel.isSaving
avatarImageView.alpha = viewModel.isSaving ? 0.6 : 1
nicknameField.isEnabled = !viewModel.isSaving
}
@objc private func nicknameChanged() {
viewModel.updateEditingNickname(nicknameField.text ?? "")
}
@objc private func pickAvatar() {
guard !viewModel.isSaving else { return }
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter = .images
configuration.selectionLimit = 1
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
@objc private func saveTapped() {
nicknameField.resignFirstResponder()
Task { await saveProfile() }
}
private func saveProfile() async {
let scenicId = AppStore.shared.currentScenicId
guard scenicId > 0 else {
showToast("当前景区信息缺失")
return
}
showLoading()
defer { hideLoading() }
do {
try await viewModel.saveProfile(
api: profileAPI,
uploader: ossUploadService,
scenicId: scenicId
)
showToast("保存成功")
navigationController?.popViewController(animated: true)
} catch {
showToast(error.localizedDescription)
}
}
}
extension ProfileEditViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let provider = results.first?.itemProvider, provider.canLoadObject(ofClass: UIImage.self) else { return }
provider.loadObject(ofClass: UIImage.self) { [weak self] object, _ in
guard let self, let image = object as? UIImage, let data = image.jpegData(compressionQuality: 0.9) else { return }
Task { @MainActor in
do {
try self.viewModel.prepareAvatarImage(data: data)
} catch {
self.showToast(error.localizedDescription)
}
}
}
}
}