update call message after a call ended

pull/560/head
ryanzhao 3 years ago
parent f019fe7733
commit ff79c58f44

@ -9,7 +9,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
let sessionID: String
let mode: Mode
let webRTCSession: WebRTCSession
let isOutgoing: Bool
var remoteSDP: RTCSessionDescription? = nil
var callMessageTimestamp: UInt64?
var isWaitingForRemoteSDP = false
var contactName: String {
let contact = Storage.shared.getContact(with: self.sessionID)
@ -59,6 +61,12 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
case answer
}
// MARK: End call mode
enum EndCallMode {
case local
case remote
}
// MARK: Call State Properties
var connectingDate: Date? {
didSet {
@ -115,16 +123,20 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
guard let connectedDate = connectedDate else {
return 0
}
if let endDate = endDate {
return endDate.timeIntervalSince(connectedDate)
}
return Date().timeIntervalSince(connectedDate)
}
// MARK: Initialization
init(for sessionID: String, uuid: String, mode: Mode) {
init(for sessionID: String, uuid: String, mode: Mode, outgoing: Bool = false) {
self.sessionID = sessionID
self.uuid = UUID(uuidString: uuid)!
self.mode = mode
self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid)
self.isOutgoing = outgoing
WebRTCSession.current = self.webRTCSession
super.init()
self.webRTCSession.delegate = self
@ -160,8 +172,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
}, completion: { [weak self] in
let _ = promise.done {
Storage.shared.write { transaction in
self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done {
self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done { timestamp in
self?.hasStartedConnecting = true
self?.callMessageTimestamp = timestamp
}.retainUntilComplete()
}
}
@ -186,11 +199,49 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
hasEnded = true
}
// MARK: Update call message
func updateCallMessage(mode: EndCallMode) {
guard let callMessageTimestamp = callMessageTimestamp else { return }
Storage.write { transaction in
let tsMessage: TSMessage?
if self.isOutgoing {
tsMessage = TSOutgoingMessage.find(withTimestamp: callMessageTimestamp)
} else {
tsMessage = TSIncomingMessage.find(withAuthorId: self.sessionID, timestamp: callMessageTimestamp, transaction: transaction)
}
if let messageToUpdate = tsMessage {
var shouldMarkAsRead = false
let newMessageBody: String
if self.duration > 0 {
let durationString = NSString.formatDurationSeconds(UInt32(self.duration), useShortFormat: true)
newMessageBody = "\(self.isOutgoing ? NSLocalizedString("call_outgoing", comment: "") : NSLocalizedString("call_incoming", comment: "")): \(durationString)"
shouldMarkAsRead = true
} else {
switch mode {
case .local:
newMessageBody = self.isOutgoing ? NSLocalizedString("call_cancelled", comment: "") : NSLocalizedString("call_rejected", comment: "")
shouldMarkAsRead = true
case .remote:
newMessageBody = self.isOutgoing ? NSLocalizedString("call_rejected", comment: "") : NSLocalizedString("call_missing", comment: "")
}
}
messageToUpdate.updateCall(withNewBody: newMessageBody, transaction: transaction)
if let incomingMessage = tsMessage as? TSIncomingMessage, shouldMarkAsRead {
incomingMessage.markAsReadNow(withSendReadReceipt: false, transaction: transaction)
}
}
}
}
// MARK: Renderer
func attachRemoteVideoRenderer(_ renderer: RTCVideoRenderer) {
webRTCSession.attachRemoteRenderer(renderer)
}
func removeRemoteVideoRenderer(_ renderer: RTCVideoRenderer) {
webRTCSession.removeRemoteRenderer(renderer)
}
func attachLocalVideoRenderer(_ renderer: RTCVideoRenderer) {
webRTCSession.attachLocalRenderer(renderer)
}

@ -95,6 +95,9 @@ public final class SessionCallManager: NSObject {
guard let call = currentCall else { return }
if let reason = reason {
self.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason)
call.updateCallMessage(mode: .remote)
} else {
call.updateCallMessage(mode: .local)
}
self.currentCall?.webRTCSession.dropConnection()
self.currentCall = nil

@ -125,6 +125,7 @@ final class MiniCallView: UIView {
UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {
self.alpha = 0.0
}, completion: { _ in
self.callVC.call.removeRemoteVideoRenderer(self.remoteVideoView)
MiniCallView.current = nil
self.removeFromSuperview()
})

@ -32,7 +32,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
if userDefaults[.hasSeenCallIPExposureWarning] {
guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return }
guard AppEnvironment.shared.callManager.currentCall == nil else { return }
let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer)
let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer, outgoing: true)
let callVC = CallVC(for: call)
callVC.conversationVC = self
self.inputAccessoryView?.isHidden = true

