diff --git a/Classes/Swift/Voip/ViewModels/ConferenceParticipantDeviceData.swift b/Classes/Swift/Voip/ViewModels/ConferenceParticipantDeviceData.swift index 948622017..fc62410c3 100644 --- a/Classes/Swift/Voip/ViewModels/ConferenceParticipantDeviceData.swift +++ b/Classes/Swift/Voip/ViewModels/ConferenceParticipantDeviceData.swift @@ -26,11 +26,14 @@ class ConferenceParticipantDeviceData { let isMe:Bool let videoEnabled = MutableLiveData() - let activeSpeaker = MutableLiveData() + let isSpeaking = MutableLiveData() let micMuted = MutableLiveData() let isInConference = MutableLiveData() + let isJoining = MutableLiveData() + + var core : Core { get { Core.get() } } private var participantDeviceDelegate : ParticipantDeviceDelegate? @@ -41,19 +44,22 @@ class ConferenceParticipantDeviceData { participantDeviceDelegate = ParticipantDeviceDelegateStub( onIsSpeakingChanged: { (participantDevice, isSpeaking) in Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) isspeaking = \(isSpeaking)") - self.activeSpeaker.value = isSpeaking + self.isSpeaking.value = isSpeaking }, onIsMuted: { (participantDevice, isMuted) in Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) muted = \(isMuted)") self.micMuted.value = isMuted }, - onConferenceJoined: { (participantDevice) in - Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) has joined the conference") - self.isInConference.value = true - }, - onConferenceLeft: { (participantDevice) in - Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) has left the conference") - self.isInConference.value = false + onStateChanged: { (participantDevice, state) in + Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) state has changed: \(state)") + if ([.Joining,.Alerting].contains(state)) { + self.isJoining.value = true + } else if (state == .OnHold) { + self.isInConference.value = false + } else if (state == .Present) { + self.isJoining.value = false + self.isInConference.value = true + } }, onStreamCapabilityChanged: { (participantDevice, direction, streamType) in Log.i("[Conference Participant Device] Participant \(participantDevice.address?.asStringUriOnly()) video stream direction changed: \(direction)") @@ -72,7 +78,7 @@ class ConferenceParticipantDeviceData { ) participantDevice.addDelegate(delegate: participantDeviceDelegate!) - activeSpeaker.value = false + isSpeaking.value = false micMuted.value = participantDevice.isMuted videoEnabled.value = participantDevice.getStreamAvailability(streamType: .Video) @@ -90,7 +96,7 @@ class ConferenceParticipantDeviceData { func clearObservers() { isInConference.clearObservers() videoEnabled.clearObservers() - activeSpeaker.clearObservers() + isSpeaking.clearObservers() } func switchCamera() { diff --git a/Classes/Swift/Voip/ViewModels/ConferenceViewModel.swift b/Classes/Swift/Voip/ViewModels/ConferenceViewModel.swift index 6c20e3f93..4793b94d8 100644 --- a/Classes/Swift/Voip/ViewModels/ConferenceViewModel.swift +++ b/Classes/Swift/Voip/ViewModels/ConferenceViewModel.swift @@ -97,16 +97,16 @@ class ConferenceViewModel { Log.w("[Conference] Failed to find participant [\(participant.address!.asStringUriOnly())] in conferenceParticipants list") } }, - onParticipantDeviceLeft: { (conference: Conference, device: ParticipantDevice) in + onParticipantDeviceStateChanged: { (conference: Conference, device: ParticipantDevice, state: ParticipantDeviceState) in if (conference.isMe(uri: device.address!)) { - Log.i("[Conference] Left conference") - self.isConferenceLocallyPaused.value = true - } - }, - onParticipantDeviceJoined: { (conference: Conference, device: ParticipantDevice) in - if (conference.isMe(uri: device.address!)) { - Log.i("[Conference] Joined conference") - self.isConferenceLocallyPaused.value = false + if (state == .Present) { + Log.i("[Conference] Entered conference") + self.isConferenceLocallyPaused.value = false + } + if (state == .OnHold) { + Log.i("[Conference] Left conference") + self.isConferenceLocallyPaused.value = true + } } }, onStateChanged: { (conference: Conference, state: Conference.State) in diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift index d0ada751c..08b12614f 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift @@ -32,39 +32,31 @@ class VoipActiveSpeakerParticipantCell: UICollectionViewCell { let switch_camera_button_size = 30 static let mute_size = 25 let mute_margin = 5 - - + + let videoView = UIView() let avatar = Avatar(diameter:VoipActiveSpeakerParticipantCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium) let pause = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white)) let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white)) let displayName = StyledLabel(VoipTheme.conference_participant_name_font_as) - let pauseLabel = StyledLabel(VoipTheme.conference_participant_name_font_as,VoipTexts.conference_participant_paused) let muted = MicMuted(VoipActiveSpeakerParticipantCell.mute_size) - + let joining = RotatingSpinner() + var participantData: ConferenceParticipantDeviceData? = nil { didSet { if let data = participantData { + self.updateElements() + data.isJoining.clearObservers() + data.isJoining.observe { _ in + self.updateElements() + } data.isInConference.clearObservers() - data.isInConference.readCurrentAndObserve { (isIn) in - self.updateBackground() - self.pause.isHidden = isIn == true - self.pauseLabel.isHidden = self.pause.isHidden - self.videoView.isHidden = data.videoEnabled.value != true - self.switchCamera.isHidden = data.videoEnabled.value != true || !data.isSwitchCameraAvailable() + data.isInConference.observe { _ in + self.updateElements() } data.videoEnabled.clearObservers() - data.videoEnabled.readCurrentAndObserve { (videoEnabled) in - self.updateBackground() - if (videoEnabled == true) { - self.videoView.isHidden = false - data.setVideoView(view: self.videoView) - self.avatar.isHidden = true - } else { - self.videoView.isHidden = true - self.avatar.isHidden = false - } - self.switchCamera.isHidden = videoEnabled != true || !data.isSwitchCameraAvailable() + data.videoEnabled.observe { _ in + self.updateElements() } data.participantDevice.address.map { avatar.fillFromAddress(address: $0) @@ -72,32 +64,60 @@ class VoipActiveSpeakerParticipantCell: UICollectionViewCell { self.displayName.text = displayName } } - data.activeSpeaker.clearObservers() - data.activeSpeaker.readCurrentAndObserve { (active) in - if (active == true) { - self.layer.borderWidth = 2 - } else { - self.layer.borderWidth = 0 - } + data.isSpeaking.clearObservers() + data.isSpeaking.observe { _ in + self.updateElements(skipVideo: true) } data.micMuted.clearObservers() - data.micMuted.readCurrentAndObserve { (muted) in - self.muted.isHidden = muted != true + data.micMuted.observe { _ in + self.updateElements(skipVideo: true) } } } } - func updateBackground() { + func updateElements(skipVideo:Bool = false) { if let data = participantData { - if (data.isInConference.value != true) { + + // Background + if (data.isInConference.value != true && data.isJoining.value != true) { self.contentView.backgroundColor = VoipTheme.voip_conference_participant_paused_background } else if (data.videoEnabled.value == true) { self.contentView.backgroundColor = .black } else { self.contentView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get() - } + + // Avatar + self.avatar.isHidden = (data.isInConference.value != true && data.isJoining.value != true) || data.videoEnabled.value == true + + // Video + if (!skipVideo) { + self.videoView.isHidden = data.isInConference.value != true || data.videoEnabled.value != true + if (!self.videoView.isHidden) { + data.setVideoView(view: self.videoView) + } + self.switchCamera.isHidden = self.videoView.isHidden || !data.isSwitchCameraAvailable() + } + + // Pause + self.pause.isHidden = data.isInConference.value == true || data.isJoining.value == true + + // Border for active speaker + self.layer.borderWidth = data.isSpeaking.value == true ? 2 : 0 + + // Joining indicator + if (data.isJoining.value == true) { + self.joining.isHidden = false + self.joining.startRotation() + } else { + self.joining.isHidden = true + self.joining.stopRotation() + } + + // Muted + self.muted.isHidden = data.micMuted.value != true + } } @@ -130,14 +150,13 @@ class VoipActiveSpeakerParticipantCell: UICollectionViewCell { contentView.addSubview(displayName) displayName.matchParentSideBorders(insetedByDx:ActiveCallView.bottom_displayname_margin_left).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done() - - // Paused label commented out as in Android 10.06.2022 - // contentView.addSubview(pauseLabel) - //pauseLabel.toRightOf(displayName).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done() - + contentView.addSubview(muted) muted.alignParentLeft(withMargin: mute_margin).alignParentTop(withMargin:mute_margin).done() + contentView.addSubview(joining) + joining.square(VoipActiveSpeakerParticipantCell.mute_size).alignParentTop(withMargin: mute_margin).alignParentLeft(withMargin: mute_margin).done() + contentView.matchParentDimmensions().done() makeHeightMatchWidth().done() diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipAudioOnlyParticipantCell.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipAudioOnlyParticipantCell.swift index 1334dd97f..4b76580ad 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipAudioOnlyParticipantCell.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipAudioOnlyParticipantCell.swift @@ -36,43 +36,68 @@ class VoipAudioOnlyParticipantCell: UICollectionViewCell { let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_small) let paused = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white)) let muted = MicMuted(VoipAudioOnlyParticipantCell.mute_size) + let joining = RotatingSpinner() let displayName = StyledLabel(VoipTheme.conference_participant_name_font_as) var participantData: ConferenceParticipantDeviceData? = nil { didSet { if let data = participantData { - self.displayName.text = "" + self.updateElements() + data.isJoining.clearObservers() + data.isJoining.observe { _ in + self.updateElements() + } data.isInConference.clearObservers() - data.isInConference.readCurrentAndObserve { (isIn) in - self.avatar.isHidden = isIn != true - self.paused.isHidden = isIn == true - data.participantDevice.address.map { - self.avatar.fillFromAddress(address: $0) - if let displayName = $0.addressBookEnhancedDisplayName() { - self.displayName.text = displayName + (isIn == true ? "" : " \(VoipTexts.conference_participant_paused)") - } + data.isInConference.observe { _ in + self.updateElements() + } + data.participantDevice.address.map { + avatar.fillFromAddress(address: $0) + if let displayName = $0.addressBookEnhancedDisplayName() { + self.displayName.text = displayName } } - if (data.participantDevice.address == nil) { - avatar.isHidden = true - } - data.activeSpeaker.clearObservers() - data.activeSpeaker.readCurrentAndObserve { (active) in - if (active == true) { - self.layer.borderWidth = 2 - } else { - self.layer.borderWidth = 0 - } + data.isSpeaking.clearObservers() + data.isSpeaking.observe { _ in + self.updateElements(skipVideo: true) } data.micMuted.clearObservers() - data.micMuted.readCurrentAndObserve { (muted) in - self.muted.isHidden = muted != true + data.micMuted.observe { _ in + self.updateElements(skipVideo: true) } } } } + func updateElements(skipVideo:Bool = false) { + if let data = participantData { + + // Avatar + self.avatar.isHidden = data.isInConference.value != true && data.isJoining.value != true + + + // Pause + self.paused.isHidden = data.isInConference.value == true || data.isJoining.value == true + + // Border for active speaker + self.layer.borderWidth = data.isSpeaking.value == true ? 2 : 0 + + // Joining indicator + if (data.isJoining.value == true) { + self.joining.isHidden = false + self.joining.startRotation() + } else { + self.joining.isHidden = true + self.joining.stopRotation() + } + + // Muted + self.muted.isHidden = data.micMuted.value != true + + } + } + override init(frame:CGRect) { super.init(frame:.zero) @@ -98,6 +123,10 @@ class VoipAudioOnlyParticipantCell: UICollectionViewCell { contentView.addSubview(muted) muted.alignParentRight(withMargin: common_margin).toRightOf(displayName,withLeftMargin: common_margin).centerY().done() + + contentView.addSubview(joining) + joining.alignParentRight(withMargin: common_margin).toRightOf(displayName,withLeftMargin: common_margin).centerY().done() + } required init?(coder: NSCoder) { diff --git a/Classes/Swift/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift index 837163684..849650d79 100644 --- a/Classes/Swift/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift @@ -42,67 +42,85 @@ class VoipGridParticipantCell: UICollectionViewCell { let displayName = StyledLabel(VoipTheme.conference_participant_name_font_grid) let pauseLabel = StyledLabel(VoipTheme.conference_participant_name_font_grid,VoipTexts.conference_participant_paused) let muted = MicMuted(VoipActiveSpeakerParticipantCell.mute_size) + let joining = RotatingSpinner() var participantData: ConferenceParticipantDeviceData? = nil { didSet { if let data = participantData { + self.updateElements() + data.isJoining.clearObservers() + data.isJoining.observe { _ in + self.updateElements() + } data.isInConference.clearObservers() - data.isInConference.readCurrentAndObserve { (isIn) in - self.updateBackground() - self.pause.isHidden = isIn == true - self.pauseLabel.isHidden = self.pause.isHidden - self.videoView.isHidden = data.videoEnabled.value != true - self.switchCamera.isHidden = data.videoEnabled.value != true || !data.isSwitchCameraAvailable() + data.isInConference.observe { _ in + self.updateElements() } data.videoEnabled.clearObservers() - data.videoEnabled.readCurrentAndObserve { (videoEnabled) in - self.updateBackground() - if (videoEnabled == true) { - self.videoView.isHidden = false - data.setVideoView(view: self.videoView) - self.avatar.isHidden = true - } else { - self.videoView.isHidden = true - self.avatar.isHidden = false - } - self.switchCamera.isHidden = videoEnabled != true || !data.isSwitchCameraAvailable() + data.videoEnabled.observe { _ in + self.updateElements() } - if (data.participantDevice.address == nil) { - avatar.isHidden = true - } - self.displayName.text = "" data.participantDevice.address.map { avatar.fillFromAddress(address: $0) if let displayName = $0.addressBookEnhancedDisplayName() { self.displayName.text = displayName } } - data.activeSpeaker.clearObservers() - data.activeSpeaker.readCurrentAndObserve { (active) in - if (active == true) { - self.layer.borderWidth = 2 - } else { - self.layer.borderWidth = 0 - } + data.isSpeaking.clearObservers() + data.isSpeaking.observe { _ in + self.updateElements(skipVideo: true) } data.micMuted.clearObservers() - data.micMuted.readCurrentAndObserve { (muted) in - self.muted.isHidden = muted != true + data.micMuted.observe { _ in + self.updateElements(skipVideo: true) } } } } - func updateBackground() { + func updateElements(skipVideo:Bool = false) { if let data = participantData { - if (data.isInConference.value != true) { + + // Background + if (data.isInConference.value != true && data.isJoining.value != true) { self.contentView.backgroundColor = VoipTheme.voip_conference_participant_paused_background } else if (data.videoEnabled.value == true) { self.contentView.backgroundColor = .black } else { self.contentView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get() - } + + // Avatar + self.avatar.isHidden = (data.isInConference.value != true && data.isJoining.value != true) || data.videoEnabled.value == true + + // Video + if (!skipVideo) { + self.videoView.isHidden = data.isInConference.value != true || data.videoEnabled.value != true + if (!self.videoView.isHidden) { + data.setVideoView(view: self.videoView) + } + self.switchCamera.isHidden = self.videoView.isHidden || !data.isSwitchCameraAvailable() + } + + // Pause + self.pause.isHidden = data.isInConference.value == true || data.isJoining.value == true + self.pauseLabel.isHidden = self.pause.isHidden + + // Border for active speaker + self.layer.borderWidth = data.isSpeaking.value == true ? 2 : 0 + + // Joining indicator + if (data.isJoining.value == true) { + self.joining.isHidden = false + self.joining.startRotation() + } else { + self.joining.isHidden = true + self.joining.stopRotation() + } + + // Muted + self.muted.isHidden = data.micMuted.value != true + } } @@ -142,6 +160,10 @@ class VoipGridParticipantCell: UICollectionViewCell { contentView.addSubview(muted) muted.alignParentLeft(withMargin: mute_margin).alignParentTop(withMargin:mute_margin).done() + contentView.addSubview(joining) + joining.square(VoipActiveSpeakerParticipantCell.mute_size).alignParentTop(withMargin: mute_margin).alignParentLeft(withMargin: mute_margin).done() + + contentView.matchParentDimmensions().done() }