SettingsViewController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. //
  2. // SettingsViewController.swift
  3. // VenusKitto
  4. //
  5. // Created by Neoa on 2025/8/27.
  6. //
  7. import Foundation
  8. import UIKit
  9. import StoreKit
  10. // MARK: - Settings
  11. final class SettingsViewController: UIViewController {
  12. // 背景图
  13. private let bgImageView: UIImageView = {
  14. let iv = UIImageView(image: UIImage(named: "wode465"))
  15. iv.contentMode = .scaleAspectFill
  16. iv.clipsToBounds = true
  17. return iv
  18. }()
  19. private let titleLabel: UILabel = {
  20. let l = UILabel()
  21. l.text = "设置"
  22. l.font = .systemFont(ofSize: 28, weight: .semibold)
  23. l.textColor = UIColor(hex: "#2B2B2B")
  24. return l
  25. }()
  26. // 顶部用户卡片(手绘边框)
  27. private let profileCard = UIImageView(image: UIImage(named: "wode402"))
  28. private let avatarView = UIImageView()
  29. private let phoneLabel = UILabel()
  30. private let idLabel = UILabel()
  31. private let profileChevron = UIImageView(image: UIImage(systemName: "chevron.right"))
  32. // 三个功能行
  33. private lazy var rateRow = makeRow(title: "给个好评", action: #selector(tapRate))
  34. private lazy var peteRow = makeRow(title: "我的宠物", action: #selector(tapPet))
  35. private lazy var feedbackRow = makeRow(title: "意见反馈", action: #selector(tapFeedback))
  36. private lazy var aboutRow = makeRow(title: "关于我们", action: #selector(tapAbout))
  37. override func viewDidLoad() {
  38. super.viewDidLoad()
  39. view.backgroundColor = UIColor(hex: "#FFFEFC")
  40. setupBackground()
  41. if let backImage = UIImage(named: "AddPet385") {
  42. let backButton = UIBarButtonItem(image: backImage.withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(tapCancel))
  43. navigationItem.leftBarButtonItem = backButton
  44. }
  45. NotificationCenter.default.addObserver(self,
  46. selector: #selector(handleAvatarUpdated(_:)),
  47. name: Notification.Name("UserAvatarUpdated"),
  48. object: nil)
  49. buildUI()
  50. fillUserInfo()
  51. }
  52. deinit {
  53. NotificationCenter.default.removeObserver(self, name: Notification.Name("UserAvatarUpdated"), object: nil)
  54. }
  55. @objc private func handleAvatarUpdated(_ note: Notification) {
  56. if let urlStr = note.object as? String, let url = URL(string: urlStr) {
  57. // 直接用通知携带的新 URL 刷新头像
  58. loadImage(from: url) { [weak self] img in
  59. self?.avatarView.image = img ?? UIImage(named: "Home372")
  60. }
  61. // 同步到本地,保证下次进入可以读到
  62. UserDefaults.standard.set(urlStr, forKey: "memberIcon")
  63. } else if let urlStr = (UserDefaults.standard.string(forKey: "memberIcon")),
  64. let url = URL(string: urlStr) {
  65. // 兜底:没有携带 object 时,从本地读取刷新
  66. loadImage(from: url) { [weak self] img in
  67. self?.avatarView.image = img ?? UIImage(named: "Home372")
  68. }
  69. }
  70. }
  71. private func buildUI() {
  72. // 顶部标题
  73. view.addSubview(titleLabel)
  74. titleLabel.translatesAutoresizingMaskIntoConstraints = false
  75. NSLayoutConstraint.activate([
  76. titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),
  77. titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24)
  78. ])
  79. // 用户卡片
  80. profileCard.contentMode = .scaleToFill
  81. profileCard.isUserInteractionEnabled = true
  82. let tapCard = UITapGestureRecognizer(target: self, action: #selector(tapProfile))
  83. profileCard.addGestureRecognizer(tapCard)
  84. view.addSubview(profileCard)
  85. profileCard.translatesAutoresizingMaskIntoConstraints = false
  86. NSLayoutConstraint.activate([
  87. profileCard.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12),
  88. profileCard.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
  89. profileCard.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
  90. profileCard.heightAnchor.constraint(equalToConstant: 92)
  91. ])
  92. avatarView.backgroundColor = UIColor(hex: "#FFF4CC")
  93. avatarView.layer.cornerRadius = 12
  94. avatarView.layer.masksToBounds = true
  95. avatarView.contentMode = .scaleAspectFill
  96. phoneLabel.font = .systemFont(ofSize: 16, weight: .semibold)
  97. phoneLabel.textColor = UIColor(hex: "#2B2B2B")
  98. idLabel.font = .systemFont(ofSize: 12)
  99. idLabel.textColor = UIColor(hex: "#9B9B9B")
  100. profileChevron.tintColor = UIColor(hex: "#5B4227")
  101. profileChevron.setContentCompressionResistancePriority(.required, for: .horizontal)
  102. [avatarView, phoneLabel, idLabel, profileChevron].forEach {
  103. $0.translatesAutoresizingMaskIntoConstraints = false
  104. profileCard.addSubview($0)
  105. }
  106. NSLayoutConstraint.activate([
  107. avatarView.leadingAnchor.constraint(equalTo: profileCard.leadingAnchor, constant: 16),
  108. avatarView.centerYAnchor.constraint(equalTo: profileCard.centerYAnchor),
  109. avatarView.widthAnchor.constraint(equalToConstant: 56),
  110. avatarView.heightAnchor.constraint(equalToConstant: 56),
  111. profileChevron.trailingAnchor.constraint(equalTo: profileCard.trailingAnchor, constant: -12),
  112. profileChevron.centerYAnchor.constraint(equalTo: profileCard.centerYAnchor),
  113. profileChevron.widthAnchor.constraint(equalToConstant: 16),
  114. profileChevron.heightAnchor.constraint(equalToConstant: 16),
  115. phoneLabel.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, constant: 12),
  116. phoneLabel.trailingAnchor.constraint(lessThanOrEqualTo: profileChevron.leadingAnchor, constant: -8),
  117. phoneLabel.bottomAnchor.constraint(equalTo: profileCard.centerYAnchor, constant: -2),
  118. idLabel.leadingAnchor.constraint(equalTo: phoneLabel.leadingAnchor),
  119. idLabel.topAnchor.constraint(equalTo: profileCard.centerYAnchor, constant: 2),
  120. idLabel.trailingAnchor.constraint(lessThanOrEqualTo: profileChevron.leadingAnchor, constant: -8)
  121. ])
  122. // 三个功能 row
  123. let stack = UIStackView(arrangedSubviews: [rateRow, peteRow, feedbackRow, aboutRow])
  124. stack.axis = .vertical
  125. stack.spacing = 12
  126. stack.alignment = .fill
  127. view.addSubview(stack)
  128. stack.translatesAutoresizingMaskIntoConstraints = false
  129. NSLayoutConstraint.activate([
  130. stack.topAnchor.constraint(equalTo: profileCard.bottomAnchor, constant: 16),
  131. stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
  132. stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16)
  133. ])
  134. }
  135. private func setupBackground() {
  136. view.insertSubview(bgImageView, at: 0)
  137. bgImageView.translatesAutoresizingMaskIntoConstraints = false
  138. NSLayoutConstraint.activate([
  139. bgImageView.topAnchor.constraint(equalTo: view.topAnchor),
  140. bgImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  141. bgImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  142. bgImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
  143. ])
  144. }
  145. @objc private func tapCancel() { navigationController?.popViewController(animated: true)
  146. }
  147. private func makeRow(title: String, action: Selector) -> UIControl {
  148. let container = UIControl()
  149. container.backgroundColor = .white
  150. container.layer.cornerRadius = 14
  151. container.layer.shadowColor = UIColor.black.cgColor
  152. container.layer.shadowOpacity = 0.08
  153. container.layer.shadowRadius = 10
  154. container.layer.shadowOffset = CGSize(width: 0, height: 4)
  155. container.heightAnchor.constraint(equalToConstant: 64).isActive = true
  156. let label = UILabel()
  157. label.text = title
  158. label.font = .systemFont(ofSize: 16)
  159. label.textColor = UIColor(hex: "#2B2B2B")
  160. let chevron = UIImageView(image: UIImage(systemName: "chevron.right"))
  161. chevron.tintColor = UIColor(hex: "#5B4227")
  162. chevron.setContentCompressionResistancePriority(.required, for: .horizontal)
  163. container.addSubview(label)
  164. container.addSubview(chevron)
  165. label.translatesAutoresizingMaskIntoConstraints = false
  166. chevron.translatesAutoresizingMaskIntoConstraints = false
  167. NSLayoutConstraint.activate([
  168. label.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
  169. label.centerYAnchor.constraint(equalTo: container.centerYAnchor),
  170. chevron.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16),
  171. chevron.centerYAnchor.constraint(equalTo: container.centerYAnchor),
  172. chevron.widthAnchor.constraint(equalToConstant: 16),
  173. chevron.heightAnchor.constraint(equalToConstant: 16)
  174. ])
  175. container.addTarget(self, action: action, for: .touchUpInside)
  176. return container
  177. }
  178. private func fillUserInfo() {
  179. // 显示手机号(打码)与 ID
  180. let phone = UserDefaults.standard.string(forKey: "memberPhone") ?? "18000000000"
  181. let uid = UserDefaults.standard.string(forKey: "userId") ?? "--"
  182. phoneLabel.text = maskedPhone(phone)
  183. idLabel.text = "ID:\(uid)"
  184. if let urlStr = UserDefaults.standard.string(forKey: "memberIcon"),
  185. let url = URL(string: urlStr) {
  186. loadImage(from: url) { [weak self] img in
  187. self?.avatarView.image = img ?? UIImage(named: "Home372")
  188. }
  189. } else {
  190. avatarView.image = UIImage(named: "Home372")
  191. }
  192. }
  193. private func maskedPhone(_ s: String) -> String {
  194. let digits = s.filter { $0.isNumber }
  195. guard digits.count >= 7 else { return s }
  196. let start = digits.prefix(3)
  197. let end = digits.suffix(4)
  198. return "\(start)****\(end)"
  199. }
  200. private func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
  201. URLSession.shared.dataTask(with: url) { data, _, _ in
  202. guard let data = data, let img = UIImage(data: data) else {
  203. DispatchQueue.main.async { completion(nil) }
  204. return
  205. }
  206. DispatchQueue.main.async { completion(img) }
  207. }.resume()
  208. }
  209. // MARK: - Actions
  210. @objc private func tapProfile() {
  211. // 这里可跳个人信息页;先展示提示
  212. // let ac = UIAlertController(title: "个人信息", message: "点击了头像卡片", preferredStyle: .alert)
  213. // ac.addAction(UIAlertAction(title: "好的", style: .default))
  214. // present(ac, animated: true)
  215. let userSettingsVC = UserSettingsViewController()
  216. userSettingsVC.hidesBottomBarWhenPushed = true
  217. navigationController?.pushViewController(userSettingsVC, animated: true)
  218. }
  219. @objc private func tapRate() {
  220. // 跳 App Store 评分(占位)
  221. SKStoreReviewController.requestReview()
  222. }
  223. @objc private func tapPet() {
  224. //进入我的宠物界面
  225. let vc = MyPetsViewController()
  226. vc.hidesBottomBarWhenPushed = true
  227. navigationController?.pushViewController(vc, animated: true)
  228. }
  229. @objc private func tapFeedback() {
  230. let vc = FeedbackViewController()
  231. vc.hidesBottomBarWhenPushed = true
  232. navigationController?.pushViewController(vc, animated: true)
  233. }
  234. @objc private func tapAbout() {
  235. let vc = AboutViewController()
  236. vc.hidesBottomBarWhenPushed = true
  237. navigationController?.pushViewController(vc, animated: true)
  238. }
  239. }