LNAudioCallFloatingView.swift 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. //
  2. // LNAudioCallFloatingView.swift
  3. // Gami
  4. //
  5. // Created by OneeChan on 2026/2/3.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. class LNAudioCallFloatingView: UIView {
  11. private let size: CGFloat = 67
  12. private let stackView = UIStackView()
  13. private let stateIc = UIImageView()
  14. private let durationLabel = UILabel()
  15. private var timer: Timer?
  16. override init(frame: CGRect) {
  17. super.init(frame: frame)
  18. setupViews()
  19. setupGesture()
  20. LNEventDeliver.addObserver(self)
  21. if let callInfo = LNIMManager.shared.curCallInfo,
  22. callInfo.beginTime > 0 {
  23. updateCallDuration()
  24. startTimer()
  25. }
  26. }
  27. func show() {
  28. guard let window = UIView.appKeyWindow else { return }
  29. window.addSubview(self)
  30. frame = .init(x: window.bounds.width - 16 - size,
  31. y: window.bounds.height - 100.0 - size,
  32. width: size, height: size)
  33. }
  34. required init?(coder: NSCoder) {
  35. fatalError("init(coder:) has not been implemented")
  36. }
  37. }
  38. extension LNAudioCallFloatingView {
  39. func dismiss() {
  40. removeFromSuperview()
  41. }
  42. private func updateCallDuration() {
  43. guard let callInfo = LNIMManager.shared.curCallInfo else { return }
  44. let duration = curTime - callInfo.beginTime
  45. durationLabel.text = duration.timeCountDisplay
  46. durationLabel.isHidden = false
  47. }
  48. private func startTimer() {
  49. stopTimer()
  50. let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
  51. guard let self else { return }
  52. guard LNIMManager.shared.curCallInfo != nil else {
  53. stopTimer()
  54. return
  55. }
  56. updateCallDuration()
  57. }
  58. RunLoop.main.add(timer, forMode: .common)
  59. self.timer = timer
  60. }
  61. private func stopTimer() {
  62. timer?.invalidate()
  63. timer = nil
  64. }
  65. }
  66. extension LNAudioCallFloatingView {
  67. @objc
  68. private func handlePan(_ ges: UIPanGestureRecognizer) {
  69. let location = ges.location(in: superview)
  70. switch ges.state {
  71. case .began:
  72. break
  73. case .changed:
  74. center = location
  75. break
  76. default:
  77. updatePosition(animated: true)
  78. break
  79. }
  80. }
  81. }
  82. extension LNAudioCallFloatingView {
  83. private func updatePosition(animated: Bool) {
  84. guard let superview else { return }
  85. let movement = { [weak self] in
  86. guard let self else { return }
  87. let y = center.y.bounded(min: 160, max: superview.bounds.height - 160)
  88. // if center.x > superview.bounds.width * 0.5 {
  89. center = .init(x: superview.bounds.width - bounds.width * 0.5 - 16, y: y)
  90. // } else {
  91. // center = .init(x: bounds.width * 0.5 + 16, y: y)
  92. // }
  93. }
  94. if animated {
  95. UIView.animate(withDuration: 0.25, animations: movement)
  96. } else {
  97. movement()
  98. }
  99. }
  100. private func setupViews() {
  101. layer.shadowColor = UIColor.black.withAlphaComponent(0.08).cgColor
  102. layer.shadowOffset = .init(width: 0, height: 4)
  103. layer.shadowRadius = 6
  104. layer.shadowOpacity = 0.5
  105. layer.masksToBounds = false
  106. backgroundColor = .fill
  107. layer.cornerRadius = 12
  108. stackView.axis = .vertical
  109. stackView.spacing = 8
  110. stackView.alignment = .center
  111. addSubview(stackView)
  112. stackView.snp.makeConstraints { make in
  113. make.center.equalToSuperview()
  114. }
  115. stateIc.image = .icCalling
  116. stackView.addArrangedSubview(stateIc)
  117. durationLabel.isHidden = true
  118. durationLabel.font = .heading_h5
  119. durationLabel.textColor = .text_6
  120. stackView.addArrangedSubview(durationLabel)
  121. }
  122. private func setupGesture() {
  123. let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
  124. addGestureRecognizer(pan)
  125. onTap {
  126. let panel = LNAudioCallPanel()
  127. panel.resume()
  128. panel.popup()
  129. }
  130. }
  131. }
  132. extension LNAudioCallFloatingView: LNIMManagerNotify {
  133. func onVoiceCallEnd() {
  134. dismiss()
  135. }
  136. func onVoiceCallBegin() {
  137. updateCallDuration()
  138. startTimer()
  139. }
  140. }