From 9e6c81d28ba8388404268dca22f8201dda8901c7 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 29 Nov 2021 12:10:33 +1100 Subject: [PATCH] improve call message UI --- Session.xcodeproj/project.pbxproj | 4 ++ .../Calls/Call Management/SessionCall.swift | 45 +++++++----------- .../Call Management/SessionCallManager.swift | 6 +-- .../Message Cells/CallMessageCell.swift | 13 +++-- .../Message Cells/MessageCell.swift | 8 ++-- Session/Meta/AppDelegate.swift | 7 +-- .../CallIncoming.imageset/CallIncoming.pdf | Bin 5103 -> 5425 bytes .../CallMissed.imageset/CallMissed.pdf | Bin 0 -> 5337 bytes .../Session/CallMissed.imageset/Contents.json | 12 +++++ .../CallOutgoing.imageset/CallOutgoing.pdf | Bin 5106 -> 5418 bytes .../Translations/en.lproj/Localizable.strings | 6 +-- .../PushRegistrationManager.swift | 7 ++- SessionMessagingKit/Calls/WebRTCSession.swift | 12 +++-- .../Messages/Signal/TSInfoMessage+Calls.swift | 40 ++++++++++++++++ .../Messages/Signal/TSInfoMessage.h | 13 ++++- .../Messages/Signal/TSInfoMessage.m | 1 + 16 files changed, 117 insertions(+), 57 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/CallMissed.imageset/CallMissed.pdf create mode 100644 Session/Meta/Images.xcassets/Session/CallMissed.imageset/Contents.json create mode 100644 SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 97c79506e..569aadfef 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -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 = ""; }; 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = ""; }; 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCell.swift; sourceTree = ""; }; + 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+Calls.swift"; sourceTree = ""; }; 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; 7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = ""; }; 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = ""; }; @@ -2633,6 +2635,7 @@ C33FDB56255A580D00E217F9 /* TSOutgoingMessage.m */, B84072952565E9F50037CB17 /* TSOutgoingMessage+Conversion.swift */, 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */, + 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */, ); path = Signal; sourceTree = ""; @@ -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 */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 63c94149f..15bc4b4c9 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -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! + var promise: Promise! 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) } } } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index ed3ccc1be..a229f92cb 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -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() { diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index 1715210a7..1c7454967 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -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 } } diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index 56d93b2b2..e98f0281c 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -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() } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 7ca15247e..f90ec017c 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -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) } } diff --git a/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf b/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf index fbc5a4a875971060d79f642f11a200ff3d3d7e9d..93e911237cdeabd01b7c703d8b78c100b9d3fa15 100644 GIT binary patch delta 2136 zcmb7FX;>5I76uiN0fYn)N^Z-@!z$9uOeV<$5e!QVh_wiaf~b=al0Y_-Ac*2v+Y2aK z#URCMp@QI61xrg2i%X$$1wjP0P@tu#AQVOFQf1LQ3EJZI-XC{Oa0IcLP< z%>2Cp1O$Qp{(wfS7U6Or&G_AA=YVaHP1oek>cKDX$Dew<@<(f%2iCy$K(oP@AIDtT zYi7P_L*GBUyywL{JMf9u>9(jpxCO+1tbW_)&0kf^7eCBAPNDzHPNAm2_Te+3#jLNo#7q#LdJ`q+ile)X`i}mIwSG8K{#3^ZR1M;!j z=;jvp`Ct5o$5HzW8y3q?W>9AHx5-()DBxv{u;I$5hL^L6@Dozi#db-@X;;)V`kggAfJOMrk3WM|@T*VhjNq&GV(h)sUE0V9&Lsq@gGGX#L?nbep;UqO61f(y+ z)25CL2qH21{CHBC2mxTkz?{FSZBV?fhMo*|S_w=BiI7fTaf6Mrrnq56tXLXu8)3Jo zT<&X6^N&8fZ?;K<3%_*2WrzEOtmELb)t;#Qu%szU;#Hk%V!?HASKM%46k}Xn*mB128tj_cHK1al*zn^TPHNt|#?()rzDh1m5?{U8wVGDdGVxQKD&OR0MW1Wc3O=bN7C46In^Y*F?v4A+65NV`$Q7 z;WeT7P{XdyV;$M=M0gE3uBPyP^A0wG<94zTr>c1t$Lxu@v+_Q^xCbu0SM<%HSnTYz zlv`_J+Nr-jv!FvEb42xkYBF}tojTgflyAAAMXLtfT8r`Yom(T^{q8k*bq88KT^tA3 z9v!@6+1%=xQM@{xH*$}d=hMoLEgtzM{I=auGi{OSJ&RLqNUGV2>qZX1kt@S~``g7% zWo(q6iap(N=X#TK2QbjzZesP*VM>T3GwYF4Z@p$Xxa@H&nz|+2A~x>_@6=+8wJ+lP zpUb{aU${~7?juzJeZj$ivMsqeJ+h|ZbC0t>XqSh%u5(@2sC!yqO%#;J(sI31OM42= z`b`2p_K&L45I&)_BTH6>WsF8BYn^tCO`O?RQ*n)`3f;Z2 z@Xx5cuTi(yAb5GkliU+w^sUC9nOH9PFO$QVp?g6|G~+-J%0?kwGW8#S$)8f4X27WK zIF;rI!5kFSPjv`XfPg`O7%+_Kx~Z-Ngs|VfgfPZ`0t|2icA66e#on@Gu*qd$u%7l9 zhx3+>jgY@SL~h>cQK6__`KFG;A@|LjIut{u1*lcyi?om@xctXQE`lH+S? zErlu)l^{$a^jj-57>on`V2qg{f`xF5K!{+dAQ4Z%1spbm$z)0|I=h@ Yi)pmDTAQX8B?2f1!vHt8kmWq!-)?#+X#fBK delta 1965 zcmah~dpOjI8g|*jFj#U+__-}f=3?#|w?Qjfjmx&=d!P6Fp7)>khMCwT-G+<8p#f`a zNMsZn3=D%DlMKsoskXo>@Lt_JE3)0;l0!RDq&7-c4+ln@_XDGAq$c*pD?8cX@Pd`K z^nI7kZ#3Q?+ZcV!?(W~OXFXK>G2kOZuv8xryl~;h3(>WMeMLu6K9LKVi|TRLBhsdb zVZ)2jh5yLEBOWQdvv)A8jKb9;GDE+gC~nvifDwzH3^pAXz(#!IzPl*)#ykN9i%&-q zVxHe&gg0KB8pbc}5=NIM&kz>6o{dfEf(KbSkAnv1cz2^2PaMs~S0WpA}t|Fhic)9R^fxbFV z^|fk-Y4sd)9VFYOE9rCFb{8bCiMHeg*DbxNW7gI^v#1tp7*Lwb)jY1YaMBePp!Ul> z^-zvkj{d|brFGk}9ejEDw#fx4GLaTIa<{>XgKuR5JV{#MPbZ2wspXAx0de@-uy9j`u+OL{QvMa`BrBrD}!wU(j8 zSa!NlYW7Mey2x78VRm+BpPK!#v-NjRcVAa%*3`IpjYlbF+}8JyHZ`S&Ptvb3WK(-x zi=@`+zz=g=$b6nDw79B5!uu4uKim-VR?3r1GB$YMRgi%yjVG4NsbA7kOk&RH6SkF4 zJ2{>?jXQoi>M|0VmH23?66z9ieN~_1VGH_P>w963IuYY*us$CBNto(4UUlcpi84W( zti;P0vnTQw0}s8f63TeSy?oRmf3W2_BW6kD+i(jq!CTN)rO~=OZ(S@+@(Y` z@aE$rhDVTaTi4YP8Nb{k21OpTd-VAghR5xjNsXzEWSosZv}fe|62@>`$LwMAv&}y- zw0*p|7rXfhXH1PnFd`Ii{pDmk{Oz+2ie7xVevLI?UbXTlT+1a9nkpcNysY4i6Znd` zmDv_bq^l1?GL@}BgS;ur)8=VUt$M5Rnrf(_!wnC}2HQLiOynPE(%SOS+FZ3}y0Ce8 zX+vSWVRcnsF$q7}ay_xiHDN}+hUaOQYbN4a`ANWy{dZ4nKUW*^S&ZAMva1md37*y2 z8+(@f0jD{@nK-{(&(|Egq!Qaw>12OwUB+i?a&WLs(JeZL)A~-cU5l^V{voM_m$1c( zr`ejJ{z2t~z%1>;KylUzEu(UW&A|Z6E_mFecSpL^jGu00qA-88gY%n&u?;zmX}HGC zS8|HbeJ)3c2-rSvA)-VoJI^`Kzc~SC9BI&<)<33__ndatbyY-(0g_*!h zWvCSgLbBObfo@7Wp^TGy&H25iDJPy=>Dwo%JUl6ocU)Cpo=nE{Dn^NAhI(u;RV!vZJoS@6{h#_ne>??^#K| zuJbW#uvbP=ChSe37<97IJ)do4oY3$`M#WLd55Aum^j|-TKR(afKgJc^Utdyf`FwYE zd38B@?WEkh^QWe87iy-GUYR6mILwRFa|9fqQt#vMBDbvhb^YQC8)xPR zz(_0xi|r8}7z6@)_H2%k2mlSh0r$-W_!0sjlg6SRfnWqtl2;&-m`p$cBZLeE0@KENIt z0;D6+I4Y7(#bZDu4vxVhK_ra|r_wNJ1RZ4!`FjfB_pc-lWJi6q35CVrA;!jzZcdOt E0bj4{U8Ml)kcNy-vN_JkL&J!FeYghV8= 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 WII6_eN-P1ObJ#H8>>L=%2Yv^>065wJ delta 1893 zcmai#do?LeZ9cO0MHh%B@+gEt(d)ZQHwlyyx_u^FHVKJ(1R)iVPYnh3p;y$KVDZkyN^p9 zuxU={r8QJ_)ENloz3;6p&2C~t)?3^C$Mm8L{TnmQ0=t@h2F-*0hL~f^XyJF;Clg{- z;-~KR^w7sc-nG{abt@xc3qg)>?)651WH}5pP zfDSw;R~mP_SUg)ZJmA6uJ2Oj{YXx1K~DlKW=JyDiMTghNjZC9b=yNP{!%TA_=bGd|JX>-Z{eC`tX?UUVJnV)iMP3Fh7dv68K=9Dem>>Ui6Cueg(5B#@hY_P3;X`GB z1E^<>@9a|;F6@7!1HE;1B^lKu=s_m9Thfbbyd#iBW@h?`jt5(>8UvML-a0SNtukp6 zVi9Hv!hJ6>^=G}`NYX_uQSYB9tm*Chp@$nE#Zid4y6q0ig5ft*Y>%o8w(wJmPc8XF zeHvF?A{H%Bom%H#mBF28Pu^j#1Z3T@$ht}?rxZ%X>$IIXmT{usZ3?pfR1>E-FA+{H zWNjg*N0kk`@Qzbf0x|*dLicQGPUlFjDRQCAw`az_M7=I~*-Z92Gd2UouWZl}H)&vT zn4T95(W(g7;}8AMM*vO~ZZ~FQYOz`gIGw^*N+v@;SNbU0H;#^02JhGA)O<`mLVZ&&{XDPv8qL}z_lhFxaO93|5qlV> zAgJbs`yb4{XeFVnZ5}paXjduh}9xyv*iR}U?`t`A0&HL^sv!jzqTHEp0aZB7~( zi5R%kp+ArGPZnCpN!4!827%}ujLar4-#E+J1DIzP&RP8B%wI{ z@maZ<4}07EVf-Eh-b@~SJTQ-=)~)P%Ak9ACo!iJFGd~}2+K*%}A*%8#tDCf%7g}Sh zyw~XF=&h zxQ-aL)?t^LhD|!~l?}E+zq_?Mb3^s*ZJzGoa%Xs(M-1>MuKfu6- z&16TohXn-E0aMd2V}2N N!ceHAC+r}wzW~MRHMal& diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 15cf77447..5af272ffc 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -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"; diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index b2299e23c..3344d05ae 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -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() diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 65a6246eb..17fa4ec19 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -117,19 +117,21 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } // MARK: Signaling - public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { 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.pending() + let (promise, seal) = Promise.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) } diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift b/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift new file mode 100644 index 000000000..fcf0af1be --- /dev/null +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift @@ -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) + } +} diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage.h b/SessionMessagingKit/Messages/Signal/TSInfoMessage.h index 745ffd0d6..30f607378 100644 --- a/SessionMessagingKit/Messages/Signal/TSInfoMessage.h +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage.h @@ -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 diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage.m b/SessionMessagingKit/Messages/Signal/TSInfoMessage.m index 67bb6b339..833c1cd37 100644 --- a/SessionMessagingKit/Messages/Signal/TSInfoMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage.m @@ -65,6 +65,7 @@ NSUInteger TSInfoMessageSchemaVersion = 1; } _messageType = infoMessage; + _callState = TSInfoMessageCallStateUnknown; _infoMessageSchemaVersion = TSInfoMessageSchemaVersion; if (self.isDynamicInteraction) {