ConferenceInvitationViewController.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. //
  2. // ConferenceInvitationViewController.swift
  3. // TUIRoomKit
  4. //
  5. // Created by jeremiawang on 2024/8/6.
  6. //
  7. import Foundation
  8. import RTCRoomEngine
  9. import UIKit
  10. import Combine
  11. import Factory
  12. class ConferenceInvitationViewController: UIViewController {
  13. private var cancellableSet = Set<AnyCancellable>()
  14. var roomInfo: RoomInfo
  15. var invitation: TUIInvitation
  16. init(roomInfo: RoomInfo, invitation: TUIInvitation) {
  17. self.roomInfo = roomInfo
  18. self.invitation = invitation
  19. super.init(nibName: nil, bundle: nil)
  20. self.modalPresentationStyle = .fullScreen
  21. }
  22. required init?(coder: NSCoder) {
  23. fatalError("init(coder:) has not been implemented")
  24. }
  25. override public var shouldAutorotate: Bool {
  26. return false
  27. }
  28. override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
  29. return .portrait
  30. }
  31. private let backgroundImageView: UIImageView = {
  32. let view = UIImageView()
  33. view.contentMode = .scaleAspectFill
  34. view.alpha = 0.2
  35. return view
  36. }()
  37. private let avatarImageView: UIImageView = {
  38. let avatarImageView = UIImageView()
  39. avatarImageView.contentMode = .scaleAspectFill
  40. avatarImageView.layer.cornerRadius = 25
  41. avatarImageView.clipsToBounds = true
  42. return avatarImageView
  43. }()
  44. private let inviteLabel: UILabel = {
  45. let label = UILabel()
  46. label.textColor = .white
  47. label.font = UIFont(name: "PingFangSC-Regular", size: 14)
  48. return label
  49. }()
  50. private let conferenceNameLabel: UILabel = {
  51. let label = UILabel()
  52. label.textColor = .white
  53. label.font = UIFont.boldSystemFont(ofSize: 24)
  54. label.numberOfLines = 1
  55. label.lineBreakMode = .byTruncatingTail
  56. return label
  57. }()
  58. private let detailsLabel: UILabel = {
  59. let label = UILabel()
  60. label.textColor = .white
  61. label.font = UIFont(name: "PingFangSC-Regular", size: 14)
  62. return label
  63. }()
  64. private let joinSliderView: UIView = {
  65. let view = UIView()
  66. view.backgroundColor = .white.withAlphaComponent(0.1)
  67. view.layer.cornerRadius = 39.scale375()
  68. return view
  69. }()
  70. private let joinLabel: UILabel = {
  71. let label = UILabel()
  72. label.text = .joinNowText
  73. label.textColor = .white.withAlphaComponent(0.8)
  74. label.font = UIFont(name: "PingFangSC-Medium", size: 16)
  75. label.textAlignment = .center
  76. return label
  77. }()
  78. private let sliderThumbView: UIView = {
  79. let view = UIView()
  80. view.backgroundColor = UIColor.tui_color(withHex: "1C66E5")
  81. view.layer.cornerRadius = 32
  82. return view
  83. }()
  84. private let arrowImageView: UIImageView = {
  85. let imageView = UIImageView()
  86. imageView.image = UIImage(named: "room_rightlink_arrow", in: tuiRoomKitBundle(), compatibleWith: nil)
  87. imageView.tintColor = .white
  88. return imageView
  89. }()
  90. private let declineButton: UIButton = {
  91. let button = UIButton(type: .system)
  92. button.setTitle(.notEnterText, for: .normal)
  93. button.setTitleColor(.white.withAlphaComponent(0.8), for: .normal)
  94. button.titleLabel?.font = UIFont(name: "PingFangSC-Medium", size: 16)
  95. return button
  96. }()
  97. override func viewDidLoad() {
  98. super.viewDidLoad()
  99. view.backgroundColor = .black
  100. initState()
  101. initializeData()
  102. constructViewHierarchy()
  103. activateConstraints()
  104. bindInteraction()
  105. operation.select(ViewSelectors.getDismissInvitationFlag)
  106. .receive(on: DispatchQueue.main)
  107. .removeDuplicates()
  108. .sink { [weak self] shouldDismissInvitation in
  109. guard let self = self else { return }
  110. if shouldDismissInvitation {
  111. InvitationObserverService.shared.dismissInvitationWindow()
  112. }
  113. }
  114. .store(in: &cancellableSet)
  115. }
  116. deinit {
  117. debugPrint("deinit \(self)")
  118. }
  119. private func initState() {
  120. self.operation.dispatch(action: InvitationViewActions.resetInvitationFlag())
  121. }
  122. private func constructViewHierarchy() {
  123. view.addSubview(backgroundImageView)
  124. view.addSubview(avatarImageView)
  125. view.addSubview(inviteLabel)
  126. view.addSubview(conferenceNameLabel)
  127. view.addSubview(detailsLabel)
  128. view.addSubview(joinSliderView)
  129. joinSliderView.addSubview(joinLabel)
  130. joinSliderView.addSubview(sliderThumbView)
  131. sliderThumbView.addSubview(arrowImageView)
  132. view.addSubview(declineButton)
  133. }
  134. private func activateConstraints() {
  135. backgroundImageView.snp.makeConstraints { make in
  136. make.edges.equalToSuperview()
  137. }
  138. avatarImageView.snp.makeConstraints { make in
  139. make.centerX.equalToSuperview()
  140. make.height.width.equalTo(50.scale375())
  141. make.top.equalToSuperview().offset(150.scale375Height())
  142. }
  143. inviteLabel.snp.makeConstraints { make in
  144. make.centerX.equalToSuperview()
  145. make.top.equalTo(avatarImageView.snp.bottom).offset(16)
  146. }
  147. conferenceNameLabel.snp.makeConstraints { make in
  148. make.centerX.equalToSuperview()
  149. make.width.lessThanOrEqualTo(300.scale375())
  150. make.top.equalTo(inviteLabel.snp.bottom).offset(30)
  151. }
  152. detailsLabel.snp.makeConstraints { make in
  153. make.centerX.equalToSuperview()
  154. make.top.equalTo(conferenceNameLabel.snp.bottom).offset(10)
  155. }
  156. declineButton.snp.makeConstraints { make in
  157. make.centerX.equalToSuperview()
  158. make.bottom.equalToSuperview().offset(-94.scale375Height())
  159. }
  160. joinSliderView.snp.makeConstraints { make in
  161. make.centerX.equalToSuperview()
  162. make.width.equalTo(200.scale375())
  163. make.height.equalTo(78.scale375())
  164. make.bottom.equalTo(declineButton.snp.top).offset(-30)
  165. }
  166. joinLabel.snp.makeConstraints { make in
  167. make.centerY.equalToSuperview()
  168. make.centerX.equalToSuperview().offset(32.scale375())
  169. }
  170. sliderThumbView.snp.makeConstraints { make in
  171. make.left.equalTo(joinSliderView.snp.left).offset(5)
  172. make.centerY.equalTo(joinSliderView.snp.centerY)
  173. make.width.height.equalTo(64.scale375())
  174. }
  175. arrowImageView.snp.makeConstraints { make in
  176. make.center.equalTo(sliderThumbView)
  177. make.width.height.equalTo(20.scale375())
  178. }
  179. }
  180. private func bindInteraction() {
  181. let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
  182. sliderThumbView.addGestureRecognizer(panGesture)
  183. declineButton.addTarget(self, action: #selector(rejectAction), for: .touchUpInside)
  184. }
  185. private func initializeData() {
  186. inviteLabel.text = invitation.inviter.userName + .inviteJoinConferenceText
  187. conferenceNameLabel.text = roomInfo.name
  188. detailsLabel.text = .hostText + roomInfo.ownerName + " | " + .participantText + String(roomInfo.memberCount)
  189. let placeholder = UIImage(named: "room_default_user", in: tuiRoomKitBundle(), compatibleWith: nil)
  190. if let url = URL(string: invitation.inviter.avatarUrl) {
  191. avatarImageView.sd_setImage(with: url, placeholderImage: placeholder)
  192. backgroundImageView.sd_setImage(with: url, placeholderImage: placeholder)
  193. } else {
  194. avatarImageView.image = placeholder
  195. backgroundImageView.image = placeholder
  196. }
  197. }
  198. @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
  199. let translation = gesture.translation(in: joinSliderView)
  200. let maxTranslation = joinSliderView.frame.width - sliderThumbView.frame.width - 10
  201. switch gesture.state {
  202. case .changed:
  203. if translation.x >= 0 && translation.x <= maxTranslation {
  204. sliderThumbView.snp.updateConstraints { make in
  205. make.left.equalTo(joinSliderView.snp.left).offset(5 + translation.x)
  206. }
  207. }
  208. case .ended:
  209. if translation.x >= maxTranslation {
  210. sliderThumbView.snp.updateConstraints { make in
  211. make.left.equalTo(joinSliderView.snp.left).offset(5 + maxTranslation)
  212. }
  213. gesture.isEnabled = false
  214. self.acceptAction()
  215. } else {
  216. sliderThumbView.snp.updateConstraints { make in
  217. make.left.equalTo(joinSliderView.snp.left).offset(5)
  218. }
  219. UIView.animate(withDuration: 0.3) {
  220. self.view.layoutIfNeeded()
  221. }
  222. }
  223. default:
  224. break
  225. }
  226. }
  227. private func acceptAction() {
  228. operation.dispatch(action: ConferenceInvitationActions.accept(payload: roomInfo.roomId))
  229. }
  230. @objc func rejectAction() {
  231. operation.dispatch(action: InvitationViewActions.dismissInvitationView())
  232. operation.dispatch(action: InvitationObserverActions.stopCallingBellAndVibration())
  233. operation.dispatch(action: ConferenceInvitationActions.reject(payload: (roomInfo.roomId, .rejectToEnter)))
  234. }
  235. @Injected(\.navigation) var route
  236. @Injected(\.conferenceStore) var operation
  237. }
  238. private extension String {
  239. static var inviteJoinConferenceText: String {
  240. localized(" invite you to join the conference")
  241. }
  242. static var hostText: String {
  243. localized("Conference Host")
  244. }
  245. static var participantText: String {
  246. localized("Participant")
  247. }
  248. static var peopleText: String {
  249. localized("People")
  250. }
  251. static var joinNowText: String {
  252. localized("Join now")
  253. }
  254. static var notEnterText: String {
  255. localized("Do not enter for now")
  256. }
  257. }