Merge remote-tracking branch 'upstream/dev' into menu-redesign

# Conflicts:
#	Session.xcodeproj/project.pbxproj
pull/689/head
Morgan Pretty 3 years ago
commit 1045e79729

@ -130,6 +130,7 @@
7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; };
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; };
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; };
7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 7B50D64C28AC7CF80086CCEC /* silence.aiff */; };
7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */; };
7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037442834BCC0000DCF35 /* ReactionView.swift */; };
7B7CB189270430D20079FF93 /* CallMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB188270430D20079FF93 /* CallMessageView.swift */; };
@ -158,6 +159,7 @@
7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; };
7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */; };
7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */; };
7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */; };
7BAADFCC27B0EF23007BCF92 /* CallVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAADFCB27B0EF23007BCF92 /* CallVideoView.swift */; };
7BAADFCE27B215FE007BCF92 /* UIView+Draggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAADFCD27B215FE007BCF92 /* UIView+Draggable.swift */; };
7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54CC27ACCEEC003D12F8 /* GlobalSearchViewController.swift */; };
@ -169,6 +171,7 @@
7BB92B3F28C825FD0082762F /* NewConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB92B3E28C825FD0082762F /* NewConversationViewModel.swift */; };
7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */; };
7BBBDC44286EAD2D00747E59 /* TappableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */; };
7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */; };
7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; };
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; };
@ -1175,6 +1178,7 @@
7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = "<group>"; };
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; };
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; };
7B50D64C28AC7CF80086CCEC /* silence.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = silence.aiff; sourceTree = "<group>"; };
7B7037422834B81F000DCF35 /* ReactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionContainerView.swift; sourceTree = "<group>"; };
7B7037442834BCC0000DCF35 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; };
7B7CB188270430D20079FF93 /* CallMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageView.swift; sourceTree = "<group>"; };
@ -1203,6 +1207,7 @@
7BA68908272A27BE00EFC32F /* SessionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCall.swift; sourceTree = "<group>"; };
7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXCallController.swift"; sourceTree = "<group>"; };
7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXProvider.swift"; sourceTree = "<group>"; };
7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _009_OpenGroupPermission.swift; sourceTree = "<group>"; };
7BAADFCB27B0EF23007BCF92 /* CallVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVideoView.swift; sourceTree = "<group>"; };
7BAADFCD27B215FE007BCF92 /* UIView+Draggable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Draggable.swift"; sourceTree = "<group>"; };
7BAF54CC27ACCEEC003D12F8 /* GlobalSearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalSearchViewController.swift; sourceTree = "<group>"; };
@ -1214,6 +1219,7 @@
7BB92B3E28C825FD0082762F /* NewConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationViewModel.swift; sourceTree = "<group>"; };
7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentTitleViewController.swift; sourceTree = "<group>"; };
7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableLabel.swift; sourceTree = "<group>"; };
7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentTitleViewController.swift; sourceTree = "<group>"; };
7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; };
7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -2083,6 +2089,7 @@
34074FC5203E5435004596AE /* messageReceivedSounds */ = {
isa = PBXGroup;
children = (
7B50D64C28AC7CF80086CCEC /* silence.aiff */,
45B74A5B2044AAB300CD42F8 /* aurora-quiet.aifc */,
45B74A6F2044AAB500CD42F8 /* aurora.aifc */,
45B74A5F2044AAB400CD42F8 /* bamboo-quiet.aifc */,
@ -3555,6 +3562,7 @@
FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */,
7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */,
FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */,
7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */,
);
path = Migrations;
sourceTree = "<group>";
@ -4571,6 +4579,7 @@
45B74A872044AAB600CD42F8 /* complete-quiet.aifc in Resources */,
45B74A772044AAB600CD42F8 /* hello.aifc in Resources */,
45B74A7C2044AAB600CD42F8 /* hello-quiet.aifc in Resources */,
7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */,
45B74A792044AAB600CD42F8 /* input.aifc in Resources */,
C3CA3ABE255CDB0D00F4C6D4 /* portuguese.txt in Resources */,
45B74A8C2044AAB600CD42F8 /* input-quiet.aifc in Resources */,
@ -5313,6 +5322,7 @@
FD09799527FE7B8E00936362 /* Interaction.swift in Sources */,
FD5C72FF284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift in Sources */,
FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */,
7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */,
FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */,
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */,
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */,
@ -6955,7 +6965,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 374;
CURRENT_PROJECT_VERSION = 375;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -6994,7 +7004,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 2.1.0;
MARKETING_VERSION = 2.1.1;
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -7027,7 +7037,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 374;
CURRENT_PROJECT_VERSION = 375;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -7066,7 +7076,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 2.1.0;
MARKETING_VERSION = 2.1.1;
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session;

@ -71,6 +71,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
var connectingDate: Date? {
didSet {
stateDidChange?()
resetTimeoutTimerIfNeeded()
hasStartedConnectingDidChange?()
}
}
@ -113,12 +114,12 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
set { connectingDate = newValue ? Date() : nil }
}
var hasConnected: Bool {
public var hasConnected: Bool {
get { return connectedDate != nil }
set { connectedDate = newValue ? Date() : nil }
}
var hasEnded: Bool {
public var hasEnded: Bool {
get { return endDate != nil }
set { endDate = newValue ? Date() : nil }
}
@ -277,55 +278,60 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
let duration: TimeInterval = self.duration
let hasStartedConnecting: Bool = self.hasStartedConnecting
Storage.shared.writeAsync { db in
guard let interaction: Interaction = try? Interaction.fetchOne(db, id: callInteractionId) else {
return
}
let updateToMissedIfNeeded: () throws -> () = {
let missedCallInfo: CallMessage.MessageInfo = CallMessage.MessageInfo(state: .missed)
Storage.shared.writeAsync(
updates: { db in
guard let interaction: Interaction = try? Interaction.fetchOne(db, id: callInteractionId) else {
return
}
guard
let infoMessageData: Data = (interaction.body ?? "").data(using: .utf8),
let messageInfo: CallMessage.MessageInfo = try? JSONDecoder().decode(
CallMessage.MessageInfo.self,
from: infoMessageData
),
messageInfo.state == .incoming,
let missedCallInfoData: Data = try? JSONEncoder().encode(missedCallInfo)
else { return }
let updateToMissedIfNeeded: () throws -> () = {
let missedCallInfo: CallMessage.MessageInfo = CallMessage.MessageInfo(state: .missed)
guard
let infoMessageData: Data = (interaction.body ?? "").data(using: .utf8),
let messageInfo: CallMessage.MessageInfo = try? JSONDecoder().decode(
CallMessage.MessageInfo.self,
from: infoMessageData
),
messageInfo.state == .incoming,
let missedCallInfoData: Data = try? JSONEncoder().encode(missedCallInfo)
else { return }
_ = try interaction
.with(body: String(data: missedCallInfoData, encoding: .utf8))
.saved(db)
}
let shouldMarkAsRead: Bool = try {
if duration > 0 { return true }
if hasStartedConnecting { return true }
switch mode {
case .local:
try updateToMissedIfNeeded()
return true
case .remote, .unanswered:
try updateToMissedIfNeeded()
return false
case .answeredElsewhere: return true
}
}()
_ = try interaction
.with(body: String(data: missedCallInfoData, encoding: .utf8))
.saved(db)
}
let shouldMarkAsRead: Bool = try {
if duration > 0 { return true }
if hasStartedConnecting { return true }
guard shouldMarkAsRead else { return }
switch mode {
case .local:
try updateToMissedIfNeeded()
return true
case .remote, .unanswered:
try updateToMissedIfNeeded()
return false
case .answeredElsewhere: return true
}
}()
guard shouldMarkAsRead else { return }
try Interaction.markAsRead(
db,
interactionId: interaction.id,
threadId: interaction.threadId,
includingOlder: false,
trySendReadReceipt: false
)
}
try Interaction.markAsRead(
db,
interactionId: interaction.id,
threadId: interaction.threadId,
includingOlder: false,
trySendReadReceipt: false
)
},
completion: { _, _ in
SessionCallManager.suspendDatabaseIfCallEndedInBackground()
}
)
}
// MARK: - Renderer
@ -410,7 +416,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
public func setupTimeoutTimer() {
invalidateTimeoutTimer()
let timeInterval: TimeInterval = (hasConnected ? 60 : 30)
let timeInterval: TimeInterval = 60
timeOutTimer = Timer.scheduledTimerOnMainThread(withTimeInterval: timeInterval, repeats: false) { _ in
self.didTimeout = true
@ -421,6 +427,11 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
}
}
public func resetTimeoutTimerIfNeeded() {
if self.timeOutTimer == nil { return }
setupTimeoutTimer()
}
public func invalidateTimeoutTimer() {
timeOutTimer?.invalidate()
timeOutTimer = nil

@ -73,13 +73,19 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
// MARK: - Report calls
public static func reportFakeCall(info: String) {
SessionCallManager.sharedProvider(useSystemCallLog: false)
.reportNewIncomingCall(
with: UUID(),
update: CXCallUpdate()
) { _ in
SNLog("[Calls] Reported fake incoming call to CallKit due to: \(info)")
}
let callId = UUID()
let provider = SessionCallManager.sharedProvider(useSystemCallLog: false)
provider.reportNewIncomingCall(
with: callId,
update: CXCallUpdate()
) { _ in
SNLog("[Calls] Reported fake incoming call to CallKit due to: \(info)")
}
provider.reportCall(
with: callId,
endedAt: nil,
reason: .failed
)
}
public func reportOutgoingCall(_ call: SessionCall) {
@ -98,30 +104,22 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
}
public func reportIncomingCall(_ call: SessionCall, callerName: String, completion: @escaping (Error?) -> Void) {
AssertIsOnMainThread()
if let provider = provider {
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
update.localizedCallerName = callerName
update.remoteHandle = CXHandle(type: .generic, value: call.callId.uuidString)
update.hasVideo = false
let provider = provider ?? Self.sharedProvider(useSystemCallLog: false)
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
update.localizedCallerName = callerName
update.remoteHandle = CXHandle(type: .generic, value: call.callId.uuidString)
update.hasVideo = false
disableUnsupportedFeatures(callUpdate: update)
disableUnsupportedFeatures(callUpdate: update)
// Report the incoming call to the system
provider.reportNewIncomingCall(with: call.callId, update: update) { error in
guard error == nil else {
self.reportCurrentCallEnded(reason: .failed)
completion(error)
return
}
UserDefaults.sharedLokiProject?.set(true, forKey: "isCallOngoing")
completion(nil)
// Report the incoming call to the system
provider.reportNewIncomingCall(with: call.callId, update: update) { error in
guard error == nil else {
self.reportCurrentCallEnded(reason: .failed)
completion(error)
return
}
}
else {
SessionCallManager.reportFakeCall(info: "No CXProvider instance")
UserDefaults.sharedLokiProject?.set(true, forKey: "isCallOngoing")
completion(nil)
}
@ -135,7 +133,16 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
return
}
guard let call = currentCall else { return }
func handleCallEnded() {
WebRTCSession.current = nil
UserDefaults.sharedLokiProject?.set(false, forKey: "isCallOngoing")
}
guard let call = currentCall else {
handleCallEnded()
Self.suspendDatabaseIfCallEndedInBackground()
return
}
if let reason = reason {
self.provider?.reportCall(with: call.callId, endedAt: nil, reason: reason)
@ -153,8 +160,7 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
call.webRTCSession.dropConnection()
self.currentCall = nil
WebRTCSession.current = nil
UserDefaults.sharedLokiProject?.set(false, forKey: "isCallOngoing")
handleCallEnded()
}
// MARK: - Util
@ -172,15 +178,18 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
callUpdate.supportsDTMF = false
}
public static func suspendDatabaseIfCallEndedInBackground() {
if CurrentAppContext().isInBackground() {
// Stop all jobs except for message sending and when completed suspend the database
JobRunner.stopAndClearPendingJobs(exceptForVariant: .messageSend) {
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
}
}
}
// MARK: - UI
public func showCallUIForCall(caller: String, uuid: String, mode: CallMode, interactionId: Int64?) {
guard Thread.isMainThread else {
DispatchQueue.main.async {
self.showCallUIForCall(caller: caller, uuid: uuid, mode: mode, interactionId: interactionId)
}
return
}
guard let call: SessionCall = Storage.shared.read({ db in SessionCall(db, for: caller, uuid: uuid, mode: mode) }) else {
return
}
@ -193,20 +202,23 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
}
guard CurrentAppContext().isMainAppAndActive else { return }
guard let presentingVC = CurrentAppContext().frontmostViewController() else {
preconditionFailure() // FIXME: Handle more gracefully
}
if let conversationVC: ConversationVC = presentingVC as? ConversationVC, conversationVC.viewModel.threadData.threadId == call.sessionId {
let callVC = CallVC(for: call)
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
conversationVC.inputAccessoryView?.alpha = 0
presentingVC.present(callVC, animated: true, completion: nil)
}
else if !Preferences.isCallKitSupported {
let incomingCallBanner = IncomingCallBanner(for: call)
incomingCallBanner.show()
DispatchQueue.main.async {
guard let presentingVC = CurrentAppContext().frontmostViewController() else {
preconditionFailure() // FIXME: Handle more gracefully
}
if let conversationVC: ConversationVC = presentingVC as? ConversationVC, conversationVC.viewModel.threadData.threadId == call.sessionId {
let callVC = CallVC(for: call)
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
conversationVC.inputAccessoryView?.alpha = 0
presentingVC.present(callVC, animated: true, completion: nil)
}
else if !Preferences.isCallKitSupported {
let incomingCallBanner = IncomingCallBanner(for: call)
incomingCallBanner.show()
}
}
}
}

@ -315,12 +315,11 @@ extension ConversationVC:
let modal = SendSeedModal()
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
modal.proceed = { self.sendMessage(hasPermissionToSendSeed: true) }
modal.proceed = { [weak self] in self?.sendMessage(hasPermissionToSendSeed: true) }
return present(modal, animated: true, completion: nil)
}
// Clearing this out immediately (even though it already happens in 'messageSent') to prevent
// "double sending" if the user rapidly taps the send button
// Clearing this out immediately to make this appear more snappy
DispatchQueue.main.async { [weak self] in
self?.snInputView.text = ""
self?.snInputView.quoteDraftInfo = nil
@ -416,7 +415,7 @@ extension ConversationVC:
)
}
func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) {
func sendAttachments(_ attachments: [SignalAttachment], with text: String, hasPermissionToSendSeed: Bool = false, onComplete: (() -> ())? = nil) {
guard !showBlockedModalIfNeeded() else { return }
for attachment in attachments {
@ -426,6 +425,25 @@ extension ConversationVC:
}
let text = replaceMentions(in: snInputView.text.trimmingCharacters(in: .whitespacesAndNewlines))
if text.contains(mnemonic) && !viewModel.threadData.threadIsNoteToSelf && !hasPermissionToSendSeed {
// Warn the user if they're about to send their seed to someone
let modal = SendSeedModal()
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
modal.proceed = { [weak self] in
self?.sendAttachments(attachments, with: text, hasPermissionToSendSeed: true, onComplete: onComplete)
}
return present(modal, animated: true, completion: nil)
}
// Clearing this out immediately to make this appear more snappy
DispatchQueue.main.async { [weak self] in
self?.snInputView.text = ""
self?.snInputView.quoteDraftInfo = nil
self?.resetMentions()
}
// Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can
// use it to determine if the user is creating a new thread and update the 'isApproved'
@ -492,13 +510,6 @@ extension ConversationVC:
}
func handleMessageSent() {
DispatchQueue.main.async { [weak self] in
self?.snInputView.text = ""
self?.snInputView.quoteDraftInfo = nil
self?.resetMentions()
}
if Storage.shared[.playNotificationSoundInForeground] {
let soundID = Preferences.Sound.systemSoundId(for: .messageSent, quiet: true)
AudioServicesPlaySystemSound(soundID)
@ -920,6 +931,7 @@ extension ConversationVC:
}
func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?) {
guard viewModel.threadData.canWrite else { return }
guard SessionId.Prefix(from: sessionId) == .blinded else {
Storage.shared.write { db in
try SessionThread.fetchOrCreate(db, id: sessionId, variant: .contact)

@ -69,10 +69,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
}
override var inputAccessoryView: UIView? {
guard
viewModel.threadData.threadVariant != .closedGroup ||
viewModel.threadData.currentUserIsClosedGroupMember == true
else { return nil }
guard viewModel.threadData.canWrite else { return nil }
return (isShowingSearchUI ? searchController.resultsBar : snInputView)
}
@ -142,10 +139,11 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
result.showsVerticalScrollIndicator = false
result.contentInsetAdjustmentBehavior = .never
result.keyboardDismissMode = .interactive
let bottomInset: CGFloat = viewModel.threadData.canWrite ? Values.mediumSpacing : Values.mediumSpacing + UIApplication.shared.keyWindow!.safeAreaInsets.bottom
result.contentInset = UIEdgeInsets(
top: 0,
leading: 0,
bottom: Values.mediumSpacing,
bottom: bottomInset,
trailing: 0
)
result.registerHeaderFooterView(view: UITableViewHeaderFooterView.self)
@ -894,7 +892,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
deleteSectionsAnimation: .none,
insertSectionsAnimation: .none,
reloadSectionsAnimation: .none,
deleteRowsAnimation: .bottom,
deleteRowsAnimation: .fade,
insertRowsAnimation: .none,
reloadRowsAnimation: .none,
interrupt: { itemChangeInfo?.isInsertAtTop == true || $0.changeCount > ConversationViewModel.pageSize }

@ -47,12 +47,24 @@ final class CallMessageCell: MessageCell {
private lazy var container: UIView = {
let result: UIView = UIView()
result.set(.height, to: 50)
result.layer.cornerRadius = 18
result.backgroundColor = Colors.callMessageBackground
result.addSubview(label)
label.autoCenterInSuperview()
label.pin(.top, to: .top, of: result, withInset: CallMessageCell.inset)
label.pin(
.left,
to: .left,
of: result,
withInset: ((CallMessageCell.inset * 2) + infoImageView.bounds.size.width)
)
label.pin(
.right,
to: .right,
of: result,
withInset: -((CallMessageCell.inset * 2) + infoImageView.bounds.size.width)
)
label.pin(.bottom, to: .bottom, of: result, withInset: -CallMessageCell.inset)
result.addSubview(iconImageView)
iconImageView.autoVCenterInSuperview()

@ -78,6 +78,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
SNAppearance.switchToSessionAppearance()
if Environment.shared?.callManager.wrappedValue?.currentCall == nil {
UserDefaults.sharedLokiProject?.set(false, forKey: "isCallOngoing")
}
// No point continuing if we are running tests
guard !CurrentAppContext().isRunningTests else { return true }
@ -132,21 +136,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// NOTE: Fix an edge case where user taps on the callkit notification
// but answers the call on another device
stopPollers(shouldStopUserPoller: !self.hasIncomingCallWaiting())
stopPollers(shouldStopUserPoller: !self.hasCallOngoing())
// Stop all jobs except for message sending and when completed suspend the database
JobRunner.stopAndClearPendingJobs(exceptForVariant: .messageSend) {
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
if !self.hasCallOngoing() {
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
}
}
}
func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
Logger.info("applicationDidReceiveMemoryWarning")
}
func applicationWillTerminate(_ application: UIApplication) {
DDLog.flushLog()
stopPollers()
}
@ -244,16 +250,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
Configuration.performMainSetup()
JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens)
// Trigger any launch-specific jobs and start the JobRunner
JobRunner.appDidFinishLaunching()
/// Setup the UI
///
/// **Note:** This **MUST** be run before calling `AppReadiness.setAppIsReady()` otherwise if
/// we are launching the app from a push notification the HomeVC won't be setup yet and it won't open the
/// related thread
/// **Note:** This **MUST** be run before calling:
/// - `AppReadiness.setAppIsReady()`:
/// If we are launching the app from a push notification the HomeVC won't be setup yet
/// and it won't open the related thread
///
/// - `JobRunner.appDidFinishLaunching()`:
/// The jobs which run on launch (eg. DisappearingMessages job) can impact the interactions
/// which get fetched to display on the home screen, if the PagedDatabaseObserver hasn't
/// been setup yet then the home screen can show stale (ie. deleted) interactions incorrectly
self.ensureRootViewController(isPreAppReadyCall: true)
// Trigger any launch-specific jobs and start the JobRunner
JobRunner.appDidFinishLaunching()
// Note that this does much more than set a flag;
// it will also run all deferred blocks (including the JobRunner
// 'appDidBecomeActive' method)
@ -634,6 +646,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
return !call.hasStartedConnecting
}
func hasCallOngoing() -> Bool {
guard let call = AppEnvironment.shared.callManager.currentCall else { return false }
return !call.hasEnded
}
func handleAppActivatedWithOngoingCallIfNeeded() {
guard
let call: SessionCall = (AppEnvironment.shared.callManager.currentCall as? SessionCall),

@ -313,21 +313,25 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
AppNotificationUserInfoKey.threadId: thread.id
]
let notificationTitle: String = interaction.previewText(db)
let threadName: String = SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: nil, // Not supported
openGroupName: nil // Not supported
)
var notificationBody: String?
let notificationTitle: String = "Session"
let senderName: String = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant)
let notificationBody: String? = {
switch messageInfo.state {
case .permissionDenied:
return String(
format: "modal_call_missed_tips_explanation".localized(),
senderName
)
case .missed:
return String(
format: "call_missed".localized(),
senderName
)
default:
return nil
}
}()
if messageInfo.state == .permissionDenied {
notificationBody = String(
format: "modal_call_missed_tips_explanation".localized(),
threadName
)
}
let fallbackSound: Preferences.Sound = db[.defaultNotificationSound]
.defaulting(to: Preferences.Sound.defaultNotificationSound)
@ -345,7 +349,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
previewType: previewType,
sound: sound,
threadVariant: thread.variant,
threadName: threadName,
threadName: senderName,
replacingIdentifier: UUID().uuidString
)
}

@ -6,7 +6,7 @@ import Foundation
import PromiseKit
import PushKit
import SignalUtilitiesKit
import SignalUtilitiesKit
import GRDB
public enum PushRegistrationError: Error {
case assertionError(description: String)
@ -251,6 +251,9 @@ public enum PushRegistrationError: Error {
return
}
// Resume database
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
let maybeCall: SessionCall? = Storage.shared.write { db in
let messageInfo: CallMessage.MessageInfo = CallMessage.MessageInfo(
state: (caller == getUserHexEncodedPublicKey(db) ?
@ -259,7 +262,13 @@ public enum PushRegistrationError: Error {
)
)
guard let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) else { return nil }
let messageInfoString: String? = {
if let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) {
return String(data: messageInfoData, encoding: .utf8)
} else {
return "Incoming call." // TODO: We can do better here.
}
}()
let call: SessionCall = SessionCall(db, for: caller, uuid: uuid, mode: .answer)
let thread: SessionThread = try SessionThread.fetchOrCreate(db, id: caller, variant: .contact)
@ -269,7 +278,7 @@ public enum PushRegistrationError: Error {
threadId: thread.id,
authorId: caller,
variant: .infoCall,
body: String(data: messageInfoData, encoding: .utf8),
body: messageInfoString,
timestampMs: timestampMs
).inserted(db)
call.callInteractionId = interaction.id

@ -9,6 +9,7 @@ public protocol CurrentCallProtocol {
var callId: UUID { get }
var webRTCSession: WebRTCSession { get }
var hasStartedConnecting: Bool { get set }
var hasEnded: Bool { get set }
func updateCallMessage(mode: EndCallMode)
func didReceiveRemoteSDP(sdp: RTCSessionDescription)

@ -22,7 +22,8 @@ public enum SNMessagingKit { // Just to make the external API nice
_007_HomeQueryOptimisationIndexes.self
],
[
_008_EmojiReacts.self
_008_EmojiReacts.self,
_009_OpenGroupPermission.self
]
]
)

