Skip to content
Snippets Groups Projects
Commit 3581fdea authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Kateryna Kostiuk
Browse files

conference: add raise hand

GitLab: jami-project#855
Change-Id: Ie4bd27b3a1f01dc0886e48787bd499184d48eaf7
parent 89c096b8
No related branches found
No related tags found
No related merge requests found
Showing
with 138 additions and 12 deletions
......@@ -55,4 +55,5 @@
- (void)setConferenceModerator:(NSString*)participantId forConference:(NSString*)conferenceId accountId:(NSString*)accountId active:(BOOL)isActive;
- (void)muteConferenceParticipant:(NSString*)participantId forConference:(NSString*)conferenceId accountId:(NSString*)accountId active:(BOOL)isActive;
- (void)hangupConferenceParticipant:(NSString*)participantId forConference:(NSString*)conferenceId accountId:(NSString*)accountId;
-(void)setHandRaised:(NSString*)participantId forConference:(NSString*)conferenceId accountId:(NSString*)accountId state:(BOOL)state;
@end
......@@ -285,7 +285,9 @@ static id <CallsAdapterDelegate> _delegate;
- (void)hangupConferenceParticipant:(NSString*)participantId forConference:(NSString*)conferenceId accountId:(NSString*)accountId {
hangupParticipant(std::string([accountId UTF8String]), std::string([conferenceId UTF8String]), std::string([participantId UTF8String]));
}
-(void)setHandRaised:(NSString*)participantId forConference:(NSString*)conferenceId accountId:(NSString*)accountId state:(BOOL)state {
raiseParticipantHand(std::string([accountId UTF8String]), std::string([conferenceId UTF8String]), std::string([participantId UTF8String]), state);
}
#pragma mark AccountAdapterDelegate
......
......@@ -50,6 +50,7 @@ class ButtonsContainerView: UIView, NibLoadable, UIScrollViewDelegate {
var switchCameraButton: UIButton!
var switchSpeakerButton: UIButton!
var addParticipantButton: UIButton!
var raiseHandButton: UIButton!
private let disposeBag = DisposeBag()
var callViewMode: CallViewMode = .audio
......@@ -103,6 +104,7 @@ class ButtonsContainerView: UIView, NibLoadable, UIScrollViewDelegate {
switchCameraButton = configureButton(image: UIImage(asset: Asset.switchCamera))
switchSpeakerButton = configureButton(image: UIImage(asset: Asset.enableSpeakerphone))
addParticipantButton = configureButton(image: UIImage(asset: Asset.addPerson))
raiseHandButton = configureButton(image: UIImage(asset: Asset.raiseHand)?.withRenderingMode(.alwaysTemplate))
pageControl.addTarget(self, action: #selector(changePage), for: UIControl.Event.valueChanged)
}
......@@ -138,6 +140,7 @@ class ButtonsContainerView: UIView, NibLoadable, UIScrollViewDelegate {
cancelButton.isHidden = true
switchSpeakerButton.isEnabled = enable
let isSip = self.viewModel?.isSipCall ?? false
let isConference = self.viewModel?.isConference ?? false
let audioOnly = self.callViewMode == .audio
var havePages = false
if isSip {
......@@ -151,7 +154,7 @@ class ButtonsContainerView: UIView, NibLoadable, UIScrollViewDelegate {
} else if audioOnly {
let screenRect = UIScreen.main.bounds
let screenWidth: CGFloat = screenRect.size.width
let buttonsWidth: CGFloat = 6 * 50 + 30 * 5
let buttonsWidth: CGFloat = 7 * 50 + 30 * 6
havePages = screenWidth < buttonsWidth
firstPageStackView.removeSubviews()
secondPageStackView.removeSubviews()
......@@ -162,14 +165,20 @@ class ButtonsContainerView: UIView, NibLoadable, UIScrollViewDelegate {
if havePages {
secondPageStackView.addArrangedSubview(muteAudioButton)
secondPageStackView.addArrangedSubview(muteVideoButton)
if isConference {
secondPageStackView.addArrangedSubview(raiseHandButton)
}
} else {
firstPageStackView.addArrangedSubview(muteAudioButton)
firstPageStackView.addArrangedSubview(muteVideoButton)
if isConference {
firstPageStackView.addArrangedSubview(raiseHandButton)
}
}
} else {
let screenRect = UIScreen.main.bounds
let screenWidth: CGFloat = screenRect.size.width
let buttonsWidth: CGFloat = 7 * 50 + 30 * 6 // 540
let buttonsWidth: CGFloat = 8 * 50 + 30 * 7 // 540
havePages = screenWidth < buttonsWidth
firstPageStackView.removeSubviews()
secondPageStackView.removeSubviews()
......@@ -181,9 +190,15 @@ class ButtonsContainerView: UIView, NibLoadable, UIScrollViewDelegate {
if havePages {
secondPageStackView.addArrangedSubview(muteAudioButton)
secondPageStackView.addArrangedSubview(muteVideoButton)
if isConference {
secondPageStackView.addArrangedSubview(raiseHandButton)
}
} else {
firstPageStackView.addArrangedSubview(muteAudioButton)
firstPageStackView.addArrangedSubview(muteVideoButton)
if isConference {
firstPageStackView.addArrangedSubview(raiseHandButton)
}
}
}
pageControl.isHidden = !havePages
......@@ -228,6 +243,7 @@ class ButtonsContainerView: UIView, NibLoadable, UIScrollViewDelegate {
dialpadButton.borderColor = UIColor.gray
switchSpeakerButton.tintColor = UIColor.gray
switchSpeakerButton.borderColor = UIColor.gray
raiseHandButton.tintColor = UIColor.gray
}
func updateColorForVideo() {
......@@ -245,6 +261,7 @@ class ButtonsContainerView: UIView, NibLoadable, UIScrollViewDelegate {
muteVideoButton.tintColor = UIColor.white
muteVideoButton.borderColor = UIColor.white
switchCameraButton.tintColor = UIColor.white
raiseHandButton.tintColor = UIColor.white
}
@objc
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
......@@ -127,7 +127,7 @@
</userDefinedRuntimeAttributes>
</button>
<pageControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" defersCurrentPageDisplay="YES" numberOfPages="2" translatesAutoresizingMaskIntoConstraints="NO" id="p1T-pp-N9T">
<rect key="frame" x="198.5" y="172.5" width="103.5" height="27.5"/>
<rect key="frame" x="199" y="172.5" width="102.5" height="27.5"/>
<color key="pageIndicatorTintColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</pageControl>
</subviews>
......
......@@ -148,7 +148,8 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased, Con
guard let self = self, updated else { return }
self.updateconferenceLayoutSize()
let participants = self.viewModel.getConferenceParticipants()
self.conferenceLayout.setParticipants(participants: participants)
self.conferenceLayout.setParticipants(participants: participants, isCurrentModerator: self.viewModel.isCurrentModerator() || self.viewModel.isHostCall )
guard let unwrapParticipants = participants, self.viewModel.isCurrentModerator(), !self.viewModel.isHostCall else { return }
self.conferenceCalls.arrangedSubviews.forEach({ (view) in
view.removeFromSuperview()
......@@ -295,6 +296,11 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased, Con
self.viewModel.showContactPickerVC()
})
.disposed(by: self.disposeBag)
self.buttonsContainer.raiseHandButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.viewModel.togleRaiseHand()
})
.disposed(by: self.disposeBag)
// Data bindings
self.viewModel.videoButtonState
.observe(on: MainScheduler.instance)
......@@ -906,6 +912,10 @@ extension CallViewController: ConferenceParticipantViewDelegate {
self?.removeConferenceParticipantMenu()
self?.viewModel.muteParticipant(participantId: participantId, active: !isAudioMuted)
}
menuView.addLowerHandAction { [weak self] in
self?.removeConferenceParticipantMenu()
self?.viewModel.lowerHandFor(participantId: participantId)
}
let point = conferenceCallsScrolView.convert(menuView.frame.origin, to: self.view)
let offset = self.view.frame.size.width - point.x - menuView.frame.size.width
......
......@@ -753,11 +753,12 @@ extension CallViewModel {
return self.callService.isModerator(participantId: account.jamiId, inConference: self.rendererId)
}
func getItemsForConferenceMenu(participantId: String, callId: String) -> [MenuItem] {
guard let participant = self.getConferencePartisipant(participantId: participantId) else { return [MenuItem]() }
let conference = self.callService.call(callID: self.rendererId)
let active = self.callService.isParticipant(participantURI: participantId, activeIn: self.rendererId, accountId: conference?.accountId ?? "")
// menu for local call
if self.isLocalCall(participantId: participantId) || participantId.isEmpty {
return menuItemsManager.getMenuItemsForLocalCall(conference: conference, active: active)
return menuItemsManager.getMenuItemsForLocalCall(conference: conference, active: active, isHandRised: participant.isHandRaised)
}
let isModerator = self.isCurrentModerator()
var role = RoleInCall.regular
......@@ -768,6 +769,19 @@ extension CallViewModel {
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)
return menuItemsManager.getMenuItemsFor(call: participantCall, isHost: callIsHost, conference: conference, active: active, role: role, isHandRised: participant.isHandRaised)
}
func lowerHandFor(participantId: String) {
self.callService.setRaiseHand(confId: self.rendererId, participantId: participantId.filterOutHost(), state: false)
}
func togleRaiseHand() {
guard let account = self.accountService.currentAccount else { return }
guard let partisipant = self.getConferenceParticipants()?.filter({ participant in
participant.displayName == L10n.Account.me
}).first else { return }
let state = !partisipant.isHandRaised
self.callService.setRaiseHand(confId: self.rendererId, participantId: account.jamiId, state: state)
}
}
......@@ -27,9 +27,11 @@ class ConferenceLayout: UIView {
private var participants: [ConferenceParticipant] = [ConferenceParticipant]()
private let textSize: CGFloat = 16
private let labelHight: CGFloat = 30
private let controlSize: CGFloat = 25
private let margin: CGFloat = 15
private let minWidth: CGFloat = 50
private let conferenceLayoutHelper: ConferenceLayoutHelper = ConferenceLayoutHelper()
private var isCurrentModerator: Bool = false
private let disposeBag = DisposeBag()
func setUpWithVideoSize(size: CGSize) {
......@@ -46,12 +48,13 @@ class ConferenceLayout: UIView {
self.updateViewSize()
}
func setParticipants(participants: [ConferenceParticipant]?) {
func setParticipants(participants: [ConferenceParticipant]?, isCurrentModerator: Bool) {
if let participants = participants {
self.participants = participants
} else {
self.participants.removeAll()
}
self.isCurrentModerator = isCurrentModerator
self.layoutParticipantsViews()
}
......@@ -81,6 +84,7 @@ class ConferenceLayout: UIView {
let origX: CGFloat = participant.originX * widthRatio
let origY: CGFloat = participant.originY * heightRatio
let width: CGFloat = participant.width * widthRatio
let height: CGFloat = participant.height * heightRatio
// do not add labels when view width is too small
if width < minWidth { return }
let background = UIView(frame: CGRect(x: origX, y: origY, width: width, height: self.labelHight))
......@@ -95,5 +99,16 @@ class ConferenceLayout: UIView {
label.font = label.font.withSize(self.textSize)
self.addSubview(background)
self.addSubview(label)
if !participant.isHandRaised || !self.isCurrentModerator {
return
}
let raisedHandImage = UIButton(frame: CGRect(x: origX + width - self.controlSize, y: origY + height - self.controlSize, width: self.controlSize, height: self.controlSize))
let image = UIImage(asset: Asset.raiseHand)?.withRenderingMode(.alwaysTemplate)
raisedHandImage.setImage(image, for: .normal)
raisedHandImage.tintColor = UIColor.white
raisedHandImage.backgroundColor = UIColor.conferenceRaiseHand
raisedHandImage.layer.cornerRadius = 4
raisedHandImage.layer.maskedCorners = [.layerMinXMinYCorner]
self.addSubview(raisedHandImage)
}
}
......@@ -23,7 +23,7 @@ enum RoleInCall {
case regular
}
class ConferenceMenuItemsManager {
func getMenuItemsForLocalCall(conference: CallModel?, active: Bool?) -> [MenuItem] {
func getMenuItemsForLocalCall(conference: CallModel?, active: Bool?, isHandRised: Bool) -> [MenuItem] {
var menu = [MenuItem]()
menu.append(.name)
guard let conference = conference else {
......@@ -32,6 +32,9 @@ class ConferenceMenuItemsManager {
guard let active = active else {
return menu
}
if isHandRised {
menu.append(.lowerHand)
}
switch conference.layout {
case .grid:
menu.append(.maximize)
......@@ -51,7 +54,8 @@ class ConferenceMenuItemsManager {
return menu
}
func getMenuItemsFor(call: CallModel?, isHost: Bool, conference: CallModel?, active: Bool?, role: RoleInCall) -> [MenuItem] {
// swiftlint:disable cyclomatic_complexity
func getMenuItemsFor(call: CallModel?, isHost: Bool, conference: CallModel?, active: Bool?, role: RoleInCall, isHandRised: Bool) -> [MenuItem] {
var menu = [MenuItem]()
menu.append(.name)
guard let conference = conference,
......@@ -65,6 +69,9 @@ class ConferenceMenuItemsManager {
guard let active = active else {
return menu
}
if isHandRised {
menu.append(.lowerHand)
}
switch conference.layout {
case .grid:
menu.append(.maximize)
......
......@@ -28,6 +28,7 @@ enum MenuItem {
case maximize
case setModerator
case muteAudio
case lowerHand
}
class ConferenceActionMenu: UIView {
......@@ -42,6 +43,7 @@ class ConferenceActionMenu: UIView {
private var minimizeButton: UIButton?
private var setModeratorButton: UIButton?
private var muteAudioButton: UIButton?
private var lowerHandButton: UIButton?
private let disposeBag = DisposeBag()
private var muteLabelText: String = ""
private var moderatorLabelText: String = ""
......@@ -81,6 +83,8 @@ class ConferenceActionMenu: UIView {
self.addHangUpButton(positionY: positionY)
case .name:
break
case .lowerHand:
self.addLowerHandButton(positionY: positionY)
}
}
......@@ -91,6 +95,13 @@ class ConferenceActionMenu: UIView {
.disposed(by: self.disposeBag)
}
func addLowerHandAction(lowerHand: @escaping (() -> Void)) {
guard let button = lowerHandButton else { return }
button.rx.tap
.subscribe(onNext: { lowerHand() })
.disposed(by: self.disposeBag)
}
func addMaximizeAction(maximize: @escaping (() -> Void)) {
guard let button = maximizeButton else { return }
button.rx.tap
......@@ -149,6 +160,17 @@ class ConferenceActionMenu: UIView {
self.addSubview(self.hangUpButton!)
}
private func addLowerHandButton(positionY: CGFloat) {
let lowerHandLabel = UILabel(frame: CGRect(x: marginX, y: positionY, width: menuItemWidth, height: menuItemHight))
lowerHandLabel.font = lowerHandLabel.font.withSize(self.textSize)
lowerHandLabel.text = L10n.Calls.lowerHand
lowerHandLabel.sizeToFit()
lowerHandLabel.textAlignment = .center
self.lowerHandButton = UIButton(frame: lowerHandLabel.frame)
self.addSubview(lowerHandLabel)
self.addSubview(self.lowerHandButton!)
}
private func addMaximizeButton(positionY: CGFloat) {
let maximizeLabel = UILabel(frame: CGRect(x: marginX, y: positionY, width: menuItemWidth, height: menuItemHight))
maximizeLabel.font = maximizeLabel.font.withSize(self.textSize)
......
......@@ -66,6 +66,7 @@ internal enum Asset {
internal static let phoneBook = ImageAsset(name: "phone_book")
internal static let qrCode = ImageAsset(name: "qr_code")
internal static let qrCodeScan = ImageAsset(name: "qr_code_scan")
internal static let raiseHand = ImageAsset(name: "raise_hand")
internal static let revokeDevice = ImageAsset(name: "revoke_device")
internal static let ringLogo = ImageAsset(name: "ring_logo")
internal static let rowSelected = ColorAsset(name: "row_selected")
......
......@@ -293,6 +293,8 @@ internal enum L10n {
internal static let haghUp = L10n.tr("Localizable", "calls.haghUp")
/// wants to talk to you
internal static let incomingCallInfo = L10n.tr("Localizable", "calls.incomingCallInfo")
/// lower hand
internal static let lowerHand = L10n.tr("Localizable", "calls.lowerHand")
/// maximize
internal static let maximize = L10n.tr("Localizable", "calls.maximize")
/// minimize
......
......@@ -74,6 +74,7 @@ extension UIColor {
}
static let jamiMain = UIColor(hex: 0x3F6DA7, alpha: 1.0)
static let conferenceRaiseHand = UIColor(red: 0, green: 184, blue: 255, alpha: 1.0)
static let jamiSecondary = UIColor(hex: 0x1F4971, alpha: 1.0)
static let jamiButtonLight = UIColor(hex: 0x285F97, alpha: 1.0)
static let jamiButtonDark = UIColor(hex: 0x0F2643, alpha: 1.0)
......
......@@ -30,6 +30,7 @@ class ConferenceParticipant {
var isAudioLocalyMuted: Bool = false
var isAudioMuted: Bool = false
var isVideoMuted: Bool = false
var isHandRaised: Bool = false
init (info: [String: String], onlyURIAndActive: Bool) {
self.uri = info["uri"]
......@@ -63,5 +64,8 @@ class ConferenceParticipant {
if let isModerator = info["isModerator"] {
self.isModerator = isModerator.boolValue
}
if let isHandRaised = info["handRaised"] {
self.isHandRaised = isHandRaised.boolValue
}
}
}
{
"images" : [
{
"filename" : "hand_black.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "hand_black_50.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "hand_black_75.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Ring/Ring/Resources/Images.xcassets/raise_hand.imageset/hand_black.png

2.1 KiB

Ring/Ring/Resources/Images.xcassets/raise_hand.imageset/hand_black_50.png

2.45 KiB

Ring/Ring/Resources/Images.xcassets/raise_hand.imageset/hand_black_75.png

2.84 KiB

......@@ -199,6 +199,7 @@
"calls.removeModerator" = "unset moderator";
"calls.muteAudio" = "mute audio";
"calls.unmuteAudio" = "unmute audio";
"calls.lowerHand" = "lower hand";
//Account Page
"accountPage.devicesListHeader" = "Devices";
......
......@@ -819,4 +819,10 @@ class CallsService: CallsAdapterDelegate {
guard let conference = call(callID: confId) else { return }
self.callsAdapter.hangupConferenceParticipant(participantId, forConference: confId, accountId: conference.accountId)
}
func setRaiseHand(confId: String, participantId: String, state: Bool) {
guard let conference = call(callID: confId) else { return }
self.callsAdapter.setHandRaised(participantId, forConference: confId, accountId: conference.accountId, state: state)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment