session-ios/Session/Calls/Views & Modals/IncomingCallBanner.swift

199 lines
7.5 KiB
Swift
Raw Normal View History

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
2021-10-06 08:00:12 +02:00
import WebRTC
import SessionUIKit
2021-10-06 08:00:12 +02:00
import SessionMessagingKit
2021-10-07 07:53:57 +02:00
final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate {
private static let swipeToOperateThreshold: CGFloat = 60
private var previousY: CGFloat = 0
2021-10-28 08:02:41 +02:00
let call: SessionCall
2021-10-06 08:00:12 +02:00
// MARK: UI Components
private lazy var profilePictureView: ProfilePictureView = {
let result = ProfilePictureView()
let size = CGFloat(60)
result.size = size
result.set(.width, to: size)
result.set(.height, to: size)
return result
}()
private lazy var displayNameLabel: UILabel = {
let result = UILabel()
result.textColor = UIColor.white
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
2021-10-06 08:00:12 +02:00
result.lineBreakMode = .byTruncatingTail
return result
}()
private lazy var answerButton: UIButton = {
let result = UIButton(type: .custom)
let image = UIImage(named: "AnswerCall")!.withTint(.white)?.resizedImage(to: CGSize(width: 24.8, height: 24.8))
result.setImage(image, for: UIControl.State.normal)
result.set(.width, to: 48)
result.set(.height, to: 48)
result.backgroundColor = Colors.accent
result.layer.cornerRadius = 24
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)?.resizedImage(to: CGSize(width: 29.6, height: 11.2))
result.setImage(image, for: UIControl.State.normal)
result.set(.width, to: 48)
result.set(.height, to: 48)
result.backgroundColor = Colors.destructive
result.layer.cornerRadius = 24
result.addTarget(self, action: #selector(endCall), for: UIControl.Event.touchUpInside)
return result
}()
2021-10-07 07:53:57 +02:00
private lazy var panGestureRecognizer: UIPanGestureRecognizer = {
let result = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
result.delegate = self
return result
}()
2021-10-06 08:00:12 +02:00
// MARK: Initialization
public static var current: IncomingCallBanner?
2021-10-28 08:02:41 +02:00
init(for call: SessionCall) {
self.call = call
2021-10-06 08:00:12 +02:00
super.init(frame: CGRect.zero)
setUpViewHierarchy()
2021-10-07 07:53:57 +02:00
setUpGestureRecognizers()
2021-10-06 08:00:12 +02:00
if let incomingCallBanner = IncomingCallBanner.current {
incomingCallBanner.dismiss()
}
IncomingCallBanner.current = self
}
override init(frame: CGRect) {
preconditionFailure("Use init(message:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(coder:) instead.")
}
private func setUpViewHierarchy() {
self.backgroundColor = UIColor(hex: 0x000000).withAlphaComponent(0.8)
self.layer.cornerRadius = Values.largeSpacing
2021-10-06 08:00:12 +02:00
self.layer.masksToBounds = true
self.set(.height, to: 100)
profilePictureView.update(
publicKey: call.sessionId,
profile: Profile.fetchOrCreate(id: call.sessionId),
threadVariant: .contact
)
2021-10-28 08:02:41 +02:00
displayNameLabel.text = call.contactName
2021-10-13 06:15:04 +02:00
let stackView = UIStackView(arrangedSubviews: [profilePictureView, displayNameLabel, hangUpButton, answerButton])
2021-10-06 08:00:12 +02:00
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Values.largeSpacing
self.addSubview(stackView)
stackView.center(.vertical, in: self)
stackView.autoPinWidthToSuperview(withMargin: Values.mediumSpacing)
}
2021-10-07 07:53:57 +02:00
private func setUpGestureRecognizers() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tapGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(tapGestureRecognizer)
addGestureRecognizer(panGestureRecognizer)
}
// MARK: Interaction
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == panGestureRecognizer {
let v = panGestureRecognizer.velocity(in: self)
return abs(v.y) > abs(v.x) // It has to be more vertical than horizontal
} else {
return true
}
}
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
showCallVC(answer: false)
}
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
let translationY = gestureRecognizer.translation(in: self).y
switch gestureRecognizer.state {
case .changed:
self.transform = CGAffineTransform(translationX: 0, y: min(translationY, IncomingCallBanner.swipeToOperateThreshold))
if abs(translationY) > IncomingCallBanner.swipeToOperateThreshold && abs(previousY) < IncomingCallBanner.swipeToOperateThreshold {
UIImpactFeedbackGenerator(style: .heavy).impactOccurred() // Let the user know when they've hit the swipe to reply threshold
}
previousY = translationY
case .ended, .cancelled:
if abs(translationY) > IncomingCallBanner.swipeToOperateThreshold {
if translationY > 0 { showCallVC(answer: false) }
else { endCall() } // TODO: Or just put the call on hold?
} else {
self.transform = .identity
}
default: break
}
}
2021-10-06 08:00:12 +02:00
@objc private func answerCall() {
showCallVC(answer: true)
2021-10-06 08:00:12 +02:00
}
@objc private func endCall() {
2021-11-09 01:53:38 +01:00
AppEnvironment.shared.callManager.endCall(call) { error in
if let _ = error {
self.call.endSessionCall()
AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: nil)
}
2021-11-07 23:12:18 +01:00
self.dismiss()
}
2021-10-06 08:00:12 +02:00
}
public func showCallVC(answer: Bool) {
dismiss()
2022-02-15 03:59:01 +01:00
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // FIXME: Handle more gracefully
2021-10-28 08:02:41 +02:00
let callVC = CallVC(for: self.call)
2021-10-06 08:00:12 +02:00
if let conversationVC = presentingVC as? ConversationVC {
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
conversationVC.inputAccessoryView?.alpha = 0
}
2021-11-12 00:31:58 +01:00
presentingVC.present(callVC, animated: true) {
if answer { self.call.answerSessionCall() }
}
2021-10-06 08:00:12 +02:00
}
public func show() {
self.alpha = 0.0
let window = CurrentAppContext().mainWindow!
window.addSubview(self)
2022-01-06 00:08:59 +01:00
let topMargin = window.safeAreaInsets.top - Values.smallSpacing
2021-10-06 08:00:12 +02:00
self.autoPinWidthToSuperview(withMargin: Values.smallSpacing)
self.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin)
UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {
self.alpha = 1.0
}, completion: nil)
2021-11-29 06:32:02 +01:00
CallRingTonePlayer.shared.startVibration()
CallRingTonePlayer.shared.startPlayingRingTone()
2021-10-06 08:00:12 +02:00
}
public func dismiss() {
2021-11-29 06:32:02 +01:00
CallRingTonePlayer.shared.stopVibrationIfPossible()
CallRingTonePlayer.shared.stopPlayingRingTone()
2021-10-06 08:00:12 +02:00
UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {
self.alpha = 0.0
}, completion: { _ in
IncomingCallBanner.current = nil
self.removeFromSuperview()
})
}
}