@ -0,0 +1,27 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
enum _009_OpenGroupPermission: Migration {
static let target: TargetMigrations.Identifier = .messagingKit
static let identifier: String = "OpenGroupPermission"
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: GRDB.Database) throws {
try db.alter(table: OpenGroup.self) { t in
t.add(.permissions, .integer)
.defaults(to: OpenGroup.Permissions.all)
}
// When modifying OpenGroup behaviours we should always look to reset the `infoUpdates`
// value for all OpenGroups to ensure they all have the correct state for newly
// added/changed fields
_ = try OpenGroup
.updateAll(db, OpenGroup.Columns.infoUpdates.set(to: 0))
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
}
}

@ -523,8 +523,13 @@ public extension Interaction {
.asRequest(of: Int64.self)
.fetchAll(db)
// Don't bother continuing if there are not interactions to mark as read
guard !interactionIdsToMarkAsRead.isEmpty else { return }
// If there are no other interactions to mark as read then just schedule the jobs
// for this interaction (need to ensure the disapeparing messages run for sync'ed
// outgoing messages which will always have 'wasRead' as false)
guard !interactionIdsToMarkAsRead.isEmpty else {
scheduleJobs(interactionIds: [interactionId])
return
}
// Update the `wasRead` flag to true
try interactionQuery.updateAll(db, Columns.wasRead.set(to: true))

@ -27,6 +27,38 @@ public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableReco
case inboxLatestMessageId
case outboxLatestMessageId
case pollFailureCount
case permissions
}
public struct Permissions: OptionSet, Codable, DatabaseValueConvertible, Hashable {
public let rawValue: UInt16
public init(rawValue: UInt16) {
self.rawValue = rawValue
}
public init(roomInfo: OpenGroupAPI.RoomPollInfo) {
var permissions: Permissions = []
if roomInfo.read { permissions.insert(.read) }
if roomInfo.write { permissions.insert(.write) }
if roomInfo.upload { permissions.insert(.upload) }
self.init(rawValue: permissions.rawValue)
}
public func toString() -> String {
return ""
.appending(self.contains(.read) ? "r" : "-")
.appending(self.contains(.write) ? "w" : "-")
.appending(self.contains(.upload) ? "u" : "-")
}
static let read: Permissions = Permissions(rawValue: 1 << 0)
static let write: Permissions = Permissions(rawValue: 1 << 1)
static let upload: Permissions = Permissions(rawValue: 1 << 2)
static let all: Permissions = [ .read, .write, .upload ]
}
public var id: String { threadId } // Identifiable
@ -90,6 +122,9 @@ public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableReco
/// The number of times this room has failed to poll since the last successful poll
public let pollFailureCount: Int64
/// The permissions this room has for current user
public let permissions: Permissions?
// MARK: - Relationships
public var thread: QueryInterfaceRequest<SessionThread> {
@ -122,7 +157,8 @@ public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableReco
sequenceNumber: Int64 = 0,
inboxLatestMessageId: Int64 = 0,
outboxLatestMessageId: Int64 = 0,
pollFailureCount: Int64 = 0
pollFailureCount: Int64 = 0,
permissions: Permissions? = nil
) {
self.threadId = OpenGroup.idFor(roomToken: roomToken, server: server)
self.server = server.lowercased()
@ -139,6 +175,7 @@ public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableReco
self.inboxLatestMessageId = inboxLatestMessageId
self.outboxLatestMessageId = outboxLatestMessageId
self.pollFailureCount = pollFailureCount
self.permissions = permissions
}
}
@ -166,7 +203,8 @@ public extension OpenGroup {
sequenceNumber: 0,
inboxLatestMessageId: 0,
outboxLatestMessageId: 0,
pollFailureCount: 0
pollFailureCount: 0,
permissions: nil
)
}
@ -200,7 +238,8 @@ extension OpenGroup: CustomStringConvertible, CustomDebugStringConvertible {
"sequenceNumber: \(sequenceNumber)",
"inboxLatestMessageId: \(inboxLatestMessageId)",
"outboxLatestMessageId: \(outboxLatestMessageId)",
"pollFailureCount: \(pollFailureCount))"
"pollFailureCount: \(pollFailureCount))",
"permissions: \(permissions?.toString() ?? "---")"
].joined(separator: ", ")
}
}

