From 53541856967428a33d0659be3315f4200e1ed9cb Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 23 Sep 2021 12:55:28 +1000 Subject: [PATCH] improve UI --- Session/Calls/CallVC.swift | 108 +++++++++++++----- .../ConversationVC+Interaction.swift | 29 +---- Session/Meta/AppDelegate.swift | 17 +-- .../AnswerCall.imageset/AnswerCall.pdf | Bin 0 -> 4646 bytes .../Session/AnswerCall.imageset/Contents.json | 12 ++ 5 files changed, 99 insertions(+), 67 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf create mode 100644 Session/Meta/Images.xcassets/Session/AnswerCall.imageset/Contents.json diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index b6d45009e..b6a67f828 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -9,6 +9,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { let mode: Mode let webRTCSession: WebRTCSession var isMuted = false + var isVideoEnabled = false var conversationVC: ConversationVC? = nil lazy var cameraManager: CameraManager = { @@ -24,6 +25,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { // MARK: UI Components private lazy var localVideoView: RTCMTLVideoView = { let result = RTCMTLVideoView() + result.isHidden = !isVideoEnabled result.contentMode = .scaleAspectFill result.set(.width, to: 80) result.set(.height, to: 173) @@ -52,6 +54,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { private lazy var minimizeButton: UIButton = { let result = UIButton(type: .custom) + result.isHidden = true let image = UIImage(named: "Minimize")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -60,6 +63,18 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() + private lazy var answerButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "AnswerCall")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = Colors.accent + result.layer.cornerRadius = 30 + result.addTarget(self, action: #selector(answerCall), for: UIControl.Event.touchUpInside) + return result + }() + private lazy var hangUpButton: UIButton = { let result = UIButton(type: .custom) let image = UIImage(named: "EndCall")!.withTint(.white) @@ -68,12 +83,20 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { result.set(.height, to: 60) result.backgroundColor = Colors.destructive result.layer.cornerRadius = 30 - result.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + result.addTarget(self, action: #selector(endCall), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var responsePanel: UIStackView = { + let result = UIStackView(arrangedSubviews: [hangUpButton, answerButton]) + result.axis = .horizontal + result.spacing = Values.veryLargeSpacing * 2 + 40 return result }() private lazy var switchCameraButton: UIButton = { let result = UIButton(type: .custom) + result.isEnabled = isVideoEnabled let image = UIImage(named: "SwitchCamera")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -96,6 +119,26 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() + private lazy var videoButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "VideoCall")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = UIColor(hex: 0x1F1F1F) + result.layer.cornerRadius = 30 + result.alpha = 0.5 + result.addTarget(self, action: #selector(operateCamera), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var operationPanel: UIStackView = { + let result = UIStackView(arrangedSubviews: [videoButton, switchAudioButton, switchCameraButton]) + result.axis = .horizontal + result.spacing = Values.veryLargeSpacing + return result + }() + private lazy var titleLabel: UILabel = { let result = UILabel() result.textColor = .white @@ -146,9 +189,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { Storage.write { transaction in self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() } - } else if case let .answer(sdp) = mode { - callInfoLabel.text = "Connecting..." - webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + answerButton.isHidden = true } } @@ -186,21 +227,14 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.center(.vertical, in: minimizeButton) titleLabel.center(.horizontal, in: view) - // End call button - view.addSubview(hangUpButton) - hangUpButton.translatesAutoresizingMaskIntoConstraints = false - hangUpButton.center(.horizontal, in: view) - hangUpButton.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) - // Switch camera button - view.addSubview(switchCameraButton) - switchCameraButton.translatesAutoresizingMaskIntoConstraints = false - switchCameraButton.center(.vertical, in: hangUpButton) - switchCameraButton.pin(.right, to: .left, of: hangUpButton, withInset: -Values.veryLargeSpacing) - // Switch audio button - view.addSubview(switchAudioButton) - switchAudioButton.translatesAutoresizingMaskIntoConstraints = false - switchAudioButton.center(.vertical, in: hangUpButton) - switchAudioButton.pin(.left, to: .right, of: hangUpButton, withInset: Values.veryLargeSpacing) + // Response Panel + view.addSubview(responsePanel) + responsePanel.center(.horizontal, in: view) + responsePanel.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) + // Operation Panel + view.addSubview(operationPanel) + operationPanel.center(.horizontal, in: view) + operationPanel.pin(.bottom, to: .top, of: responsePanel, withInset: -Values.veryLargeSpacing) } private func getBackgroudView() -> UIView { @@ -229,12 +263,12 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - cameraManager.start() + if (isVideoEnabled) { cameraManager.start() } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - cameraManager.stop() + if (isVideoEnabled) { cameraManager.stop() } } // MARK: Interaction @@ -256,7 +290,18 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } } - @objc private func close() { + @objc private func answerCall() { + if case let .answer(sdp) = mode { + callInfoLabel.text = "Connecting..." + webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + self.answerButton.alpha = 0 + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + self.answerButton.isHidden = true + }, completion: nil) + } + } + + @objc private func endCall() { Storage.write { transaction in WebRTCSession.current?.endCall(with: self.sessionID, using: transaction) } @@ -265,16 +310,25 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } @objc private func minimize() { - if (localVideoView.isHidden) { + + } + + @objc private func operateCamera() { + if (isVideoEnabled) { + webRTCSession.turnOffVideo() + localVideoView.isHidden = true + cameraManager.stop() + videoButton.alpha = 0.5 + switchCameraButton.isEnabled = false + } else { webRTCSession.turnOnVideo() localVideoView.isHidden = false cameraManager.prepare() cameraManager.start() - } else { - webRTCSession.turnOffVideo() - localVideoView.isHidden = true - cameraManager.stop() + videoButton.alpha = 1.0 + switchCameraButton.isEnabled = true } + isVideoEnabled = !isVideoEnabled } @objc private func switchCamera() { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 269f1a5ca..6aeaa4e64 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -28,38 +28,13 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc // MARK: Call @objc func startCall(_ sender: Any) { - let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet) - let voiceCallAction = UIAlertAction.init(title: NSLocalizedString("voice_call", comment: ""), style: .default) { _ in - self.startVoiceCall() - } - voiceCallAction.setValue(UIImage(named: "Phone"), forKey: "image") - alertVC.addAction(voiceCallAction) - - let videoCallAction = UIAlertAction.init(title: NSLocalizedString("video_call", comment: ""), style: .default) { _ in - self.startVideoCall() - } - videoCallAction.setValue(UIImage(named: "VideoCall"), forKey: "image") - alertVC.addAction(videoCallAction) - - let cancelAction = UIAlertAction.init(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel) {_ in - self.showInputAccessoryView() - } - alertVC.addAction(cancelAction) - self.inputAccessoryView?.isHidden = true - self.inputAccessoryView?.alpha = 0 - self.presentAlert(alertVC, animated: true) - } - - private func startVoiceCall() { - - } - - private func startVideoCall() { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } let callVC = CallVC(for: contactSessionID, mode: .offer) callVC.conversationVC = self callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve + self.inputAccessoryView?.isHidden = true + self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 48fc38f4a..ae0fbfc3e 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -10,19 +10,10 @@ extension AppDelegate { DispatchQueue.main.async { let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - let alert = UIAlertController(title: "Incoming Call", message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { _ in - let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) - presentingVC.dismiss(animated: true) { - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve - presentingVC.present(callVC, animated: true, completion: nil) - } - })) - alert.addAction(UIAlertAction(title: "Decline", style: .default, handler: { _ in - // Do nothing - })) - presentingVC.present(alert, animated: true, completion: nil) + let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve + presentingVC.present(callVC, animated: true, completion: nil) } } // Answer messages diff --git a/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf b/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4350772dea364f094fff538fc56afb49dc0b79a9 GIT binary patch literal 4646 zcmai&2UJtdw#O+^ARt8%q$mfZh*A=IRC*VrNRcY0(IFB-K#(d$nsgD6j`XI0Gy~F6 zl+Y0okP?bC5u}RT@RfVL?|W~(S+mZWwP)`=XU#tAzkV|U25K6jP%%lcKs)I>X}MrC ztFOHiED1mW48jq7{W>77;oyuzW65u4I1a6bMq*HCKwKB?YKOB2pb$w2pr8PD$6?WM z7qAzlb<#~&ep!Z)9S^WI0M<5St?#9?O`?`!)}_!72wZ2HL)H|mf+jx_Ppf}F93pPG z{N$GI(CyIdO~~o#ipiE}!|%!&>8})L%OpE<`DD}(u!Z_`FzE;TkFD_Xp=&9eS2|}n zn@`ERcd>EFNN?+XW%USJ9;`8Tx*od3N+i(4T!<|5OZjkd=9GET@>tsgPWdJFMe(ZGw~82QyQ>16?z> zr*3Z(t6&{bQH3LqmW!!d+oVo0zfLa=9hj`3MBk>kT2uSX30hNljmvt?#OgrpC8N;} z!N%-8x~0C{ICX{hu1aqaeQ;<7s%Xf9xP$M6F)u!kH{f$G5(&t-S`&7~QY1kAS=o&= zb{#SL>a2IP%)28#qaALbEK-_v=@K)MBh9Z6DOu8Ww3{~k>GSEDa^e|=%dw9i&GZ3@ z1qSp=hNB;7B4~Hl3nJYvl^U5aUd~CmRMqno*Y9*u2i)6d^})J25G9@UwT#8)8x`M6 z{E^G60YmQ;XSGs@Z!GDDN?}5!82kw4QOd{ZR>OwTqaB^l`UEHK$jd#h+lOtk8TXnn zRz3E8=Q2oq#NkbWz z>PjSOxT&+xO2s6Q+DGk0p2=FE`3yBAPh%Vi-IUANiu0S26v1O`lRnF1wO)sP2=5!a zHkx?1ey52jnkC+CICSt}&7(L-dgqr&t9@~wn!?UnPwaurgjwc zSOoLnJgv8|pHTN@eDRtya-AD*TwJO_MP>(jLh;MqPV@yu6a5!EyFdQ)O56`y?;mpz zo&KVsRlsKSMJi{Wk%pTM-|=~cV|FS#C`;~PKllBnSQ=i_z(qw~{sO%{4G1`H)G-fb zjjLBsVXjgYIUifi8%d;k$!Nx#MPzvpGPA2F$lu|1(Z?TWZ<%@~B~N}OmO)hYSET9H zO7ELCv1`v|`($mpXfq~Q&lFrzTjyuN)7*QM{|ktt)2_r;Xg~VmhH~}EcI9Kr$#5OB zsEk?VZn?;8g-<=WQ}AZFpB!AoZs~72}E{&$|Q1 za&+rhXa34}JpZ>m8DKF;BQy@MBqvqV0IUIVRg5zRYjh8eL<7hArRoj^r2k6rTMn7u za_Es)5LZ5yIajjA0pez`!-FvmnE8?EnL+&|pEl+6fI-7GYDA@f%9bFg~O@EB(VD#3C zW|VKHZEZoCyJ}QJKMC?FDTP{{RoiP~0jS1tk!Axy_mLz2I6hfb{*x47^evCDlxgC-bjr&5r1E)5RuKGUUuu%_6&Ul7c84wq18YE2ZKDHi~V2i?s7q zO-Uvc)h;KMNYkbI>j1^~6BN~x&o(cFI^T(JpJ5Q9k6c-CcBjmA61%b}dypA~dDk2Y z+eztJJ21VL&#oAFQTwbUTAh?%kgucm{yhU@G7)vS!+bRR2IlbLZJWrm%s1>CA&Vx4 zi-~l>;r5Lo*8|OB#Zcg2|KVx>b236S9DeCeto9x@6uu!agF0=RZj3yI>P2dPS4D3s z-(^c?D7d@nwS8G!g?o&I_pgP09(t^io;jBL!F)`jHa<(D5F{P@s1oNH0j{ z-bpLa?Kh|H(K7^SDuF{=bRd+Ifey4m-O`Fk}+Icllv)wyJ^r)sDusgD$|0>&z@anowH z_Iy$EI{S=<{muCFjXE|3#@bf)X|8V+A|W=d3A9SxwAB3?X0%)zYAUg2{FE%bPH}vB zR7L8kv5a|K_0d{!ZgbQp!?NK|&#`rK%!ZjqO(RUC&GPcZ;fqOyE z5yLO?&4fx7@pLjQY0;wXrgvlRT2>~%p#BJ7xyH@d^@d}H`<`FjIV%ZQ>2gpl(`Zy< zz;fHEX~7Mz*Ow<**Mpc^+`l0n@HT<{x&1Fp#qe}C3&qk|fSiJuc%K0L&iuyAoU{wH zhrukZQig$r`Z&H&=KJkd2;moxKPtc0<`&dTVAT%gXTQZSoKhWVtnJ8WEhsNY;7xdY z6{U$iXRdTdznni4un}O2S2RexB*?0NgIir?Ob>RWUg^Hlebwi;P8GTinH4{6NVm`@ zsQKRT71vCVOx{eMeOs1L66ch(oa`f@pE#QAnQSALDP+Uzf8$sF&pIl@7Nr)Wz}!mH zY>nyaR{0QZ_0*gs#3Mv1A{_B>M!LgL;B|apeC=udBBiCu0fFbdoGOFQ?+?^f->km+ z3H=FjUQ;&jU@&2+V~b~zutkR!KwFY7k+?~mq^ftfPPRhs?n>U`cpjAy)fg4CDK%eD zoF$ISR5;u@dM%u^ciHvwgCxEr+a$`Qm||TM%rNV4da+Bf{t(1b&=Kx9=6EnHRuWAt zAbMsH(`}@V=6;5Kt!xPQuG@F~(e_pTYXWB~XA>s}rwM1WRHrmYs%z@oR8s1KR9CgV z1$_<3f@HB!a|e=?vi9ECR-h^pmR(l9S|Hl5kT+2xUxqBKGPBFotLi;t9WQ1nt6XbU zYvie-uOn_IZc|~no~>Y*S(cgCtl4bo|L#p6@ab-(a#~Aac0_hFJOiFPjl5VKSJHnu z*CO{?ryTN?Tf~OKtfgROc)E7HcJ$kuMjwYJjqf%PtBDLw^Y(&LCDM&uIhCKy%}X^# zvS4l3yC3AO$xXrlDcOZBGrI8Qw3L`M?e8k}|H3s_*vn(%8 z*Y$2h&C&?@2)!88E|^L;?-lMn)tgJt!2XSWNc0@G-)*{%8yhEv5{t6&x|cmPgkgCu|L=sE1e{Ytv%xcKHGq&-p3lqwq7WMI_zwEt`5z5@Gr{ES0`;%@>cCHs1s1iV|ZJbA=^~`inj|1Xu4? z52^^jBPy?0OUero#M`8DA*v1U9ER6S&g3Zd$LU^jH@9tk`uLab53MNvG=Yca^i_^8 zC+#%RnePnWv&?VJrOvtboPWFd_P|ftR@&3Ut_lAu^k<9oniFGL$Vj=>c)gydbxXta zkEZ1q7r;ep>|2jUPc)iq$~9tg$S}*WvgxjU)%tv=O1+BVG(oi2lF_-t`Kr<; zx{UPxd(QXT#@j1Rrf^HQyG#x-sF-Sr!k2^_6^@$21z+>N26D!v&J(`+)UKv~>KY$y zo^tUYq>QHSpi#KM%Kh5Ed&j`3OxdLa{&~8zjkuiiwV~P0-^O2URcj-Ts2QU9ysI_F zWBA(8sBEEZqD*UaeCLZtpvLe2bTa z4l}o3^;tw%WRH9v34fI*^?~^0c7reJ@liS#b>zwYHL)h2qdTAF zmwYD=`)P`p?kK+ZmOHdRM6S%{2!Zu4XA~=z9S-k~+7=PERZk=0%~z?(9>Z(zaV0=q)Bl*@G$!Txz!c7w4%SOpOhJ`==p%|=XWnb z7u)%%o#4%bf3os1svff~nP;_CRaM~bXcTbFsCW?tc;be*j$W zw*WGLqdbu08-hzNaHPHwV2Z}NJ78SNcLQQj8Nlk2iid+U3Xp(k!lbQ)0YeWs7UvC+ zQT-3P_reL237vd-fFp;fk>MMBN$sixL;?zdNk~E