@ -6,34 +6,34 @@ import UIKit
extension AppDelegate {
// MARK: Call handling
func createNewIncomingCall(caller: String, uuid: String) {
DispatchQueue.main.async {
let call = SessionCall(for: caller, uuid: uuid, mode: .answer)
if CurrentAppContext().isMainAppAndActive {
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller {
let callVC = CallVC(for: call)
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
conversationVC.inputAccessoryView?.alpha = 0
presentingVC.present(callVC, animated: true, completion: nil)
}
}
call.reportIncomingCallIfNeeded{ error in
if let error = error {
SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)")
let incomingCallBanner = IncomingCallBanner(for: call)
incomingCallBanner.show()
}
}
}
}
@objc func setUpCallHandling() {
// Pre offer messages
MessageReceiver.handlePreOfferCallMessage = { message in
guard CurrentAppContext().isMainApp else { return }
self.createNewIncomingCall(caller: message.sender!, uuid: message.uuid!)
DispatchQueue.main.async {
if let caller = message.sender, let uuid = message.uuid {
let call = SessionCall(for: caller, uuid: uuid, mode: .answer)
call.callMessageTimestamp = message.sentTimestamp
if CurrentAppContext().isMainAppAndActive {
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller {
let callVC = CallVC(for: call)
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
conversationVC.inputAccessoryView?.alpha = 0
presentingVC.present(callVC, animated: true, completion: nil)
}
}
call.reportIncomingCallIfNeeded{ error in
if let error = error {
SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)")
let incomingCallBanner = IncomingCallBanner(for: call)
incomingCallBanner.show()
}
}
}
}
}
// Offer messages
MessageReceiver.handleOfferCallMessage = { message in

@ -577,6 +577,9 @@
"OPEN_SETTINGS_BUTTON" = "Settings";
"call_outgoing" = "Outgoing Call";
"call_incoming" = "Incoming Call";
"call_missing" = "Missing Call";
"call_rejected" = "Rejected Call";
"call_cancelled" = "Cancelled Call";
"voice_call" = "Voice Call";
"video_call" = "Video Call";
"APN_Message" = "You've got a new message";

@ -10,6 +10,10 @@ extension WebRTCSession {
remoteVideoTrack?.add(renderer)
}
public func removeRemoteRenderer(_ renderer: RTCVideoRenderer) {
remoteVideoTrack?.remove(renderer)
}
public func handleLocalFrameCaptured(_ videoFrame: RTCVideoFrame) {
guard let videoCapturer = delegate?.videoCapturer else { return }
localVideoSource.capturer(videoCapturer, didCapture: videoFrame)

@ -128,10 +128,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
return promise
}
public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<UInt64> {
print("[Calls] Sending offer message.")
guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) }
let (promise, seal) = Promise<Void>.pending()
let (promise, seal) = Promise<UInt64>.pending()
peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in
if let error = error {
seal.reject(error)
@ -152,7 +152,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
tsMessage.save(with: transaction)
MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 {
seal.fulfill(())
seal.fulfill(tsMessage.timestamp)
}.catch2 { error in
seal.reject(error)
}

@ -86,6 +86,8 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold;
- (void)updateForDeletionWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateCallMessageWithNewBody:(NSString *)newBody transaction:(YapDatabaseReadWriteTransaction *)transaction;
@end
NS_ASSUME_NONNULL_END

@ -442,6 +442,15 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
}];
}
- (void)updateCallMessageWithNewBody:(NSString *)newBody transaction:(YapDatabaseReadWriteTransaction *)transaction
{
if (!_isCallMessage) { return; }
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSMessage *message) {
[message setBody:newBody];
}];
}
@end
NS_ASSUME_NONNULL_END

@ -281,13 +281,6 @@ extension MessageReceiver {
// TODO: Call in progress, put the new call on hold/reject
return
}
handlePreOfferCallMessage?(message)
case .offer:
print("[Calls] Received offer message.")
if getWebRTCSession().uuid != message.uuid! {
// TODO: Call in progress, put the new call on hold/reject
return
}
let storage = SNMessagingKitConfiguration.shared.storage
let transaction = transaction as! YapDatabaseReadWriteTransaction
if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction),
@ -295,9 +288,13 @@ extension MessageReceiver {
let tsMessage = TSIncomingMessage.from(message, associatedWith: thread)
tsMessage.save(with: transaction)
}
// Delegate to the main app, which is expected to show a dialog confirming
// that the user wants to pick up the call. When they do, the SDP contained
// in the offer message will be passed to WebRTCSession.handleRemoteSDP(_:from:).
handlePreOfferCallMessage?(message)
case .offer:
print("[Calls] Received offer message.")
if getWebRTCSession().uuid != message.uuid! {
// TODO: Call in progress, put the new call on hold/reject
return
}
handleOfferCallMessage?(message)
case .answer:
print("[Calls] Received answer message.")

Loading…
Cancel
Save