// // PigeonMessageHistoryVC.swift // QuickLocation // import UIKit final class PigeonMessageHistoryVC: BaseViewController { private let members: [GroupMemberModel] private var items: [PigeonHistoryItem] = [] private var rootView: PigeonMessageHistoryView! private var playingItemId: String? private var isVoicePlaying = false private var voiceElapsed: TimeInterval = 0 private var voiceDuration: TimeInterval = 10 private var voiceTimer: Timer? init(members: [GroupMemberModel]) { self.members = members super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func loadView() { rootView = PigeonMessageHistoryView(frame: UIScreen.main.bounds) view = rootView } override func viewDidLoad() { super.viewDidLoad() items = Self.makeMockItems(members: members) rootView.tableView.dataSource = self rootView.tableView.delegate = self rootView.tableView.register(PigeonHistoryCell.self, forCellReuseIdentifier: PigeonHistoryCell.reuseId) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) stopVoicePlayback(reset: true) } @objc private func goBack() { navigationController?.popViewController(animated: true) } private func togglePlayback(for item: PigeonHistoryItem) { guard item.kind == .voice else { return } if playingItemId == item.id { if isVoicePlaying { pauseVoicePlayback() } else { startVoicePlayback(item: item) } return } stopVoicePlayback(reset: true) startVoicePlayback(item: item) } private func startVoicePlayback(item: PigeonHistoryItem) { playingItemId = item.id voiceDuration = max(1, item.duration) isVoicePlaying = true voiceTimer?.invalidate() let timer = Timer(timeInterval: 0.05, repeats: true) { [weak self] _ in self?.tickVoicePlayback() } RunLoop.main.add(timer, forMode: .common) voiceTimer = timer refreshVisiblePlayback() } private func pauseVoicePlayback() { isVoicePlaying = false voiceTimer?.invalidate() voiceTimer = nil refreshVisiblePlayback() } private func stopVoicePlayback(reset: Bool) { isVoicePlaying = false voiceTimer?.invalidate() voiceTimer = nil if reset { voiceElapsed = 0 playingItemId = nil } refreshVisiblePlayback() } private func tickVoicePlayback() { voiceElapsed = min(voiceDuration, voiceElapsed + 0.05) refreshVisiblePlayback() guard voiceElapsed >= voiceDuration else { return } stopVoicePlayback(reset: true) } private func refreshVisiblePlayback() { for cell in rootView.tableView.visibleCells { guard let historyCell = cell as? PigeonHistoryCell, let indexPath = rootView.tableView.indexPath(for: historyCell) else { continue } let item = items[indexPath.row] let isCurrent = item.id == playingItemId historyCell.applyPlayback( isPlaying: isCurrent && isVoicePlaying, elapsed: isCurrent ? voiceElapsed : 0, duration: item.duration ) } } private static func makeMockItems(members: [GroupMemberModel]) -> [PigeonHistoryItem] { let fallback = UIImage(named: "UserIcon/1") let sender = AppContextManager.shared.avaterIcon.size == .zero ? fallback : AppContextManager.shared.avaterIcon var receivers = members .filter { $0.user_id != AppContextManager.shared.userId } .map { member -> UIImage in member.userIcon.size == .zero ? (fallback ?? UIImage()) : member.userIcon } if receivers.isEmpty { receivers = [fallback ?? UIImage()] } while receivers.count < 3 { receivers.append(receivers[receivers.count % max(receivers.count, 1)]) } let dateText = "8.14 周五" let timeText = "下午 10:12" let caption = "和红红火火恍恍惚惚或或或或或或或或" return [ PigeonHistoryItem( id: "image-1", dateText: dateText, timeText: timeText, kind: .image, caption: caption, image: UIImage(named: "PigeonMessage/history_photo") ?? sender ?? fallback, senderAvatar: sender, receiverAvatars: [receivers[0]], duration: 0 ), PigeonHistoryItem( id: "voice-1", dateText: dateText, timeText: timeText, kind: .voice, caption: "", image: nil, senderAvatar: sender, receiverAvatars: [receivers[0]], duration: 10 ), PigeonHistoryItem( id: "voice-2", dateText: dateText, timeText: timeText, kind: .voice, caption: "", image: nil, senderAvatar: sender, receiverAvatars: Array(receivers.prefix(3)), duration: 10 ) ] } } extension PigeonMessageHistoryVC: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell( withIdentifier: PigeonHistoryCell.reuseId, for: indexPath ) as! PigeonHistoryCell let item = items[indexPath.row] let isCurrent = item.id == playingItemId cell.configure( item: item, isPlaying: isCurrent && isVoicePlaying, elapsed: isCurrent ? voiceElapsed : 0 ) cell.onTogglePlay = { [weak self] in self?.togglePlayback(for: item) } return cell } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { guard isVoicePlaying else { return } stopVoicePlayback(reset: true) } }