OverlayPanelView.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. //
  2. // SettingsPopupView.swift
  3. // RoderickRalph
  4. //
  5. // Created by Neoa on 2025/8/19.
  6. //
  7. import Foundation
  8. import UIKit
  9. // 在文件顶部 import 之后、class 之前加:
  10. protocol OverlayPanelAgent: AnyObject {
  11. func overlayPanelDidDismiss(_ panel: OverlayPanelView)
  12. }
  13. class OverlayPanelView: UIView {
  14. weak var delegate: OverlayPanelAgent?
  15. // MARK: - UI Components
  16. private let backdropView: UIView = {
  17. let view = UIView()
  18. view.backgroundColor = UIColor.black.withAlphaComponent(0.5) // Semi-transparent gray background
  19. view.translatesAutoresizingMaskIntoConstraints = false
  20. return view
  21. }()
  22. private let titleButton: UIButton = {
  23. let button = UIButton(type: .system)
  24. button.setBackgroundImage(UIImage(named: "res_x8brwne3"), for: .normal)
  25. button.setTitle("设置", for: .normal)
  26. button.setTitleColor(.white, for: .normal)
  27. button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
  28. button.translatesAutoresizingMaskIntoConstraints = false
  29. return button
  30. }()
  31. private let avatarFrameView: UIImageView = {
  32. let iv = UIImageView()
  33. iv.image = UIImage(named: "res_b8xhu2tz")
  34. iv.contentMode = .scaleAspectFit
  35. iv.translatesAutoresizingMaskIntoConstraints = false
  36. return iv
  37. }()
  38. private let avatarImageView: UIImageView = {
  39. let iv = UIImageView()
  40. iv.image = UIImage(named: "catlogos")
  41. iv.contentMode = .scaleAspectFit
  42. iv.layer.cornerRadius = 24
  43. iv.clipsToBounds = true
  44. iv.translatesAutoresizingMaskIntoConstraints = false
  45. return iv
  46. }()
  47. private let nicknameLabel: UILabel = {
  48. let label = UILabel()
  49. label.text = "昵 称"
  50. label.font = UIFont.systemFont(ofSize: 12)
  51. label.textColor = .white
  52. label.translatesAutoresizingMaskIntoConstraints = false
  53. return label
  54. }()
  55. private let nicknameValueLabel: UILabel = {
  56. let label = UILabel()
  57. label.text = "XXXX"
  58. label.backgroundColor = .white
  59. label.font = UIFont.systemFont(ofSize: 12)
  60. label.textColor = UIColor(hexString:"#E28814")
  61. label.layer.cornerRadius = 4
  62. label.clipsToBounds = true
  63. label.textAlignment = .center
  64. label.translatesAutoresizingMaskIntoConstraints = false
  65. return label
  66. }()
  67. private let roleIdLabel: UILabel = {
  68. let label = UILabel()
  69. label.text = "角色ID"
  70. label.font = UIFont.systemFont(ofSize: 12)
  71. label.textColor = .white
  72. label.translatesAutoresizingMaskIntoConstraints = false
  73. return label
  74. }()
  75. private let roleIdValueLabel: UILabel = {
  76. let label = UILabel()
  77. label.text = "XXXX"
  78. label.backgroundColor = .white
  79. label.font = UIFont.systemFont(ofSize: 12)
  80. label.textColor = UIColor(hexString:"#E28814")
  81. label.layer.cornerRadius = 4
  82. label.clipsToBounds = true
  83. label.textAlignment = .center
  84. label.translatesAutoresizingMaskIntoConstraints = false
  85. return label
  86. }()
  87. private let dismissButton: UIButton = {
  88. let button = UIButton(type: .custom)
  89. button.setImage(UIImage(named: "res_9mx2yiyi"), for: .normal)
  90. button.addTarget(self, action: #selector(dismissPanel), for: .touchUpInside)
  91. button.translatesAutoresizingMaskIntoConstraints = false
  92. return button
  93. }()
  94. private let musicLabel: UILabel = {
  95. let label = UILabel()
  96. label.text = "音乐"
  97. label.font = UIFont.systemFont(ofSize: 12)
  98. label.textColor = .white
  99. label.translatesAutoresizingMaskIntoConstraints = false
  100. return label
  101. }()
  102. private let musicToggle: UISwitch = {
  103. let switchControl = UISwitch()
  104. switchControl.isOn = false
  105. switchControl.translatesAutoresizingMaskIntoConstraints = false
  106. return switchControl
  107. }()
  108. private let sfxLabel: UILabel = {
  109. let label = UILabel()
  110. label.text = "音效"
  111. label.font = UIFont.systemFont(ofSize: 12)
  112. label.textColor = .white
  113. label.translatesAutoresizingMaskIntoConstraints = false
  114. return label
  115. }()
  116. private let sfxToggle: UISwitch = {
  117. let switchControl = UISwitch()
  118. switchControl.isOn = false
  119. switchControl.translatesAutoresizingMaskIntoConstraints = false
  120. return switchControl
  121. }()
  122. private let registeredAtLabel: UILabel = {
  123. let label = UILabel()
  124. label.text = "注册时间: 2025-05-26 17:58:48"
  125. label.font = UIFont.systemFont(ofSize: 10)
  126. label.textColor = .white
  127. label.translatesAutoresizingMaskIntoConstraints = false
  128. return label
  129. }()
  130. private let appNameLabel: UILabel = {
  131. let label = UILabel()
  132. label.text = "青柠檬记账"
  133. label.font = UIFont.systemFont(ofSize: 10)
  134. label.textColor = .white
  135. label.translatesAutoresizingMaskIntoConstraints = false
  136. return label
  137. }()
  138. private let panelImageView: UIImageView = {
  139. let imageView = UIImageView()
  140. imageView.image = UIImage(named: "res_ew6zkg25") // Background image for the pop-up
  141. imageView.contentMode = .scaleToFill
  142. imageView.translatesAutoresizingMaskIntoConstraints = false
  143. return imageView
  144. }()
  145. // MARK: - Initialization
  146. init() {
  147. super.init(frame: .zero)
  148. buildInterface()
  149. applyConstraints()
  150. }
  151. required init?(coder: NSCoder) {
  152. fatalError("init(coder:) has not been implemented")
  153. }
  154. // MARK: - UI Setup
  155. private func buildInterface() {
  156. addSubview(backdropView)
  157. backdropView.isUserInteractionEnabled = true // 允许事件穿透
  158. addSubview(panelImageView)
  159. addSubview(titleButton)
  160. addSubview(avatarFrameView)
  161. addSubview(avatarImageView)
  162. addSubview(nicknameLabel)
  163. addSubview(nicknameValueLabel)
  164. addSubview(roleIdLabel)
  165. addSubview(roleIdValueLabel)
  166. addSubview(musicLabel)
  167. addSubview(sfxLabel)
  168. addSubview(musicToggle)
  169. addSubview(sfxToggle)
  170. addSubview(dismissButton)
  171. addSubview(registeredAtLabel)
  172. addSubview(appNameLabel)
  173. // Prefill from saved user info (if available)
  174. let nickname = UserDefaults.standard.string(forKey: "nickname") ?? "XXXX"
  175. let roleID = UserDefaults.standard.string(forKey: "roleID") ?? "XXXX"
  176. let regTime = UserDefaults.standard.string(forKey: "registryTimeStr") ?? "--"
  177. nicknameValueLabel.text = nickname
  178. roleIdValueLabel.text = roleID
  179. registeredAtLabel.text = "注册时间: \(regTime)"
  180. // 显示已选择的渠道名(如果有),否则使用默认占位
  181. if let savedChannel = UserDefaults.standard.string(forKey: "selectedChannelName"), !savedChannel.isEmpty {
  182. appNameLabel.text = savedChannel
  183. }
  184. let head = UserDefaults.standard.string(forKey: "headImgURL")
  185. assignAvatar(with: head)
  186. // 最后添加背景视图(确保它在最底层)
  187. // 添加背景点击手势
  188. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(backdropTapped))
  189. backdropView.addGestureRecognizer(tapGesture)
  190. }
  191. private func applyConstraints() {
  192. NSLayoutConstraint.activate([
  193. // Background (Gray) for the outer area
  194. backdropView.topAnchor.constraint(equalTo: topAnchor),
  195. backdropView.bottomAnchor.constraint(equalTo: bottomAnchor),
  196. backdropView.leadingAnchor.constraint(equalTo: leadingAnchor),
  197. backdropView.trailingAnchor.constraint(equalTo: trailingAnchor),
  198. // Popup background image
  199. panelImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
  200. panelImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
  201. panelImageView.widthAnchor.constraint(equalToConstant: 333),
  202. panelImageView.heightAnchor.constraint(equalToConstant: 215),
  203. // Close Button
  204. dismissButton.topAnchor.constraint(equalTo: panelImageView.topAnchor, constant: -12),
  205. dismissButton.trailingAnchor.constraint(equalTo: panelImageView.trailingAnchor, constant: 12),
  206. dismissButton.widthAnchor.constraint(equalToConstant: 24),
  207. dismissButton.heightAnchor.constraint(equalToConstant: 24),
  208. titleButton.topAnchor.constraint(equalTo: panelImageView.topAnchor, constant: 10),
  209. titleButton.centerXAnchor.constraint(equalTo: panelImageView.centerXAnchor, constant: 0),
  210. titleButton.widthAnchor.constraint(equalToConstant: 132),
  211. titleButton.heightAnchor.constraint(equalToConstant: 37),
  212. avatarFrameView.topAnchor.constraint(equalTo: titleButton.bottomAnchor, constant: 5),
  213. avatarFrameView.leadingAnchor.constraint(equalTo: panelImageView.leadingAnchor, constant: 33),
  214. avatarFrameView.widthAnchor.constraint(equalToConstant: 58),
  215. avatarFrameView.heightAnchor.constraint(equalToConstant: 58),
  216. avatarImageView.centerXAnchor.constraint(equalTo: avatarFrameView.centerXAnchor),
  217. avatarImageView.centerYAnchor.constraint(equalTo: avatarFrameView.centerYAnchor),
  218. avatarImageView.widthAnchor.constraint(equalToConstant: 48),
  219. avatarImageView.heightAnchor.constraint(equalToConstant: 48),
  220. nicknameValueLabel.trailingAnchor.constraint(equalTo: panelImageView.trailingAnchor,constant: -35),
  221. nicknameValueLabel.topAnchor.constraint(equalTo: titleButton.bottomAnchor,constant: 14),
  222. nicknameValueLabel.widthAnchor.constraint(equalToConstant: 130),
  223. nicknameValueLabel.heightAnchor.constraint(equalToConstant: 24),
  224. nicknameLabel.trailingAnchor.constraint(equalTo: nicknameValueLabel.leadingAnchor,constant: -12),
  225. nicknameLabel.centerYAnchor.constraint(equalTo: nicknameValueLabel.centerYAnchor,constant: 0),
  226. roleIdValueLabel.trailingAnchor.constraint(equalTo: nicknameValueLabel.trailingAnchor,constant: 0),
  227. roleIdValueLabel.topAnchor.constraint(equalTo: nicknameValueLabel.bottomAnchor,constant: 3),
  228. roleIdValueLabel.widthAnchor.constraint(equalToConstant: 130),
  229. roleIdValueLabel.heightAnchor.constraint(equalToConstant: 24),
  230. roleIdLabel.trailingAnchor.constraint(equalTo: roleIdValueLabel.leadingAnchor,constant: -12),
  231. roleIdLabel.centerYAnchor.constraint(equalTo: roleIdValueLabel.centerYAnchor,constant: 0),
  232. musicLabel.leadingAnchor.constraint(equalTo: avatarFrameView.leadingAnchor,constant: 0),
  233. musicLabel.topAnchor.constraint(equalTo: avatarFrameView.bottomAnchor,constant: 24),
  234. // Music Switch
  235. musicToggle.centerYAnchor.constraint(equalTo: musicLabel.centerYAnchor, constant: 0),
  236. musicToggle.leadingAnchor.constraint(equalTo: musicLabel.trailingAnchor, constant: 10),
  237. // Sound Switch
  238. sfxToggle.centerYAnchor.constraint(equalTo: musicToggle.centerYAnchor),
  239. sfxToggle.trailingAnchor.constraint(equalTo: panelImageView.trailingAnchor, constant: -28),
  240. sfxLabel.centerYAnchor.constraint(equalTo: musicLabel.centerYAnchor,constant: 0),
  241. sfxLabel.trailingAnchor.constraint(equalTo: sfxToggle.trailingAnchor,constant: -60),
  242. // Registration Time Label
  243. registeredAtLabel.leadingAnchor.constraint(equalTo: musicLabel.leadingAnchor, constant: 0),
  244. registeredAtLabel.topAnchor.constraint(equalTo: musicLabel.bottomAnchor,constant: 22),
  245. appNameLabel.centerYAnchor.constraint(equalTo: registeredAtLabel.centerYAnchor, constant: 0),
  246. appNameLabel.trailingAnchor.constraint(equalTo: sfxToggle.trailingAnchor,constant: 0),
  247. ])
  248. }
  249. // MARK: - Actions
  250. @objc private func dismissPanel() {
  251. delegate?.overlayPanelDidDismiss(self)
  252. removeFromSuperview()
  253. }
  254. @objc private func backdropTapped() {
  255. dismissPanel()
  256. }
  257. // MARK: - Avatar Loading
  258. private static let avatarCache = NSCache<NSURL, UIImage>()
  259. private func assignAvatar(with urlString: String?) {
  260. guard let s = urlString, !s.isEmpty, let url = URL(string: s) else {
  261. avatarImageView.image = UIImage(named: "catlogos")
  262. return
  263. }
  264. if let cached = OverlayPanelView.avatarCache.object(forKey: url as NSURL) {
  265. avatarImageView.image = cached
  266. return
  267. }
  268. URLSession.shared.dataTask(with: url) { data, _, _ in
  269. guard let data = data, let img = UIImage(data: data) else { return }
  270. OverlayPanelView.avatarCache.setObject(img, forKey: url as NSURL)
  271. DispatchQueue.main.async { [weak self] in
  272. self?.avatarImageView.image = img
  273. }
  274. }.resume()
  275. }
  276. }