@ -17,7 +17,7 @@ public enum DisappearingMessagesJob: JobExecutor {
deferred: @escaping (Job) -> ()
) {
// The 'backgroundTask' gets captured and cleared within the 'completion' block
let timestampNowMs: TimeInterval = (Date().timeIntervalSince1970 * 1000)
let timestampNowMs: TimeInterval = ceil(Date().timeIntervalSince1970 * 1000)
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function)
let updatedJob: Job? = Storage.shared.write { db in
@ -29,12 +29,9 @@ public enum DisappearingMessagesJob: JobExecutor {
// Update the next run timestamp for the DisappearingMessagesJob (if the call
// to 'updateNextRunIfNeeded' returns 'nil' then it doesn't need to re-run so
// should have it's 'nextRunTimestamp' cleared)
return updateNextRunIfNeeded(db)
.defaulting(
to: try job
.with(nextRunTimestamp: 0)
.saved(db)
)
return try updateNextRunIfNeeded(db)
.defaulting(to: job.with(nextRunTimestamp: 0))
.saved(db)
}
success(updatedJob ?? job, false)
@ -65,7 +62,7 @@ public extension DisappearingMessagesJob {
return try? Job
.filter(Job.Columns.variant == Job.Variant.disappearingMessages)
.fetchOne(db)?
.with(nextRunTimestamp: ((nextExpirationTimestampMs / 1000) + 1))
.with(nextRunTimestamp: ceil(nextExpirationTimestampMs / 1000))
.saved(db)
}

@ -369,9 +369,15 @@ public extension ClosedGroupControlMessage.Kind {
return String(format: "GROUP_TITLE_CHANGED".localized(), name)
case .membersAdded(let membersAsData):
let addedMemberNames: [String] = try Profile
.fetchAll(db, ids: membersAsData.map { $0.toHexString() })
.map { $0.displayName() }
let memberIds: [String] = membersAsData.map { $0.toHexString() }
let knownMemberNameMap: [String: String] = try Profile
.fetchAll(db, ids: memberIds)
.reduce(into: [:]) { result, next in result[next.id] = next.displayName() }
let addedMemberNames: [String] = memberIds
.map {
knownMemberNameMap[$0] ??
Profile.truncated(id: $0, threadVariant: .closedGroup)
}
return String(
format: "GROUP_MEMBER_JOINED".localized(),
@ -387,9 +393,14 @@ public extension ClosedGroupControlMessage.Kind {
var infoMessage: String = ""
if !memberIds.removing(userPublicKey).isEmpty {
let removedMemberNames: [String] = try Profile
let knownMemberNameMap: [String: String] = try Profile
.fetchAll(db, ids: memberIds.removing(userPublicKey))
.map { $0.displayName() }
.reduce(into: [:]) { result, next in result[next.id] = next.displayName() }
let removedMemberNames: [String] = memberIds.removing(userPublicKey)
.map {
knownMemberNameMap[$0] ??
Profile.truncated(id: $0, threadVariant: .closedGroup)
}
let format: String = (removedMemberNames.count > 1 ?
"GROUP_MEMBERS_REMOVED".localized() :
"GROUP_MEMBER_REMOVED".localized()

@ -384,6 +384,8 @@ public final class OpenGroupManager: NSObject {
// Only update the database columns which have changed (this is to prevent the UI from triggering
// updates due to changing database columns to the existing value)
let permissions = OpenGroup.Permissions(roomInfo: pollInfo)
try OpenGroup
.filter(id: openGroup.id)
.updateAll(
@ -412,6 +414,10 @@ public final class OpenGroupManager: NSObject {
(openGroup.infoUpdates != pollInfo.details?.infoUpdates ?
(pollInfo.details?.infoUpdates).map { OpenGroup.Columns.infoUpdates.set(to: $0) } :
nil
),
(openGroup.permissions != permissions ?
OpenGroup.Columns.permissions.set(to: permissions) :
nil
)
].compactMap { $0 }
)

@ -377,7 +377,7 @@ extension MessageReceiver {
.membersRemoved(
members: removedMembers
.asSet()
.subtracting(groupMembers.map { $0.profileId })
.intersection(groupMembers.map { $0.profileId })
.map { Data(hex: $0) }
)
.infoMessage(db, sender: sender),

@ -340,13 +340,14 @@ extension MessageReceiver {
sortId: sortId
)
try reaction.insert(db)
Environment.shared?.notificationsManager.wrappedValue?
.notifyUser(
db,
forReaction: reaction,
in: thread
)
if sender != getUserHexEncodedPublicKey(db) {
Environment.shared?.notificationsManager.wrappedValue?
.notifyUser(
db,
forReaction: reaction,
in: thread
)
}
case .remove:
try Reaction
.filter(Reaction.Columns.interactionId == interactionId)

@ -98,7 +98,7 @@ public class TypingIndicators {
withTimeInterval: (direction == .outgoing ? 3 : 5),
repeats: false
) { _ in
Storage.shared.write { db in
Storage.shared.writeAsync { db in
TypingIndicators.didStopTyping(db, threadId: threadId, direction: direction)
}
}
@ -123,7 +123,7 @@ public class TypingIndicators {
withTimeInterval: 10,
repeats: false
) { [weak self] _ in
Storage.shared.write { db in
Storage.shared.writeAsync { db in
self?.scheduleRefreshCallback(db)
}
}

@ -45,6 +45,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
public static let openGroupRoomTokenKey: SQL = SQL(stringLiteral: CodingKeys.openGroupRoomToken.stringValue)
public static let openGroupProfilePictureDataKey: SQL = SQL(stringLiteral: CodingKeys.openGroupProfilePictureData.stringValue)
public static let openGroupUserCountKey: SQL = SQL(stringLiteral: CodingKeys.openGroupUserCount.stringValue)
public static let openGroupPermissionsKey: SQL = SQL(stringLiteral: CodingKeys.openGroupPermissions.stringValue)
public static let interactionIdKey: SQL = SQL(stringLiteral: CodingKeys.interactionId.stringValue)
public static let interactionVariantKey: SQL = SQL(stringLiteral: CodingKeys.interactionVariant.stringValue)
public static let interactionTimestampMsKey: SQL = SQL(stringLiteral: CodingKeys.interactionTimestampMs.stringValue)
@ -94,6 +95,14 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
public let threadUnreadCount: UInt?
public let threadUnreadMentionCount: UInt?
public var canWrite: Bool {
switch threadVariant {
case .contact: return true
case .closedGroup: return currentUserIsClosedGroupMember == true
case .openGroup: return openGroupPermissions?.contains(.write) ?? false
}
}
// Thread display info
private let contactProfile: Profile?
@ -109,6 +118,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
public let openGroupRoomToken: String?
public let openGroupProfilePictureData: Data?
private let openGroupUserCount: Int?
private let openGroupPermissions: OpenGroup.Permissions?
// Interaction display info
@ -262,6 +272,7 @@ public extension SessionThreadViewModel {
self.openGroupRoomToken = nil
self.openGroupProfilePictureData = nil
self.openGroupUserCount = nil
self.openGroupPermissions = nil
// Interaction display info
@ -320,6 +331,7 @@ public extension SessionThreadViewModel {
openGroupRoomToken: self.openGroupRoomToken,
openGroupProfilePictureData: self.openGroupProfilePictureData,
openGroupUserCount: self.openGroupUserCount,
openGroupPermissions: self.openGroupPermissions,
interactionId: self.interactionId,
interactionVariant: self.interactionVariant,
interactionTimestampMs: self.interactionTimestampMs,
@ -371,6 +383,7 @@ public extension SessionThreadViewModel {
openGroupRoomToken: self.openGroupRoomToken,
openGroupProfilePictureData: self.openGroupProfilePictureData,
openGroupUserCount: self.openGroupUserCount,
openGroupPermissions: self.openGroupPermissions,
interactionId: self.interactionId,
interactionVariant: self.interactionVariant,
interactionTimestampMs: self.interactionTimestampMs,
@ -726,6 +739,7 @@ public extension SessionThreadViewModel {
\(openGroup[.server]) AS \(ViewModel.openGroupServerKey),
\(openGroup[.roomToken]) AS \(ViewModel.openGroupRoomTokenKey),
\(openGroup[.userCount]) AS \(ViewModel.openGroupUserCountKey),
\(openGroup[.permissions]) AS \(ViewModel.openGroupPermissionsKey),
\(Interaction.self).\(ViewModel.interactionIdKey),

@ -232,7 +232,7 @@ public enum Preferences {
// Other
case .messageSent: return "message_sent.aiff"
case .none: return nil
case .none: return "silence.aiff"
}
}

@ -157,18 +157,15 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
notificationContent.badge = NSNumber(value: newBadgeNumber)
CurrentAppContext().appUserDefaults().set(newBadgeNumber, forKey: "currentBadgeNumber")
notificationContent.title = interaction.previewText(db)
notificationContent.title = "Session"
notificationContent.body = ""
let senderName: String = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant)
if messageInfo.state == .permissionDenied {
notificationContent.body = String(
format: "modal_call_missed_tips_explanation".localized(),
SessionThread.displayName(
threadId: thread.id,
variant: thread.variant,
closedGroupName: nil, // Not supported
openGroupName: nil // Not supported
)
senderName
)
}

@ -25,6 +25,9 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
self.contentHandler = contentHandler
self.request = request
// Resume database
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
guard let notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
return self.completeSilenty()
}
@ -237,6 +240,10 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
private func completeSilenty() {
SNLog("Complete silenty")
// Suspend the database
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
self.contentHandler!(.init())
}
@ -298,11 +305,10 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
SNLog("Add remote notification request")
}
private func handleSuccess(for content: UNMutableNotificationContent) {
contentHandler!(content)
}
private func handleFailure(for content: UNMutableNotificationContent) {
// Suspend the database
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
content.body = "You've got a new message"
content.title = "Session"
let userInfo: [String:Any] = [ NotificationServiceExtension.isFromRemoteKey : true ]

@ -220,6 +220,8 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
shareVC?.dismiss(animated: true, completion: nil)
ModalActivityIndicatorViewController.present(fromViewController: shareVC!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in
// Resume database
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
Storage.shared
.writeAsync { [weak self] db -> Promise<Void> in
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else {
@ -271,10 +273,14 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
)
}
.done { [weak self] _ in
// Suspend the database
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
activityIndicator.dismiss { }
self?.shareVC?.shareViewWasCompleted()
}
.catch { [weak self] error in
// Suspend the database
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
activityIndicator.dismiss { }
self?.shareVC?.shareViewFailed(error: error)
}

@ -888,30 +888,36 @@ private final class JobQueue {
// but we want at least 1 second to pass before doing so - the job itself should
// really update it's own 'nextRunTimestamp' (this is just a safety net)
case .recurring where job.nextRunTimestamp <= Date().timeIntervalSince1970:
guard let jobId: Int64 = job.id else { break }
Storage.shared.write { db in
_ = try job
.with(nextRunTimestamp: (Date().timeIntervalSince1970 + 1))
.saved(db)
_ = try Job
.filter(id: jobId)
.updateAll(
db,
Job.Columns.failureCount.set(to: 0),
Job.Columns.nextRunTimestamp.set(to: (Date().timeIntervalSince1970 + 1))
)
}
// For `recurringOnLaunch/Active` jobs which have already run, we want to clear their
// `failureCount` and `nextRunTimestamp` to prevent them from endlessly running over
// and over and reset their retry backoff in case they fail next time
// For `recurringOnLaunch/Active` jobs which have already run but failed once, we need to
// clear their `failureCount` and `nextRunTimestamp` to prevent them from endlessly running
// over and over again
case .recurringOnLaunch, .recurringOnActive:
if
guard
let jobId: Int64 = job.id,
job.failureCount != 0 &&
job.nextRunTimestamp > TimeInterval.leastNonzeroMagnitude
{
Storage.shared.write { db in
_ = try Job
.filter(id: jobId)
.updateAll(
db,
Job.Columns.failureCount.set(to: 0),
Job.Columns.nextRunTimestamp.set(to: 0)
)
}
else { break }
Storage.shared.write { db in
_ = try Job
.filter(id: jobId)
.updateAll(
db,
Job.Columns.failureCount.set(to: 0),
Job.Columns.nextRunTimestamp.set(to: 0)
)
}
default: break

Loading…
Cancel
Save