keep edit
This commit is contained in:
159
YuMi/E-P/Mine/Controllers/EPAboutUsViewController.swift
Normal file
159
YuMi/E-P/Mine/Controllers/EPAboutUsViewController.swift
Normal file
@@ -0,0 +1,159 @@
|
||||
|
||||
|
||||
// Created by AI on 2025-01-28.
|
||||
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
|
||||
class EPAboutUsViewController: BaseViewController {
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
private lazy var appIconImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.layer.cornerRadius = 20
|
||||
imageView.layer.masksToBounds = true
|
||||
|
||||
if let iconName = Bundle.main.object(forInfoDictionaryKey: "CFBundleIconName") as? String {
|
||||
imageView.image = UIImage(named: iconName)
|
||||
} else if let icons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any],
|
||||
let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any],
|
||||
let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String],
|
||||
let lastIcon = iconFiles.last {
|
||||
imageView.image = UIImage(named: lastIcon)
|
||||
} else {
|
||||
|
||||
imageView.image = UIImage(named: "pi_app_logo_new_bg")
|
||||
}
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var appNameLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
|
||||
?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String
|
||||
?? "YuMi"
|
||||
label.textColor = .white
|
||||
label.font = .systemFont(ofSize: 24, weight: .bold)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var versionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0.0"
|
||||
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "1"
|
||||
label.text = "Version \(version) (\(build))"
|
||||
label.textColor = UIColor.white.withAlphaComponent(0.7)
|
||||
label.font = .systemFont(ofSize: 16)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupNavigationBar()
|
||||
setupUI()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
private func setupNavigationBar() {
|
||||
title = YMLocalizedString("EPEditSetting.AboutUs")
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
view.backgroundColor = UIColor(hex: "#0C0527")
|
||||
|
||||
|
||||
let containerView = UIView()
|
||||
view.addSubview(containerView)
|
||||
|
||||
|
||||
containerView.addSubview(appIconImageView)
|
||||
containerView.addSubview(appNameLabel)
|
||||
containerView.addSubview(versionLabel)
|
||||
|
||||
|
||||
containerView.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(view).offset(-50)
|
||||
make.leading.trailing.equalTo(view).inset(40)
|
||||
}
|
||||
|
||||
|
||||
appIconImageView.snp.makeConstraints { make in
|
||||
make.top.equalTo(containerView)
|
||||
make.centerX.equalTo(containerView)
|
||||
make.size.equalTo(100)
|
||||
}
|
||||
|
||||
|
||||
appNameLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(appIconImageView.snp.bottom).offset(24)
|
||||
make.leading.trailing.equalTo(containerView)
|
||||
}
|
||||
|
||||
|
||||
versionLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(appNameLabel.snp.bottom).offset(12)
|
||||
make.leading.trailing.equalTo(containerView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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:
|
||||
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
|
||||
case 6:
|
||||
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
|
||||
case 8:
|
||||
(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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
162
YuMi/E-P/Mine/Controllers/EPAboutUsViewController.swift.backup
Normal file
162
YuMi/E-P/Mine/Controllers/EPAboutUsViewController.swift.backup
Normal file
@@ -0,0 +1,162 @@
|
||||
//
|
||||
// EPAboutUsViewController.swift
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-01-28.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
/// About Us 页面
|
||||
/// 展示应用图标和版本信息
|
||||
class EPAboutUsViewController: BaseViewController {
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
private lazy var appIconImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.layer.cornerRadius = 20
|
||||
imageView.layer.masksToBounds = true
|
||||
// 获取应用图标
|
||||
if let iconName = Bundle.main.object(forInfoDictionaryKey: "CFBundleIconName") as? String {
|
||||
imageView.image = UIImage(named: iconName)
|
||||
} else if let icons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any],
|
||||
let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any],
|
||||
let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String],
|
||||
let lastIcon = iconFiles.last {
|
||||
imageView.image = UIImage(named: lastIcon)
|
||||
} else {
|
||||
// 使用占位图标
|
||||
imageView.image = UIImage(named: "pi_app_logo_new_bg")
|
||||
}
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var appNameLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
|
||||
?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String
|
||||
?? "YuMi"
|
||||
label.textColor = .white
|
||||
label.font = .systemFont(ofSize: 24, weight: .bold)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var versionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0.0"
|
||||
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "1"
|
||||
label.text = "Version \(version) (\(build))"
|
||||
label.textColor = UIColor.white.withAlphaComponent(0.7)
|
||||
label.font = .systemFont(ofSize: 16)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupNavigationBar()
|
||||
setupUI()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
private func setupNavigationBar() {
|
||||
title = YMLocalizedString("EPEditSetting.AboutUs")
|
||||
|
||||
// 配置导航栏外观(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)
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
view.backgroundColor = UIColor(hex: "#0C0527")
|
||||
|
||||
// 创建容器视图
|
||||
let containerView = UIView()
|
||||
view.addSubview(containerView)
|
||||
|
||||
// 添加 UI 组件到容器
|
||||
containerView.addSubview(appIconImageView)
|
||||
containerView.addSubview(appNameLabel)
|
||||
containerView.addSubview(versionLabel)
|
||||
|
||||
// 布局容器(垂直居中)
|
||||
containerView.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(view).offset(-50) // 稍微偏上
|
||||
make.leading.trailing.equalTo(view).inset(40)
|
||||
}
|
||||
|
||||
// 应用图标
|
||||
appIconImageView.snp.makeConstraints { make in
|
||||
make.top.equalTo(containerView)
|
||||
make.centerX.equalTo(containerView)
|
||||
make.size.equalTo(100)
|
||||
}
|
||||
|
||||
// 应用名称
|
||||
appNameLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(appIconImageView.snp.bottom).offset(24)
|
||||
make.leading.trailing.equalTo(containerView)
|
||||
}
|
||||
|
||||
// 版本号
|
||||
versionLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(appNameLabel.snp.bottom).offset(12)
|
||||
make.leading.trailing.equalTo(containerView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
846
YuMi/E-P/Mine/Controllers/EPEditSettingViewController.swift
Normal file
846
YuMi/E-P/Mine/Controllers/EPEditSettingViewController.swift
Normal file
@@ -0,0 +1,846 @@
|
||||
|
||||
|
||||
// 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
|
||||
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
|
||||
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()
|
||||
|
||||
|
||||
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),
|
||||
UIColor(red: 0x50/255.0, green: 0x0F/255.0, blue: 0xFF/255.0, alpha: 1.0)
|
||||
],
|
||||
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")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
navigationController?.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(
|
||||
title: "",
|
||||
style: .plain,
|
||||
target: nil,
|
||||
action: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func restoreParentNavigationBarStyle() {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
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() {
|
||||
|
||||
if userInfo != nil {
|
||||
updateProfileImage()
|
||||
tableView.reloadData()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let uid = AccountInfoStorage.instance().getUid(), !uid.isEmpty else {
|
||||
print("[EPEditSetting] 未登录,无法获取用户信息")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 调用API获取用户详细信息
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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.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)] - 功能触发")
|
||||
|
||||
|
||||
if title == "AboutUs" {
|
||||
let aboutVC = EPAboutUsViewController()
|
||||
navigationController?.pushViewController(aboutVC, animated: true)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if title == "PersonalInfo" {
|
||||
showPolicyOptionsSheet()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if title == "Help" {
|
||||
let faqUrl = getFAQURL()
|
||||
openPolicyInExternalBrowser(faqUrl)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
SDWebImageManager.shared.imageCache.clear?(with: .all) {
|
||||
print("[EPEditSetting] SDWebImage 缓存已清理")
|
||||
|
||||
|
||||
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
|
||||
))
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
private func getUserAgreementURL() -> String {
|
||||
|
||||
let url = URLWithType(URLType(rawValue: 4)!) as String
|
||||
print("[EPEditSetting] User agreement URL from URLWithType: \(url)")
|
||||
return url
|
||||
}
|
||||
|
||||
|
||||
private func getPrivacyPolicyURL() -> String {
|
||||
|
||||
let url = URLWithType(URLType(rawValue: 0)!) as String
|
||||
print("[EPEditSetting] Privacy policy URL from URLWithType: \(url)")
|
||||
return url
|
||||
}
|
||||
|
||||
|
||||
private func getFAQURL() -> String {
|
||||
|
||||
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)")
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
let count = settingItems.count + 1
|
||||
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
|
||||
}
|
||||
|
||||
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.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)")
|
||||
|
||||
|
||||
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) {
|
||||
|
||||
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) {
|
||||
|
||||
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:
|
||||
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
|
||||
case 6:
|
||||
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
|
||||
case 8:
|
||||
(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
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,850 @@
|
||||
//
|
||||
// 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
|
||||
)
|
||||
}
|
||||
}
|
16
YuMi/E-P/Mine/Controllers/EPMineViewController.h
Normal file
16
YuMi/E-P/Mine/Controllers/EPMineViewController.h
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
// Created by AI on 2025-10-09.
|
||||
// Copyright © 2025 YuMi. All rights reserved.
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@interface EPMineViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
20
YuMi/E-P/Mine/Controllers/EPMineViewController.h.backup
Normal file
20
YuMi/E-P/Mine/Controllers/EPMineViewController.h.backup
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// EPMineViewController.h
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-09.
|
||||
// Copyright © 2025 YuMi. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 新的个人中心页面控制器
|
||||
/// 采用纵向卡片式设计,完全不同于原 XPMineViewController
|
||||
/// 注意:直接继承 UIViewController,不继承 BaseViewController(避免依赖链)
|
||||
@interface EPMineViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
218
YuMi/E-P/Mine/Controllers/EPMineViewController.m
Normal file
218
YuMi/E-P/Mine/Controllers/EPMineViewController.m
Normal file
@@ -0,0 +1,218 @@
|
||||
|
||||
|
||||
// Created by AI on 2025-10-09.
|
||||
// Copyright © 2025 YuMi. All rights reserved.
|
||||
|
||||
|
||||
#import "EPMineViewController.h"
|
||||
#import "EPMineHeaderView.h"
|
||||
#import "EPMomentListView.h"
|
||||
#import "EPMineAPIHelper.h"
|
||||
#import "AccountInfoStorage.h"
|
||||
#import "UserInfoModel.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "YuMi-Swift.h"
|
||||
|
||||
@interface EPMineViewController ()
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
|
||||
@property (nonatomic, strong) EPMomentListView *momentListView;
|
||||
|
||||
|
||||
@property (nonatomic, strong) EPMineHeaderView *headerView;
|
||||
|
||||
// MARK: - Data
|
||||
|
||||
|
||||
@property (nonatomic, strong) UserInfoModel *userInfo;
|
||||
|
||||
|
||||
@property (nonatomic, strong) EPMineAPIHelper *apiHelper;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EPMineViewController
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[self setupUI];
|
||||
|
||||
NSLog(@"[EPMineViewController] viewDidLoad 完成");
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[self.navigationController setNavigationBarHidden:YES animated:animated];
|
||||
|
||||
[self loadUserDetailInfo];
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
- (void)setupUI {
|
||||
UIImageView *bgImageView = [[UIImageView alloc] initWithImage:kImage(@"vc_bg")];
|
||||
bgImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
bgImageView.clipsToBounds = YES;
|
||||
[self.view addSubview:bgImageView];
|
||||
[bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self.view);
|
||||
}];
|
||||
|
||||
[self setupHeaderView];
|
||||
[self setupMomentListView];
|
||||
|
||||
NSLog(@"[EPMineViewController] UI 设置完成");
|
||||
}
|
||||
|
||||
- (void)setupHeaderView {
|
||||
self.headerView = [[EPMineHeaderView alloc] initWithFrame:CGRectZero];
|
||||
[self.view addSubview:self.headerView];
|
||||
|
||||
|
||||
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.view);
|
||||
make.leading.mas_equalTo(self.view);
|
||||
make.trailing.mas_equalTo(self.view);
|
||||
make.height.mas_equalTo(kGetScaleWidth(260));
|
||||
}];
|
||||
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.headerView.onSettingsButtonTapped = ^{
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
[self openSettings];
|
||||
};
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onAvatarUpdated:)
|
||||
name:@"EPEditSettingAvatarUpdated"
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)setupMomentListView {
|
||||
self.momentListView = [[EPMomentListView alloc] initWithFrame:CGRectZero];
|
||||
[self.view addSubview:self.momentListView];
|
||||
|
||||
[self.momentListView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.headerView.mas_bottom);
|
||||
make.bottom.mas_equalTo(self.view);
|
||||
make.leading.mas_equalTo(self.view);
|
||||
make.trailing.mas_equalTo(self.view);
|
||||
}];
|
||||
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.momentListView loadWithDynamicInfo:@[] refreshCallback:^{
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
[self loadUserDetailInfo];
|
||||
}];
|
||||
}
|
||||
|
||||
// MARK: - Data Loading
|
||||
|
||||
- (void)loadUserDetailInfo {
|
||||
NSString *uid = [[AccountInfoStorage instance] getUid];
|
||||
if (!uid || uid.length == 0) {
|
||||
NSLog(@"[EPMineViewController] 用户未登录");
|
||||
return;
|
||||
}
|
||||
|
||||
@kWeakify(self);
|
||||
[self.apiHelper getUserDetailInfoWithUid:uid
|
||||
completion:^(UserInfoModel * _Nullable userInfo) {
|
||||
@kStrongify(self);
|
||||
if (!userInfo) {
|
||||
NSLog(@"[EPMineViewController] 加载用户信息失败");
|
||||
return;
|
||||
}
|
||||
|
||||
self.userInfo = userInfo;
|
||||
[self updateHeaderWithUserInfo:userInfo];
|
||||
|
||||
|
||||
if (userInfo.dynamicInfo && userInfo.dynamicInfo.count > 0) {
|
||||
[self.momentListView loadWithDynamicInfo:userInfo.dynamicInfo refreshCallback:^{
|
||||
[self loadUserDetailInfo];
|
||||
}];
|
||||
}
|
||||
} failure:^(NSInteger code, NSString * _Nullable msg) {
|
||||
NSLog(@"[EPMineViewController] 加载用户信息失败: %@", msg);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateHeaderWithUserInfo:(UserInfoModel *)userInfo {
|
||||
NSDictionary *userInfoDict = @{
|
||||
@"nickname": userInfo.nick ?: @"未设置昵称",
|
||||
@"uid": [NSString stringWithFormat:@"%ld", (long)userInfo.erbanNo],
|
||||
@"avatar": userInfo.avatar ?: @"",
|
||||
@"following": @(userInfo.followNum),
|
||||
@"followers": @(userInfo.fansNum)
|
||||
};
|
||||
|
||||
[self.headerView updateWithUserInfo:userInfoDict];
|
||||
}
|
||||
|
||||
// MARK: - Lazy Loading
|
||||
|
||||
- (EPMomentListView *)momentListView {
|
||||
if (!_momentListView) {
|
||||
_momentListView = [[EPMomentListView alloc] init];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_momentListView.onSelectMoment = ^(NSInteger index) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
NSLog(@"[EPMineViewController] 点击了第 %ld 条动态", (long)index);
|
||||
// TODO: 跳转到动态详情页
|
||||
};
|
||||
}
|
||||
return _momentListView;
|
||||
}
|
||||
|
||||
- (EPMineAPIHelper *)apiHelper {
|
||||
if (!_apiHelper) {
|
||||
_apiHelper = [[EPMineAPIHelper alloc] init];
|
||||
}
|
||||
return _apiHelper;
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
- (void)openSettings {
|
||||
|
||||
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@""
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
|
||||
EPEditSettingViewController *settingsVC = [[EPEditSettingViewController alloc] init];
|
||||
|
||||
if (self.userInfo) {
|
||||
[settingsVC updateWithUserInfo:self.userInfo];
|
||||
}
|
||||
[self.navigationController pushViewController:settingsVC animated:YES];
|
||||
NSLog(@"[EPMineViewController] 打开设置页面,已传递用户信息");
|
||||
}
|
||||
|
||||
- (void)onAvatarUpdated:(NSNotification *)notification {
|
||||
NSString *avatarUrl = notification.userInfo[@"avatarUrl"];
|
||||
if (avatarUrl && self.userInfo) {
|
||||
|
||||
self.userInfo.avatar = avatarUrl;
|
||||
|
||||
|
||||
[self updateHeaderWithUserInfo:self.userInfo];
|
||||
|
||||
NSLog(@"[EPMineViewController] 头像已更新: %@", avatarUrl);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"EPEditSettingAvatarUpdated" object:nil];
|
||||
}
|
||||
|
||||
@end
|
220
YuMi/E-P/Mine/Controllers/EPMineViewController.m.backup
Normal file
220
YuMi/E-P/Mine/Controllers/EPMineViewController.m.backup
Normal file
@@ -0,0 +1,220 @@
|
||||
//
|
||||
// EPMineViewController.m
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-09.
|
||||
// Copyright © 2025 YuMi. All rights reserved.
|
||||
//
|
||||
|
||||
#import "EPMineViewController.h"
|
||||
#import "EPMineHeaderView.h"
|
||||
#import "EPMomentListView.h"
|
||||
#import "EPMineAPIHelper.h"
|
||||
#import "AccountInfoStorage.h"
|
||||
#import "UserInfoModel.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "YuMi-Swift.h" // 导入Swift桥接
|
||||
|
||||
@interface EPMineViewController ()
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
/// 动态列表视图(复用 EPMomentListView)
|
||||
@property (nonatomic, strong) EPMomentListView *momentListView;
|
||||
|
||||
/// 顶部个人信息卡片
|
||||
@property (nonatomic, strong) EPMineHeaderView *headerView;
|
||||
|
||||
// MARK: - Data
|
||||
|
||||
/// 用户信息模型
|
||||
@property (nonatomic, strong) UserInfoModel *userInfo;
|
||||
|
||||
/// API Helper
|
||||
@property (nonatomic, strong) EPMineAPIHelper *apiHelper;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EPMineViewController
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[self setupUI];
|
||||
|
||||
NSLog(@"[EPMineViewController] viewDidLoad 完成");
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[self.navigationController setNavigationBarHidden:YES animated:animated];
|
||||
// 每次显示时加载最新数据
|
||||
[self loadUserDetailInfo];
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
- (void)setupUI {
|
||||
UIImageView *bgImageView = [[UIImageView alloc] initWithImage:kImage(@"vc_bg")];
|
||||
bgImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
bgImageView.clipsToBounds = YES;
|
||||
[self.view addSubview:bgImageView];
|
||||
[bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self.view);
|
||||
}];
|
||||
|
||||
[self setupHeaderView];
|
||||
[self setupMomentListView];
|
||||
|
||||
NSLog(@"[EPMineViewController] UI 设置完成");
|
||||
}
|
||||
|
||||
- (void)setupHeaderView {
|
||||
self.headerView = [[EPMineHeaderView alloc] initWithFrame:CGRectZero];
|
||||
[self.view addSubview:self.headerView];
|
||||
|
||||
// 使用 Masonry 约束布局
|
||||
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.view);
|
||||
make.leading.mas_equalTo(self.view);
|
||||
make.trailing.mas_equalTo(self.view);
|
||||
make.height.mas_equalTo(kGetScaleWidth(260));
|
||||
}];
|
||||
|
||||
// 设置按钮点击回调
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.headerView.onSettingsButtonTapped = ^{
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
[self openSettings];
|
||||
};
|
||||
|
||||
// 监听头像更新事件
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onAvatarUpdated:)
|
||||
name:@"EPEditSettingAvatarUpdated"
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)setupMomentListView {
|
||||
self.momentListView = [[EPMomentListView alloc] initWithFrame:CGRectZero];
|
||||
[self.view addSubview:self.momentListView];
|
||||
|
||||
[self.momentListView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.headerView.mas_bottom);
|
||||
make.bottom.mas_equalTo(self.view);
|
||||
make.leading.mas_equalTo(self.view);
|
||||
make.trailing.mas_equalTo(self.view);
|
||||
}];
|
||||
|
||||
// 初始化为空的本地模式,避免在数据加载前触发网络请求
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.momentListView loadWithDynamicInfo:@[] refreshCallback:^{
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
[self loadUserDetailInfo];
|
||||
}];
|
||||
}
|
||||
|
||||
// MARK: - Data Loading
|
||||
|
||||
- (void)loadUserDetailInfo {
|
||||
NSString *uid = [[AccountInfoStorage instance] getUid];
|
||||
if (!uid || uid.length == 0) {
|
||||
NSLog(@"[EPMineViewController] 用户未登录");
|
||||
return;
|
||||
}
|
||||
|
||||
@kWeakify(self);
|
||||
[self.apiHelper getUserDetailInfoWithUid:uid
|
||||
completion:^(UserInfoModel * _Nullable userInfo) {
|
||||
@kStrongify(self);
|
||||
if (!userInfo) {
|
||||
NSLog(@"[EPMineViewController] 加载用户信息失败");
|
||||
return;
|
||||
}
|
||||
|
||||
self.userInfo = userInfo;
|
||||
[self updateHeaderWithUserInfo:userInfo];
|
||||
|
||||
// 如果有动态信息,直接使用
|
||||
if (userInfo.dynamicInfo && userInfo.dynamicInfo.count > 0) {
|
||||
[self.momentListView loadWithDynamicInfo:userInfo.dynamicInfo refreshCallback:^{
|
||||
[self loadUserDetailInfo]; // 刷新时重新加载
|
||||
}];
|
||||
}
|
||||
} failure:^(NSInteger code, NSString * _Nullable msg) {
|
||||
NSLog(@"[EPMineViewController] 加载用户信息失败: %@", msg);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateHeaderWithUserInfo:(UserInfoModel *)userInfo {
|
||||
NSDictionary *userInfoDict = @{
|
||||
@"nickname": userInfo.nick ?: @"未设置昵称",
|
||||
@"uid": [NSString stringWithFormat:@"%ld", (long)userInfo.erbanNo],
|
||||
@"avatar": userInfo.avatar ?: @"",
|
||||
@"following": @(userInfo.followNum),
|
||||
@"followers": @(userInfo.fansNum)
|
||||
};
|
||||
|
||||
[self.headerView updateWithUserInfo:userInfoDict];
|
||||
}
|
||||
|
||||
// MARK: - Lazy Loading
|
||||
|
||||
- (EPMomentListView *)momentListView {
|
||||
if (!_momentListView) {
|
||||
_momentListView = [[EPMomentListView alloc] init];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_momentListView.onSelectMoment = ^(NSInteger index) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
NSLog(@"[EPMineViewController] 点击了第 %ld 条动态", (long)index);
|
||||
// TODO: 跳转到动态详情页
|
||||
};
|
||||
}
|
||||
return _momentListView;
|
||||
}
|
||||
|
||||
- (EPMineAPIHelper *)apiHelper {
|
||||
if (!_apiHelper) {
|
||||
_apiHelper = [[EPMineAPIHelper alloc] init];
|
||||
}
|
||||
return _apiHelper;
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
- (void)openSettings {
|
||||
// 隐藏返回按钮文字,只保留白色箭头
|
||||
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@""
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
|
||||
EPEditSettingViewController *settingsVC = [[EPEditSettingViewController alloc] init];
|
||||
// 传递用户信息到设置页面
|
||||
if (self.userInfo) {
|
||||
[settingsVC updateWithUserInfo:self.userInfo];
|
||||
}
|
||||
[self.navigationController pushViewController:settingsVC animated:YES];
|
||||
NSLog(@"[EPMineViewController] 打开设置页面,已传递用户信息");
|
||||
}
|
||||
|
||||
- (void)onAvatarUpdated:(NSNotification *)notification {
|
||||
NSString *avatarUrl = notification.userInfo[@"avatarUrl"];
|
||||
if (avatarUrl && self.userInfo) {
|
||||
// 更新本地用户信息
|
||||
self.userInfo.avatar = avatarUrl;
|
||||
|
||||
// 更新 UI 显示
|
||||
[self updateHeaderWithUserInfo:self.userInfo];
|
||||
|
||||
NSLog(@"[EPMineViewController] 头像已更新: %@", avatarUrl);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// 只移除头像更新通知的观察者,设置按钮现在使用 block 回调
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"EPEditSettingAvatarUpdated" object:nil];
|
||||
}
|
||||
|
||||
@end
|
Reference in New Issue
Block a user