| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- //
- // LNIMChatVoiceMessageCell.swift
- // Lanu
- //
- // Created by OneeChan on 2025/12/5.
- //
- import Foundation
- import UIKit
- import SnapKit
- class LNIMChatVoiceMessageCell: LNIMChatBaseMessageCell {
- private let voiceView = UIStackView()
-
- private let controlIc = UIImageView()
- private let waveBg = UIImageView()
- private let indicator = UIView()
- private let waveBlack = UIImageView()
- private let waveProgress = UIView()
- private let durationLabel = UILabel()
-
- private let speedView = UIView()
- private let speedLabel = UILabel()
-
- private var curItem: LNIMMessageData?
-
- private var isPlaying: Bool {
- if let content = curItem?.content,
- content.lowercased().starts(with: "http") {
- return LNVoicePlayer.shared.playingUrl == content
- }
-
- guard let playingUrl = LNVoicePlayer.shared.playingUrl else {
- return false
- }
- return playingUrl == curItem?.imMessage.soundElem?.uuid
- }
-
- override func update(_ data: LNIMMessageData, viewModel: LNIMChatViewModel) {
- super.update(data, viewModel: viewModel)
-
- curItem = data
- if data.imMessage.isSelf {
- waveBg.image = .icImChatMyVoice
- waveBlack.image = .icImChatMyVoiceBlack
- } else {
- waveBg.image = .icImChatPeerVoice
- waveBlack.image = .icImChatPeerVoiceBlack
- }
-
- if isPlaying {
- updatePlayUI()
- } else {
- updateStopUI()
- }
-
- LNEventDeliver.addObserver(self)
- }
-
- private func updatePlayUI(animated: Bool = false) {
- guard let curItem else { return }
- controlIc.image = .icImChatVoicePause
-
- let currentTime = LNVoicePlayer.shared.currentTime + 0.25
- let duration = LNVoicePlayer.shared.duration
- durationLabel.text = .init(format: "%02d:%02d", Int(currentTime) / 60, Int(currentTime) % 60)
-
- if curItem.imMessage.isSelf {
- indicator.backgroundColor = .text_3
- } else {
- indicator.backgroundColor = .primary_3
- }
- waveProgress.snp.remakeConstraints { make in
- make.leading.verticalEdges.equalTo(waveBg)
- if duration == 0 || currentTime == 0 {
- make.width.equalTo(0)
- } else {
- make.width.equalTo(waveBg).multipliedBy(currentTime / Double(duration))
- }
- }
- if speedView.superview == nil {
- voiceView.addArrangedSubview(speedView)
- }
- speedLabel.text = "\(Int(LNVoicePlayer.shared.curSpeed))x"
- if animated {
- UIView.animate(withDuration: 0.25) { [weak self] in
- guard let self else { return }
- waveProgress.superview?.layoutIfNeeded()
- }
- }
- }
-
- private func updateStopUI() {
- guard let curItem else { return }
- controlIc.image = .icImChatVoicePlay
-
- durationLabel.text = .init(format: "%02d:%02d", curItem.voiceDuration / 60, curItem.voiceDuration % 60)
-
- if curItem.imMessage.isSelf {
- indicator.backgroundColor = .text_3
- } else {
- indicator.backgroundColor = .init(hex: "#1789FF")
- }
- waveProgress.snp.remakeConstraints { make in
- make.leading.verticalEdges.equalTo(waveBg)
- make.width.equalTo(0)
- }
-
- voiceView.removeArrangedSubview(speedView)
- speedView.removeFromSuperview()
- }
-
- override func setupViews() {
- LNEventDeliver.addObserver(self)
-
- super.setupViews()
-
- voiceView.axis = .horizontal
- voiceView.spacing = 8
- container.addSubview(voiceView)
- voiceView.snp.makeConstraints { make in
- make.edges.equalToSuperview()
- }
-
- controlIc.isUserInteractionEnabled = false
- voiceView.addArrangedSubview(controlIc)
-
- voiceView.addArrangedSubview(waveBg)
-
- waveProgress.clipsToBounds = true
- waveBg.addSubview(waveProgress)
- waveProgress.snp.makeConstraints { make in
- make.leading.verticalEdges.equalToSuperview()
- make.width.equalTo(0)
- }
-
- waveProgress.addSubview(waveBlack)
- waveBlack.snp.makeConstraints { make in
- make.leading.verticalEdges.equalToSuperview()
- }
-
- indicator.backgroundColor = .text_3
- indicator.layer.cornerRadius = 5
- waveBg.addSubview(indicator)
- indicator.snp.makeConstraints { make in
- make.centerX.equalTo(waveProgress.snp.trailing)
- make.centerY.equalToSuperview()
- make.width.height.equalTo(10)
- }
-
- durationLabel.font = .body_xs
- durationLabel.textColor = .text_3
- container.addSubview(durationLabel)
- durationLabel.snp.makeConstraints { make in
- make.leading.equalTo(waveBg)
- make.top.equalTo(container.snp.bottom).offset(4)
- }
-
- speedView.backgroundColor = .text_5.withAlphaComponent(0.4)
- speedView.layer.cornerRadius = 14
- speedView.snp.makeConstraints { make in
- make.height.equalTo(28)
- }
- speedLabel.font = .body_m
- speedLabel.textColor = .text_1
- speedView.addSubview(speedLabel)
- speedLabel.snp.makeConstraints { make in
- make.center.equalToSuperview()
- make.leading.greaterThanOrEqualToSuperview().offset(17.5)
- }
-
- speedView.onTap { [weak self] in
- guard self != nil else { return }
- if LNVoicePlayer.shared.curSpeed == 1.0 {
- LNVoicePlayer.shared.setSpeed(speed: 2.0)
- } else {
- LNVoicePlayer.shared.setSpeed(speed: 1.0)
- }
- }
-
- container.onTap { [weak self] in
- guard let self else { return }
- guard let curItem else { return }
- if let content = curItem.content,
- content.lowercased().starts(with: "http") {
- if LNVoicePlayer.shared.playingUrl == content {
- LNVoicePlayer.shared.stop()
- } else {
- LNVoicePlayer.shared.play(content)
- }
- return
- }
- if LNVoicePlayer.shared.playingUrl == curItem.imMessage.soundElem?.uuid {
- LNVoicePlayer.shared.stop()
- return
- }
- LNVoicePlayer.shared.playVoiceMessage(message: curItem)
- }
- }
- }
- extension LNIMChatVoiceMessageCell: LNVoicePlayerNotify {
- func onAudioStartPlay(path: String) {
- guard isPlaying else { return }
- updatePlayUI(animated: true)
- }
-
- func onAudioUpdateDuration(path: String, cur: TimeInterval, total: TimeInterval) {
- guard isPlaying else { return }
- updatePlayUI(animated: true)
- }
-
- func onAudioStopPlay(path: String) {
- guard isPlaying else { return }
- updateStopUI()
- }
- }
|