improve call message UI

pull/560/head
Ryan Zhao 3 years ago
parent 0a3d84d5c8
commit 9e6c81d28b

@ -138,6 +138,7 @@
76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; };
7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */; };
7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */; };
7B0EFDF2275449AA00FFAAE7 /* TSInfoMessage+Calls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */; };
7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; };
7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; };
7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; };
@ -1129,6 +1130,7 @@
76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = "<group>"; };
7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCell.swift; sourceTree = "<group>"; };
7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+Calls.swift"; sourceTree = "<group>"; };
7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = "<group>"; };
7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = "<group>"; };
7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = "<group>"; };
@ -2633,6 +2635,7 @@
C33FDB56255A580D00E217F9 /* TSOutgoingMessage.m */,
B84072952565E9F50037CB17 /* TSOutgoingMessage+Conversion.swift */,
34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */,
7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */,
);
path = Signal;
sourceTree = "<group>";
@ -4847,6 +4850,7 @@
C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */,
C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */,
C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */,
7B0EFDF2275449AA00FFAAE7 /* TSInfoMessage+Calls.swift in Sources */,
C32C5A75256DBBCF003C73A2 /* TSAttachmentPointer+Conversion.swift in Sources */,
C32C5AF8256DC051003C73A2 /* OWSDisappearingMessagesConfiguration.m in Sources */,
C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */,

