// // 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() 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 } }