| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- //
- // PowerPopupView.swift
- // RoderickRalph
- //
- // Created by Neoa on 2025/8/20.
- //
- import Foundation
- import UIKit
- struct LoginUserInfo {
- let nickName: String
- let userId: String
- let registryTimeStr: String
- let todayAnswerCount: Int
- let historyAnswerCount: Int
- let headImgURL: String?
- let lastLoginTimeStr: String
- let answerLogs: [String] // ✅ 新增:作答时间记录
- }
- protocol EnergyPanelAgent: AnyObject {
- func energyPanelDidTapAcquire(_ panel: EnergyPanelView)
- func energyPanelDidDismiss(_ panel: EnergyPanelView)
- }
- class EnergyPanelView: UIView, UITableViewDelegate {
- weak var delegate: EnergyPanelAgent?
-
- // MARK: - UI Components
- private let backdropView: UIView = {
- let view = UIView()
- view.backgroundColor = UIColor.black.withAlphaComponent(0.5) // Semi-transparent gray background
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- private let openTimeLabel: UILabel = {
- let label = UILabel()
- label.text = ""
- label.font = UIFont.systemFont(ofSize: 14)
- label.textColor = .white
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- private let crimsonPlate: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(named: "res_xxjjp1pi")
- iv.contentMode = .scaleToFill
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let cardView: UIView = {
- let view = UIView()
- view.backgroundColor = .white
- view.layer.cornerRadius = 12
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
- private let ribbonView: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(named: "res_q8pwnfk2")
- iv.contentMode = .scaleToFill
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let appNameLabel: UILabel = {
- let label = UILabel()
- label.text = "青柠檬记账"
- label.font = UIFont.systemFont(ofSize: 16)
- label.textColor = .white
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private let timerLabel: UILabel = {
- let label = UILabel()
- label.text = "7s"
- label.font = UIFont.systemFont(ofSize: 24)
- label.textColor = .white
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private let acquireBtn: UIButton = {
- let button = UIButton(type: .system)
-
- button.setBackgroundImage(UIImage(named: "res_ux9fin4y"), for: .normal)
- button.setTitle("获取体力", for: .normal)
- button.setTitleColor(.white, for: .normal)
- button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 24)
- button.contentHorizontalAlignment = .center
- button.translatesAutoresizingMaskIntoConstraints = false
- button.isHidden = true // Initially hidden
- return button
- }()
-
- private let profilePlate: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(named: "res_aohtobqz")
- iv.contentMode = .scaleToFill
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let avatarFrame: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(named: "res_x5tz2ey7")
- iv.contentMode = .scaleAspectFit
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let avatarView: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(named: "catlogos")
- iv.contentMode = .scaleAspectFit
- iv.layer.cornerRadius = 24
- iv.clipsToBounds = true
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let nicknameCaption: UILabel = {
- let label = UILabel()
- label.text = "昵 称"
- label.font = UIFont.systemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- private let nicknameBadge: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(named: "res_pn8idmua")
- iv.contentMode = .scaleToFill
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
-
- private let nicknameValue: UILabel = {
- let label = UILabel()
- label.text = "XXXX"
- label.font = UIFont.systemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- label.textAlignment = .center
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private let roleCaption: UILabel = {
- let label = UILabel()
- label.text = "角色ID"
- label.font = UIFont.systemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- private let roleBadge: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(named: "res_pn8idmua")
- iv.contentMode = .scaleToFill
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let roleValue: UILabel = {
- let label = UILabel()
- label.text = "XXXX"
- label.font = UIFont.systemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- if let image = UIImage(named: "res_pn8idmua") {
- label.layer.contents = image.cgImage
- label.layer.contentsGravity = .resizeAspect // 控制图片显示方式
- label.layer.contentsScale = UIScreen.main.scale
- }
- label.textAlignment = .center
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private let registeredAtLabel: UILabel = {
- let label = UILabel()
- label.text = "注册时间: 2025-05-26 17:58:48"
- label.font = UIFont.systemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- private let lastLoginLabel: UILabel = {
- let label = UILabel()
- label.text = "登录时间: 2025-05-26 17:58:48"
- label.font = UIFont.systemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private let todayCountLabel: UILabel = {
- let label = UILabel()
- label.text = "今日答题: XX题"
- label.font = UIFont.boldSystemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- private let historyCountLabel: UILabel = {
- let label = UILabel()
- label.text = "历史答题: XX题"
- label.font = UIFont.boldSystemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private let logsPlate: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(named: "res_aohtobqz")
- iv.contentMode = .scaleToFill
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let logsTitle: UILabel = {
- let label = UILabel()
- label.text = "记录"
- label.font = UIFont.boldSystemFont(ofSize: 12)
- label.textColor = UIColor(hexString:"#E28814")
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private let logsDivider: UIView = {
- let view = UIView()
- view.backgroundColor = UIColor(hexString: "#EEEEEE")
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- private let logsTable: UITableView = {
- let tableView = UITableView()
- tableView.translatesAutoresizingMaskIntoConstraints = false
- tableView.isScrollEnabled = false
- tableView.isScrollEnabled = true // Enable scrolling if records exceed visible area
- tableView.backgroundColor = .clear // Transparent background
- tableView.separatorStyle = .none // No separators between rows
- tableView.rowHeight = 20 // Adjust the row height if necessary
- return tableView
- }()
- private let dismissBtn: UIButton = {
- let button = UIButton(type: .custom)
- button.setImage(UIImage(named: "res_9mx2yiyi"), for: .normal)
- button.addTarget(self, action: #selector(closePopup), for: .touchUpInside)
- button.translatesAutoresizingMaskIntoConstraints = false
- button.isHidden = true // Initially hidden
- return button
- }()
- private var tickTimer: Timer?
- private var secondsLeft = 7
- private var didRecordOpen = false
- private var logsData: [String] = [] // ✅ 来自登录返回 answerRecordTimeList
- // MARK: - Initialization
- init() {
- super.init(frame: .zero)
-
- // Register the cell class for the table view
- logsTable.register(UITableViewCell.self, forCellReuseIdentifier: "AnswerLogCell")
- logsTable.dataSource = self
- logsTable.delegate = self
-
- buildUI()
- applyLayout()
-
- stampOpenTime()
- beginCountdown() // Start countdown as soon as the view is initialized
- }
- override func didMoveToWindow() {
- super.didMoveToWindow()
- if window != nil && !didRecordOpen {
- stampOpenTime()
- didRecordOpen = true
- }
- }
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- // MARK: - UI Setup
- private func buildUI() {
- addSubview(backdropView)
- addSubview(openTimeLabel)
- addSubview(crimsonPlate)
- addSubview(cardView)
- addSubview(profilePlate)
- addSubview(avatarFrame)
- addSubview(avatarView)
- addSubview(nicknameCaption)
- addSubview(nicknameBadge)
- addSubview(nicknameValue)
- addSubview(roleCaption)
- addSubview(roleBadge)
- addSubview(roleValue)
- addSubview(registeredAtLabel)
- addSubview(lastLoginLabel)
- addSubview(todayCountLabel)
- addSubview(historyCountLabel)
- addSubview(logsPlate)
- addSubview(logsTitle)
- addSubview(logsDivider)
- addSubview(logsTable)
- addSubview(dismissBtn)
- addSubview(ribbonView)
- addSubview(appNameLabel)
- addSubview(timerLabel)
- addSubview(acquireBtn)
- // Prefill software name with selected channel if available
- syncChannelName()
- // 让“获取体力”按钮把点击事件回传给外部
- acquireBtn.addTarget(self, action: #selector(handleGetPowerButtonTap), for: .touchUpInside)
- }
-
- func configure(with info: LoginUserInfo) {
- // Sync software/app name with selected channel
- syncChannelName()
- nicknameValue.text = info.nickName
- roleValue.text = info.userId
- registeredAtLabel.text = "注册时间: \(info.registryTimeStr)"
- lastLoginLabel.text = "登录时间: \(info.lastLoginTimeStr)"
- todayCountLabel.text = "今日答题: \(info.todayAnswerCount)题"
- historyCountLabel.text = "历史答题: \(info.historyAnswerCount)题"
- if let urlStr = info.headImgURL, let url = URL(string: urlStr) {
- fetchImage(into: avatarView, from: url)
- }
- // ✅ 写入答题记录并刷新列表
- logsData = info.answerLogs
- logsTable.reloadData()
- }
- // Helper to apply saved channel name to softwareNameLabel
- private func syncChannelName() {
- if let savedChannel = UserDefaults.standard.string(forKey: "selectedChannelName"), !savedChannel.isEmpty {
- appNameLabel.text = savedChannel
- }
- }
-
- private static let imageCache = NSCache<NSURL, UIImage>()
- private func fetchImage(into imageView: UIImageView, from url: URL) {
- if let cached = EnergyPanelView.imageCache.object(forKey: url as NSURL) {
- imageView.image = cached
- return
- }
- URLSession.shared.dataTask(with: url) { data, _, _ in
- guard let data = data, let img = UIImage(data: data) else { return }
- EnergyPanelView.imageCache.setObject(img, forKey: url as NSURL)
- DispatchQueue.main.async { imageView.image = img }
- }.resume()
- }
- @objc private func handleGetPowerButtonTap() {
- delegate?.energyPanelDidTapAcquire(self)
- }
-
- private func applyLayout() {
- NSLayoutConstraint.activate([
- // Background
- backdropView.topAnchor.constraint(equalTo: topAnchor),
- backdropView.bottomAnchor.constraint(equalTo: bottomAnchor),
- backdropView.leadingAnchor.constraint(equalTo: leadingAnchor),
- backdropView.trailingAnchor.constraint(equalTo: trailingAnchor),
-
-
- // User Info Container
- openTimeLabel.topAnchor.constraint(equalTo: topAnchor, constant: 142),
- openTimeLabel.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0),
-
- crimsonPlate.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0),
- crimsonPlate.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0),
- crimsonPlate.widthAnchor.constraint(equalToConstant: 331),
- crimsonPlate.heightAnchor.constraint(equalToConstant: 300),
- cardView.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0),
- cardView.bottomAnchor.constraint(equalTo: crimsonPlate.bottomAnchor, constant: -90),
- cardView.widthAnchor.constraint(equalToConstant: 317),
- cardView.heightAnchor.constraint(equalToConstant: 307),
- ribbonView.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0),
- ribbonView.bottomAnchor.constraint(equalTo: crimsonPlate.bottomAnchor, constant: 0),
- ribbonView.widthAnchor.constraint(equalToConstant: 331),
- ribbonView.heightAnchor.constraint(equalToConstant: 133),
- appNameLabel.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0),
- appNameLabel.topAnchor.constraint(equalTo: ribbonView.topAnchor, constant: 43),
- timerLabel.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0),
- timerLabel.bottomAnchor.constraint(equalTo: ribbonView.bottomAnchor, constant: -20),
- acquireBtn.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0),
- acquireBtn.bottomAnchor.constraint(equalTo: ribbonView.bottomAnchor, constant: -20),
- acquireBtn.widthAnchor.constraint(equalToConstant: 221),
- acquireBtn.heightAnchor.constraint(equalToConstant: 47),
- profilePlate.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 14),
- profilePlate.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 14),
- profilePlate.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -14),
- profilePlate.heightAnchor.constraint(equalToConstant: 133),
- avatarFrame.topAnchor.constraint(equalTo: profilePlate.topAnchor, constant: 12),
- avatarFrame.leadingAnchor.constraint(equalTo: profilePlate.leadingAnchor, constant: 16),
- avatarFrame.widthAnchor.constraint(equalToConstant: 54),
- avatarFrame.heightAnchor.constraint(equalToConstant: 54),
- avatarView.centerXAnchor.constraint(equalTo: avatarFrame.centerXAnchor, constant: 0),
- avatarView.centerYAnchor.constraint(equalTo: avatarFrame.centerYAnchor, constant: 0),
- avatarView.widthAnchor.constraint(equalToConstant: 46),
- avatarView.heightAnchor.constraint(equalToConstant: 46),
- nicknameBadge.trailingAnchor.constraint(equalTo: profilePlate.trailingAnchor,constant: -16),
- nicknameBadge.topAnchor.constraint(equalTo: profilePlate.topAnchor,constant: 14),
- nicknameBadge.widthAnchor.constraint(equalToConstant: 130),
- nicknameBadge.heightAnchor.constraint(equalToConstant: 24),
-
- nicknameValue.trailingAnchor.constraint(equalTo: profilePlate.trailingAnchor,constant: -16),
- nicknameValue.topAnchor.constraint(equalTo: profilePlate.topAnchor,constant: 14),
- nicknameValue.widthAnchor.constraint(equalToConstant: 130),
- nicknameValue.heightAnchor.constraint(equalToConstant: 24),
- nicknameCaption.trailingAnchor.constraint(equalTo: nicknameValue.leadingAnchor,constant: -5),
- nicknameCaption.centerYAnchor.constraint(equalTo: nicknameValue.centerYAnchor,constant: 0),
- roleBadge.trailingAnchor.constraint(equalTo: nicknameValue.trailingAnchor,constant: 0),
- roleBadge.topAnchor.constraint(equalTo: nicknameValue.bottomAnchor,constant: 3),
- roleBadge.widthAnchor.constraint(equalToConstant: 130),
- roleBadge.heightAnchor.constraint(equalToConstant: 24),
- roleValue.trailingAnchor.constraint(equalTo: nicknameValue.trailingAnchor,constant: 0),
- roleValue.topAnchor.constraint(equalTo: nicknameValue.bottomAnchor,constant: 3),
- roleValue.widthAnchor.constraint(equalToConstant: 130),
- roleValue.heightAnchor.constraint(equalToConstant: 24),
-
- roleCaption.trailingAnchor.constraint(equalTo: roleValue.leadingAnchor,constant: -5),
- roleCaption.centerYAnchor.constraint(equalTo: roleValue.centerYAnchor,constant: 0),
-
- registeredAtLabel.topAnchor.constraint(equalTo: avatarFrame.bottomAnchor,constant: 15),
- registeredAtLabel.leadingAnchor.constraint(equalTo: avatarFrame.leadingAnchor,constant: 0),
- lastLoginLabel.topAnchor.constraint(equalTo: registeredAtLabel.bottomAnchor,constant: 3),
- lastLoginLabel.leadingAnchor.constraint(equalTo: avatarFrame.leadingAnchor,constant: 0),
- todayCountLabel.topAnchor.constraint(equalTo: profilePlate.bottomAnchor,constant: 12),
- todayCountLabel.leadingAnchor.constraint(equalTo: profilePlate.leadingAnchor,constant: 31),
-
- historyCountLabel.topAnchor.constraint(equalTo: profilePlate.bottomAnchor,constant: 12),
- historyCountLabel.trailingAnchor.constraint(equalTo: profilePlate.trailingAnchor,constant: -31),
- logsPlate.topAnchor.constraint(equalTo: todayCountLabel.bottomAnchor,constant: 12),
- logsPlate.leadingAnchor.constraint(equalTo: profilePlate.leadingAnchor,constant: 0),
- logsPlate.trailingAnchor.constraint(equalTo: profilePlate.trailingAnchor,constant: 0),
- logsPlate.heightAnchor.constraint(equalToConstant: 90),
- logsTitle.centerYAnchor.constraint(equalTo: logsPlate.centerYAnchor,constant: 0),
- logsTitle.leadingAnchor.constraint(equalTo: logsPlate.leadingAnchor,constant: 18),
-
- logsDivider.topAnchor.constraint(equalTo: logsPlate.topAnchor,constant: 8),
- logsDivider.bottomAnchor.constraint(equalTo: logsPlate.bottomAnchor,constant: -8),
- logsDivider.leadingAnchor.constraint(equalTo: logsTitle.trailingAnchor,constant: 13),
- logsDivider.widthAnchor.constraint(equalToConstant: 1),
- logsTable.topAnchor.constraint(equalTo: logsPlate.topAnchor,constant: 5),
- logsTable.bottomAnchor.constraint(equalTo: logsPlate.bottomAnchor,constant: -5),
- logsTable.leadingAnchor.constraint(equalTo: logsDivider.trailingAnchor,constant: 28),
- logsTable.trailingAnchor.constraint(equalTo: logsPlate.trailingAnchor,constant: -30),
-
- dismissBtn.topAnchor.constraint(equalTo: cardView.topAnchor,constant: -12),
- dismissBtn.trailingAnchor.constraint(equalTo: cardView.trailingAnchor,constant: 12),
- dismissBtn.heightAnchor.constraint(equalToConstant: 24),
- dismissBtn.widthAnchor.constraint(equalToConstant: 24),
-
- ])
- }
-
- // MARK: - Countdown Logic
- private func beginCountdown() {
- timerLabel.text = "\(secondsLeft)s"
- tickTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(tickDown), userInfo: nil, repeats: true)
- }
- @objc private func tickDown() {
- secondsLeft -= 1
- timerLabel.text = "\(secondsLeft)s"
-
- if secondsLeft == 0 {
- tickTimer?.invalidate() // Stop the countdown
- tickTimer = nil
- revealActions() // Show the buttons once countdown reaches 0
- }
- }
- private func revealActions() {
- acquireBtn.isHidden = false
- dismissBtn.isHidden = false
- timerLabel.isHidden = true
- }
- // MARK: - Actions
- @objc private func closePopup() {
- delegate?.energyPanelDidDismiss(self)
- removeFromSuperview()
- }
-
- private func stampOpenTime() {
- let formatter = DateFormatter()
- formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
- formatter.locale = Locale(identifier: "zh_CN")
- formatter.timeZone = .current
- openTimeLabel.text = formatter.string(from: Date())
- }
- }
- extension EnergyPanelView: UITableViewDataSource {
-
- // Return number of rows (the number of records)
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return self.logsData.count // ✅ 改这里
- }
-
- // Configure each cell to display the record
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerLogCell") ?? UITableViewCell(style: .default, reuseIdentifier: "AnswerLogCell")
-
- cell.selectionStyle = .none // Disable selection highlight
- // Display the answer log in each row
- cell.textLabel?.text = self.logsData[indexPath.row] // ✅ 改这里
- cell.textLabel?.font = UIFont.systemFont(ofSize: 12)
- cell.textLabel?.textColor = UIColor(hexString: "#E28814")
- cell.backgroundColor = .clear // Transparent background for each cell
-
- return cell
- }
-
- }
|