@ -14,7 +14,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
let webRTCSession: WebRTCSession
let isOutgoing: Bool
var remoteSDP: RTCSessionDescription? = nil
var callMessageTimestamp: UInt64?
var callMessageID: String?
var answerCallAction: CXAnswerCallAction? = nil
var contactName: String {
let contact = Storage.shared.getContact(with: self.sessionID)
@ -182,12 +182,12 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
// MARK: Actions
func startSessionCall() {
guard case .offer = mode else { return }
var promise: Promise<UInt64>!
var promise: Promise<String?>!
Storage.write(with: { transaction in
promise = self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction)
}, completion: { [weak self] in
let _ = promise.done { timestamp in
self?.callMessageTimestamp = timestamp
let _ = promise.done { messageID in
self?.callMessageID = messageID
Storage.shared.write { transaction in
self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).retainUntilComplete()
}
@ -219,41 +219,28 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
// MARK: Update call message
func updateCallMessage(mode: EndCallMode) {
guard let callMessageTimestamp = callMessageTimestamp else { return }
guard let callMessageID = callMessageID 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 {
let infoMessage = TSInfoMessage.fetch(uniqueId: callMessageID, transaction: transaction)
if let messageToUpdate = infoMessage {
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 if self.hasStartedConnecting {
newMessageBody = NSLocalizedString("call_cancelled", comment: "")
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: "")
case .unanswered:
newMessageBody = NSLocalizedString("call_timeout", comment: "")
case .answeredElsewhere:
newMessageBody = messageToUpdate.body!
shouldMarkAsRead = true
case .local: shouldMarkAsRead = true
case .remote: break
case .unanswered: break
case .answeredElsewhere: shouldMarkAsRead = true
}
if messageToUpdate.callState == .incoming {
messageToUpdate.updateCallInfoMessage(.missed, using: transaction)
}
}
messageToUpdate.updateCall(withNewBody: newMessageBody, transaction: transaction)
if let incomingMessage = tsMessage as? TSIncomingMessage, shouldMarkAsRead {
incomingMessage.markAsReadNow(withSendReadReceipt: false, transaction: transaction)
if shouldMarkAsRead {
messageToUpdate.markAsRead(atTimestamp: NSDate.ows_millisecondTimeStamp(), sendReadReceipt: false, transaction: transaction)
}
}
}

@ -140,9 +140,9 @@ public final class SessionCallManager: NSObject {
message.kind = .endCall
print("[Calls] Sending end call message.")
MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete()
if let tsMessage = TSIncomingMessage.find(withAuthorId: caller, timestamp: offerMessage.sentTimestamp!, transaction: transaction) {
tsMessage.updateCall(withNewBody: NSLocalizedString("call_missing", comment: ""), transaction: transaction)
}
let infoMessage = TSInfoMessage.from(offerMessage, associatedWith: thread)
infoMessage.save(with: transaction)
infoMessage.updateCallInfoMessage(.missed, using: transaction)
}
public func invalidateTimeoutTimer() {

@ -51,11 +51,12 @@ final class CallMessageCell : MessageCell {
// MARK: Updating
override func update() {
guard let message = viewItem?.interaction as? TSMessage, message.isCallMessage else { return }
guard let message = viewItem?.interaction as? TSInfoMessage, message.messageType == .call else { return }
let icon: UIImage?
switch message.interactionType() {
case .outgoingMessage: icon = UIImage(named: "CallOutgoing")
case .incomingMessage: icon = UIImage(named: "CallIncoming")
switch message.callState {
case .outgoing: icon = UIImage(named: "CallOutgoing")?.withTint(Colors.text)
case .incoming: icon = UIImage(named: "CallIncoming")?.withTint(Colors.text)
case .missed: icon = UIImage(named: "CallMissed")?.withTint(Colors.destructive)
default: icon = nil
}
if let icon = icon {
@ -63,8 +64,6 @@ final class CallMessageCell : MessageCell {
}
iconImageViewWidthConstraint.constant = (icon != nil) ? CallMessageCell.iconSize : 0
iconImageViewHeightConstraint.constant = (icon != nil) ? CallMessageCell.iconSize : 0
Storage.read { transaction in
self.label.text = message.previewText(with: transaction)
}
self.label.text = message.customMessage
}
}

@ -46,12 +46,12 @@ class MessageCell : UITableViewCell {
static func getCellType(for viewItem: ConversationViewItem) -> MessageCell.Type {
switch viewItem.interaction {
case is TSIncomingMessage: fallthrough
case is TSOutgoingMessage:
if let message = viewItem.interaction as? TSMessage, message.isCallMessage {
case is TSOutgoingMessage: return VisibleMessageCell.self
case is TSInfoMessage:
if let message = viewItem.interaction as? TSInfoMessage, message.messageType == .call {
return CallMessageCell.self
}
return VisibleMessageCell.self
case is TSInfoMessage: return InfoMessageCell.self
return InfoMessageCell.self
case is TypingIndicatorInteraction: return TypingIndicatorCell.self
default: preconditionFailure()
}

@ -2,6 +2,7 @@ import PromiseKit
import WebRTC
import SessionUIKit
import UIKit
import SessionMessagingKit
extension AppDelegate {
@ -64,12 +65,12 @@ extension AppDelegate {
}
// Create incoming call message
let thread = TSContactThread.getOrCreateThread(withContactSessionID: message.sender!, transaction: transaction)
let tsMessage = TSIncomingMessage.from(message, associatedWith: thread)
tsMessage.save(with: transaction)
let infoMessage = TSInfoMessage.from(message, associatedWith: thread)
infoMessage.save(with: transaction)
// Handle UI
if let caller = message.sender, let uuid = message.uuid {
let call = SessionCall(for: caller, uuid: uuid, mode: .answer)
call.callMessageTimestamp = message.sentTimestamp
call.callMessageID = infoMessage.uniqueId
self.showCallUIForCall(call)
}
}

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "CallMissed.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -581,9 +581,9 @@
"DISMISS_BUTTON_TEXT" = "Dismiss";
/* Button text which opens the settings app */
"OPEN_SETTINGS_BUTTON" = "Settings";
"call_outgoing" = "Outgoing Call";
"call_incoming" = "Incoming Call";
"call_missing" = "Missing Call";
"call_outgoing" = "You called %@";
"call_incoming" = "%@ called you";
"call_missed" = "Missed Call from %@";
"call_rejected" = "Rejected Call";
"call_cancelled" = "Cancelled Call";
"call_timeout" = "Unanswered Call";

@ -242,7 +242,12 @@ public enum PushRegistrationError: Error {
let payload = payload.dictionaryPayload
if let uuid = payload["uuid"] as? String, let caller = payload["caller"] as? String, let timestamp = payload["timestamp"] as? UInt64 {
let call = SessionCall(for: caller, uuid: uuid, mode: .answer)
call.callMessageTimestamp = timestamp
Storage.write{ transaction in
let thread = TSContactThread.getOrCreateThread(withContactSessionID: caller, transaction: transaction)
let infoMessage = TSInfoMessage.callInfoMessage(from: caller, timestamp: timestamp, in: thread)
infoMessage.save(with: transaction)
call.callMessageID = infoMessage.uniqueId
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startPollerIfNeeded()
appDelegate.startClosedGroupPoller()

@ -117,19 +117,21 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
}
// MARK: Signaling
public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<UInt64> {
public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<String?> {
print("[Calls] Sending pre-offer message.")
guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) }
let (promise, seal) = Promise<UInt64>.pending()
let (promise, seal) = Promise<String?>.pending()
DispatchQueue.main.async {
let message = CallMessage()
message.sender = getUserHexEncodedPublicKey()
message.sentTimestamp = NSDate.millisecondTimestamp()
message.uuid = self.uuid
message.kind = .preOffer
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
tsMessage.save(with: transaction)
let infoMessage = TSInfoMessage.from(message, associatedWith: thread)
infoMessage.save(with: transaction)
MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 {
print("[Calls] Pre-offer message has been sent.")
seal.fulfill((tsMessage.timestamp))
seal.fulfill((infoMessage.uniqueId))
}.catch2 { error in
seal.reject(error)
}

@ -0,0 +1,40 @@
@objc public extension TSInfoMessage {
@objc(fromCallOffer:associatedWith:)
static func from(_ callMessage: CallMessage, associatedWith thread: TSThread) -> TSInfoMessage {
return callInfoMessage(from: callMessage.sender!, timestamp: callMessage.sentTimestamp!, in: thread)
}
static func callInfoMessage(from caller: String, timestamp: UInt64, in thread: TSThread) -> TSInfoMessage {
let callState: TSInfoMessageCallState
let messageBody: String
var contactName: String = ""
if let contactThread = thread as? TSContactThread {
let sessionID = contactThread.contactSessionID()
contactName = Storage.shared.getContact(with: sessionID)?.displayName(for: Contact.Context.regular) ?? sessionID
}
if caller == getUserHexEncodedPublicKey() {
callState = .outgoing
messageBody = String(format: NSLocalizedString("call_outgoing", comment: ""), contactName)
} else {
callState = .incoming
messageBody = String(format: NSLocalizedString("call_incoming", comment: ""), contactName)
}
let infoMessage = TSInfoMessage.init(timestamp: timestamp, in: thread, messageType: .call, customMessage: messageBody)
infoMessage.callState = callState
return infoMessage
}
@objc(updateCallInfoMessageWithNewState:usingTransaction:)
func updateCallInfoMessage(_ newCallState: TSInfoMessageCallState, using transaction: YapDatabaseReadWriteTransaction) {
guard self.messageType == .call else { return }
self.callState = newCallState
var contactName: String = ""
if let contactThread = self.thread as? TSContactThread {
let sessionID = contactThread.contactSessionID()
contactName = Storage.shared.getContact(with: sessionID)?.displayName(for: Contact.Context.regular) ?? sessionID
}
self.customMessage = String(format: NSLocalizedString("call_missed", comment: ""), contactName)
self.save(with: transaction)
}
}

@ -15,12 +15,21 @@ typedef NS_ENUM(NSInteger, TSInfoMessageType) {
TSInfoMessageTypeGroupCurrentUserLeft,
TSInfoMessageTypeDisappearingMessagesUpdate,
TSInfoMessageTypeScreenshotNotification,
TSInfoMessageTypeMediaSavedNotification
TSInfoMessageTypeMediaSavedNotification,
TSInfoMessageTypeCall
};
typedef NS_ENUM(NSInteger, TSInfoMessageCallState) {
TSInfoMessageCallStateIncoming,
TSInfoMessageCallStateOutgoing,
TSInfoMessageCallStateMissed,
TSInfoMessageCallStateUnknown
};
@property (atomic, readonly) TSInfoMessageType messageType;
@property (atomic, readonly, nullable) NSString *customMessage;
@property (atomic, nullable) NSString *customMessage;
@property (atomic, readonly, nullable) NSString *unregisteredRecipientId;
@property (atomic) TSInfoMessageCallState callState;
- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp
inThread:(nullable TSThread *)thread

@ -65,6 +65,7 @@ NSUInteger TSInfoMessageSchemaVersion = 1;
}
_messageType = infoMessage;
_callState = TSInfoMessageCallStateUnknown;
_infoMessageSchemaVersion = TSInfoMessageSchemaVersion;
if (self.isDynamicInteraction) {

Loading…
Cancel
Save