LNRoomSeatSpeakingView.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. //
  2. // LNRoomSeatSpeakingView.swift
  3. // Gami
  4. //
  5. // Created by OneeChan on 2026/3/17.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. class LNRoomSeatSpeakingView: UIView {
  11. private var borderColor: UIColor = .fill
  12. private var borderWidth: CGFloat = 1
  13. private var fillColor: UIColor = .fill.withAlphaComponent(0.5)
  14. private var duration: Double = 2
  15. private var offset = 10.0
  16. private let waveCount = 2
  17. private var borderLayers: [CAShapeLayer] = []
  18. private weak var roomSession: LNRoomViewModel?
  19. private var curIndex = -1
  20. override init(frame: CGRect) {
  21. super.init(frame: frame)
  22. clipsToBounds = false
  23. isUserInteractionEnabled = false
  24. for _ in 0..<waveCount {
  25. let borderLayer = CAShapeLayer()
  26. borderLayer.borderColor = borderColor.cgColor
  27. borderLayer.borderWidth = borderWidth
  28. borderLayer.backgroundColor = fillColor.cgColor
  29. layer.addSublayer(borderLayer)
  30. borderLayers.append(borderLayer)
  31. }
  32. LNEventDeliver.addObserver(self)
  33. }
  34. required init?(coder: NSCoder) {
  35. fatalError("init(coder:) has not been implemented")
  36. }
  37. func update(_ index: Int, room: LNRoomViewModel?) {
  38. roomSession = room
  39. curIndex = index
  40. }
  41. override func layoutSubviews() {
  42. super.layoutSubviews()
  43. if borderLayers.first?.bounds.height != bounds.height {
  44. rebuild()
  45. }
  46. }
  47. }
  48. extension LNRoomSeatSpeakingView: LNRoomViewModelNotify {
  49. func onRoomSeatsChanged() {
  50. check()
  51. }
  52. func onRoomSpeakingUsersChanged() {
  53. check()
  54. }
  55. private func check() {
  56. guard curIndex != -1,
  57. let seat = roomSession?.seatsInfo.first(where: { $0.index == curIndex }) else {
  58. dismiss()
  59. return
  60. }
  61. if roomSession?.speakingUser.contains(seat.uid) == true {
  62. show()
  63. } else {
  64. dismiss()
  65. }
  66. }
  67. }
  68. extension LNRoomSeatSpeakingView {
  69. private func startAnimate(layer: CAShapeLayer, index: Int) {
  70. let scaleAnim = CABasicAnimation(keyPath: "transform.scale")
  71. scaleAnim.fromValue = 1.0
  72. scaleAnim.toValue = 1.0 + offset / CGFloat(bounds.width / 2)
  73. scaleAnim.duration = duration
  74. let opacityAnim = CABasicAnimation(keyPath: "opacity")
  75. opacityAnim.fromValue = 1.0
  76. opacityAnim.toValue = 0.0
  77. opacityAnim.duration = duration
  78. let animGroup = CAAnimationGroup()
  79. animGroup.animations = [scaleAnim, opacityAnim]
  80. animGroup.duration = duration
  81. animGroup.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
  82. animGroup.isRemovedOnCompletion = false
  83. animGroup.fillMode = .forwards
  84. animGroup.repeatCount = .infinity
  85. animGroup.beginTime = CACurrentMediaTime() + Double(index) * duration * 0.5
  86. layer.add(animGroup, forKey: "scaleAndFadeGroup")
  87. }
  88. private func rebuild() {
  89. for (index, layer) in borderLayers.enumerated() {
  90. layer.frame = bounds
  91. layer.cornerRadius = bounds.height * 0.5
  92. layer.removeAllAnimations()
  93. startAnimate(layer: layer, index: index)
  94. }
  95. }
  96. private func show() {
  97. UIView.animate(withDuration: 0.25) {
  98. self.alpha = 1.0
  99. }
  100. }
  101. private func dismiss() {
  102. UIView.animate(withDuration: 0.25) {
  103. self.alpha = 0.0
  104. }
  105. }
  106. }
  107. #if DEBUG
  108. import SwiftUI
  109. struct LNRoomSeatSpeakingViewPreview: UIViewRepresentable {
  110. func makeUIView(context: Context) -> some UIView {
  111. let container = UIView()
  112. container.backgroundColor = .lightGray
  113. let view = LNRoomSeatSpeakingView()
  114. container.addSubview(view)
  115. view.snp.makeConstraints { make in
  116. make.center.equalToSuperview()
  117. make.width.height.equalTo(50)
  118. }
  119. return container
  120. }
  121. func updateUIView(_ uiView: UIViewType, context: Context) { }
  122. }
  123. #Preview(body: {
  124. LNRoomSeatSpeakingViewPreview()
  125. })
  126. #endif