
主要变更: 1. 在 .gitignore 中添加了 Docs/ 文件夹,以忽略文档相关文件。 2. 删除了多个过时的文档,包括构建指南、编译修复指南和当前状态报告等。 此更新旨在清理项目文件,确保版本控制的整洁性。
851 lines
32 KiB
Swift
851 lines
32 KiB
Swift
//
|
||
// EPEditSettingViewController.swift
|
||
// YuMi
|
||
//
|
||
// Created by AI on 2025-01-27.
|
||
//
|
||
|
||
import UIKit
|
||
import Photos
|
||
import SnapKit
|
||
import WebKit
|
||
|
||
/// 设置编辑页面
|
||
/// 支持头像更新、昵称修改和退出登录功能
|
||
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
|
||
}()
|
||
|
||
private lazy var logoutButton: UIButton = {
|
||
let button = UIButton(type: .system)
|
||
button.setTitle(YMLocalizedString("EPEditSetting.Logout"), for: .normal)
|
||
button.setTitleColor(.white, for: .normal)
|
||
button.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold)
|
||
button.layer.cornerRadius = 25
|
||
button.addTarget(self, action: #selector(logoutButtonTapped), for: .touchUpInside)
|
||
return button
|
||
}()
|
||
|
||
// MARK: - Data
|
||
|
||
private var settingItems: [SettingItem] = []
|
||
private var userInfo: UserInfoModel?
|
||
private var apiHelper: EPMineAPIHelper = EPMineAPIHelper()
|
||
private var hasAddedGradient = false
|
||
|
||
// 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()
|
||
}
|
||
|
||
override func viewDidLayoutSubviews() {
|
||
super.viewDidLayoutSubviews()
|
||
|
||
// 添加渐变背景到 Logout 按钮(只添加一次)
|
||
if !hasAddedGradient && logoutButton.bounds.width > 0 {
|
||
logoutButton.addGradientBackground(
|
||
with: [
|
||
UIColor(red: 0xF8/255.0, green: 0x54/255.0, blue: 0xFC/255.0, alpha: 1.0), // #F854FC
|
||
UIColor(red: 0x50/255.0, green: 0x0F/255.0, blue: 0xFF/255.0, alpha: 1.0) // #500FFF
|
||
],
|
||
start: CGPoint(x: 0, y: 0.5),
|
||
end: CGPoint(x: 1, y: 0.5),
|
||
cornerRadius: 25
|
||
)
|
||
hasAddedGradient = true
|
||
}
|
||
}
|
||
|
||
// 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)
|
||
}
|
||
|
||
// 设置 Logout 按钮布局
|
||
view.addSubview(logoutButton)
|
||
logoutButton.snp.makeConstraints { make in
|
||
make.leading.trailing.equalTo(view).inset(20)
|
||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-40)
|
||
make.height.equalTo(50)
|
||
}
|
||
|
||
// 设置 TableView 布局
|
||
view.addSubview(tableView)
|
||
tableView.snp.makeConstraints { make in
|
||
make.top.equalTo(profileImageView.snp.bottom).offset(40)
|
||
make.leading.trailing.equalTo(view)
|
||
make.bottom.equalTo(logoutButton.snp.top).offset(-20)
|
||
}
|
||
|
||
// 添加头像点击手势
|
||
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.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")
|
||
}
|
||
|
||
@objc private func logoutButtonTapped() {
|
||
showLogoutConfirm()
|
||
}
|
||
|
||
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)] - 功能触发")
|
||
|
||
// About Us 已实现
|
||
if title == "AboutUs" {
|
||
let aboutVC = EPAboutUsViewController()
|
||
navigationController?.pushViewController(aboutVC, animated: true)
|
||
return
|
||
}
|
||
|
||
// Personal Info - 显示协议和隐私政策选项
|
||
if title == "PersonalInfo" {
|
||
showPolicyOptionsSheet()
|
||
return
|
||
}
|
||
|
||
// Help - 跳转到 FAQ 页面
|
||
if title == "Help" {
|
||
let faqUrl = getFAQURL()
|
||
openPolicyInExternalBrowser(faqUrl)
|
||
return
|
||
}
|
||
|
||
// Clear Cache - 清理图片和网页缓存
|
||
if title == "ClearCache" {
|
||
showClearCacheConfirmation()
|
||
return
|
||
}
|
||
|
||
// 其他功能预留
|
||
// 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)
|
||
}
|
||
|
||
private func showClearCacheConfirmation() {
|
||
let alert = UIAlertController(
|
||
title: YMLocalizedString("EPEditSetting.ClearCacheTitle"),
|
||
message: YMLocalizedString("EPEditSetting.ClearCacheMessage"),
|
||
preferredStyle: .alert
|
||
)
|
||
|
||
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Cancel"), style: .cancel))
|
||
alert.addAction(UIAlertAction(title: YMLocalizedString("EPEditSetting.Confirm"), style: .destructive) { [weak self] _ in
|
||
self?.performClearCache()
|
||
})
|
||
|
||
present(alert, animated: true)
|
||
}
|
||
|
||
private func performClearCache() {
|
||
print("[EPEditSetting] 开始清理缓存")
|
||
|
||
// 显示加载状态
|
||
showLoading()
|
||
|
||
// 1. 清理 SDWebImage 图片缓存
|
||
SDWebImageManager.shared.imageCache.clear?(with: .all) {
|
||
print("[EPEditSetting] SDWebImage 缓存已清理")
|
||
|
||
// 2. 清理 WKWebsiteDataStore 网页缓存
|
||
let websiteDataTypes = WKWebsiteDataStore.allWebsiteDataTypes()
|
||
let dateFrom = Date(timeIntervalSince1970: 0)
|
||
WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: dateFrom) { [weak self] in
|
||
print("[EPEditSetting] WKWebsiteDataStore 缓存已清理")
|
||
|
||
DispatchQueue.main.async {
|
||
self?.hideHUD()
|
||
self?.showSuccessToast(YMLocalizedString("EPEditSetting.ClearCacheSuccess"))
|
||
print("[EPEditSetting] 缓存清理完成")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private func showPolicyOptionsSheet() {
|
||
let alert = UIAlertController(
|
||
title: nil,
|
||
message: nil,
|
||
preferredStyle: .actionSheet
|
||
)
|
||
|
||
// 用户服务协议
|
||
alert.addAction(UIAlertAction(
|
||
title: YMLocalizedString("EPEditSetting.UserAgreement"),
|
||
style: .default
|
||
) { [weak self] _ in
|
||
let url = self?.getUserAgreementURL() ?? ""
|
||
self?.openPolicyInExternalBrowser(url)
|
||
})
|
||
|
||
// 隐私政策
|
||
alert.addAction(UIAlertAction(
|
||
title: YMLocalizedString("EPEditSetting.PrivacyPolicy"),
|
||
style: .default
|
||
) { [weak self] _ in
|
||
let url = self?.getPrivacyPolicyURL() ?? ""
|
||
self?.openPolicyInExternalBrowser(url)
|
||
})
|
||
|
||
// 取消
|
||
alert.addAction(UIAlertAction(
|
||
title: YMLocalizedString("EPEditSetting.Cancel"),
|
||
style: .cancel
|
||
))
|
||
|
||
// iPad 支持
|
||
if let popover = alert.popoverPresentationController {
|
||
popover.sourceView = view
|
||
popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
||
popover.permittedArrowDirections = []
|
||
}
|
||
|
||
present(alert, animated: true)
|
||
}
|
||
|
||
/// 获取用户协议 URL
|
||
private func getUserAgreementURL() -> String {
|
||
// kUserProtocalURL 对应枚举值 4
|
||
let url = URLWithType(URLType(rawValue: 4)!) as String
|
||
print("[EPEditSetting] User agreement URL from URLWithType: \(url)")
|
||
return url
|
||
}
|
||
|
||
/// 获取隐私政策 URL
|
||
private func getPrivacyPolicyURL() -> String {
|
||
// kPrivacyURL 对应枚举值 0
|
||
let url = URLWithType(URLType(rawValue: 0)!) as String
|
||
print("[EPEditSetting] Privacy policy URL from URLWithType: \(url)")
|
||
return url
|
||
}
|
||
|
||
/// 获取 FAQ 帮助页面 URL
|
||
private func getFAQURL() -> String {
|
||
// kFAQURL 对应枚举值 6
|
||
let url = URLWithType(URLType(rawValue: 6)!) as String
|
||
print("[EPEditSetting] FAQ URL from URLWithType: \(url)")
|
||
return url
|
||
}
|
||
|
||
private func openPolicyInExternalBrowser(_ urlString: String) {
|
||
print("[EPEditSetting] Original URL: \(urlString)")
|
||
|
||
// 如果不是完整 URL,拼接域名
|
||
var fullUrl = urlString
|
||
if !urlString.hasPrefix("http") && !urlString.hasPrefix("https") {
|
||
let hostUrl = HttpRequestHelper.getHostUrl()
|
||
fullUrl = "\(hostUrl)/\(urlString)"
|
||
print("[EPEditSetting] Added host URL, full URL: \(fullUrl)")
|
||
}
|
||
|
||
print("[EPEditSetting] Opening URL in external browser: \(fullUrl)")
|
||
|
||
guard let url = URL(string: fullUrl) else {
|
||
print("[EPEditSetting] ❌ Invalid URL: \(fullUrl)")
|
||
return
|
||
}
|
||
|
||
print("[EPEditSetting] URL object created: \(url)")
|
||
|
||
// 在外部浏览器中打开
|
||
if UIApplication.shared.canOpenURL(url) {
|
||
print("[EPEditSetting] ✅ Can open URL, attempting to open...")
|
||
UIApplication.shared.open(url, options: [:]) { success in
|
||
print("[EPEditSetting] Open external browser: \(success ? "✅ Success" : "❌ Failed")")
|
||
}
|
||
} else {
|
||
print("[EPEditSetting] ❌ Cannot open URL: \(fullUrl)")
|
||
}
|
||
}
|
||
|
||
// 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
|
||
cell.textLabel?.textColor = .white
|
||
|
||
// 添加右箭头图标
|
||
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)
|
||
}
|
||
}
|
||
|
||
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 action: () -> Void
|
||
|
||
init(title: String, action: @escaping () -> Void) {
|
||
self.title = title
|
||
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
|
||
)
|
||
}
|
||
}
|