Files
real-e-party-iOS/YuMi/E-P/NewMine/Controllers/EPEditSettingViewController.swift
edwinQQQ 90360448a1 fix: 统一应用名称为 "E-Party" 并更新相关描述
主要变更:
1. 在 Info.plist 中将应用名称和描述中的 "E-Parti" 替换为 "E-Party"。
2. 更新多个本地化字符串和提示信息,确保一致性。
3. 修改部分代码中的错误提示信息,使用本地化字符串替代硬编码文本。

此更新旨在提升品牌一致性,确保用户在使用过程中获得统一的体验。
2025-10-15 19:11:01 +08:00

662 lines
25 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.

//
// EPEditSettingViewController.swift
// YuMi
//
// Created by AI on 2025-01-27.
//
import UIKit
import Photos
import SnapKit
///
/// 退
class EPEditSettingViewController: BaseViewController {
// MARK: - UI Components
private lazy var profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 60 // 120/2 = 60
imageView.layer.masksToBounds = true
imageView.backgroundColor = .systemGray5
imageView.isUserInteractionEnabled = true
return imageView
}()
private lazy var cameraIconView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(named: "icon_setting_camear")
imageView.backgroundColor = UIColor(hex: "#0C0527")
imageView.layer.cornerRadius = 15 // 30/2 = 15
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.backgroundColor = UIColor(hex: "#0C0527")
tableView.separatorStyle = .none
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SettingCell")
tableView.isScrollEnabled = true //
return tableView
}()
// MARK: - Data
private var settingItems: [SettingItem] = []
private var userInfo: UserInfoModel?
private var apiHelper: EPMineAPIHelper = EPMineAPIHelper()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
setupUI()
setupData()
loadUserInfo()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//
restoreParentNavigationBarStyle()
}
// MARK: - Setup
private func setupNavigationBar() {
title = YMLocalizedString("EPEditSetting.Title")
// iOS 13+
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor(hex: "#0C0527")
appearance.titleTextAttributes = [
.foregroundColor: UIColor.white,
.font: UIFont.systemFont(ofSize: 18, weight: .medium)
]
appearance.shadowColor = .clear // 线
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
navigationController?.navigationBar.compactAppearance = appearance
navigationController?.navigationBar.tintColor = .white //
//
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
// push backButtonTitle
navigationController?.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(
title: "",
style: .plain,
target: nil,
action: nil
)
}
private func restoreParentNavigationBarStyle() {
// EPMineViewController 使
let transparentAppearance = UINavigationBarAppearance()
transparentAppearance.configureWithTransparentBackground()
transparentAppearance.backgroundColor = .clear
transparentAppearance.shadowColor = .clear
navigationController?.navigationBar.standardAppearance = transparentAppearance
navigationController?.navigationBar.scrollEdgeAppearance = transparentAppearance
navigationController?.navigationBar.compactAppearance = transparentAppearance
}
private func setupUI() {
view.backgroundColor = UIColor(hex: "#0C0527")
//
view.addSubview(profileImageView)
profileImageView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(40)
make.centerX.equalTo(view)
make.size.equalTo(120)
}
//
view.addSubview(cameraIconView)
cameraIconView.snp.makeConstraints { make in
make.bottom.equalTo(profileImageView.snp.bottom)
make.trailing.equalTo(profileImageView.snp.trailing)
make.size.equalTo(30)
}
// TableView
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.top.equalTo(profileImageView.snp.bottom).offset(40)
make.leading.trailing.bottom.equalTo(view)
}
//
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(profileImageTapped))
profileImageView.addGestureRecognizer(tapGesture)
//
let cameraTapGesture = UITapGestureRecognizer(target: self, action: #selector(profileImageTapped))
cameraIconView.addGestureRecognizer(cameraTapGesture)
}
private func setupData() {
settingItems = [
SettingItem(
title: YMLocalizedString("EPEditSetting.PersonalInfo"),
action: { [weak self] in self?.handleReservedAction("PersonalInfo") }
),
SettingItem(
title: YMLocalizedString("EPEditSetting.Help"),
action: { [weak self] in self?.handleReservedAction("Help") }
),
SettingItem(
title: YMLocalizedString("EPEditSetting.ClearCache"),
action: { [weak self] in self?.handleReservedAction("ClearCache") }
),
SettingItem(
title: YMLocalizedString("EPEditSetting.CheckUpdate"),
action: { [weak self] in self?.handleReservedAction("CheckUpdate") }
),
SettingItem(
title: YMLocalizedString("EPEditSetting.Logout"),
style: .default,
action: { [weak self] in self?.showLogoutConfirm() }
),
SettingItem(
title: YMLocalizedString("EPEditSetting.AboutUs"),
action: { [weak self] in self?.handleReservedAction("AboutUs") }
)
]
NSLog("[EPEditSetting] setupData 完成,设置项数量: \(settingItems.count)")
}
private func loadUserInfo() {
// EPMineViewController
if userInfo != nil {
updateProfileImage()
tableView.reloadData()
return
}
//
guard let uid = AccountInfoStorage.instance().getUid(), !uid.isEmpty else {
print("[EPEditSetting] 未登录,无法获取用户信息")
return
}
// TODO: API
// UserInfoModel
let tempUserInfo = UserInfoModel()
tempUserInfo.nick = "User"
tempUserInfo.avatar = ""
userInfo = tempUserInfo
updateProfileImage()
tableView.reloadData()
}
private func updateProfileImage() {
guard let avatarUrl = userInfo?.avatar, !avatarUrl.isEmpty else {
profileImageView.image = UIImage(systemName: "person.circle.fill")
return
}
// 使SDWebImage
if let url = URL(string: avatarUrl) {
profileImageView.sd_setImage(with: url, placeholderImage: UIImage(systemName: "person.circle.fill"))
}
}
// MARK: - Actions
@objc private func profileImageTapped() {
showAvatarSelectionSheet()
}
@objc private func openSettings() {
//
handleReservedAction("Settings")
}
private func showAvatarSelectionSheet() {
let alert = UIAlertController(title: YMLocalizedString("EPEditSetting.EditNickname"), message: nil, preferredStyle: .actionSheet)
//
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Camera"), style: .default) { [weak self] _ in
self?.checkCameraPermissionAndPresent()
})
//
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.PhotoLibrary"), style: .default) { [weak self] _ in
self?.checkPhotoLibraryPermissionAndPresent()
})
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel))
// iPad
if let popover = alert.popoverPresentationController {
popover.sourceView = profileImageView
popover.sourceRect = profileImageView.bounds
}
present(alert, animated: true)
}
private func checkCameraPermissionAndPresent() {
YYUtility.checkCameraAvailable { [weak self] in
self?.presentImagePicker(sourceType: .camera)
} denied: { [weak self] in
self?.showPermissionAlert(title: "Camera Access", message: "Please allow camera access in Settings")
} restriction: { [weak self] in
self?.showPermissionAlert(title: "Camera Restricted", message: "Camera access is restricted on this device")
}
}
private func checkPhotoLibraryPermissionAndPresent() {
YYUtility.checkAssetsLibrayAvailable { [weak self] in
self?.presentImagePicker(sourceType: .photoLibrary)
} denied: { [weak self] in
self?.showPermissionAlert(title: "Photo Library Access", message: "Please allow photo library access in Settings")
} restriction: { [weak self] in
self?.showPermissionAlert(title: "Photo Library Restricted", message: "Photo library access is restricted on this device")
}
}
private func presentImagePicker(sourceType: UIImagePickerController.SourceType) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = sourceType
imagePicker.allowsEditing = true
present(imagePicker, animated: true)
}
private func showPermissionAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsURL)
}
})
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel))
present(alert, animated: true)
}
private func showNicknameEditAlert() {
let alert = UIAlertController(
title: YMLocalizedString("EPEditSetting.EditNickname"),
message: nil,
preferredStyle: .alert
)
alert.addTextField { [weak self] textField in
textField.text = self?.userInfo?.nick ?? ""
textField.placeholder = YMLocalizedString("EPEditSetting.EnterNickname")
}
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel))
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Confirm"), style: .default) { [weak self] _ in
guard let newNickname = alert.textFields?.first?.text, !newNickname.isEmpty else { return }
self?.updateNickname(newNickname)
})
present(alert, animated: true)
}
private func updateNickname(_ newNickname: String) {
//
showLoading()
// API
apiHelper.updateNickname(withNick: newNickname,
completion: { [weak self] in
self?.hideHUD()
//
self?.userInfo?.nick = newNickname
self?.tableView.reloadData()
//
self?.showSuccessToast(YMLocalizedString("XPMineUserInfoEditViewController13"))
print("[EPEditSetting] 昵称更新成功: \(newNickname)")
},
failure: { [weak self] (code: Int, msg: String?) in
self?.hideHUD()
//
let errorMsg = msg ?? YMLocalizedString("setting.nickname_update_failed")
self?.showErrorToast(errorMsg)
print("[EPEditSetting] 昵称更新失败: \(code) - \(errorMsg)")
}
)
}
private func showLogoutConfirm() {
let alert = UIAlertController(
title: YMLocalizedString("EPEditSetting.LogoutConfirm"),
message: nil,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel))
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Logout"), style: .destructive) { [weak self] _ in
self?.performLogout()
})
present(alert, animated: true)
}
private func performLogout() {
guard let account = AccountInfoStorage.instance().accountModel else {
print("[EPEditSetting] 账号信息不存在")
return
}
// API
Api.logoutCurrentAccount({ [weak self] (data, code, msg) in
DispatchQueue.main.async {
//
AccountInfoStorage.instance().saveAccountInfo(nil)
AccountInfoStorage.instance().saveTicket(nil)
//
self?.navigateToLogin()
}
}, access_token: account.access_token)
}
private func navigateToLogin() {
let loginVC = EPLoginViewController()
let nav = UINavigationController(rootViewController: loginVC)
if let window = UIApplication.shared.windows.first {
window.rootViewController = nav
window.makeKeyAndVisible()
}
print("[EPEditSetting] 已跳转到登录页面")
}
private func handleReservedAction(_ title: String) {
print("[\(title)] - 功能预留,待后续实现")
// TODO: Phase 2 implementation
//
let alert = UIAlertController(title: "Coming Soon", message: "This feature will be available in the next update.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
// MARK: - Public Methods
/// EPMineViewController
@objc func updateWithUserInfo(_ userInfo: UserInfoModel) {
self.userInfo = userInfo
updateProfileImage()
tableView.reloadData()
NSLog("[EPEditSetting] 已更新用户信息: \(userInfo.nick ?? "未知")")
}
}
// MARK: - UITableViewDataSource & UITableViewDelegate
extension EPEditSettingViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1 // section
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = settingItems.count + 1 // +1 for nickname row
NSLog("[EPEditSetting] TableView rows count: \(count), settingItems: \(settingItems.count)")
return count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SettingCell", for: indexPath)
cell.backgroundColor = UIColor(hex: "#0C0527")
cell.textLabel?.textColor = .white
cell.selectionStyle = .none
//
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
if indexPath.row == 0 {
//
cell.textLabel?.text = YMLocalizedString("EPEditSetting.Nickname")
//
let arrowImageView = UIImageView()
arrowImageView.image = UIImage(named: "icon_setting_right_arrow")
arrowImageView.contentMode = .scaleAspectFit
cell.contentView.addSubview(arrowImageView)
arrowImageView.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-20)
make.centerY.equalToSuperview()
make.size.equalTo(22)
}
//
let nicknameLabel = UILabel()
nicknameLabel.text = userInfo?.nick ?? YMLocalizedString("user.not_set")
nicknameLabel.textColor = .lightGray
nicknameLabel.font = UIFont.systemFont(ofSize: 16)
cell.contentView.addSubview(nicknameLabel)
nicknameLabel.snp.makeConstraints { make in
make.trailing.equalTo(arrowImageView.snp.leading).offset(-12)
make.centerY.equalToSuperview()
}
} else {
//
let item = settingItems[indexPath.row - 1]
cell.textLabel?.text = item.title
//
let arrowImageView = UIImageView()
arrowImageView.image = UIImage(named: "icon_setting_right_arrow")
arrowImageView.contentMode = .scaleAspectFit
cell.contentView.addSubview(arrowImageView)
arrowImageView.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-20)
make.centerY.equalToSuperview()
make.size.equalTo(22)
}
if item.style == .default {
cell.textLabel?.textColor = .systemRed
} else {
cell.textLabel?.textColor = .white
}
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60 // 60pt
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if indexPath.row == 0 {
//
showNicknameEditAlert()
} else {
//
let item = settingItems[indexPath.row - 1]
item.action()
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
}
// MARK: - UIImagePickerControllerDelegate & UINavigationControllerDelegate
extension EPEditSettingViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let image = info[.editedImage] as? UIImage ?? info[.originalImage] as? UIImage else {
print("[EPEditSetting] 未能获取选择的图片")
return
}
//
profileImageView.image = image
//
uploadAvatar(image)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true)
}
private func uploadAvatar(_ image: UIImage) {
//
EPProgressHUD.showProgress(0, total: 1)
// 使 EPSDKManager OCR
EPSDKManager.shared.uploadImages([image],
progress: { uploaded, total in
EPProgressHUD.showProgress(uploaded, total: total)
},
success: { [weak self] resList in
EPProgressHUD.dismiss()
guard !resList.isEmpty,
let firstRes = resList.first,
let avatarUrl = firstRes["resUrl"] as? String else {
print("[EPEditSetting] 头像上传成功但无法获取URL")
return
}
print("[EPEditSetting] 头像上传成功: \(avatarUrl)")
// API
self?.updateAvatarAPI(avatarUrl: avatarUrl)
},
failure: { [weak self] errorMsg in
EPProgressHUD.dismiss()
print("[EPEditSetting] 头像上传失败: \(errorMsg)")
//
DispatchQueue.main.async {
let alert = UIAlertController(title: YMLocalizedString("common.upload_failed"), message: errorMsg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: YMLocalizedString("common.confirm"), style: .default))
self?.present(alert, animated: true)
}
}
)
}
private func updateAvatarAPI(avatarUrl: String) {
// 使 API Helper
apiHelper.updateAvatar(withUrl: avatarUrl, completion: { [weak self] in
print("[EPEditSetting] 头像更新成功")
//
self?.userInfo?.avatar = avatarUrl
//
self?.notifyParentAvatarUpdated(avatarUrl)
}, failure: { [weak self] (code: Int, msg: String?) in
print("[EPEditSetting] 头像更新失败: \(code) - \(msg ?? "未知错误")")
//
DispatchQueue.main.async {
let alert = UIAlertController(
title: YMLocalizedString("common.update_failed"),
message: msg ?? YMLocalizedString("setting.avatar_update_failed"),
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: YMLocalizedString("common.confirm"), style: .default))
self?.present(alert, animated: true)
}
})
}
private func notifyParentAvatarUpdated(_ avatarUrl: String) {
// EPMineViewController
let userInfo = ["avatarUrl": avatarUrl]
NotificationCenter.default.post(name: NSNotification.Name("EPEditSettingAvatarUpdated"), object: nil, userInfo: userInfo)
}
}
// MARK: - Helper Models
private struct SettingItem {
let title: String
let style: UITableViewCell.SelectionStyle
let action: () -> Void
init(title: String, style: UITableViewCell.SelectionStyle = .default, action: @escaping () -> Void) {
self.title = title
self.style = style
self.action = action
}
}
// MARK: - UIColor Extension
private extension UIColor {
convenience init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (1, 1, 1, 0)
}
self.init(
red: CGFloat(r) / 255,
green: CGFloat(g) / 255,
blue: CGFloat(b) / 255,
alpha: CGFloat(a) / 255
)
}
}