diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 8491502b2..0265598c6 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -897,6 +897,8 @@ EC9E9B102575B33E0007C0A0 /* CallServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9E9B0F2575B33E0007C0A0 /* CallServiceDelegate.swift */; }; EC9E9B112575B3960007C0A0 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DB4F0A223131600065DBFA /* String.swift */; }; EC9E9B122575B3A70007C0A0 /* Character.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DB4F05223015080065DBFA /* Character.swift */; }; + EC9E9B1425767C460007C0A0 /* CallBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9E9B1325767C460007C0A0 /* CallBar.swift */; }; + EC9E9B1725767D4B0007C0A0 /* CallBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC9E9B1625767D4B0007C0A0 /* CallBar.xib */; }; ECAE7AE524EC0E01002FA813 /* TableViewSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECAE7AE424EC0E01002FA813 /* TableViewSections.swift */; }; ECAE7AE724EC15F7002FA813 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECAE7AE624EC15F7002FA813 /* Section.swift */; }; ECAE7AE924EC1888002FA813 /* Row.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECAE7AE824EC1888002FA813 /* Row.swift */; }; @@ -2132,6 +2134,8 @@ EC9E9B0B2575AF9B0007C0A0 /* CallBarDismissOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallBarDismissOperation.swift; sourceTree = ""; }; EC9E9B0D2575AFB80007C0A0 /* AsyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = ""; }; EC9E9B0F2575B33E0007C0A0 /* CallServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallServiceDelegate.swift; sourceTree = ""; }; + EC9E9B1325767C460007C0A0 /* CallBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallBar.swift; sourceTree = ""; }; + EC9E9B1625767D4B0007C0A0 /* CallBar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CallBar.xib; sourceTree = ""; }; ECAE7AE424EC0E01002FA813 /* TableViewSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSections.swift; sourceTree = ""; }; ECAE7AE624EC15F7002FA813 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; ECAE7AE824EC1888002FA813 /* Row.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Row.swift; sourceTree = ""; }; @@ -4008,6 +4012,7 @@ B1B5577B20EE84BE00210D55 /* CircleButton.m */, B1B5577A20EE84BD00210D55 /* IncomingCallView.h */, B1B5577C20EE84BF00210D55 /* IncomingCallView.m */, + EC9E9B1525767D3A0007C0A0 /* CallBar */, ); path = Views; sourceTree = ""; @@ -5185,6 +5190,15 @@ path = Operations; sourceTree = ""; }; + EC9E9B1525767D3A0007C0A0 /* CallBar */ = { + isa = PBXGroup; + children = ( + EC9E9B1325767C460007C0A0 /* CallBar.swift */, + EC9E9B1625767D4B0007C0A0 /* CallBar.xib */, + ); + path = CallBar; + sourceTree = ""; + }; ECAE7AEA24EC223D002FA813 /* Models */ = { isa = PBXGroup; children = ( @@ -5959,6 +5973,7 @@ B1C562E5228C7C8D0037F12A /* RoomContextualMenuViewController.storyboard in Resources */, 3232ABA2225730E100AD6A5C /* DeviceVerificationStartViewController.storyboard in Resources */, 3284A35120A07C210044F922 /* postMessageAPI.js in Resources */, + EC9E9B1725767D4B0007C0A0 /* CallBar.xib in Resources */, EC1CA8D424D8103400DE9EBF /* App-Common.xcconfig in Resources */, B1B557A220EF58AD00210D55 /* ContactTableViewCell.xib in Resources */, ECFBD5CE250A7AAF00DD5F5A /* DirectoryNetworkTableHeaderFooterView.xib in Resources */, @@ -6415,6 +6430,7 @@ B1B5573E20EE6C4D00210D55 /* RiotNavigationController.m in Sources */, B1B5593B20EF7BAC00210D55 /* TableViewCellWithCheckBoxAndLabel.m in Sources */, B1B5581A20EF625800210D55 /* ExpandedRoomTitleView.m in Sources */, + EC9E9B1425767C460007C0A0 /* CallBar.swift in Sources */, B1107EC82200B0720038014B /* KeyBackupRecoverSuccessViewController.swift in Sources */, B1BEE74C23E093260003A4CB /* UserVerificationSessionStatusViewController.swift in Sources */, EC711BAB24A63B58008F830C /* SecureBackupSetupIntroViewController.swift in Sources */, diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 3f8352f7e..dc1414171 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -64,6 +64,13 @@ "more" = "More"; "less" = "Less"; +// Call Bar +"callbar_only_single_active" = "Active call (%@)"; +"callbar_active_and_single_paused" = "1 active call (%@) · 1 paused call"; +"callbar_active_and_multiple_paused" = "1 active call (%@) · %@ paused calls"; +"callbar_only_single_paused" = "Paused call"; +"callbar_only_multiple_paused" = "%@ paused calls"; + // Accessibility "accessibility_checkbox_label" = "checkbox"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 0e4c26ed0..843107a36 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -466,6 +466,26 @@ internal enum VectorL10n { internal static func callNoStunServerErrorUseFallbackButton(_ p1: String) -> String { return VectorL10n.tr("Vector", "call_no_stun_server_error_use_fallback_button", p1) } + /// 1 active call (%@) · %@ paused calls + internal static func callbarActiveAndMultiplePaused(_ p1: String, _ p2: String) -> String { + return VectorL10n.tr("Vector", "callbar_active_and_multiple_paused", p1, p2) + } + /// 1 active call (%@) · 1 paused call + internal static func callbarActiveAndSinglePaused(_ p1: String) -> String { + return VectorL10n.tr("Vector", "callbar_active_and_single_paused", p1) + } + /// %@ paused calls + internal static func callbarOnlyMultiplePaused(_ p1: String) -> String { + return VectorL10n.tr("Vector", "callbar_only_multiple_paused", p1) + } + /// Active call (%@) + internal static func callbarOnlySingleActive(_ p1: String) -> String { + return VectorL10n.tr("Vector", "callbar_only_single_active", p1) + } + /// Paused call + internal static var callbarOnlySinglePaused: String { + return VectorL10n.tr("Vector", "callbar_only_single_paused") + } /// Camera internal static var camera: String { return VectorL10n.tr("Vector", "camera") diff --git a/Riot/Managers/Call/CallService.swift b/Riot/Managers/Call/CallService.swift index d98f085dd..b46f63069 100644 --- a/Riot/Managers/Call/CallService.swift +++ b/Riot/Managers/Call/CallService.swift @@ -18,6 +18,7 @@ import Foundation import MatrixKit @objcMembers +/// Service to manage call screens and call bar UI management. class CallService: NSObject { private var callVCs: [String: CallViewController] = [:] @@ -26,11 +27,23 @@ class CallService: NSObject { private weak var inBarCallVC: CallViewController? private var uiOperationQueue: OperationQueue = .main private var isStarted: Bool = false + private var callTimer: Timer! private var isCallKitEnabled: Bool { MXCallKitAdapter.callKitAvailable() && MXKAppSettings.standard()?.isCallKitEnabled == true } + private var activeCallVC: CallViewController? { + return callVCs.values.filter { (callVC) -> Bool in + guard let call = callVC.mxCall else { + return false + } + return !call.isOnHold + }.first + } + + // MARK: - Public + /// Maximum number of concurrent calls allowed. let maximumNumberOfConcurrentCalls: UInt = 2 @@ -40,22 +53,77 @@ class CallService: NSObject { /// Start the service func start() { addCallObservers() + startCallTimer() } /// Stop the service func stop() { removeCallObservers() + stopCallTimer() } /// Method to be called when the call status bar is tapped. /// - Returns: If the user interaction handled or not func callStatusBarButtonTapped() -> Bool { - guard let inBarCallVC = inBarCallVC else { + if let callVC = inBarCallVC ?? activeCallVC { + dismissCallBar(for: callVC) + presentCallVC(callVC) + return true + } + return false + } + + // MARK: - Private + + private func shouldHandleCall(_ call: MXCall) -> Bool { + if let delegate = delegate, !delegate.callService(self, shouldHandleNewCall: call) { return false } - dismissCallBar(for: inBarCallVC) - presentCallVC(inBarCallVC) - return true + return callVCs.count < maximumNumberOfConcurrentCalls + } + + private func endCall(withCallId callId: String) { + guard let callVC = callVCs[callId] else { + return + } + + let completion = { [weak self] in + self?.callVCs.removeValue(forKey: callId) + callVC.destroy() + self?.callBackgroundTasks[callId]?.stop() + self?.callBackgroundTasks.removeValue(forKey: callId) + } + + if inBarCallVC == callVC { + // this call currently in the status bar, + // first present it and then dismiss it + dismissCallBar(for: callVC) + presentCallVC(callVC) + } + dismissCallVC(callVC, completion: completion) + } + + // MARK: - Timer + + private func startCallTimer() { + callTimer = Timer.scheduledTimer(timeInterval: 1.0, + target: self, + selector: #selector(callTimerFired(_:)), + userInfo: nil, + repeats: true) + } + + private func stopCallTimer() { + callTimer.invalidate() + callTimer = nil + } + + @objc private func callTimerFired(_ timer: Timer) { + guard let inBarCallVC = inBarCallVC else { + return + } + + presentCallBar(for: inBarCallVC, isUpdateOnly: true) } // MARK: - Observers @@ -136,44 +204,26 @@ class CallService: NSObject { case .createAnswer: if call.isIncoming, isCallKitEnabled, let callVC = callVCs[call.callId] { presentCallVC(callVC) + return } NSLog("[CallService] callStateChanged: call created answer: \(call.callId)") + case .connected: + callTimer.fire() + case .onHold: + callTimer.fire() + NSLog("[CallService] callStateChanged: call holded: \(call.callId)") + case .remotelyOnHold: + callTimer.fire() + NSLog("[CallService] callStateChanged: call remotely holded: \(call.callId)") case .ended: NSLog("[CallService] callStateChanged: call ended: \(call.callId)") endCall(withCallId: call.callId) + return default: break } } - private func shouldHandleCall(_ call: MXCall) -> Bool { - if let delegate = delegate, !delegate.callService(self, shouldHandleNewCall: call) { - return false - } - return callVCs.count < maximumNumberOfConcurrentCalls - } - - private func endCall(withCallId callId: String) { - guard let callVC = callVCs[callId] else { - return - } - - let completion = { [weak self] in - self?.callVCs.removeValue(forKey: callId) - callVC.destroy() - self?.callBackgroundTasks[callId]?.stop() - self?.callBackgroundTasks.removeValue(forKey: callId) - } - - if inBarCallVC == callVC { - // this call currently in the status bar, - // first present it and then dismiss it - dismissCallBar(for: callVC) - presentCallVC(callVC) - } - dismissCallVC(callVC, completion: completion) - } - // MARK: - Call Screens private func presentCallVC(_ callVC: CallViewController, completion: (() -> Void)? = nil) { @@ -204,11 +254,24 @@ class CallService: NSObject { // MARK: - Call Bar - private func presentCallBar(for callVC: CallViewController, completion: (() -> Void)? = nil) { - NSLog("[CallService] presentCallBar: call: \(String(describing: callVC.mxCall?.callId))") + private func presentCallBar(for callVC: CallViewController?, isUpdateOnly: Bool = false, completion: (() -> Void)? = nil) { + NSLog("[CallService] presentCallBar: call: \(String(describing: callVC?.mxCall?.callId))") + + let activeCallVC = self.activeCallVC - let operation = CallBarPresentOperation(service: self, callVC: callVC) { [weak self] in - self?.inBarCallVC = callVC + let numberOfPausedCalls = UInt(callVCs.values.filter { (callVC) -> Bool in + guard let call = callVC.mxCall else { + return false + } + return call.isOnHold + }.count) + + let operation = CallBarPresentOperation(service: self, activeCallVC: activeCallVC, numberOfPausedCalls: numberOfPausedCalls) { [weak self] in + // active calls are more prior to paused ones. + // So, if user taps the bar when we have one active and one paused calls, we navigate to the active one. + if !isUpdateOnly { + self?.inBarCallVC = activeCallVC ?? callVC + } completion?() } uiOperationQueue.addOperation(operation) @@ -217,17 +280,20 @@ class CallService: NSObject { private func dismissCallBar(for callVC: CallViewController, completion: (() -> Void)? = nil) { NSLog("[CallService] dismissCallBar: call: \(String(describing: callVC.mxCall?.callId))") - let operation = CallBarDismissOperation(service: self, callVC: callVC) { [weak self] in + let operation = CallBarDismissOperation(service: self) { [weak self] in if callVC == self?.inBarCallVC { self?.inBarCallVC = nil } completion?() } + uiOperationQueue.addOperation(operation) } } +// MARK: - MXKCallViewControllerDelegate + extension CallService: MXKCallViewControllerDelegate { func dismiss(_ callViewController: MXKCallViewController!, completion: (() -> Void)!) { diff --git a/Riot/Managers/Call/CallServiceDelegate.swift b/Riot/Managers/Call/CallServiceDelegate.swift index 9b93080bf..2f6f58907 100644 --- a/Riot/Managers/Call/CallServiceDelegate.swift +++ b/Riot/Managers/Call/CallServiceDelegate.swift @@ -32,9 +32,13 @@ protocol CallServiceDelegate: class { // Call Bar func callService(_ service: CallService, - presentCallBarFor viewController: CallViewController, + presentCallBarFor activeCallViewController: CallViewController?, + numberOfPausedCalls: UInt, completion:(() -> Void)?) func callService(_ service: CallService, - dismissCallBarFor viewController: CallViewController, + updateCallBarFor activeCallViewController: CallViewController?, + numberOfPausedCalls: UInt, completion:(() -> Void)?) + func callService(_ service: CallService, + dismissCallBar completion:(() -> Void)?) } diff --git a/Riot/Managers/Call/Operations/CallBarDismissOperation.swift b/Riot/Managers/Call/Operations/CallBarDismissOperation.swift index 102beb244..f7e1e2c81 100644 --- a/Riot/Managers/Call/Operations/CallBarDismissOperation.swift +++ b/Riot/Managers/Call/Operations/CallBarDismissOperation.swift @@ -19,19 +19,16 @@ import Foundation class CallBarDismissOperation: AsyncOperation { private var service: CallService - private var callVC: CallViewController private var completion: (() -> Void)? init(service: CallService, - callVC: CallViewController, completion: (() -> Void)? = nil) { self.service = service - self.callVC = callVC self.completion = completion } override func main() { - service.delegate?.callService(service, dismissCallBarFor: callVC, completion: { + service.delegate?.callService(service, dismissCallBar: { self.finish() self.completion?() }) diff --git a/Riot/Managers/Call/Operations/CallBarPresentOperation.swift b/Riot/Managers/Call/Operations/CallBarPresentOperation.swift index b7d685c44..032613c4a 100644 --- a/Riot/Managers/Call/Operations/CallBarPresentOperation.swift +++ b/Riot/Managers/Call/Operations/CallBarPresentOperation.swift @@ -19,19 +19,22 @@ import Foundation class CallBarPresentOperation: AsyncOperation { private var service: CallService - private var callVC: CallViewController + private var activeCallVC: CallViewController? + private var numberOfPausedCalls: UInt private var completion: (() -> Void)? init(service: CallService, - callVC: CallViewController, + activeCallVC: CallViewController?, + numberOfPausedCalls: UInt, completion: (() -> Void)? = nil) { self.service = service - self.callVC = callVC + self.activeCallVC = activeCallVC + self.numberOfPausedCalls = numberOfPausedCalls self.completion = completion } override func main() { - service.delegate?.callService(service, presentCallBarFor: callVC, completion: { + service.delegate?.callService(service, presentCallBarFor: activeCallVC, numberOfPausedCalls: numberOfPausedCalls, completion: { self.finish() self.completion?() }) diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h index 41afbabc2..d69b9148a 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.h +++ b/Riot/Modules/Application/LegacyAppDelegate.h @@ -29,6 +29,7 @@ @protocol Configurable; @protocol LegacyAppDelegateDelegate; +@class CallBar; #pragma mark - Notifications /** @@ -238,7 +239,7 @@ JitsiViewControllerDelegate> Call status window displayed when user goes back to app during a call. */ @property (nonatomic, readonly) UIWindow* callStatusBarWindow; -@property (nonatomic, readonly) UIButton* callStatusBarButton; +@property (nonatomic, readonly) CallBar* callBar; #pragma mark - App version management diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 625931ed6..a902d9f0a 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -90,7 +90,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUniversalLinkDidChangeNotification"; -@interface LegacyAppDelegate () +@interface LegacyAppDelegate () { /** Reachability observer @@ -3043,7 +3043,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni MXRoom *room = [_jitsiViewController.widget.mxSession roomWithRoomId:_jitsiViewController.widget.roomId]; NSString *btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"active_call_details", @"Vector", nil), room.summary.displayname]; - [self addCallStatusBar:btnTitle]; + [self updateCallStatusBar:btnTitle]; if (completion) { @@ -3186,8 +3186,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni return result; } -- (void)addCallStatusBar:(NSString*)buttonTitle +- (void)updateCallStatusBar:(NSString*)title { + if (_callBar) + { + _callBar.title = title; + return; + } // Add a call status bar CGSize topBarSize = CGSizeMake([[UIScreen mainScreen] bounds].size.width, [self calculateCallStatusBarHeight]); @@ -3195,42 +3200,20 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni _callStatusBarWindow.windowLevel = UIWindowLevelStatusBar; // Create statusBarButton - _callStatusBarButton = [UIButton buttonWithType:UIButtonTypeCustom]; - _callStatusBarButton.frame = CGRectMake(0, 0, topBarSize.width, topBarSize.height); - - [_callStatusBarButton setTitle:buttonTitle forState:UIControlStateNormal]; - [_callStatusBarButton setTitle:buttonTitle forState:UIControlStateHighlighted]; - - _callStatusBarButton.titleLabel.textColor = ThemeService.shared.theme.backgroundColor; - - _callStatusBarButton.titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium]; - - [_callStatusBarButton setBackgroundColor:ThemeService.shared.theme.tintColor]; - [_callStatusBarButton addTarget:self action:@selector(onCallStatusBarButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + _callBar = [CallBar instantiate]; + _callBar.frame = CGRectMake(0, 0, topBarSize.width, topBarSize.height); + _callBar.title = title; + _callBar.backgroundColor = ThemeService.shared.theme.tintColor; + _callBar.delegate = self; // Place button into the new window - [_callStatusBarButton setTranslatesAutoresizingMaskIntoConstraints:NO]; - [_callStatusBarWindow addSubview:_callStatusBarButton]; - - // Force callStatusBarButton to fill the window (to handle auto-layout in case of screen rotation) - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_callStatusBarButton - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:_callStatusBarWindow - attribute:NSLayoutAttributeWidth - multiplier:1.0 - constant:0]; - - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:_callStatusBarButton - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:_callStatusBarWindow - attribute:NSLayoutAttributeHeight - multiplier:1.0 - constant:0]; - - [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint]]; + [_callBar setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_callStatusBarWindow addSubview:_callBar]; + // Force callBar to fill the window (to handle auto-layout in case of screen rotation) + [_callBar.widthAnchor constraintEqualToAnchor:_callStatusBarWindow.widthAnchor].active = YES; + [_callBar.heightAnchor constraintEqualToAnchor:_callStatusBarWindow.heightAnchor].active = YES; + _callStatusBarWindow.hidden = NO; [self statusBarDidChangeFrame]; @@ -3251,26 +3234,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Hide & destroy it _callStatusBarWindow.hidden = YES; - [_callStatusBarButton removeFromSuperview]; - _callStatusBarButton = nil; + [_callBar removeFromSuperview]; + _callBar = nil; _callStatusBarWindow = nil; [self statusBarDidChangeFrame]; } } -- (void)onCallStatusBarButtonPressed -{ - if ([_callService callStatusBarButtonTapped]) - { - return; - } - else if (_jitsiViewController) - { - [self presentJitsiViewController:nil]; - } -} - - (void)statusBarDidChangeFrame { UIApplication *app = [UIApplication sharedApplication]; @@ -3307,20 +3278,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } } - UIEdgeInsets callBarButtonContentEdgeInsets = UIEdgeInsetsZero; - - if (@available(iOS 11.0, *)) - { - callBarButtonContentEdgeInsets = UIApplication.sharedApplication.keyWindow.safeAreaInsets; - // should override top inset - callBarButtonContentEdgeInsets.top = callStatusBarHeight - CALL_STATUS_BAR_HEIGHT; - // should ignore bottom inset - callBarButtonContentEdgeInsets.bottom = 0.0; - // should keep left, and right insets as original - } - - _callStatusBarButton.contentEdgeInsets = callBarButtonContentEdgeInsets; - // Apply the vertical offset due to call status bar rootControllerFrame.origin.y = callStatusBarHeight; rootControllerFrame.size.height -= callStatusBarHeight; @@ -4490,10 +4447,43 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } } -- (void)callService:(CallService *)service presentCallBarFor:(CallViewController *)viewController completion:(void (^)(void))completion +- (void)callService:(CallService *)service presentCallBarFor:(CallViewController *)activeCallViewController numberOfPausedCalls:(NSUInteger)numberOfPausedCalls completion:(void (^)(void))completion { - NSString *btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"active_call_details", @"Vector", nil), viewController.callerNameLabel.text]; - [self addCallStatusBar:btnTitle]; + NSString *btnTitle; + + if (activeCallViewController) + { + // TODO: Add timer for active call duration + if (numberOfPausedCalls == 0) + { + // only one active + btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_only_single_active", @"Vector", nil), activeCallViewController.callStatusLabel.text]; + } + else if (numberOfPausedCalls == 1) + { + // one active and one paused + btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_active_and_single_paused", @"Vector", nil), activeCallViewController.callStatusLabel.text]; + } + else + { + // one active and multiple paused + btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_active_and_multiple_paused", @"Vector", nil), activeCallViewController.callStatusLabel.text, @(numberOfPausedCalls)]; + } + } + else + { + // no active calls + if (numberOfPausedCalls == 1) + { + btnTitle = NSLocalizedStringFromTable(@"callbar_only_single_paused", @"Vector", nil); + } + else + { + btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_only_multiple_paused", @"Vector", nil), @(numberOfPausedCalls)]; + } + } + + [self updateCallStatusBar:btnTitle]; if (completion) { @@ -4501,7 +4491,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } } -- (void)callService:(CallService *)service dismissCallBarFor:(CallViewController *)viewController completion:(void (^)(void))completion +- (void)callService:(CallService *)service dismissCallBar:(void (^)(void))completion { [self removeCallStatusBar]; @@ -4511,4 +4501,18 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } } +#pragma mark - CallBarDelegate + +- (void)callBarDidTapReturnButton:(CallBar *)callBar +{ + if ([_callService callStatusBarButtonTapped]) + { + return; + } + else if (_jitsiViewController) + { + [self presentJitsiViewController:nil]; + } +} + @end diff --git a/Riot/Modules/Call/Views/CallBar/CallBar.swift b/Riot/Modules/Call/Views/CallBar/CallBar.swift new file mode 100644 index 000000000..e65f08afd --- /dev/null +++ b/Riot/Modules/Call/Views/CallBar/CallBar.swift @@ -0,0 +1,52 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Reusable + +@objc protocol CallBarDelegate: class { + func callBarDidTapReturnButton(_ callBar: CallBar) +} + +@objcMembers +class CallBar: UIView, NibLoadable { + + @IBOutlet private weak var callIcon: UIImageView! { + didSet { + callIcon.image = Asset.Images.voiceCallHangonIcon.image.vc_tintedImage(usingColor: .white) + } + } + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var returnButton: UIButton! + + weak var delegate: CallBarDelegate? + + static func instantiate() -> CallBar { + return CallBar.loadFromNib() + } + + var title: String? { + get { + return titleLabel.text + } set { + titleLabel.text = newValue + } + } + + @IBAction private func returnButtonTapped(_ sender: UIButton) { + delegate?.callBarDidTapReturnButton(self) + } +} diff --git a/Riot/Modules/Call/Views/CallBar/CallBar.xib b/Riot/Modules/Call/Views/CallBar/CallBar.xib new file mode 100644 index 000000000..fabba59d6 --- /dev/null +++ b/Riot/Modules/Call/Views/CallBar/CallBar.xib @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +