// // 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 ?? "昵称更新失败,请稍后重试" 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 ?? "未设置" 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: "上传失败", message: errorMsg, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "确定", 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: "更新失败", message: msg ?? "头像更新失败,请稍后重试", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "确定", 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 ) } }