feat: 添加云盘与消息中心功能

This commit is contained in:
2026-07-09 22:37:43 +08:00
parent 8e356973bd
commit f20ec7f06c
91 changed files with 5437 additions and 89 deletions

View File

@ -25,6 +25,7 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
private let nicknameField = UITextField()
private let editProfileButton = UIButton(type: .system)
private let introductionTextView = UITextView()
private let introductionPlaceholderLabel = UILabel()
private let certificationWrap = ProfileTagWrapView()
private let attrWrap = ProfileTagWrapView()
@ -139,13 +140,14 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
hideLoading()
}
saveButton.isEnabled = viewModel.hasChanges
avatarImageView.loadRemoteImage(urlString: viewModel.avatarURL)
updateAvatar(urlString: viewModel.avatarURL)
realNameLabel.text = "姓名: \(viewModel.realName)"
nicknameField.text = viewModel.isEditingProfile ? viewModel.editingNickname : "昵称: \(viewModel.nickname)"
nicknameField.isEnabled = viewModel.isEditingProfile
introductionTextView.isEditable = viewModel.isEditingProfile
introductionTextView.text = viewModel.introduction.isEmpty && !viewModel.isEditingProfile ? "暂无简介" : viewModel.introduction
introductionTextView.textColor = viewModel.introduction.isEmpty && !viewModel.isEditingProfile ? UIColor(hex: 0xB6BECA) : UIColor(hex: 0x565656)
updateIntroductionPlaceholder()
editProfileButton.setImage(UIImage(systemName: viewModel.isEditingProfile ? "checkmark" : "square.and.pencil"), for: .normal)
certificationWrap.setItems(viewModel.scenicCertification, style: .certification, removable: false)
attrWrap.setItems(viewModel.attrLabels, style: .label, removable: true) { [weak self] label in
@ -204,6 +206,9 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
nicknameField.font = .systemFont(ofSize: 16)
nicknameField.textColor = .black
nicknameField.borderStyle = .none
nicknameField.placeholder = "请输入昵称"
nicknameField.returnKeyType = .done
nicknameField.delegate = self
nicknameField.addTarget(self, action: #selector(nicknameChanged), for: .editingChanged)
editProfileButton.tintColor = AppColor.primary
editProfileButton.snp.makeConstraints { make in
@ -221,6 +226,13 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
introductionTextView.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(24)
}
introductionPlaceholderLabel.text = "请输入个人简介"
introductionPlaceholderLabel.font = introductionTextView.font
introductionPlaceholderLabel.textColor = UIColor(hex: 0xB6BECA)
introductionTextView.addSubview(introductionPlaceholderLabel)
introductionPlaceholderLabel.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
}
textStack.addArrangedSubview(realNameLabel)
textStack.addArrangedSubview(nicknameRow)
@ -357,6 +369,8 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
row.alignment = .fill
field.placeholder = placeholder
field.font = .systemFont(ofSize: 14)
field.returnKeyType = .done
field.delegate = self
field.layer.cornerRadius = 8
field.layer.borderColor = AppColor.border.cgColor
field.layer.borderWidth = 1
@ -390,12 +404,14 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 14)
titleLabel.textColor = UIColor(hex: 0x565656)
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
let toLabel = UILabel()
toLabel.text = ""
toLabel.font = .systemFont(ofSize: 14)
toLabel.textColor = UIColor(hex: 0x565656)
let spacer = UIView()
row.addArrangedSubview(titleLabel)
row.addArrangedSubview(spacer)
row.addArrangedSubview(start)
row.addArrangedSubview(toLabel)
row.addArrangedSubview(end)
@ -408,6 +424,24 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
return row
}
private func updateAvatar(urlString: String?) {
let trimmed = urlString?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty else {
avatarImageView.kf.cancelDownloadTask()
avatarImageView.backgroundColor = AppColor.inputBackground
avatarImageView.image = UIImage(systemName: "person.crop.circle.fill")
avatarImageView.tintColor = UIColor(hex: 0xB6BECA)
avatarImageView.contentMode = .scaleAspectFit
return
}
avatarImageView.contentMode = .scaleAspectFill
avatarImageView.loadRemoteImage(urlString: trimmed)
}
private func updateIntroductionPlaceholder() {
introductionPlaceholderLabel.isHidden = !viewModel.isEditingProfile || !introductionTextView.text.isEmpty
}
private func updateStatusButtons() {
for button in statusButtons {
let selected = button.tag == viewModel.acceptOrderStatus
@ -553,6 +587,8 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
let alert = UIAlertController(title: "添加设备", message: nil, preferredStyle: .alert)
alert.addTextField { field in
field.placeholder = "请输入设备名称"
field.returnKeyType = .done
field.delegate = self
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
alert.addAction(UIAlertAction(title: "确定", style: .default) { [weak self] _ in
@ -590,9 +626,25 @@ final class ProfileSpaceSettingsViewController: BaseViewController {
}
}
extension ProfileSpaceSettingsViewController: UITextViewDelegate {
extension ProfileSpaceSettingsViewController: UITextFieldDelegate, UITextViewDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textViewDidChange(_ textView: UITextView) {
viewModel.updateIntroduction(textView.text)
updateIntroductionPlaceholder()
}
func textView(
_ textView: UITextView,
shouldChangeTextIn range: NSRange,
replacementText text: String
) -> Bool {
guard text == "\n" else { return true }
textView.resignFirstResponder()
return false
}
}
@ -636,6 +688,7 @@ final class ProfileAddScheduleViewController: BaseViewController {
private let endButton = ProfileTimeButton()
private let orderButton = UIButton(type: .system)
private let remarkTextView = UITextView()
private let remarkPlaceholderLabel = UILabel()
private let countLabel = UILabel()
init(selectedDate: Date, viewModel: ProfileSpaceSettingsViewModel, profileAPI: ProfileAPI) {
@ -719,6 +772,8 @@ final class ProfileAddScheduleViewController: BaseViewController {
private func configureNameField() {
nameField.placeholder = "请输入日程名称"
nameField.font = .systemFont(ofSize: 14)
nameField.returnKeyType = .done
nameField.delegate = self
nameField.layer.borderColor = AppColor.border.cgColor
nameField.layer.borderWidth = 1
nameField.layer.cornerRadius = 8
@ -771,6 +826,17 @@ final class ProfileAddScheduleViewController: BaseViewController {
remarkTextView.layer.borderColor = UIColor(hex: 0xE5E7EB).cgColor
remarkTextView.layer.borderWidth = 1
remarkTextView.layer.cornerRadius = 8
remarkTextView.textContainerInset = UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12)
remarkTextView.textContainer.lineFragmentPadding = 0
remarkPlaceholderLabel.text = "请输入备注信息"
remarkPlaceholderLabel.font = remarkTextView.font
remarkPlaceholderLabel.textColor = UIColor(hex: 0xB6BECA)
remarkTextView.addSubview(remarkPlaceholderLabel)
remarkPlaceholderLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(10)
make.leading.equalToSuperview().offset(12)
make.trailing.equalToSuperview().inset(12)
}
countLabel.text = "0/200"
countLabel.textAlignment = .right
countLabel.font = .systemFont(ofSize: 12)
@ -845,13 +911,29 @@ final class ProfileAddScheduleViewController: BaseViewController {
}
}
extension ProfileAddScheduleViewController: UITextViewDelegate {
extension ProfileAddScheduleViewController: UITextFieldDelegate, UITextViewDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textViewDidChange(_ textView: UITextView) {
if textView.text.count > 200 {
textView.text = String(textView.text.prefix(200))
}
remarkPlaceholderLabel.isHidden = !textView.text.isEmpty
countLabel.text = "\(textView.text.count)/200"
}
func textView(
_ textView: UITextView,
shouldChangeTextIn range: NSRange,
replacementText text: String
) -> Bool {
guard text == "\n" else { return true }
textView.resignFirstResponder()
return false
}
}
///