conference: add moderator actions
Change-Id: Id333299348db83f8de28ba1d34412d0286a885a2
diff --git a/Ring/Ring/Bridging/CallsAdapter.h b/Ring/Ring/Bridging/CallsAdapter.h
index 9e77e3a..0db4575 100644
--- a/Ring/Ring/Bridging/CallsAdapter.h
+++ b/Ring/Ring/Bridging/CallsAdapter.h
@@ -49,4 +49,7 @@
- (void)setActiveParticipant:(NSString*)callId forConference:(NSString*)conferenceId;
- (void)setConferenceLayout:(int)layout forConference:(NSString*)conferenceId;
- (NSArray*)getConferenceInfo:(NSString*)conferenceId;
+- (void)setConferenceModerator:(NSString*)participantId forConference:(NSString*)conferenceId active:(BOOL)isActive;
+- (void)muteConferenceParticipant:(NSString*)participantId forConference:(NSString*)conferenceId active:(BOOL)isActive;
+- (void)hangupConferenceParticipant:(NSString*)participantId forConference:(NSString*)conferenceId;
@end
diff --git a/Ring/Ring/Bridging/CallsAdapter.mm b/Ring/Ring/Bridging/CallsAdapter.mm
index 17fbf5c..a6754e7 100644
--- a/Ring/Ring/Bridging/CallsAdapter.mm
+++ b/Ring/Ring/Bridging/CallsAdapter.mm
@@ -221,6 +221,18 @@
setConferenceLayout(std::string([conferenceId UTF8String]), layout);
}
+- (void)setConferenceModerator:(NSString*)participantId forConference:(NSString*)conferenceId active:(BOOL)isActive {
+ setModerator(std::string([conferenceId UTF8String]), std::string([participantId UTF8String]), isActive);
+}
+
+- (void)muteConferenceParticipant:(NSString*)participantId forConference:(NSString*)conferenceId active:(BOOL)isActive {
+ muteParticipant(std::string([conferenceId UTF8String]), std::string([participantId UTF8String]), isActive);
+}
+
+- (void)hangupConferenceParticipant:(NSString*)participantId forConference:(NSString*)conferenceId {
+ hangupParticipant(std::string([conferenceId UTF8String]), std::string([participantId UTF8String]));
+}
+
- (NSArray*)getConferenceInfo:(NSString*)conferenceId {
auto result = getConferenceInfos(std::string([conferenceId UTF8String]));
NSArray* arrayResult = [Utils vectorOfMapsToArray:result];
diff --git a/Ring/Ring/Calls/CallViewController.storyboard b/Ring/Ring/Calls/CallViewController.storyboard
index cc7f026..7106245 100644
--- a/Ring/Ring/Calls/CallViewController.storyboard
+++ b/Ring/Ring/Calls/CallViewController.storyboard
@@ -75,13 +75,13 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="z3c-S7-uGw">
<rect key="frame" x="0.0" y="0.0" width="375" height="110"/>
<subviews>
- <stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="MdN-dF-4x3">
- <rect key="frame" x="20" y="0.0" width="139.66666666666666" height="30"/>
+ <stackView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" distribution="equalSpacing" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="MdN-dF-4x3">
+ <rect key="frame" x="20" y="0.0" width="30" height="30"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GF7-hD-E63">
<rect key="frame" x="0.0" y="0.0" width="30" height="30"/>
<constraints>
- <constraint firstAttribute="width" constant="30" id="rKY-t8-TpZ"/>
+ <constraint firstAttribute="width" constant="30" id="hq4-4B-hxi"/>
<constraint firstAttribute="height" constant="30" id="uyC-ZH-E3S"/>
</constraints>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -99,17 +99,17 @@
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</button>
- <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cgd-Wa-clf">
- <rect key="frame" x="50.000000000000007" y="0.0" width="89.666666666666686" height="30"/>
- <fontDescription key="fontDescription" type="system" pointSize="23"/>
- <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <nil key="highlightedColor"/>
- </label>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="30" id="7GR-7Z-Jln"/>
</constraints>
</stackView>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cgd-Wa-clf">
+ <rect key="frame" x="70" y="15" width="0.0" height="0.0"/>
+ <fontDescription key="fontDescription" type="system" pointSize="23"/>
+ <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ <nil key="highlightedColor"/>
+ </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fac-lR-4on">
<rect key="frame" x="19.999999999999996" y="50" width="52.666666666666657" height="24"/>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="20"/>
@@ -121,7 +121,9 @@
<constraints>
<constraint firstItem="fac-lR-4on" firstAttribute="top" secondItem="MdN-dF-4x3" secondAttribute="bottom" constant="20" id="1ja-6Q-FJk"/>
<constraint firstItem="MdN-dF-4x3" firstAttribute="top" secondItem="z3c-S7-uGw" secondAttribute="top" id="TVu-Mv-p0D"/>
+ <constraint firstItem="cgd-Wa-clf" firstAttribute="leading" secondItem="MdN-dF-4x3" secondAttribute="trailing" constant="20" id="VIE-lK-GeA"/>
<constraint firstItem="fac-lR-4on" firstAttribute="leading" secondItem="MdN-dF-4x3" secondAttribute="leading" id="cJ6-kf-a3T"/>
+ <constraint firstItem="cgd-Wa-clf" firstAttribute="centerY" secondItem="MdN-dF-4x3" secondAttribute="centerY" id="unp-V8-eN9"/>
</constraints>
</view>
</subviews>
@@ -198,6 +200,7 @@
<constraint firstItem="K0W-KI-Ul4" firstAttribute="centerY" secondItem="DMu-Or-dd7" secondAttribute="centerY" id="cTk-Hv-nxM"/>
<constraint firstItem="Zmp-OX-Cez" firstAttribute="centerX" secondItem="DMu-Or-dd7" secondAttribute="centerX" id="f1S-rO-Hld"/>
<constraint firstItem="K0W-KI-Ul4" firstAttribute="height" secondItem="DMu-Or-dd7" secondAttribute="height" id="grY-ie-rVw"/>
+ <constraint firstItem="CfE-DF-buX" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="cgd-Wa-clf" secondAttribute="trailing" priority="750" constant="7" id="hmN-pb-JlG"/>
<constraint firstItem="K0W-KI-Ul4" firstAttribute="centerX" secondItem="DMu-Or-dd7" secondAttribute="centerX" id="oCc-Yp-7Ay"/>
<constraint firstItem="ZK1-Be-lcD" firstAttribute="width" secondItem="ZVy-nB-bKJ" secondAttribute="width" id="uTd-rs-MJH"/>
<constraint firstAttribute="trailing" secondItem="ZK1-Be-lcD" secondAttribute="trailing" id="ugJ-SF-Enn"/>
@@ -206,10 +209,10 @@
</constraints>
</view>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" placeholderIntrinsicWidth="375" placeholderIntrinsicHeight="128" alwaysBounceHorizontal="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bij-Xb-EKH">
- <rect key="frame" x="0.0" y="856" width="375" height="300"/>
+ <rect key="frame" x="0.0" y="806" width="375" height="360"/>
<subviews>
<stackView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" distribution="equalSpacing" alignment="top" spacing="40" translatesAutoresizingMaskIntoConstraints="NO" id="7G0-Fp-Xc3">
- <rect key="frame" x="10" y="10" width="355" height="260"/>
+ <rect key="frame" x="10" y="10" width="355" height="320"/>
</stackView>
</subviews>
<constraints>
@@ -219,7 +222,7 @@
<constraint firstItem="7G0-Fp-Xc3" firstAttribute="centerX" secondItem="bij-Xb-EKH" secondAttribute="centerX" placeholder="YES" id="JZj-B5-YW6"/>
<constraint firstItem="7G0-Fp-Xc3" firstAttribute="leading" secondItem="bij-Xb-EKH" secondAttribute="leading" constant="10" id="QC5-Mh-VcC"/>
<constraint firstAttribute="bottom" secondItem="7G0-Fp-Xc3" secondAttribute="bottom" constant="30" id="lUV-jq-9bZ"/>
- <constraint firstAttribute="height" constant="300" id="q8G-dB-syM"/>
+ <constraint firstAttribute="height" constant="360" id="q8G-dB-syM"/>
<constraint firstItem="7G0-Fp-Xc3" firstAttribute="height" secondItem="bij-Xb-EKH" secondAttribute="height" constant="-40" id="xAL-yn-Zen"/>
</constraints>
</scrollView>
@@ -334,7 +337,7 @@
<constraint firstItem="ZVy-nB-bKJ" firstAttribute="centerY" secondItem="QpJ-Sx-9dG" secondAttribute="centerY" id="bAN-gX-nPE"/>
<constraint firstAttribute="bottom" secondItem="LK6-u0-eLU" secondAttribute="bottom" id="dXj-zI-fQb"/>
<constraint firstItem="ZVy-nB-bKJ" firstAttribute="centerX" secondItem="lZI-X0-bkP" secondAttribute="centerX" id="ff0-Nw-f2Y"/>
- <constraint firstItem="bij-Xb-EKH" firstAttribute="top" secondItem="ZK1-Be-lcD" secondAttribute="bottom" id="pgr-29-Lef"/>
+ <constraint firstItem="bij-Xb-EKH" firstAttribute="top" secondItem="ZK1-Be-lcD" secondAttribute="bottom" constant="-50" id="pgr-29-Lef"/>
<constraint firstItem="5E0-lB-SkS" firstAttribute="width" secondItem="QpJ-Sx-9dG" secondAttribute="width" id="rOQ-In-yON"/>
<constraint firstItem="ZVy-nB-bKJ" firstAttribute="width" secondItem="QpJ-Sx-9dG" secondAttribute="width" id="sCh-Gw-iu0"/>
<constraint firstItem="MdN-dF-4x3" firstAttribute="leading" secondItem="lZI-X0-bkP" secondAttribute="leading" constant="20" id="tsz-q2-iDb"/>
diff --git a/Ring/Ring/Calls/CallViewController.swift b/Ring/Ring/Calls/CallViewController.swift
index e611288..13d8e01 100644
--- a/Ring/Ring/Calls/CallViewController.swift
+++ b/Ring/Ring/Calls/CallViewController.swift
@@ -156,6 +156,27 @@
self.updateconferenceLayoutSize()
let participants = self.viewModel.getConferenceParticipants()
self.conferenceLayout.setParticipants(participants: participants)
+ guard let unwrapParticipants = participants, self.viewModel.isCurrentModerator(), !self.viewModel.isHostCall else { return }
+ self.conferenceCalls.arrangedSubviews.forEach({ (view) in
+ view.removeFromSuperview()
+ })
+ for participant in unwrapParticipants {
+ let callView =
+ ConferenceParticipantView(frame:
+ CGRect(x: 0, y: 0,
+ width: inConfViewWidth, height: inConfViewHeight))
+ let injectionBag = self.viewModel.injectionBag
+ let isLocal = self.viewModel.isLocalCall(participantId: participant.uri ?? "")
+ let pendingCallViewModel =
+ ConferenceParticipantViewModel(with: nil,
+ injectionBag: injectionBag,
+ isLocal: isLocal,
+ participantId: participant.uri ?? "",
+ participantUserName: participant.displayName)
+ callView.viewModel = pendingCallViewModel
+ callView.delegate = self
+ self.conferenceCalls.addArrangedSubview(callView)
+ }
})
.disposed(by: self.disposeBag)
}
@@ -447,17 +468,20 @@
.asObservable()
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] enteredConference in
- guard let call = self?.viewModel.call else { return }
+ guard let call = self?.viewModel.call,
+ let self = self else { return }
if call.state != .current { return }
- self?.updateconferenceLayoutSize()
- self?.buttonsContainer.updateView()
- self?.infoContainer.isHidden = enteredConference ? true : false
- self?.resizeCapturedVideo(withInfoContainer: false)
+ self.updateconferenceLayoutSize()
+ self.buttonsContainer.updateView()
+ self.infoContainer.isHidden = enteredConference ? true : false
+ self.conferenceCallsTop.constant = enteredConference ? 0 : -50
+ self.resizeCapturedVideo(withInfoContainer: false)
+ // for moderator participants will be added in layoutUpdated
+ if self.viewModel.isCurrentModerator() { return }
// if entered conference add first participant to conference list
if enteredConference {
- self?.removeConferenceParticipantMenu()
- guard let injectionBag = self?.viewModel.injectionBag
- else { return }
+ self.removeConferenceParticipantMenu()
+ let injectionBag = self.viewModel.injectionBag
// add self as a master call
let mainCallView =
ConferenceParticipantView(frame: CGRect(x: 0,
@@ -466,24 +490,31 @@
height: inConfViewHeight))
let mainCallViewModel =
ConferenceParticipantViewModel(with: nil,
- injectionBag: injectionBag)
+ injectionBag: injectionBag,
+ isLocal: true,
+ participantId: "",
+ participantUserName: "")
mainCallView.viewModel = mainCallViewModel
mainCallView.delegate = self
- self?.conferenceCalls.insertArrangedSubview(mainCallView, at: 0)
+ self.conferenceCalls.insertArrangedSubview(mainCallView, at: 0)
let callView =
ConferenceParticipantView(frame: CGRect(x: 0,
y: 0,
width: inConfViewWidth,
height: inConfViewHeight))
+ let name = call.displayName.isEmpty ? call.registeredName.isEmpty ? call.participantUri.filterOutHost() : call.registeredName : call.displayName
let pendingCallViewModel =
- ConferenceParticipantViewModel(with: call,
- injectionBag: injectionBag)
+ ConferenceParticipantViewModel(with: call.callId,
+ injectionBag: injectionBag,
+ isLocal: false,
+ participantId: call.paricipantHash(),
+ participantUserName: name)
callView.viewModel = pendingCallViewModel
callView.delegate = self
- self?.conferenceCalls.insertArrangedSubview(callView, at: 1)
+ self.conferenceCalls.insertArrangedSubview(callView, at: 1)
} else {
- self?.removeConferenceParticipantMenu()
- self?.conferenceCalls.arrangedSubviews.forEach({ (view) in
+ self.removeConferenceParticipantMenu()
+ self.conferenceCalls.arrangedSubviews.forEach({ (view) in
view.removeFromSuperview()
})
}
@@ -493,17 +524,24 @@
self.viewModel.callForConference
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] call in
+ guard let self = self else { return }
+ // for moderator participants will be added in layoutUpdated
+ if self.viewModel.isCurrentModerator() { return }
let callView =
ConferenceParticipantView(frame:
CGRect(x: 0, y: 0,
width: inConfViewWidth, height: inConfViewHeight))
- guard let injectionBag = self?.viewModel.injectionBag else { return }
+ let injectionBag = self.viewModel.injectionBag
+ let name = call.displayName.isEmpty ? call.registeredName.isEmpty ? call.participantUri.filterOutHost() : call.registeredName : call.displayName
let pendingCallViewModel =
- ConferenceParticipantViewModel(with: call,
- injectionBag: injectionBag)
+ ConferenceParticipantViewModel(with: call.callId,
+ injectionBag: injectionBag,
+ isLocal: false,
+ participantId: call.paricipantHash(),
+ participantUserName: name)
callView.viewModel = pendingCallViewModel
callView.delegate = self
- self?.conferenceCalls.addArrangedSubview(callView)
+ self.conferenceCalls.addArrangedSubview(callView)
})
.disposed(by: self.disposeBag)
@@ -729,7 +767,8 @@
UIView.animate(withDuration: 0.2, animations: { [weak self] in
self?.infoContainerTopConstraint.constant = -10
- self?.conferenceCallsTop.constant = 0
+ let isConference: Bool = self?.viewModel.conferenceMode.value ?? true
+ self?.conferenceCallsTop.constant = isConference ? 0 : -50
if UIDevice.current.hasNotch && (self?.orientation == .landscapeRight || self?.orientation == .landscapeLeft) {
self?.buttonsContainerBottomConstraint.constant = 1
} else if UIDevice.current.userInterfaceIdiom == .pad {
@@ -786,26 +825,61 @@
}
extension CallViewController: ConferenceParticipantViewDelegate {
- func addConferenceParticipantMenu(origin: CGPoint, displayName: String, callId: String?, hangup: @escaping (() -> Void)) {
+ func addConferenceParticipantMenu(origin: CGPoint, displayName: String, participantId: String, callId: String?, hangup: @escaping (() -> Void)) {
// remove menu if it is already present
if self.conferenceParticipantMenu?.frame.origin == origin {
self.removeConferenceParticipantMenu()
return
}
let menuView = ConferenceActionMenu(frame: CGRect(origin: origin, size: CGSize(width: self.view.frame.size.width, height: self.view.frame.size.height)))
- menuView.configureWith(mode: self.viewModel.getItemsForConferenceMenu(participantCallId: callId), displayName: displayName)
+ var muteEnabled = false
+ var muteText = ""
+ var moderatorText = ""
+ var isModerator = false
+ var isAudioMuted = false
+ var pending = true
+
+ if let participant = self.viewModel.getConferencePartisipant(participantId: participantId) {
+ muteEnabled = !participant.isAudioLocalyMuted
+ muteText = participant.isAudioMuted ? L10n.Calls.unmuteAudio : L10n.Calls.muteAudio
+ moderatorText = participant.isModerator ? L10n.Calls.removeModerator : L10n.Calls.setModerator
+ isModerator = participant.isModerator
+ isAudioMuted = participant.isAudioMuted
+ pending = false
+ }
+
+ menuView.configureWith(items: self.viewModel.getItemsForConferenceMenu(participantId: participantId, callId: callId ?? ""),
+ displayName: displayName,
+ muteText: muteText,
+ moderatorText: moderatorText,
+ muteEnabled: muteEnabled)
menuView.addHangUpAction { [weak self] in
- hangup()
+ if pending {
+ hangup()
+ } else {
+ self?.viewModel.hangupParticipant(participantId: participantId)
+ }
self?.removeConferenceParticipantMenu()
}
menuView.addMaximizeAction { [weak self] in
self?.removeConferenceParticipantMenu()
- self?.viewModel.setActiveParticipant(callId: callId, maximize: true)
+ self?.viewModel.setActiveParticipant(jamiId: participantId, maximize: true)
}
menuView.addMinimizeAction { [weak self] in
self?.removeConferenceParticipantMenu()
- self?.viewModel.setActiveParticipant(callId: callId, maximize: false)
+ self?.viewModel.setActiveParticipant(jamiId: participantId, maximize: false)
}
+
+ menuView.addSetModeratorAction { [weak self] in
+ self?.removeConferenceParticipantMenu()
+ self?.viewModel.setModeratorParticipant(participantId: participantId, active: !isModerator)
+ }
+
+ menuView.addMuteAction { [weak self] in
+ self?.removeConferenceParticipantMenu()
+ self?.viewModel.muteParticipant(participantId: participantId, active: !isAudioMuted)
+ }
+
let point = conferenceCallsScrolView.convert(menuView.frame.origin, to: self.view)
let offset = self.view.frame.size.width - point.x - menuView.frame.size.width
if offset < 0 {
diff --git a/Ring/Ring/Calls/CallViewModel.swift b/Ring/Ring/Calls/CallViewModel.swift
index d5ef37d..151a3fa 100644
--- a/Ring/Ring/Calls/CallViewModel.swift
+++ b/Ring/Ring/Calls/CallViewModel.swift
@@ -48,6 +48,7 @@
var isHeadsetConnected = false
var isAudioOnly = false
+ var isHostCall = false
private lazy var currentCallVariable: BehaviorRelay<CallModel> = {
BehaviorRelay<CallModel>(value: self.call ?? CallModel())
@@ -86,23 +87,36 @@
self.callService.currentConferenceEvent
.asObservable()
.filter({ [weak self] conference-> Bool in
- return conference.calls.contains(self?.call?.callId ?? "") ||
- conference.conferenceID == self?.rendererId
+ guard let self = self else { return false }
+ return conference.calls.contains(self.call?.callId ?? "") ||
+ conference.conferenceID == self.rendererId
})
.subscribe(onNext: { [weak self] conf in
+ guard let self = self else { return }
if conf.conferenceID.isEmpty {
return
}
if conf.state == ConferenceState.infoUpdated.rawValue {
- self?.layoutUpdated.accept(true)
+ self.layoutUpdated.accept(true)
+ guard let account = self.accountService.currentAccount, !self.isHostCall else {
+ return
+ }
+ let isModerator = self.callService.isModerator(participantId: account.jamiId, inConference: conf.conferenceID)
+ if isModerator != self.containerViewModel?.isConference {
+// guard let updatedCall = self.callService.call(callID: call.callId) else { return }
+// self.call = updatedCall
+ self.containerViewModel?.isConference = isModerator
+ self.conferenceMode.accept(isModerator)
+ }
return
}
- guard let updatedCall = self?.callService.call(callID: call.callId) else { return }
- self?.call = updatedCall
+ guard let updatedCall = self.callService.call(callID: call.callId) else { return }
+ self.call = updatedCall
let conferenceCreated = conf.state == ConferenceState.conferenceCreated.rawValue
- self?.rendererId = conferenceCreated ? conf.conferenceID : self!.call!.callId
- self?.containerViewModel?.isConference = conferenceCreated
- self?.conferenceMode.accept(conferenceCreated)
+ self.rendererId = conferenceCreated ? conf.conferenceID : self.call!.callId
+ self.isHostCall = conferenceCreated
+ self.containerViewModel?.isConference = conferenceCreated
+ self.conferenceMode.accept(conferenceCreated)
})
.disposed(by: self.disposeBag)
self.rendererId = call.callId
@@ -627,15 +641,26 @@
}
// MARK: conference layout
extension CallViewModel {
- func setActiveParticipant(callId: String?, maximize: Bool) {
- guard let jamiId = self.accountService.currentAccount?.jamiId else { return }
- self.callService.setActiveParticipant(callId: callId, conferenceId: self.rendererId, maximixe: maximize, jamiId: jamiId)
+ func setActiveParticipant(jamiId: String, maximize: Bool) {
+ self.callService.setActiveParticipant(conferenceId: self.rendererId, maximixe: maximize, jamiId: jamiId.filterOutHost())
}
func getConferenceVideoSize() -> CGSize {
return self.videoService.getConferenceVideoSize(confId: self.rendererId)
}
+ func muteParticipant(participantId: String, active: Bool) {
+ self.callService.muteParticipant(confId: self.rendererId, participantId: participantId.filterOutHost(), active: active)
+ }
+
+ func setModeratorParticipant(participantId: String, active: Bool) {
+ self.callService.setModeratorParticipant(confId: self.rendererId, participantId: participantId.filterOutHost(), active: active)
+ }
+
+ func hangupParticipant(participantId: String) {
+ self.callService.hangupParticipant(confId: self.rendererId, participantId: participantId.filterOutHost())
+ }
+
func getConferenceParticipants() -> [ConferenceParticipant]? {
guard let account = self.accountService.currentAccount,
let participants = self.callService.getConferenceParticipants(for: self.rendererId),
@@ -645,7 +670,7 @@
// master call
if uri.isEmpty {
//check if master call is local or remote
- if !self.conferenceMode.value {
+ if !self.isHostCall {
participant.displayName = call.getDisplayName()
} else {
participant.displayName = L10n.Account.me
@@ -673,15 +698,47 @@
return participants
}
- func getItemsForConferenceMenu(participantCallId: String?) -> MenuMode {
- let conference = self.callService.call(callID: self.rendererId)
- // menu for master call
- guard let callId = participantCallId else {
- let active = self.callService.isParticipant(participantURI: "", activeIn: self.rendererId)
- return menuItemsManager.getMenuItemsForMasterCall(conference: conference, active: active)
+ func getConferencePartisipant(participantId: String) -> ConferenceParticipant? {
+ guard let participants = self.getConferenceParticipants() else { return nil }
+ return participants.filter { participant in
+ return participant.uri?.filterOutHost() == participantId.filterOutHost()
+ }.first
+ }
+
+ func isLocalCall(participantId: String) -> Bool {
+ guard let account = self.accountService.currentAccount else { return false }
+ return account.jamiId == participantId.filterOutHost()
+ }
+
+ func isHostCall(participantId: String) -> Bool {
+ guard let account = self.accountService.currentAccount else { return false }
+ if self.isHostCall {
+ return account.jamiId == participantId.filterOutHost()
}
- let call = self.callService.call(callID: callId)
- let active = self.callService.isParticipant(participantURI: call?.participantUri, activeIn: self.rendererId)
- return menuItemsManager.getMenuItemsFor(call: call, conference: conference, active: active)
+ return call?.participantUri.filterOutHost() == participantId.filterOutHost()
+ }
+
+ func isCurrentModerator() -> Bool {
+ guard let account = self.accountService.currentAccount else { return false }
+ return self.callService.isModerator(participantId: account.jamiId, inConference: self.rendererId)
+ }
+
+ func getItemsForConferenceMenu(participantId: String, callId: String) -> [MenuItem] {
+ let conference = self.callService.call(callID: self.rendererId)
+ let active = self.callService.isParticipant(participantURI: participantId, activeIn: self.rendererId)
+ // menu for local call
+ if self.isLocalCall(participantId: participantId) || participantId.isEmpty {
+ return menuItemsManager.getMenuItemsForLocalCall(conference: conference, active: active)
+ }
+ let isModerator = self.isCurrentModerator()
+ var role = RoleInCall.regular
+ let callIsHost = self.isHostCall(participantId: participantId)
+ if self.isHostCall {
+ role = RoleInCall.host
+ } else if isModerator {
+ role = RoleInCall.moderator
+ }
+ let participantCall = isModerator ? call : self.callService.call(callID: callId)
+ return menuItemsManager.getMenuItemsFor(call: participantCall, isHost: callIsHost, conference: conference, active: active, role: role)
}
}
diff --git a/Ring/Ring/Calls/Conference/ConferenceMenuItemsManager.swift b/Ring/Ring/Calls/Conference/ConferenceMenuItemsManager.swift
index 9aabd29..3d6dd63 100644
--- a/Ring/Ring/Calls/Conference/ConferenceMenuItemsManager.swift
+++ b/Ring/Ring/Calls/Conference/ConferenceMenuItemsManager.swift
@@ -17,41 +17,110 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-
+enum RoleInCall {
+ case moderator
+ case host
+ case regular
+}
class ConferenceMenuItemsManager {
- func getMenuItemsForMasterCall(conference: CallModel?, active: Bool?) -> MenuMode {
+ func getMenuItemsForLocalCall(conference: CallModel?, active: Bool?) -> [MenuItem] {
+ var menu = [MenuItem]()
+ menu.append(.name)
guard let conference = conference else {
- return MenuMode.onlyName
+ return menu
}
guard let active = active else {
- return MenuMode.onlyName
+ return menu
}
switch conference.layout {
case .grid:
- return MenuMode.withoutHangUPAndMinimize
+ menu.append(.maximize)
case .oneWithSmal:
- return active ? MenuMode.withoutHangUp : MenuMode.withoutHangUPAndMinimize
+ menu.append(.maximize)
+ if active {
+ menu.append(.minimize)
+ }
case .one:
- return active ? MenuMode.withoutHangUPAndMaximize : MenuMode.withoutHangUPAndMinimize
+ if active {
+ menu.append(.minimize)
+ } else {
+ menu.append(.maximize)
+ }
}
+ menu.append(.muteAudio)
+ return menu
}
- func getMenuItemsFor(call: CallModel?, conference: CallModel?, active: Bool?) -> MenuMode {
+ func getMenuItemsFor(call: CallModel?, isHost: Bool, conference: CallModel?, active: Bool?, role: RoleInCall) -> [MenuItem] {
+ var menu = [MenuItem]()
+ menu.append(.name)
guard let conference = conference,
let call = call else {
- return MenuMode.onlyName
+ return menu
}
if call.state != CallState.current {
- return MenuMode.withoutMaximizeAndMinimize
+ menu.append(.hangup)
+ return menu
}
- guard let active = active else { return MenuMode.onlyName }
+ guard let active = active else {
+ return menu
+ }
switch conference.layout {
case .grid:
- return MenuMode.withoutMinimize
+ menu.append(.maximize)
+ switch role {
+ case .host:
+ menu.append(.muteAudio)
+ menu.append(.setModerator)
+ menu.append(.hangup)
+ case .moderator:
+ menu.append(.muteAudio)
+ if !isHost {
+ menu.append(.hangup)
+ }
+ case .regular:
+ break
+ }
case .oneWithSmal:
- return active ? MenuMode.all : MenuMode.withoutMinimize
+ if active {
+ menu.append(.maximize)
+ menu.append(.minimize)
+ } else {
+ menu.append(.maximize)
+ }
+ switch role {
+ case .host:
+ menu.append(.muteAudio)
+ menu.append(.setModerator)
+ menu.append(.hangup)
+ case .moderator:
+ menu.append(.muteAudio)
+ if !isHost {
+ menu.append(.hangup)
+ }
+ case .regular:
+ break
+ }
case .one:
- return active ? MenuMode.withoutMaximize : MenuMode.withoutMinimize
+ if active {
+ menu.append(.minimize)
+ } else {
+ menu.append(.maximize)
+ }
+ switch role {
+ case .host:
+ menu.append(.muteAudio)
+ menu.append(.setModerator)
+ menu.append(.hangup)
+ case .moderator:
+ menu.append(.muteAudio)
+ if !isHost {
+ menu.append(.hangup)
+ }
+ case .regular:
+ break
+ }
}
+ return menu
}
}
diff --git a/Ring/Ring/Calls/views/ConferenceActionsMenu.swift b/Ring/Ring/Calls/views/ConferenceActionsMenu.swift
index 80cb87e..8839fff 100644
--- a/Ring/Ring/Calls/views/ConferenceActionsMenu.swift
+++ b/Ring/Ring/Calls/views/ConferenceActionsMenu.swift
@@ -21,54 +21,69 @@
import UIKit
import RxSwift
-enum MenuMode {
- case withoutHangUp // for master call
- case withoutMaximize
- case withoutMinimize
- case withoutMaximizeAndMinimize
- case withoutHangUPAndMinimize
- case withoutHangUPAndMaximize
- case onlyName
- case all
+enum MenuItem {
+ case name
+ case hangup
+ case minimize
+ case maximize
+ case setModerator
+ case muteAudio
}
class ConferenceActionMenu: UIView {
private let marginY: CGFloat = 20
private let marginX: CGFloat = 20
- private let maxWidth: CGFloat = 120
+ private let maxWidth: CGFloat = 200
private let menuItemWidth: CGFloat = 80
private let menuItemHight: CGFloat = 30
private let textSize: CGFloat = 20
private var hangUpButton: UIButton?
private var maximizeButton: UIButton?
private var minimizeButton: UIButton?
+ private var setModeratorButton: UIButton?
+ private var muteAudioButton: UIButton?
private let disposeBag = DisposeBag()
+ private var muteLabelText: String = ""
+ private var moderatorLabelText: String = ""
+ private var muteButtonEnabled: Bool = false
+ private var hasSetMute: Bool = false
- func configureWith(mode: MenuMode, displayName: String) {
+ func configureWith(items: [MenuItem], displayName: String, muteText: String, moderatorText: String, muteEnabled: Bool) {
self.addDisplayName(displayName: displayName)
- switch mode {
- case .withoutHangUp:
- self.configureWithoutHangUP()
- case .withoutMaximize:
- self.configureWithoutMaximize()
- case .withoutMinimize:
- self.configureWithoutMinimize()
- case .withoutMaximizeAndMinimize:
- self.configureWithoutMaximizeAndMinimize()
- case .withoutHangUPAndMinimize:
- self.configureWithoutHangUPAndMinimize()
- case .withoutHangUPAndMaximize:
- self.configureWithoutHangUPAndMaximize()
- case .all:
- self.configureWithAllItems()
- case .onlyName:
- break
+ muteLabelText = muteText
+ moderatorLabelText = moderatorText
+ muteButtonEnabled = muteEnabled
+ let itemsWithoutName = items.filter { item in
+ item != .name
+ }
+ if !itemsWithoutName.isEmpty {
+ for index in 1...itemsWithoutName.count {
+ let position: CGFloat = self.marginY * CGFloat((index + 1)) + menuItemHight * CGFloat(index)
+ self.addItem(item: itemsWithoutName[index - 1], positionY: position)
+ }
}
self.updateWidth()
self.updateHeight()
self.addBackground()
}
+ func addItem(item: MenuItem, positionY: CGFloat) {
+ switch item {
+ case .minimize:
+ self.addMinimizeButton(positionY: positionY)
+ case .maximize:
+ self.addMaximizeButton(positionY: positionY)
+ case .setModerator:
+ self.addSetModeratorButton(positionY: positionY)
+ case .muteAudio:
+ self.addMuteAudioButton(positionY: positionY)
+ case .hangup:
+ self.addHangUpButton(positionY: positionY)
+ case .name:
+ break
+ }
+ }
+
func addHangUpAction(hangup: @escaping (() -> Void)) {
guard let button = hangUpButton else { return }
button.rx.tap
@@ -90,6 +105,20 @@
.disposed(by: self.disposeBag)
}
+ func addSetModeratorAction(setModerator: @escaping (() -> Void)) {
+ guard let button = setModeratorButton else { return }
+ button.rx.tap
+ .subscribe(onNext: { setModerator() })
+ .disposed(by: self.disposeBag)
+ }
+
+ func addMuteAction(mute: @escaping (() -> Void)) {
+ guard let button = muteAudioButton else { return }
+ button.rx.tap
+ .subscribe(onNext: { mute() })
+ .disposed(by: self.disposeBag)
+ }
+
private func addDisplayName(displayName: String) {
let labelName = UILabel(frame: CGRect(x: marginX, y: marginY, width: menuItemWidth, height: menuItemHight))
labelName.font = labelName.font.withSize(self.textSize)
@@ -142,6 +171,34 @@
self.addSubview(self.minimizeButton!)
}
+ private func addSetModeratorButton(positionY: CGFloat) {
+ let setModeratotLabel = UILabel(frame: CGRect(x: marginX, y: positionY, width: menuItemWidth, height: menuItemHight))
+ setModeratotLabel.font = setModeratotLabel.font.withSize(self.textSize)
+ setModeratotLabel.text = moderatorLabelText
+ setModeratotLabel.sizeToFit()
+ setModeratotLabel.textAlignment = .center
+ self.setModeratorButton = UIButton(frame: setModeratotLabel.frame)
+ self.addSubview(setModeratotLabel)
+ self.addSubview(self.setModeratorButton!)
+ }
+
+ private func addMuteAudioButton(positionY: CGFloat) {
+ let muteAudioLabel = UILabel(frame: CGRect(x: marginX, y: positionY, width: menuItemWidth, height: menuItemHight))
+ muteAudioLabel.font = muteAudioLabel.font.withSize(self.textSize)
+ muteAudioLabel.text = muteLabelText
+ muteAudioLabel.sizeToFit()
+ muteAudioLabel.textAlignment = .center
+ if #available(iOS 13.0, *) {
+ muteAudioLabel.textColor = muteButtonEnabled ? UIColor.label : UIColor.quaternaryLabel
+ } else {
+ muteAudioLabel.textColor = muteButtonEnabled ? UIColor.white : UIColor.lightText
+ }
+ self.addSubview(muteAudioLabel)
+ if !muteButtonEnabled { return }
+ self.muteAudioButton = UIButton(frame: muteAudioLabel.frame)
+ self.addSubview(self.muteAudioButton!)
+ }
+
private func updateHeight() {
var numberOfLabels: CGFloat = 0
self.subviews.forEach { (childView) in
@@ -167,49 +224,4 @@
childView.frame.size.width = finalWidth
}
}
-
- private func configureWithoutHangUP() {
- let firstY: CGFloat = CGFloat(self.marginY * 2 + menuItemHight)
- let secondY: CGFloat = CGFloat(self.marginY * 3 + menuItemHight * 2)
- self.addMaximizeButton(positionY: firstY)
- self.addMinimizeButton(positionY: secondY)
- }
-
- private func configureWithoutMaximize() {
- let firstY: CGFloat = CGFloat(self.marginY * 2 + menuItemHight)
- let secondY: CGFloat = CGFloat(self.marginY * 3 + menuItemHight * 2)
- self.addHangUpButton(positionY: firstY)
- self.addMinimizeButton(positionY: secondY)
- }
-
- private func configureWithoutMinimize() {
- let firstY: CGFloat = CGFloat(self.marginY * 2 + menuItemHight)
- let secondY: CGFloat = CGFloat(self.marginY * 3 + menuItemHight * 2)
- self.addHangUpButton(positionY: firstY)
- self.addMaximizeButton(positionY: secondY)
- }
-
- private func configureWithoutMaximizeAndMinimize() {
- let firstY: CGFloat = CGFloat(self.marginY * 2 + menuItemHight)
- self.addHangUpButton(positionY: firstY)
- }
-
- private func configureWithoutHangUPAndMinimize() {
- let firstY: CGFloat = CGFloat(self.marginY * 2 + menuItemHight)
- self.addMaximizeButton(positionY: firstY)
- }
-
- private func configureWithoutHangUPAndMaximize() {
- let firstY: CGFloat = CGFloat(self.marginY * 2 + menuItemHight)
- self.addMinimizeButton(positionY: firstY)
- }
-
- private func configureWithAllItems() {
- let firstY: CGFloat = CGFloat(self.marginY * 2 + menuItemHight)
- let secondY: CGFloat = CGFloat(self.marginY * 3 + menuItemHight * 2)
- let thirdtY: CGFloat = CGFloat(self.marginY * 4 + menuItemHight * 3)
- self.addHangUpButton(positionY: firstY)
- self.addMaximizeButton(positionY: secondY)
- self.addMinimizeButton(positionY: thirdtY)
- }
}
diff --git a/Ring/Ring/Calls/views/ConferenceParticipantView.swift b/Ring/Ring/Calls/views/ConferenceParticipantView.swift
index c51b8e4..c747caa 100644
--- a/Ring/Ring/Calls/views/ConferenceParticipantView.swift
+++ b/Ring/Ring/Calls/views/ConferenceParticipantView.swift
@@ -23,7 +23,7 @@
import RxSwift
protocol ConferenceParticipantViewDelegate: class {
- func addConferenceParticipantMenu(origin: CGPoint, displayName: String, callId: String?, hangup: @escaping (() -> Void))
+ func addConferenceParticipantMenu(origin: CGPoint, displayName: String, participantId: String, callId: String?, hangup: @escaping (() -> Void))
func removeConferenceParticipantMenu()
}
@@ -63,12 +63,14 @@
@objc
func showMenu() {
guard let name = self.viewModel?.getName() else { return }
+ let participantId: String = self.viewModel?.getParticipantId() ?? ""
let callId = self.viewModel?.getCallId()
let menu = UIView(frame: CGRect(x: 50, y: 50, width: 50, height: 50))
let frame = self.convert(menu.frame, to: self.superview)
self.delegate?
.addConferenceParticipantMenu(origin: frame.origin,
displayName: name,
+ participantId: participantId,
callId: callId,
hangup: {
[weak self] in
diff --git a/Ring/Ring/Calls/views/ConferenceParticipantViewModel.swift b/Ring/Ring/Calls/views/ConferenceParticipantViewModel.swift
index c2a8a78..3b4abb5 100644
--- a/Ring/Ring/Calls/views/ConferenceParticipantViewModel.swift
+++ b/Ring/Ring/Calls/views/ConferenceParticipantViewModel.swift
@@ -22,18 +22,20 @@
import RxCocoa
class ConferenceParticipantViewModel {
- private let call: CallModel? // for conference master call is nil
+ private let callId: String? // for conference master or for moderator call is nil
+ private let participantId: String
+ private let participantUserName: String
private let callsSercive: CallsService
private let profileService: ProfilesService
private let accountService: AccountsService
- private let isMasterCall: Bool
+ private let isLocal: Bool
private let disposeBag = DisposeBag()
private lazy var contactImageData: Observable<String?> = {
guard let account = self.accountService.currentAccount else {
return Observable.just(nil)
}
- guard let call = call else {
+ if isLocal {
return self.profileService.getAccountProfile(accountId: account.id).map { profile in
if let alias = profile.alias, !alias.isEmpty {
self.displayName.accept(alias)
@@ -43,14 +45,13 @@
}
let type = account.type == AccountType.sip ? URIType.sip : URIType.ring
guard let uriString = JamiURI.init(schema: type,
- infoHach: call.participantUri,
+ infoHach: participantId,
account: account).uriString else { return Observable.just(nil) }
return profileService.getProfile(uri: uriString,
createIfNotexists: false,
accountId: account.id)
.map { profile in
if let alias = profile.alias, !alias.isEmpty {
- self.call?.displayName = alias
self.displayName.accept(alias)
}
return profile.photo
@@ -59,8 +60,8 @@
private lazy var displayName: BehaviorRelay<String> = {
var initialName = ""
- if let call = call {
- initialName = call.getDisplayName()
+ if !isLocal {
+ initialName = participantUserName
} else if let account = self.accountService.currentAccount {
initialName = account.registeredName
}
@@ -68,9 +69,8 @@
}()
lazy var removeView: Observable<Bool>? = {
- guard let call = call else { return nil }
- return self.callsSercive.currentCall(callId: call.callId )
- .startWith(call)
+ guard let callId = callId else { return nil }
+ return self.callsSercive.currentCall(callId: callId)
.map({ callModel in
return (callModel.state == .over ||
callModel.state == .failure ||
@@ -86,28 +86,34 @@
}
}()
- init(with call: CallModel?, injectionBag: InjectionBag) {
- self.call = call
+ init(with callId: String?, injectionBag: InjectionBag, isLocal: Bool, participantId: String, participantUserName: String) {
+ self.callId = callId
self.callsSercive = injectionBag.callService
self.profileService = injectionBag.profileService
self.accountService = injectionBag.accountService
- self.isMasterCall = call == nil
+ self.isLocal = isLocal
+ self.participantId = participantId
+ self.participantUserName = participantUserName
}
func getName() -> String {
- guard call != nil else {
+ if isLocal {
return L10n.Account.me
}
return self.displayName.value
}
func getCallId() -> String? {
- return self.call?.callId
+ return self.callId
+ }
+
+ func getParticipantId() -> String {
+ return participantId
}
func cancelCall() {
- guard let call = self.call else { return }
- self.callsSercive.hangUp(callId: call.callId)
+ guard let callId = self.callId else { return }
+ self.callsSercive.hangUp(callId: callId)
.subscribe(onCompleted: { })
.disposed(by: disposeBag)
}
diff --git a/Ring/Ring/Constants/Generated/Strings.swift b/Ring/Ring/Constants/Generated/Strings.swift
index 17e9c27..e38caff 100644
--- a/Ring/Ring/Constants/Generated/Strings.swift
+++ b/Ring/Ring/Constants/Generated/Strings.swift
@@ -297,12 +297,20 @@
internal static let maximize = L10n.tr("Localizable", "calls.maximize")
/// minimize
internal static let minimize = L10n.tr("Localizable", "calls.minimize")
+ /// mute audio
+ internal static let muteAudio = L10n.tr("Localizable", "calls.muteAudio")
+ /// unset moderator
+ internal static let removeModerator = L10n.tr("Localizable", "calls.removeModerator")
/// Ringing…
internal static let ringing = L10n.tr("Localizable", "calls.ringing")
/// Searching…
internal static let searching = L10n.tr("Localizable", "calls.searching")
+ /// set moderator
+ internal static let setModerator = L10n.tr("Localizable", "calls.setModerator")
/// Unknown
internal static let unknown = L10n.tr("Localizable", "calls.unknown")
+ /// unmute audio
+ internal static let unmuteAudio = L10n.tr("Localizable", "calls.unmuteAudio")
}
internal enum ContactPage {
diff --git a/Ring/Ring/Features/Me/Me/MeViewModel.swift b/Ring/Ring/Features/Me/Me/MeViewModel.swift
index c2c9f5a..7b7e19b 100644
--- a/Ring/Ring/Features/Me/Me/MeViewModel.swift
+++ b/Ring/Ring/Features/Me/Me/MeViewModel.swift
@@ -128,7 +128,7 @@
return Observable
.combineLatest(userName.startWith(""), ringId.startWith("")) { (name, ringID) in
var items: [SettingsSection.SectionRow] = [.sectionHeader(title: L10n.AccountPage.credentialsHeader),
- .jamiID(label: ringID)]
+ .jamiID(label: ringID)]
items.append(.jamiUserName(label: name))
items.append(.shareAccountDetails)
return SettingsSection
diff --git a/Ring/Ring/Models/ConferenceParticipant.swift b/Ring/Ring/Models/ConferenceParticipant.swift
index a478bb6..1b5a54f 100644
--- a/Ring/Ring/Models/ConferenceParticipant.swift
+++ b/Ring/Ring/Models/ConferenceParticipant.swift
@@ -26,6 +26,10 @@
var uri: String?
var isActive: Bool = false
var displayName: String = ""
+ var isModerator: Bool = false
+ var isAudioLocalyMuted: Bool = false
+ var isAudioMuted: Bool = false
+ var isVideoMuted: Bool = false
init (info: [String: String], onlyURIAndActive: Bool) {
self.uri = info["uri"]
@@ -47,5 +51,17 @@
if let participantHeight = info["h"] {
self.height = CGFloat((participantHeight as NSString).doubleValue)
}
+ if let videoMuted = info["videoMuted"] {
+ self.isVideoMuted = videoMuted.boolValue
+ }
+ if let audioLocalMuted = info["audioLocalMuted"] {
+ self.isAudioLocalyMuted = audioLocalMuted.boolValue
+ }
+ if let audioModeratorMuted = info["audioModeratorMuted"] {
+ self.isAudioMuted = audioModeratorMuted.boolValue
+ }
+ if let isModerator = info["isModerator"] {
+ self.isModerator = isModerator.boolValue
+ }
}
}
diff --git a/Ring/Ring/Resources/en.lproj/Localizable.strings b/Ring/Ring/Resources/en.lproj/Localizable.strings
index 6eaee9d..19d32d2 100644
--- a/Ring/Ring/Resources/en.lproj/Localizable.strings
+++ b/Ring/Ring/Resources/en.lproj/Localizable.strings
@@ -185,6 +185,10 @@
"calls.haghUp" = "hang up";
"calls.maximize" = "maximize";
"calls.minimize" = "minimize";
+"calls.setModerator" = "set moderator";
+"calls.removeModerator" = "unset moderator";
+"calls.muteAudio" = "mute audio";
+"calls.unmuteAudio" = "unmute audio";
//Account Page
"accountPage.devicesListHeader" = "Devices";
diff --git a/Ring/Ring/Services/CallsService.swift b/Ring/Ring/Services/CallsService.swift
index be5cdcf..7cd6958 100644
--- a/Ring/Ring/Services/CallsService.swift
+++ b/Ring/Ring/Services/CallsService.swift
@@ -174,7 +174,7 @@
guard let uri = participantURI,
let participantsArray = self.callsAdapter.getConferenceInfo(conferenceId) as? [[String: String]] else { return nil }
let participants = self.arrayToConferenceParticipants(participants: participantsArray, onlyURIAndActive: true)
- for participant in participants where participant.uri == uri {
+ for participant in participants where participant.uri?.filterOutHost() == uri.filterOutHost() {
return participant.isActive
}
return nil
@@ -196,19 +196,24 @@
currentConferenceEvent.accept(ConferenceUpdates(conferenceID, ConferenceState.infoUpdated.rawValue, [""]))
}
+ func isModerator(participantId: String, inConference confId: String) -> Bool {
+ let participants = self.conferenceInfos[confId]
+ let participant = participants?.filter({ confParticipant in
+ return confParticipant.uri?.filterOutHost() == participantId.filterOutHost()
+ }).first
+ return participant?.isModerator ?? false
+ }
+
func getConferenceParticipants(for conferenceId: String) -> [ConferenceParticipant]? {
return conferenceInfos[conferenceId]
}
- func setActiveParticipant(callId: String?, conferenceId: String, maximixe: Bool, jamiId: String) {
- let participantURI = callId == nil ? "" : self.call(callID: callId!)?.participantUri
+ func setActiveParticipant(conferenceId: String, maximixe: Bool, jamiId: String) {
guard let conference = self.call(callID: conferenceId),
- let uri = participantURI,
- let isActive = self.isParticipant(participantURI: uri, activeIn: conferenceId) else { return }
+ let isActive = self.isParticipant(participantURI: jamiId, activeIn: conferenceId) else { return }
let newLayout = isActive ? self.getNewLayoutForActiveParticipant(currentLayout: conference.layout, maximixe: maximixe) : .oneWithSmal
conference.layout = newLayout
- let participant = callId == nil ? jamiId : participantURI
- self.callsAdapter.setActiveParticipant(participant, forConference: conferenceId)
+ self.callsAdapter.setActiveParticipant(jamiId, forConference: conferenceId)
self.callsAdapter.setConferenceLayout(newLayout.rawValue, forConference: conferenceId)
}
@@ -667,4 +672,16 @@
self.call(callID: callID)?.participantsCallId = conferenceCalls
}
}
+
+ func muteParticipant(confId: String, participantId: String, active: Bool) {
+ self.callsAdapter.muteConferenceParticipant(participantId, forConference: confId, active: active)
+ }
+
+ func setModeratorParticipant(confId: String, participantId: String, active: Bool) {
+ self.callsAdapter.setConferenceModerator(participantId, forConference: confId, active: active)
+ }
+
+ func hangupParticipant(confId: String, participantId: String) {
+ self.callsAdapter.hangupConferenceParticipant(participantId, forConference: confId)
+ }
}
diff --git a/Ring/RingTests/ConferenceMenuItemsManagerTest.swift b/Ring/RingTests/ConferenceMenuItemsManagerTest.swift
index 302f61c..df50a1d 100644
--- a/Ring/RingTests/ConferenceMenuItemsManagerTest.swift
+++ b/Ring/RingTests/ConferenceMenuItemsManagerTest.swift
@@ -31,58 +31,58 @@
super.tearDown()
}
- func testGetMenuItemsForMasterCallNil() {
+ func testGetMenuItemsForLocalCallNil() {
let manager = ConferenceMenuItemsManager()
let conference: CallModel? = nil
let active = true
- XCTAssertTrue(manager.getMenuItemsForMasterCall(conference: conference, active: active) == MenuMode.onlyName)
+ XCTAssertTrue(manager.getMenuItemsForLocalCall(conference: conference, active: active) == [.name])
}
- func testGetMenuItemsForeMasterCallWithoutActiveCall() {
+ func testGetMenuItemsForeLocalCallWithoutActiveCall() {
let manager = ConferenceMenuItemsManager()
let conference = CallModel()
let active: Bool? = nil
- XCTAssertTrue(manager.getMenuItemsForMasterCall(conference: conference, active: active) == MenuMode.onlyName)
+ XCTAssertTrue(manager.getMenuItemsForLocalCall(conference: conference, active: active) == [.name])
}
- func testGetMenuItemsForMasterCallWithConferenceGridLayout() {
+ func testGetMenuItemsForLocalCallWithConferenceGridLayout() {
let manager = ConferenceMenuItemsManager()
let conference = CallModel()
conference.layout = .grid
let active = true
- XCTAssertTrue(manager.getMenuItemsForMasterCall(conference: conference, active: active) == MenuMode.withoutHangUPAndMinimize)
+ XCTAssertTrue(manager.getMenuItemsForLocalCall(conference: conference, active: active) == [.name, .maximize, .muteAudio])
}
- func testGetMenuItemsForActiveMasterCallWithConferenceOneWithSmalLayout() {
+ func testGetMenuItemsForActiveLocalCallWithConferenceOneWithSmalLayout() {
let manager = ConferenceMenuItemsManager()
let conference = CallModel()
conference.layout = .oneWithSmal
let active = true
- XCTAssertTrue(manager.getMenuItemsForMasterCall(conference: conference, active: active) == MenuMode.withoutHangUp)
+ XCTAssertTrue(manager.getMenuItemsForLocalCall(conference: conference, active: active) == [.name, .maximize, .minimize, .muteAudio])
}
- func testGetMenuItemsForNotActiveMasterCallWithConferenceOneWithSmalLayout() {
+ func testGetMenuItemsForNotActiveLocalCallWithConferenceOneWithSmalLayout() {
let manager = ConferenceMenuItemsManager()
let conference = CallModel()
conference.layout = .oneWithSmal
let active = false
- XCTAssertTrue(manager.getMenuItemsForMasterCall(conference: conference, active: active) == MenuMode.withoutHangUPAndMinimize)
+ XCTAssertTrue(manager.getMenuItemsForLocalCall(conference: conference, active: active) == [.name, .maximize, .muteAudio])
}
- func testGetMenuItemsForActiveMasterCallWithConferenceOneLayout() {
+ func testGetMenuItemsForActiveLocalCallWithConferenceOneLayout() {
let manager = ConferenceMenuItemsManager()
let conference = CallModel()
conference.layout = .one
let active = true
- XCTAssertTrue(manager.getMenuItemsForMasterCall(conference: conference, active: active) == MenuMode.withoutHangUPAndMaximize)
+ XCTAssertTrue(manager.getMenuItemsForLocalCall(conference: conference, active: active) == [.name, .minimize, .muteAudio])
}
- func testGetMenuItemsForNotActiveMasterCallWithConferenceOneLayout() {
+ func testGetMenuItemsForNotActiveLocalCallWithConferenceOneLayout() {
let manager = ConferenceMenuItemsManager()
let conference = CallModel()
conference.layout = .one
let active = false
- XCTAssertTrue(manager.getMenuItemsForMasterCall(conference: conference, active: active) == MenuMode.withoutHangUPAndMinimize)
+ XCTAssertTrue(manager.getMenuItemsForLocalCall(conference: conference, active: active) == [.name, .maximize, .muteAudio])
}
func testGetMenuItemsForNilConference() {
@@ -90,7 +90,9 @@
let conference: CallModel? = nil
let call: CallModel? = CallModel()
let active = true
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.onlyName)
+ let role = RoleInCall.host
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name])
}
func testGetMenuItemsForNilCall() {
@@ -98,7 +100,9 @@
let conference: CallModel? = CallModel()
let call: CallModel? = nil
let active = true
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.onlyName)
+ let role = RoleInCall.host
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name])
}
func testGetMenuItemsWithoutActiveCall() {
@@ -106,8 +110,10 @@
let conference: CallModel? = CallModel()
let call: CallModel? = CallModel()
call?.state = .current
+ let role = RoleInCall.host
let active: Bool? = nil
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.onlyName)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name])
}
func testGetMenuItemsForConnectingCall() {
@@ -115,8 +121,10 @@
let conference: CallModel? = CallModel()
let call: CallModel? = CallModel()
call?.state = .connecting
+ let role = RoleInCall.host
let active: Bool? = true
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.withoutMaximizeAndMinimize)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name, .hangup])
}
func testGetMenuItemsForRingingCall() {
@@ -124,8 +132,10 @@
let conference: CallModel? = CallModel()
let call: CallModel? = CallModel()
call?.state = .ringing
+ let role = RoleInCall.host
let active: Bool? = true
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.withoutMaximizeAndMinimize)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name, .hangup])
}
func testGetMenuItemsForHoldingCall() {
@@ -133,8 +143,10 @@
let conference: CallModel? = CallModel()
let call: CallModel? = CallModel()
call?.state = .hold
+ let role = RoleInCall.host
let active: Bool? = true
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.withoutMaximizeAndMinimize)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name, .hangup])
}
func testGetMenuItemsForCallWithConferenceGridLayout() {
@@ -143,8 +155,10 @@
conference?.layout = .grid
let call: CallModel? = CallModel()
call?.state = .current
+ let role = RoleInCall.host
let active: Bool? = true
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.withoutMinimize)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name, .maximize, .muteAudio, .setModerator, .hangup])
}
func testGetMenuItemsForActiveCallWithConferenceOneWithSmalLayout() {
@@ -152,9 +166,11 @@
let conference: CallModel? = CallModel()
conference?.layout = .oneWithSmal
let call: CallModel? = CallModel()
+ let role = RoleInCall.host
call?.state = .current
let active: Bool? = true
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.all)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name, .maximize, .minimize, .muteAudio, .setModerator, .hangup])
}
func testGetMenuItemsForNotActiveCallWithConferenceOneWithSmalLayout() {
@@ -162,9 +178,11 @@
let conference: CallModel? = CallModel()
conference?.layout = .oneWithSmal
let call: CallModel? = CallModel()
+ let role = RoleInCall.host
call?.state = .current
let active: Bool? = false
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.withoutMinimize)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name, .maximize, .muteAudio, .setModerator, .hangup])
}
func testGetMenuItemsForActiveCallWithConferenceOneLayout() {
@@ -173,8 +191,10 @@
conference?.layout = .one
let call: CallModel? = CallModel()
call?.state = .current
+ let role = RoleInCall.host
let active: Bool? = true
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.withoutMaximize)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name, .minimize, .muteAudio, .setModerator, .hangup])
}
func testGetMenuItemsForNotActiveCallWithConferenceOneLayout() {
@@ -183,7 +203,9 @@
conference?.layout = .one
let call: CallModel? = CallModel()
call?.state = .current
+ let role = RoleInCall.host
let active: Bool? = false
- XCTAssertTrue(manager.getMenuItemsFor(call: call, conference: conference, active: active) == MenuMode.withoutMinimize)
+ let isHost = false
+ XCTAssertTrue(manager.getMenuItemsFor(call: call, isHost: isHost, conference: conference, active: active, role: role) == [.name, .maximize, .muteAudio, .setModerator, .hangup])
}
}