Cleaned up some logic around sync messages
Added logic to indicate when a sync message failed to send (and the ability to retry) Added the retry/resync button to the long press message menu Updated sync messages to run via the MessageSendJob Updated the delivery status to always show on the last outgoing message Updated the logic to update the delivery status when retrying to send a failed message Removed the convoluted recursion logic for turning self-send messages into sync messages
This commit is contained in:
parent
d020a7a05f
commit
3344e58716
|
@ -34,6 +34,17 @@ extension ContextMenuVC {
|
|||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
static func retry(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(systemName: "arrow.triangle.2.circlepath"),
|
||||
title: (cellViewModel.state == .failedToSync ?
|
||||
"context_menu_resync".localized() :
|
||||
"context_menu_resend".localized()
|
||||
),
|
||||
accessibilityLabel: (cellViewModel.state == .failedToSync ? "Resync message" : "Resend message")
|
||||
) { delegate?.retry(cellViewModel) }
|
||||
}
|
||||
|
||||
static func reply(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
return Action(
|
||||
|
@ -126,6 +137,14 @@ extension ContextMenuVC {
|
|||
case .standardOutgoing, .standardIncoming: break
|
||||
}
|
||||
|
||||
let canRetry: Bool = (
|
||||
cellViewModel.variant == .standardOutgoing && (
|
||||
cellViewModel.state == .failed || (
|
||||
cellViewModel.threadVariant == .contact &&
|
||||
cellViewModel.state == .failedToSync
|
||||
)
|
||||
)
|
||||
)
|
||||
let canReply: Bool = (
|
||||
cellViewModel.variant != .standardOutgoing || (
|
||||
cellViewModel.state != .failed &&
|
||||
|
@ -180,6 +199,7 @@ extension ContextMenuVC {
|
|||
}()
|
||||
|
||||
let generatedActions: [Action] = [
|
||||
(canRetry ? Action.retry(cellViewModel, delegate) : nil),
|
||||
(canReply ? Action.reply(cellViewModel, delegate) : nil),
|
||||
(canCopy ? Action.copy(cellViewModel, delegate) : nil),
|
||||
(canSave ? Action.save(cellViewModel, delegate) : nil),
|
||||
|
@ -201,6 +221,7 @@ extension ContextMenuVC {
|
|||
// MARK: - Delegate
|
||||
|
||||
protocol ContextMenuActionDelegate {
|
||||
func retry(_ cellViewModel: MessageViewModel)
|
||||
func reply(_ cellViewModel: MessageViewModel)
|
||||
func copy(_ cellViewModel: MessageViewModel)
|
||||
func copySessionID(_ cellViewModel: MessageViewModel)
|
||||
|
|
|
@ -7,6 +7,7 @@ import PhotosUI
|
|||
import Sodium
|
||||
import PromiseKit
|
||||
import GRDB
|
||||
import SessionUIKit
|
||||
import SessionMessagingKit
|
||||
import SessionUtilitiesKit
|
||||
import SignalUtilitiesKit
|
||||
|
@ -828,7 +829,7 @@ extension ConversationVC:
|
|||
}
|
||||
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer) {
|
||||
guard cellViewModel.variant != .standardOutgoing || cellViewModel.state != .failed else {
|
||||
guard cellViewModel.variant != .standardOutgoing || (cellViewModel.state != .failed && cellViewModel.state != .failedToSync) else {
|
||||
// Show the failed message sheet
|
||||
showFailedMessageSheet(for: cellViewModel)
|
||||
return
|
||||
|
@ -1450,30 +1451,34 @@ extension ConversationVC:
|
|||
// MARK: --action handling
|
||||
|
||||
func showFailedMessageSheet(for cellViewModel: MessageViewModel) {
|
||||
let sheet = UIAlertController(title: cellViewModel.mostRecentFailureText, message: nil, preferredStyle: .actionSheet)
|
||||
sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
sheet.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { _ in
|
||||
Storage.shared.writeAsync { db in
|
||||
try Interaction
|
||||
.filter(id: cellViewModel.id)
|
||||
.deleteAll(db)
|
||||
}
|
||||
}))
|
||||
sheet.addAction(UIAlertAction(title: "Resend", style: .default, handler: { _ in
|
||||
Storage.shared.writeAsync { [weak self] db in
|
||||
guard
|
||||
let threadId: String = self?.viewModel.threadData.threadId,
|
||||
let interaction: Interaction = try? Interaction.fetchOne(db, id: cellViewModel.id),
|
||||
let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId)
|
||||
else { return }
|
||||
|
||||
try MessageSender.send(
|
||||
db,
|
||||
interaction: interaction,
|
||||
in: thread
|
||||
)
|
||||
}
|
||||
}))
|
||||
let sheet = UIAlertController(
|
||||
title: (cellViewModel.state == .failedToSync ?
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE".localized() :
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE".localized()
|
||||
),
|
||||
message: cellViewModel.mostRecentFailureText,
|
||||
preferredStyle: .actionSheet
|
||||
)
|
||||
sheet.addAction(UIAlertAction(title: "TXT_CANCEL_TITLE".localized(), style: .cancel, handler: nil))
|
||||
|
||||
if cellViewModel.state != .failedToSync {
|
||||
sheet.addAction(UIAlertAction(title: "TXT_DELETE_TITLE".localized(), style: .destructive, handler: { _ in
|
||||
Storage.shared.writeAsync { db in
|
||||
try Interaction
|
||||
.filter(id: cellViewModel.id)
|
||||
.deleteAll(db)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
sheet.addAction(UIAlertAction(
|
||||
title: (cellViewModel.state == .failedToSync ?
|
||||
"context_menu_resync".localized() :
|
||||
"context_menu_resend".localized()
|
||||
),
|
||||
style: .default,
|
||||
handler: { [weak self] _ in self?.retry(cellViewModel) }
|
||||
))
|
||||
|
||||
// HACK: Extracting this info from the error string is pretty dodgy
|
||||
let prefix: String = "HTTP request failed at destination (Service node "
|
||||
|
@ -1557,6 +1562,23 @@ extension ConversationVC:
|
|||
}
|
||||
|
||||
// MARK: - ContextMenuActionDelegate
|
||||
|
||||
func retry(_ cellViewModel: MessageViewModel) {
|
||||
Storage.shared.writeAsync { [weak self] db in
|
||||
guard
|
||||
let threadId: String = self?.viewModel.threadData.threadId,
|
||||
let interaction: Interaction = try? Interaction.fetchOne(db, id: cellViewModel.id),
|
||||
let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId)
|
||||
else { return }
|
||||
|
||||
try MessageSender.send(
|
||||
db,
|
||||
interaction: interaction,
|
||||
in: thread,
|
||||
isSyncMessage: (cellViewModel.state == .failedToSync)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func reply(_ cellViewModel: MessageViewModel) {
|
||||
let maybeQuoteDraft: QuotedReplyModel? = QuotedReplyModel.quotedReplyForSending(
|
||||
|
|
|
@ -298,6 +298,15 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|||
index == (sortedData.count - 1) &&
|
||||
pageInfo.pageOffset == 0
|
||||
),
|
||||
isLastOutgoing: (
|
||||
cellViewModel.id == sortedData
|
||||
.filter {
|
||||
$0.authorId == threadData.currentUserPublicKey ||
|
||||
$0.authorId == threadData.currentUserBlindedPublicKey
|
||||
}
|
||||
.last?
|
||||
.id
|
||||
),
|
||||
currentUserBlindedPublicKey: threadData.currentUserBlindedPublicKey
|
||||
)
|
||||
}
|
||||
|
|
|
@ -431,7 +431,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
cellViewModel.variant == .infoCall ||
|
||||
(
|
||||
cellViewModel.state == .sent &&
|
||||
!cellViewModel.isLast
|
||||
!cellViewModel.isLastOutgoing
|
||||
)
|
||||
)
|
||||
messageStatusLabelPaddingView.isHidden = (
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Reachability
|
||||
import SignalUtilitiesKit
|
||||
import PromiseKit
|
||||
|
|
|
@ -6,6 +6,8 @@ import AFNetworking
|
|||
import Foundation
|
||||
import PromiseKit
|
||||
import CoreServices
|
||||
import SignalUtilitiesKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
// There's no UTI type for webp!
|
||||
enum GiphyFormat {
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -600,4 +600,10 @@
|
|||
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
|
||||
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync";
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing";
|
||||
"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message";
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices";
|
||||
"delete_message_for_me_and_my_devices" = "Delete from all of my devices";
|
||||
"context_menu_resend" = "Resend";
|
||||
"context_menu_resync" = "Resync";
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
import Foundation
|
||||
import UserNotifications
|
||||
import PromiseKit
|
||||
import SignalCoreKit
|
||||
import SignalUtilitiesKit
|
||||
import SessionMessagingKit
|
||||
|
||||
class UserNotificationConfig {
|
||||
|
|
|
@ -40,6 +40,8 @@ public struct RecipientState: Codable, Equatable, FetchableRecord, PersistableRe
|
|||
case failed
|
||||
case skipped
|
||||
case sent
|
||||
case failedToSync // One-to-one Only
|
||||
case syncing // One-to-one Only
|
||||
|
||||
func message(hasAttachments: Bool, hasAtLeastOneReadReceipt: Bool) -> String {
|
||||
switch self {
|
||||
|
@ -58,6 +60,9 @@ public struct RecipientState: Codable, Equatable, FetchableRecord, PersistableRe
|
|||
}
|
||||
|
||||
return "MESSAGE_STATUS_READ".localized()
|
||||
|
||||
case .failedToSync: return "MESSAGE_DELIVERY_STATUS_FAILED_SYNC".localized()
|
||||
case .syncing: return "MESSAGE_DELIVERY_STATUS_SYNCING".localized()
|
||||
|
||||
default:
|
||||
owsFailDebug("Message has unexpected status: \(self).")
|
||||
|
@ -96,6 +101,21 @@ public struct RecipientState: Codable, Equatable, FetchableRecord, PersistableRe
|
|||
"MESSAGE_DELIVERY_STATUS_FAILED".localized(),
|
||||
.danger
|
||||
)
|
||||
|
||||
case (.failedToSync, _):
|
||||
return (
|
||||
UIImage(systemName: "exclamationmark.triangle"),
|
||||
"MESSAGE_DELIVERY_STATUS_FAILED_SYNC".localized(),
|
||||
.warning
|
||||
)
|
||||
|
||||
case (.syncing, _):
|
||||
return (
|
||||
UIImage(systemName: "ellipsis.circle"),
|
||||
"MESSAGE_DELIVERY_STATUS_SYNCING".localized(),
|
||||
.warning
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,21 +168,3 @@ public struct RecipientState: Codable, Equatable, FetchableRecord, PersistableRe
|
|||
self.mostRecentFailureText = mostRecentFailureText
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mutation
|
||||
|
||||
public extension RecipientState {
|
||||
func with(
|
||||
state: State? = nil,
|
||||
readTimestampMs: Int64? = nil,
|
||||
mostRecentFailureText: String? = nil
|
||||
) -> RecipientState {
|
||||
return RecipientState(
|
||||
interactionId: interactionId,
|
||||
recipientId: recipientId,
|
||||
state: (state ?? self.state),
|
||||
readTimestampMs: (readTimestampMs ?? self.readTimestampMs),
|
||||
mostRecentFailureText: (mostRecentFailureText ?? self.mostRecentFailureText)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,16 @@ public enum FailedMessageSendsJob: JobExecutor {
|
|||
) {
|
||||
// Update all 'sending' message states to 'failed'
|
||||
Storage.shared.write { db in
|
||||
let changeCount: Int = try RecipientState
|
||||
let sendChangeCount: Int = try RecipientState
|
||||
.filter(RecipientState.Columns.state == RecipientState.State.sending)
|
||||
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.failed))
|
||||
let syncChangeCount: Int = try RecipientState
|
||||
.filter(RecipientState.Columns.state == RecipientState.State.syncing)
|
||||
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.failedToSync))
|
||||
let attachmentChangeCount: Int = try Attachment
|
||||
.filter(Attachment.Columns.state == Attachment.State.uploading)
|
||||
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedUpload))
|
||||
let changeCount: Int = (sendChangeCount + syncChangeCount)
|
||||
|
||||
SNLog("Marked \(changeCount) message\(changeCount == 1 ? "" : "s") as failed (\(attachmentChangeCount) upload\(attachmentChangeCount == 1 ? "" : "s") cancelled)")
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ public enum MessageReceiveJob: JobExecutor {
|
|||
failure(updatedJob, error, true)
|
||||
|
||||
case .some(let error):
|
||||
failure(updatedJob, error, false) // TODO: Confirm the 'noKeyPair' errors here aren't an issue
|
||||
failure(updatedJob, error, false)
|
||||
|
||||
case .none:
|
||||
success(updatedJob, false)
|
||||
|
|
|
@ -167,7 +167,8 @@ public enum MessageSendJob: JobExecutor {
|
|||
message: details.message,
|
||||
to: details.destination
|
||||
.with(fileIds: messageFileIds),
|
||||
interactionId: job.interactionId
|
||||
interactionId: job.interactionId,
|
||||
isSyncMessage: (details.isSyncMessage == true)
|
||||
)
|
||||
}
|
||||
.done(on: queue) { _ in success(job, false) }
|
||||
|
@ -213,21 +214,25 @@ extension MessageSendJob {
|
|||
private enum CodingKeys: String, CodingKey {
|
||||
case destination
|
||||
case message
|
||||
case isSyncMessage
|
||||
case variant
|
||||
}
|
||||
|
||||
public let destination: Message.Destination
|
||||
public let message: Message
|
||||
public let isSyncMessage: Bool?
|
||||
public let variant: Message.Variant?
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init(
|
||||
destination: Message.Destination,
|
||||
message: Message
|
||||
message: Message,
|
||||
isSyncMessage: Bool? = nil
|
||||
) {
|
||||
self.destination = destination
|
||||
self.message = message
|
||||
self.isSyncMessage = isSyncMessage
|
||||
self.variant = Message.Variant(from: message)
|
||||
}
|
||||
|
||||
|
@ -243,7 +248,8 @@ extension MessageSendJob {
|
|||
|
||||
self = Details(
|
||||
destination: try container.decode(Message.Destination.self, forKey: .destination),
|
||||
message: try variant.decode(from: container, forKey: .message)
|
||||
message: try variant.decode(from: container, forKey: .message),
|
||||
isSyncMessage: try? container.decode(Bool.self, forKey: .isSyncMessage)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -257,6 +263,7 @@ extension MessageSendJob {
|
|||
|
||||
try container.encode(destination, forKey: .destination)
|
||||
try container.encode(message, forKey: .message)
|
||||
try container.encodeIfPresent(isSyncMessage, forKey: .isSyncMessage)
|
||||
try container.encode(variant, forKey: .variant)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ public enum SendReadReceiptsJob: JobExecutor {
|
|||
timestamps: details.timestampMsValues.map { UInt64($0) }
|
||||
),
|
||||
to: details.destination,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
isSyncMessage: false
|
||||
)
|
||||
}
|
||||
.done(on: queue) {
|
||||
|
|
|
@ -177,10 +177,12 @@ public extension Message {
|
|||
}
|
||||
|
||||
static func shouldSync(message: Message) -> Bool {
|
||||
// For 'Note to Self' messages we always want to sync the message
|
||||
guard message.sender != message.recipient else { return true }
|
||||
|
||||
switch message {
|
||||
case is VisibleMessage: return true
|
||||
case is ExpirationTimerUpdate: return true
|
||||
case is ConfigurationMessage: return true
|
||||
case is UnsendRequest: return true
|
||||
|
||||
case let controlMessage as ClosedGroupControlMessage:
|
||||
switch controlMessage.kind {
|
||||
case .new: return true
|
||||
|
@ -192,9 +194,7 @@ public extension Message {
|
|||
case .answer, .endCall: return true
|
||||
default: return false
|
||||
}
|
||||
|
||||
case is ConfigurationMessage: return true
|
||||
case is UnsendRequest: return true
|
||||
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,13 +139,6 @@ extension MessageReceiver {
|
|||
return recipientParts[2]
|
||||
}()
|
||||
).inserted(db)
|
||||
|
||||
// If the message was an outgoing message then immediately update the recipient state to 'sent'
|
||||
if variant == .standardOutgoing, let interactionId: Int64 = interaction.id {
|
||||
_ = try? RecipientState
|
||||
.filter(RecipientState.Columns.interactionId == interactionId)
|
||||
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.sent))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
switch error {
|
||||
|
@ -375,6 +368,12 @@ extension MessageReceiver {
|
|||
) throws {
|
||||
guard variant == .standardOutgoing else { return }
|
||||
|
||||
// Immediately update any existing outgoing message 'RecipientState' records to be 'sent'
|
||||
_ = try? RecipientState
|
||||
.filter(RecipientState.Columns.interactionId == interactionId)
|
||||
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.sent))
|
||||
|
||||
// Create any addiitonal 'RecipientState' records as needed
|
||||
switch thread.variant {
|
||||
case .contact:
|
||||
if let syncTarget: String = syncTarget {
|
||||
|
|
|
@ -9,7 +9,7 @@ extension MessageSender {
|
|||
|
||||
// MARK: - Durable
|
||||
|
||||
public static func send(_ db: Database, interaction: Interaction, with attachments: [SignalAttachment], in thread: SessionThread) throws {
|
||||
public static func send(_ db: Database, interaction: Interaction, with attachments: [SignalAttachment], in thread: SessionThread, isSyncMessage: Bool = false) throws {
|
||||
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
|
||||
|
||||
try prep(db, signalAttachments: attachments, for: interactionId)
|
||||
|
@ -18,11 +18,12 @@ extension MessageSender {
|
|||
message: VisibleMessage.from(db, interaction: interaction),
|
||||
threadId: thread.id,
|
||||
interactionId: interactionId,
|
||||
to: try Message.Destination.from(db, thread: thread)
|
||||
to: try Message.Destination.from(db, thread: thread),
|
||||
isSyncMessage: isSyncMessage
|
||||
)
|
||||
}
|
||||
|
||||
public static func send(_ db: Database, interaction: Interaction, in thread: SessionThread) throws {
|
||||
public static func send(_ db: Database, interaction: Interaction, in thread: SessionThread, isSyncMessage: Bool = false) throws {
|
||||
// Only 'VisibleMessage' types can be sent via this method
|
||||
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
||||
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
|
||||
|
@ -32,21 +33,37 @@ extension MessageSender {
|
|||
message: VisibleMessage.from(db, interaction: interaction),
|
||||
threadId: thread.id,
|
||||
interactionId: interactionId,
|
||||
to: try Message.Destination.from(db, thread: thread)
|
||||
to: try Message.Destination.from(db, thread: thread),
|
||||
isSyncMessage: isSyncMessage
|
||||
)
|
||||
}
|
||||
|
||||
public static func send(_ db: Database, message: Message, interactionId: Int64?, in thread: SessionThread) throws {
|
||||
public static func send(_ db: Database, message: Message, interactionId: Int64?, in thread: SessionThread, isSyncMessage: Bool = false) throws {
|
||||
send(
|
||||
db,
|
||||
message: message,
|
||||
threadId: thread.id,
|
||||
interactionId: interactionId,
|
||||
to: try Message.Destination.from(db, thread: thread)
|
||||
to: try Message.Destination.from(db, thread: thread),
|
||||
isSyncMessage: isSyncMessage
|
||||
)
|
||||
}
|
||||
|
||||
public static func send(_ db: Database, message: Message, threadId: String?, interactionId: Int64?, to destination: Message.Destination) {
|
||||
public static func send(_ db: Database, message: Message, threadId: String?, interactionId: Int64?, to destination: Message.Destination, isSyncMessage: Bool = false) {
|
||||
// If it's a sync message then we need to make some slight tweaks before sending so use the proper
|
||||
// sync message sending process instead of the standard process
|
||||
guard !isSyncMessage else {
|
||||
scheduleSyncMessageIfNeeded(
|
||||
db,
|
||||
message: message,
|
||||
destination: destination,
|
||||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
isAlreadySyncMessage: false
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
JobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
|
@ -55,7 +72,8 @@ extension MessageSender {
|
|||
interactionId: interactionId,
|
||||
details: MessageSendJob.Details(
|
||||
destination: destination,
|
||||
message: message
|
||||
message: message,
|
||||
isSyncMessage: isSyncMessage
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -179,7 +197,8 @@ extension MessageSender {
|
|||
message: message,
|
||||
to: destination
|
||||
.with(fileIds: fileIds),
|
||||
interactionId: interactionId
|
||||
interactionId: interactionId,
|
||||
isSyncMessage: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +219,7 @@ extension MessageSender {
|
|||
|
||||
if forceSyncNow {
|
||||
try MessageSender
|
||||
.sendImmediate(db, message: configurationMessage, to: destination, interactionId: nil)
|
||||
.sendImmediate(db, message: configurationMessage, to: destination, interactionId: nil, isSyncMessage: false)
|
||||
.done { seal.fulfill(()) }
|
||||
.catch { _ in seal.reject(StorageError.generic) }
|
||||
.retainUntilComplete()
|
||||
|
|
|
@ -42,10 +42,16 @@ public final class MessageSender {
|
|||
|
||||
// MARK: - Convenience
|
||||
|
||||
public static func sendImmediate(_ db: Database, message: Message, to destination: Message.Destination, interactionId: Int64?) throws -> Promise<Void> {
|
||||
public static func sendImmediate(
|
||||
_ db: Database,
|
||||
message: Message,
|
||||
to destination: Message.Destination,
|
||||
interactionId: Int64?,
|
||||
isSyncMessage: Bool
|
||||
) throws -> Promise<Void> {
|
||||
switch destination {
|
||||
case .contact, .closedGroup:
|
||||
return try sendToSnodeDestination(db, message: message, to: destination, interactionId: interactionId)
|
||||
return try sendToSnodeDestination(db, message: message, to: destination, interactionId: interactionId, isSyncMessage: isSyncMessage)
|
||||
|
||||
case .openGroup:
|
||||
return sendToOpenGroupDestination(db, message: message, to: destination, interactionId: interactionId)
|
||||
|
@ -65,7 +71,7 @@ public final class MessageSender {
|
|||
isSyncMessage: Bool = false
|
||||
) throws -> Promise<Void> {
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let messageSendTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
|
||||
|
||||
// Set the timestamp, sender and recipient
|
||||
|
@ -73,7 +79,7 @@ public final class MessageSender {
|
|||
message.sentTimestamp ?? // Visible messages will already have their sent timestamp set
|
||||
UInt64(messageSendTimestamp)
|
||||
)
|
||||
message.sender = userPublicKey
|
||||
message.sender = currentUserPublicKey
|
||||
message.recipient = {
|
||||
switch destination {
|
||||
case .contact(let publicKey): return publicKey
|
||||
|
@ -84,7 +90,7 @@ public final class MessageSender {
|
|||
|
||||
// Set the failure handler (need it here already for precondition failure handling)
|
||||
func handleFailure(_ db: Database, with error: MessageSenderError) {
|
||||
MessageSender.handleFailedMessageSend(db, message: message, with: error, interactionId: interactionId)
|
||||
MessageSender.handleFailedMessageSend(db, message: message, with: error, interactionId: interactionId, isSyncMessage: isSyncMessage)
|
||||
seal.reject(error)
|
||||
}
|
||||
|
||||
|
@ -94,21 +100,8 @@ public final class MessageSender {
|
|||
return promise
|
||||
}
|
||||
|
||||
// Stop here if this is a self-send, unless we should sync the message
|
||||
let isSelfSend: Bool = (message.recipient == userPublicKey)
|
||||
|
||||
guard
|
||||
!isSelfSend ||
|
||||
isSyncMessage ||
|
||||
Message.shouldSync(message: message)
|
||||
else {
|
||||
try MessageSender.handleSuccessfulMessageSend(db, message: message, to: destination, interactionId: interactionId)
|
||||
seal.fulfill(())
|
||||
return promise
|
||||
}
|
||||
|
||||
// Attach the user's profile if needed
|
||||
if var messageWithProfile: MessageWithProfile = message as? MessageWithProfile {
|
||||
if !isSyncMessage, var messageWithProfile: MessageWithProfile = message as? MessageWithProfile {
|
||||
let profile: Profile = Profile.fetchOrCreateCurrentUser(db)
|
||||
|
||||
if let profileKey: Data = profile.profileEncryptionKey?.keyData, let profilePictureUrl: String = profile.profilePictureUrl {
|
||||
|
@ -123,6 +116,9 @@ public final class MessageSender {
|
|||
}
|
||||
}
|
||||
|
||||
// Perform any pre-send actions
|
||||
handleMessageWillSend(db, message: message, interactionId: interactionId, isSyncMessage: isSyncMessage)
|
||||
|
||||
// Convert it to protobuf
|
||||
guard let proto = message.toProto(db) else {
|
||||
handleFailure(db, with: .protoConversionFailed)
|
||||
|
@ -233,6 +229,9 @@ public final class MessageSender {
|
|||
)
|
||||
|
||||
let shouldNotify: Bool = {
|
||||
// Don't send a notification when sending messages in 'Note to Self'
|
||||
guard message.recipient != currentUserPublicKey else { return false }
|
||||
|
||||
switch message {
|
||||
case is VisibleMessage, is UnsendRequest: return !isSyncMessage
|
||||
case let callMessage as CallMessage:
|
||||
|
@ -402,6 +401,9 @@ public final class MessageSender {
|
|||
return promise
|
||||
}
|
||||
|
||||
// Perform any pre-send actions
|
||||
handleMessageWillSend(db, message: message, interactionId: interactionId)
|
||||
|
||||
// Convert it to protobuf
|
||||
guard let proto = message.toProto(db) else {
|
||||
handleFailure(db, with: .protoConversionFailed)
|
||||
|
@ -465,7 +467,7 @@ public final class MessageSender {
|
|||
dependencies: SMKDependencies = SMKDependencies()
|
||||
) -> Promise<Void> {
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
|
||||
guard case .openGroupInbox(let server, let openGroupPublicKey, let recipientBlindedPublicKey) = destination else {
|
||||
preconditionFailure()
|
||||
|
@ -476,7 +478,7 @@ public final class MessageSender {
|
|||
message.sentTimestamp = UInt64(SnodeAPI.currentOffsetTimestampMs())
|
||||
}
|
||||
|
||||
message.sender = userPublicKey
|
||||
message.sender = currentUserPublicKey
|
||||
message.recipient = recipientBlindedPublicKey
|
||||
|
||||
// Set the failure handler (need it here already for precondition failure handling)
|
||||
|
@ -501,6 +503,9 @@ public final class MessageSender {
|
|||
}
|
||||
}
|
||||
|
||||
// Perform any pre-send actions
|
||||
handleMessageWillSend(db, message: message, interactionId: interactionId)
|
||||
|
||||
// Convert it to protobuf
|
||||
guard let proto = message.toProto(db) else {
|
||||
handleFailure(db, with: .protoConversionFailed)
|
||||
|
@ -569,6 +574,32 @@ public final class MessageSender {
|
|||
|
||||
// MARK: Success & Failure Handling
|
||||
|
||||
public static func handleMessageWillSend(
|
||||
_ db: Database,
|
||||
message: Message,
|
||||
interactionId: Int64?,
|
||||
isSyncMessage: Bool = false
|
||||
) {
|
||||
// If the message was a reaction then we don't want to do anything to the original
|
||||
// interaction (which the 'interactionId' is pointing to
|
||||
guard (message as? VisibleMessage)?.reaction == nil else { return }
|
||||
|
||||
// Mark messages as "sending"/"syncing" if needed (this is for retries)
|
||||
_ = try? RecipientState
|
||||
.filter(RecipientState.Columns.interactionId == interactionId)
|
||||
.filter(isSyncMessage ?
|
||||
RecipientState.Columns.state == RecipientState.State.failedToSync :
|
||||
RecipientState.Columns.state == RecipientState.State.failed
|
||||
)
|
||||
.updateAll(
|
||||
db,
|
||||
RecipientState.Columns.state.set(to: isSyncMessage ?
|
||||
RecipientState.State.syncing :
|
||||
RecipientState.State.sending
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private static func handleSuccessfulMessageSend(
|
||||
_ db: Database,
|
||||
message: Message,
|
||||
|
@ -578,7 +609,7 @@ public final class MessageSender {
|
|||
isSyncMessage: Bool = false
|
||||
) throws {
|
||||
// If the message was a reaction then we want to update the reaction instead of the original
|
||||
// interaciton (which the 'interactionId' is pointing to
|
||||
// interaction (which the 'interactionId' is pointing to
|
||||
if let visibleMessage: VisibleMessage = message as? VisibleMessage, let reaction: VisibleMessage.VMReaction = visibleMessage.reaction {
|
||||
try Reaction
|
||||
.filter(Reaction.Columns.interactionId == interactionId)
|
||||
|
@ -624,18 +655,20 @@ public final class MessageSender {
|
|||
}
|
||||
}
|
||||
|
||||
let threadId: String = {
|
||||
switch destination {
|
||||
case .contact(let publicKey): return publicKey
|
||||
case .closedGroup(let groupPublicKey): return groupPublicKey
|
||||
case .openGroup(let roomToken, let server, _, _, _):
|
||||
return OpenGroup.idFor(roomToken: roomToken, server: server)
|
||||
|
||||
case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey
|
||||
}
|
||||
}()
|
||||
|
||||
// Prevent ControlMessages from being handled multiple times if not supported
|
||||
try? ControlMessageProcessRecord(
|
||||
threadId: {
|
||||
switch destination {
|
||||
case .contact(let publicKey): return publicKey
|
||||
case .closedGroup(let groupPublicKey): return groupPublicKey
|
||||
case .openGroup(let roomToken, let server, _, _, _):
|
||||
return OpenGroup.idFor(roomToken: roomToken, server: server)
|
||||
|
||||
case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey
|
||||
}
|
||||
}(),
|
||||
threadId: threadId,
|
||||
message: message,
|
||||
serverExpirationTimestamp: (
|
||||
(TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) +
|
||||
|
@ -643,36 +676,27 @@ public final class MessageSender {
|
|||
)
|
||||
)?.insert(db)
|
||||
|
||||
// Sync the message if:
|
||||
// • it's a visible message or an expiration timer update
|
||||
// • the destination was a contact
|
||||
// • we didn't sync it already
|
||||
// • it wasn't set to 'Note to Self'
|
||||
let userPublicKey = getUserHexEncodedPublicKey(db)
|
||||
if case .contact(let publicKey) = destination, !isSyncMessage, publicKey != userPublicKey {
|
||||
if let message = message as? VisibleMessage { message.syncTarget = publicKey }
|
||||
if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey }
|
||||
|
||||
// FIXME: Make this a job
|
||||
try sendToSnodeDestination(
|
||||
db,
|
||||
message: message,
|
||||
to: .contact(publicKey: userPublicKey),
|
||||
interactionId: interactionId,
|
||||
isSyncMessage: true
|
||||
).retainUntilComplete()
|
||||
}
|
||||
// Sync the message if needed
|
||||
scheduleSyncMessageIfNeeded(
|
||||
db,
|
||||
message: message,
|
||||
destination: destination,
|
||||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
isAlreadySyncMessage: isSyncMessage
|
||||
)
|
||||
}
|
||||
|
||||
public static func handleFailedMessageSend(
|
||||
_ db: Database,
|
||||
message: Message,
|
||||
with error: MessageSenderError,
|
||||
interactionId: Int64?
|
||||
interactionId: Int64?,
|
||||
isSyncMessage: Bool = false
|
||||
) {
|
||||
// TODO: Revert the local database change
|
||||
// If the message was a reaction then we don't want to do anything to the original
|
||||
// interaciton (which the 'interactionId' is pointing to
|
||||
// interaction (which the 'interactionId' is pointing to
|
||||
guard (message as? VisibleMessage)?.reaction == nil else { return }
|
||||
|
||||
// Check if we need to mark any "sending" recipients as "failed"
|
||||
|
@ -683,7 +707,12 @@ public final class MessageSender {
|
|||
let rowIds: [Int64] = (try? RecipientState
|
||||
.select(Column.rowID)
|
||||
.filter(RecipientState.Columns.interactionId == interactionId)
|
||||
.filter(RecipientState.Columns.state == RecipientState.State.sending)
|
||||
.filter(!isSyncMessage ?
|
||||
RecipientState.Columns.state == RecipientState.State.sending : (
|
||||
RecipientState.Columns.state == RecipientState.State.syncing ||
|
||||
RecipientState.Columns.state == RecipientState.State.sent
|
||||
)
|
||||
)
|
||||
.asRequest(of: Int64.self)
|
||||
.fetchAll(db))
|
||||
.defaulting(to: [])
|
||||
|
@ -698,7 +727,9 @@ public final class MessageSender {
|
|||
.filter(rowIds.contains(Column.rowID))
|
||||
.updateAll(
|
||||
db,
|
||||
RecipientState.Columns.state.set(to: RecipientState.State.failed),
|
||||
RecipientState.Columns.state.set(
|
||||
to: (isSyncMessage ? RecipientState.State.failedToSync : RecipientState.State.failed)
|
||||
),
|
||||
RecipientState.Columns.mostRecentFailureText.set(to: error.localizedDescription)
|
||||
)
|
||||
}
|
||||
|
@ -720,6 +751,43 @@ public final class MessageSender {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
public static func scheduleSyncMessageIfNeeded(
|
||||
_ db: Database,
|
||||
message: Message,
|
||||
destination: Message.Destination,
|
||||
threadId: String?,
|
||||
interactionId: Int64?,
|
||||
isAlreadySyncMessage: Bool
|
||||
) {
|
||||
// Sync the message if it's not a sync message, wasn't already sent to the current user and
|
||||
// it's a message type which should be synced
|
||||
let currentUserPublicKey = getUserHexEncodedPublicKey(db)
|
||||
|
||||
if
|
||||
case .contact(let publicKey) = destination,
|
||||
!isAlreadySyncMessage,
|
||||
publicKey != currentUserPublicKey,
|
||||
Message.shouldSync(message: message)
|
||||
{
|
||||
if let message = message as? VisibleMessage { message.syncTarget = publicKey }
|
||||
if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey }
|
||||
|
||||
JobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .messageSend,
|
||||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
details: MessageSendJob.Details(
|
||||
destination: .contact(publicKey: currentUserPublicKey),
|
||||
message: message,
|
||||
isSyncMessage: true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Objective-C Support
|
||||
|
|
|
@ -39,6 +39,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
|
|||
public static let positionInClusterKey: SQL = SQL(stringLiteral: CodingKeys.positionInCluster.stringValue)
|
||||
public static let isOnlyMessageInClusterKey: SQL = SQL(stringLiteral: CodingKeys.isOnlyMessageInCluster.stringValue)
|
||||
public static let isLastKey: SQL = SQL(stringLiteral: CodingKeys.isLast.stringValue)
|
||||
public static let isLastOutgoingKey: SQL = SQL(stringLiteral: CodingKeys.isLastOutgoing.stringValue)
|
||||
|
||||
public static let profileString: String = CodingKeys.profile.stringValue
|
||||
public static let quoteString: String = CodingKeys.quote.stringValue
|
||||
|
@ -140,6 +141,8 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
|
|||
/// This value indicates whether this is the last message in the thread
|
||||
public let isLast: Bool
|
||||
|
||||
public let isLastOutgoing: Bool
|
||||
|
||||
/// This is the users blinded key (will only be set for messages within open groups)
|
||||
public let currentUserBlindedPublicKey: String?
|
||||
|
||||
|
@ -191,6 +194,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
|
|||
positionInCluster: self.positionInCluster,
|
||||
isOnlyMessageInCluster: self.isOnlyMessageInCluster,
|
||||
isLast: self.isLast,
|
||||
isLastOutgoing: self.isLastOutgoing,
|
||||
currentUserBlindedPublicKey: self.currentUserBlindedPublicKey
|
||||
)
|
||||
}
|
||||
|
@ -199,6 +203,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
|
|||
prevModel: MessageViewModel?,
|
||||
nextModel: MessageViewModel?,
|
||||
isLast: Bool,
|
||||
isLastOutgoing: Bool,
|
||||
currentUserBlindedPublicKey: String?
|
||||
) -> MessageViewModel {
|
||||
let cellType: CellType = {
|
||||
|
@ -403,6 +408,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
|
|||
positionInCluster: positionInCluster,
|
||||
isOnlyMessageInCluster: isOnlyMessageInCluster,
|
||||
isLast: isLast,
|
||||
isLastOutgoing: isLastOutgoing,
|
||||
currentUserBlindedPublicKey: currentUserBlindedPublicKey
|
||||
)
|
||||
}
|
||||
|
@ -498,7 +504,8 @@ public extension MessageViewModel {
|
|||
quote: Quote? = nil,
|
||||
cellType: CellType = .typingIndicator,
|
||||
isTypingIndicator: Bool? = nil,
|
||||
isLast: Bool = true
|
||||
isLast: Bool = true,
|
||||
isLastOutgoing: Bool = false
|
||||
) {
|
||||
self.threadId = "INVALID_THREAD_ID"
|
||||
self.threadVariant = .contact
|
||||
|
@ -554,6 +561,7 @@ public extension MessageViewModel {
|
|||
self.positionInCluster = .middle
|
||||
self.isOnlyMessageInCluster = true
|
||||
self.isLast = isLast
|
||||
self.isLastOutgoing = isLastOutgoing
|
||||
self.currentUserBlindedPublicKey = nil
|
||||
}
|
||||
}
|
||||
|
@ -700,7 +708,8 @@ public extension MessageViewModel {
|
|||
false AS \(ViewModel.shouldShowDateHeaderKey),
|
||||
\(Position.middle) AS \(ViewModel.positionInClusterKey),
|
||||
false AS \(ViewModel.isOnlyMessageInClusterKey),
|
||||
false AS \(ViewModel.isLastKey)
|
||||
false AS \(ViewModel.isLastKey),
|
||||
false AS \(ViewModel.isLastOutgoingKey)
|
||||
|
||||
FROM \(Interaction.self)
|
||||
JOIN \(SessionThread.self) ON \(thread[.id]) = \(interaction[.threadId])
|
||||
|
|
|
@ -10,6 +10,7 @@ internal enum Theme_ClassicDark: ThemeColors {
|
|||
.clear: .clear,
|
||||
.primary: .primary,
|
||||
.defaultPrimary: Theme.PrimaryColor.green.color,
|
||||
.warning: .warning,
|
||||
.danger: .dangerDark,
|
||||
.disabled: .disabledDark,
|
||||
.backgroundPrimary: .classicDark0,
|
||||
|
|
|
@ -10,6 +10,7 @@ internal enum Theme_ClassicLight: ThemeColors {
|
|||
.clear: .clear,
|
||||
.primary: .primary,
|
||||
.defaultPrimary: Theme.PrimaryColor.green.color,
|
||||
.warning: .warning,
|
||||
.danger: .dangerLight,
|
||||
.disabled: .disabledLight,
|
||||
.backgroundPrimary: .classicLight6,
|
||||
|
|
|
@ -41,6 +41,7 @@ public extension Theme {
|
|||
// MARK: - Standard Theme Colors
|
||||
|
||||
internal extension UIColor {
|
||||
static let warning: UIColor = #colorLiteral(red: 0.9882352941, green: 0.6941176471, blue: 0.3490196078, alpha: 1) // #FCB159
|
||||
static let dangerDark: UIColor = #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1) // #FF3A3A
|
||||
static let dangerLight: UIColor = #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1) // #E12D19
|
||||
static let disabledDark: UIColor = #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1) // #A1A2A1
|
||||
|
|
|
@ -10,6 +10,7 @@ internal enum Theme_OceanDark: ThemeColors {
|
|||
.clear: .clear,
|
||||
.primary: .primary,
|
||||
.defaultPrimary: Theme.PrimaryColor.blue.color,
|
||||
.warning: .warning,
|
||||
.danger: .dangerDark,
|
||||
.disabled: .disabledDark,
|
||||
.backgroundPrimary: .oceanDark2,
|
||||
|
|
|
@ -10,6 +10,7 @@ internal enum Theme_OceanLight: ThemeColors {
|
|||
.clear: .clear,
|
||||
.primary: .primary,
|
||||
.defaultPrimary: Theme.PrimaryColor.blue.color,
|
||||
.warning: .warning,
|
||||
.danger: .dangerLight,
|
||||
.disabled: .disabledLight,
|
||||
.backgroundPrimary: .oceanLight7,
|
||||
|
|
|
@ -98,6 +98,7 @@ public indirect enum ThemeValue: Hashable {
|
|||
case clear
|
||||
case primary
|
||||
case defaultPrimary
|
||||
case warning
|
||||
case danger
|
||||
case disabled
|
||||
case backgroundPrimary
|
||||
|
|
Loading…
Reference in New Issue