
主要变更: 1. 新增 EPEditSettingViewController,提供用户头像更新、昵称修改和退出登录功能。 2. 在 Bridging Header 中引入 UserInfoModel、XPMineUserInfoEditPresenter 等新模块,以支持设置页面的功能。 3. 更新多语言文件,添加设置页面相关的本地化字符串。 此更新旨在提升用户体验,简化用户信息管理流程。
523 lines
19 KiB
Swift
523 lines
19 KiB
Swift
//
|
|
// EPEditSettingViewController.swift
|
|
// YuMi
|
|
//
|
|
// Created by AI on 2025-01-27.
|
|
//
|
|
|
|
import UIKit
|
|
import Photos
|
|
import SnapKit
|
|
|
|
/// 设置编辑页面
|
|
/// 支持头像更新、昵称修改和退出登录功能
|
|
class EPEditSettingViewController: UIViewController {
|
|
|
|
// MARK: - UI Components
|
|
|
|
private lazy var tableView: UITableView = {
|
|
let tableView = UITableView(frame: .zero, style: .grouped)
|
|
tableView.backgroundColor = UIColor(hex: "#0C0527")
|
|
tableView.separatorStyle = .none
|
|
tableView.delegate = self
|
|
tableView.dataSource = self
|
|
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SettingCell")
|
|
return tableView
|
|
}()
|
|
|
|
private lazy var profileImageView: UIImageView = {
|
|
let imageView = UIImageView()
|
|
imageView.contentMode = .scaleAspectFill
|
|
imageView.layer.cornerRadius = 50
|
|
imageView.layer.masksToBounds = true
|
|
imageView.backgroundColor = .systemGray5
|
|
imageView.isUserInteractionEnabled = true
|
|
return imageView
|
|
}()
|
|
|
|
// MARK: - Data
|
|
|
|
private var settingItems: [SettingItem] = []
|
|
private var userInfo: UserInfoModel?
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
navigationController?.setNavigationBarHidden(true, animated: false)
|
|
setupUI()
|
|
setupData()
|
|
loadUserInfo()
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
// 显示导航栏
|
|
navigationController?.setNavigationBarHidden(false, animated: animated)
|
|
navigationController?.navigationBar.titleTextAttributes = [
|
|
.foregroundColor: UIColor.white,
|
|
.font: UIFont.systemFont(ofSize: 18, weight: .medium)
|
|
]
|
|
navigationController?.navigationBar.barTintColor = UIColor(hex: "#0C0527")
|
|
navigationController?.navigationBar.tintColor = .white
|
|
navigationController?.navigationBar.isTranslucent = false
|
|
}
|
|
|
|
// MARK: - Setup
|
|
|
|
private func setupUI() {
|
|
view.backgroundColor = UIColor(hex: "#0C0527")
|
|
title = YMLocalizedString("EPEditSetting.Title")
|
|
|
|
view.addSubview(tableView)
|
|
tableView.snp.makeConstraints { make in
|
|
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
|
|
make.leading.trailing.bottom.equalToSuperview()
|
|
}
|
|
|
|
// 添加头像点击手势
|
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(profileImageTapped))
|
|
profileImageView.addGestureRecognizer(tapGesture)
|
|
}
|
|
|
|
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") }
|
|
)
|
|
]
|
|
}
|
|
|
|
private func loadUserInfo() {
|
|
// 获取当前用户信息
|
|
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) {
|
|
// 构建UserInfoModel并调用更新方法
|
|
let userInfo = UserInfoModel()
|
|
userInfo.nick = newNickname
|
|
|
|
// 调用Presenter方法 (桥接到OC)
|
|
let presenter = XPMineUserInfoEditPresenter()
|
|
presenter.getUserInfoEditDataSource(withUserInfo: userInfo)
|
|
|
|
// 更新本地显示
|
|
self.userInfo?.nick = newNickname
|
|
tableView.reloadData()
|
|
|
|
print("[EPEditSetting] 昵称更新为: \(newNickname)")
|
|
}
|
|
|
|
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: - UITableViewDataSource & UITableViewDelegate
|
|
|
|
extension EPEditSettingViewController: UITableViewDataSource, UITableViewDelegate {
|
|
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
|
return 2 // 头像昵称section + 设置项section
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
if section == 0 {
|
|
return 2 // 头像 + 昵称
|
|
} else {
|
|
return settingItems.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
|
|
|
|
if indexPath.section == 0 {
|
|
// 头像昵称section
|
|
if indexPath.row == 0 {
|
|
// 头像行
|
|
cell.textLabel?.text = YMLocalizedString("EPEditSetting.Avatar")
|
|
cell.accessoryType = .disclosureIndicator
|
|
|
|
// 添加头像图片
|
|
if cell.contentView.subviews.contains(profileImageView) {
|
|
profileImageView.removeFromSuperview()
|
|
}
|
|
cell.contentView.addSubview(profileImageView)
|
|
profileImageView.snp.makeConstraints { make in
|
|
make.trailing.equalToSuperview().offset(-50)
|
|
make.centerY.equalToSuperview()
|
|
make.size.equalTo(100)
|
|
}
|
|
} else {
|
|
// 昵称行
|
|
cell.textLabel?.text = YMLocalizedString("EPEditSetting.Nickname")
|
|
cell.detailTextLabel?.text = userInfo?.nick ?? "未设置"
|
|
cell.detailTextLabel?.textColor = .lightGray
|
|
cell.accessoryType = .disclosureIndicator
|
|
}
|
|
} else {
|
|
// 设置项section
|
|
let item = settingItems[indexPath.row]
|
|
cell.textLabel?.text = item.title
|
|
cell.accessoryType = .disclosureIndicator
|
|
|
|
if item.style == .default {
|
|
cell.textLabel?.textColor = .systemRed
|
|
} else {
|
|
cell.textLabel?.textColor = .white
|
|
}
|
|
}
|
|
|
|
return cell
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
|
if indexPath.section == 0 && indexPath.row == 0 {
|
|
return 120 // 头像行更高
|
|
}
|
|
return 60
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
|
|
if indexPath.section == 0 {
|
|
if indexPath.row == 0 {
|
|
// 头像点击
|
|
showAvatarSelectionSheet()
|
|
} else {
|
|
// 昵称点击
|
|
showNicknameEditAlert()
|
|
}
|
|
} else {
|
|
// 设置项点击
|
|
let item = settingItems[indexPath.row]
|
|
item.action()
|
|
}
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
|
return section == 0 ? 20 : 10
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
let view = UIView()
|
|
view.backgroundColor = UIColor(hex: "#0C0527")
|
|
return view
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
|
return 10
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
|
let view = UIView()
|
|
view.backgroundColor = UIColor(hex: "#0C0527")
|
|
return view
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
// 压缩图片
|
|
guard let imageData = image.jpegData(compressionQuality: 0.5) else {
|
|
print("[EPEditSetting] 图片压缩失败")
|
|
return
|
|
}
|
|
|
|
// 生成文件名
|
|
let format = "jpg"
|
|
let name = "image/\(UUID().uuidString).\(format)"
|
|
|
|
// 上传到腾讯云
|
|
UploadFile.share().qCloudUploadImage(imageData, named: name, success: { [weak self] (key, resp) in
|
|
print("[EPEditSetting] 头像上传成功: \(key)")
|
|
|
|
// 调用API更新头像
|
|
self?.updateAvatarAPI(avatarUrl: key)
|
|
|
|
}, failure: { (resCode, message) in
|
|
print("[EPEditSetting] 头像上传失败: \(message)")
|
|
})
|
|
}
|
|
|
|
private func updateAvatarAPI(avatarUrl: String) {
|
|
// 调用API更新头像
|
|
Api.userV2UploadAvatar({ [weak self] (data, code, msg) in
|
|
DispatchQueue.main.async {
|
|
if code == 200 {
|
|
print("[EPEditSetting] 头像更新成功")
|
|
// 更新本地用户信息
|
|
self?.userInfo?.avatar = avatarUrl
|
|
} else {
|
|
print("[EPEditSetting] 头像更新失败: \(String(describing: msg))")
|
|
}
|
|
}
|
|
}, avatarUrl: avatarUrl, needPay: NSNumber(value: false))
|
|
}
|
|
}
|
|
|
|
// 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
|
|
)
|
|
}
|
|
}
|