Merge branch 'dev' into disappearing-message-redesign

pull/941/head
Ryan Zhao 3 years ago
commit 494e12adb6

@ -6056,7 +6056,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 391;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6081,7 +6081,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.4;
MARKETING_VERSION = 2.2.6;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -6129,7 +6129,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 391;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -6159,7 +6159,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.4;
MARKETING_VERSION = 2.2.6;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -6195,7 +6195,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 391;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6218,7 +6218,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.4;
MARKETING_VERSION = 2.2.6;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -6269,7 +6269,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 391;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -6297,7 +6297,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.4;
MARKETING_VERSION = 2.2.6;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -7197,7 +7197,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 391;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -7236,7 +7236,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 2.2.4;
MARKETING_VERSION = 2.2.6;
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -7269,7 +7269,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 389;
CURRENT_PROJECT_VERSION = 391;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -7308,7 +7308,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 2.2.4;
MARKETING_VERSION = 2.2.6;
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session;

@ -318,12 +318,20 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
}
}()
guard shouldMarkAsRead else { return }
guard
shouldMarkAsRead,
let threadVariant: SessionThread.Variant = try? SessionThread
.filter(id: interaction.threadId)
.select(.variant)
.asRequest(of: SessionThread.Variant.self)
.fetchOne(db)
else { return }
try Interaction.markAsRead(
db,
interactionId: interaction.id,
threadId: interaction.threadId,
threadVariant: threadVariant,
includingOlder: false,
trySendReadReceipt: false
)

@ -9,15 +9,17 @@ import SessionMessagingKit
import SignalUtilitiesKit
private protocol TableViewTouchDelegate {
func tableViewWasTouched(_ tableView: TableView)
func tableViewWasTouched(_ tableView: TableView, withView hitView: UIView?)
}
private final class TableView: UITableView {
var touchDelegate: TableViewTouchDelegate?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
touchDelegate?.tableViewWasTouched(self)
return super.hitTest(point, with: event)
let resultingView: UIView? = super.hitTest(point, with: event)
touchDelegate?.tableViewWasTouched(self, withView: resultingView)
return resultingView
}
}
@ -275,10 +277,23 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
)
}
fileprivate func tableViewWasTouched(_ tableView: TableView) {
fileprivate func tableViewWasTouched(_ tableView: TableView, withView hitView: UIView?) {
if nameTextField.isFirstResponder {
nameTextField.resignFirstResponder()
}
else if searchBar.isFirstResponder {
var hitSuperview: UIView? = hitView?.superview
while hitSuperview != nil && hitSuperview != searchBar {
hitSuperview = hitSuperview?.superview
}
// If the user hit the cancel button then do nothing (we want to let the cancel
// button remove the focus or it will instantly refocus)
if hitSuperview == searchBar { return }
searchBar.resignFirstResponder()
}
}
@objc private func close() {

@ -110,6 +110,8 @@ extension ContextMenuVC {
static func actions(
for cellViewModel: MessageViewModel,
recentEmojis: [EmojiWithSkinTones],
currentUserPublicKey: String,
currentUserBlindedPublicKey: String?,
currentUserIsOpenGroupModerator: Bool,
currentThreadIsMessageRequest: Bool,
delegate: ContextMenuActionDelegate?
@ -163,6 +165,8 @@ extension ContextMenuVC {
let canDelete: Bool = (
cellViewModel.threadVariant != .openGroup ||
currentUserIsOpenGroupModerator ||
cellViewModel.authorId == currentUserPublicKey ||
cellViewModel.authorId == currentUserBlindedPublicKey ||
cellViewModel.state == .failed
)
let canBan: Bool = (

@ -817,6 +817,8 @@ extension ConversationVC:
let actions: [ContextMenuVC.Action] = ContextMenuVC.actions(
for: cellViewModel,
recentEmojis: (self.viewModel.threadData.recentReactionEmoji ?? []).compactMap { EmojiWithSkinTones(rawValue: $0) },
currentUserPublicKey: self.viewModel.threadData.currentUserPublicKey,
currentUserBlindedPublicKey: self.viewModel.threadData.currentUserBlindedPublicKey,
currentUserIsOpenGroupModerator: OpenGroupManager.isUserModeratorOrAdmin(
self.viewModel.threadData.currentUserPublicKey,
for: self.viewModel.threadData.openGroupRoomToken,

@ -765,11 +765,17 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
// Store the 'sentMessageBeforeUpdate' state locally
let didSendMessageBeforeUpdate: Bool = self.viewModel.sentMessageBeforeUpdate
let wasOnlyUpdates: Bool = (
changeset.count == 1 &&
changeset[0].elementUpdated.count == changeset[0].changeCount
)
self.viewModel.sentMessageBeforeUpdate = false
// When sending a message we want to reload the UI instantly (with any form of animation the message
// sending feels somewhat unresponsive but an instant update feels snappy)
guard !didSendMessageBeforeUpdate else {
// When sending a message, or if there were only cell updates (ie. read status changes) we want to
// reload the UI instantly (with any form of animation the message sending feels somewhat unresponsive
// but an instant update feels snappy and without the instant update there is some overlap of the read
// status text change even though there shouldn't be any animations)
guard !didSendMessageBeforeUpdate && !wasOnlyUpdates else {
self.viewModel.updateInteractionData(updatedData)
self.tableView.reloadData()

@ -116,6 +116,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
}
)
)
.populatingCurrentUserBlindedKey()
/// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise
/// performance https://github.com/groue/GRDB.swift#valueobservation-performance
@ -131,7 +132,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
private func setupObservableThreadData(for threadId: String) -> ValueObservation<ValueReducers.RemoveDuplicates<ValueReducers.Fetch<SessionThreadViewModel?>>> {
return ValueObservation
.trackingConstantRegion { db -> SessionThreadViewModel? in
.trackingConstantRegion { [weak self] db -> SessionThreadViewModel? in
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let recentReactionEmoji: [String] = try Emoji.getRecent(db, withDefaultEmoji: true)
let threadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
@ -141,6 +142,11 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
return threadViewModel
.map { $0.with(recentReactionEmoji: recentReactionEmoji) }
.map { viewModel -> SessionThreadViewModel in
viewModel.populatingCurrentUserBlindedKey(
currentUserBlindedPublicKeyForThisThread: self?.threadData.currentUserBlindedPublicKey
)
}
}
.removeDuplicates()
}
@ -199,7 +205,17 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
return SQL("LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])")
}()
)
),
PagedData.ObservedChanges(
table: RecipientState.self,
columns: [.state, .mostRecentFailureText],
joinToPagedType: {
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
let recipientState: TypedTableAlias<RecipientState> = TypedTableAlias()
return SQL("LEFT JOIN \(RecipientState.self) ON \(recipientState[.interactionId]) = \(interaction[.id])")
}()
),
],
filterSQL: MessageViewModel.filterSQL(threadId: threadId),
groupSQL: MessageViewModel.groupSQL,
@ -407,6 +423,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
else { return }
let threadId: String = self.threadData.threadId
let threadVariant: SessionThread.Variant = self.threadData.threadVariant
let trySendReadReceipt: Bool = (self.threadData.threadIsMessageRequest == false)
self.lastInteractionIdMarkedAsRead = targetInteractionId
@ -415,6 +432,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
db,
interactionId: targetInteractionId,
threadId: threadId,
threadVariant: threadVariant,
includingOlder: true,
trySendReadReceipt: trySendReadReceipt
)

@ -142,18 +142,35 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
private lazy var reactionContainerView = ReactionContainerView()
internal lazy var messageStatusContainerView: UIView = {
let result = UIView()
return result
}()
internal lazy var messageStatusLabel: UILabel = {
let result = UILabel()
result.accessibilityLabel = "Message sent status"
result.font = .systemFont(ofSize: Values.verySmallFontSize)
result.themeTextColor = .messageBubble_deliveryStatus
return result
}()
internal lazy var messageStatusImageView: UIImageView = {
let result = UIImageView()
result.accessibilityLabel = "Message sent status tick"
result.contentMode = .scaleAspectFit
result.layer.cornerRadius = VisibleMessageCell.messageStatusImageViewSize / 2
result.layer.masksToBounds = true
result.themeTintColor = .messageBubble_deliveryStatus
return result
}()
internal lazy var messageStatusLabelPaddingView: UIView = UIView()
// MARK: - Settings
private static let messageStatusImageViewSize: CGFloat = 16
private static let messageStatusImageViewSize: CGFloat = 12
private static let authorLabelBottomSpacing: CGFloat = 4
private static let groupThreadHSpacing: CGFloat = 12
private static let profilePictureSize = Values.verySmallProfilePictureSize
@ -236,13 +253,25 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
underBubbleStackView.pin(.bottom, to: .bottom, of: self)
underBubbleStackView.addArrangedSubview(reactionContainerView)
underBubbleStackView.addArrangedSubview(messageStatusImageView)
underBubbleStackView.addArrangedSubview(messageStatusContainerView)
underBubbleStackView.addArrangedSubview(messageStatusLabelPaddingView)
messageStatusContainerView.addSubview(messageStatusLabel)
messageStatusContainerView.addSubview(messageStatusImageView)
reactionContainerView.widthAnchor
.constraint(lessThanOrEqualTo: underBubbleStackView.widthAnchor)
.isActive = true
messageStatusImageView.pin(.top, to: .top, of: messageStatusContainerView)
messageStatusImageView.pin(.bottom, to: .bottom, of: messageStatusContainerView)
messageStatusImageView.pin(.trailing, to: .trailing, of: messageStatusContainerView)
messageStatusImageView.set(.width, to: VisibleMessageCell.messageStatusImageViewSize)
messageStatusImageView.set(.height, to: VisibleMessageCell.messageStatusImageViewSize)
messageStatusLabel.center(.vertical, in: messageStatusContainerView)
messageStatusLabel.pin(.leading, to: .leading, of: messageStatusContainerView)
messageStatusLabel.pin(.trailing, to: .leading, of: messageStatusImageView, withInset: -2)
messageStatusLabelPaddingView.pin(.leading, to: .leading, of: messageStatusContainerView)
messageStatusLabelPaddingView.pin(.trailing, to: .trailing, of: messageStatusContainerView)
}
override func setUpGestureRecognizers() {
@ -389,13 +418,15 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
)
// Message status image view
let (image, tintColor) = cellViewModel.state.statusIconInfo(
let (image, statusText, tintColor) = cellViewModel.state.statusIconInfo(
variant: cellViewModel.variant,
hasAtLeastOneReadReceipt: cellViewModel.hasAtLeastOneReadReceipt
)
messageStatusLabel.text = statusText
messageStatusLabel.themeTextColor = tintColor
messageStatusImageView.image = image
messageStatusImageView.themeTintColor = tintColor
messageStatusImageView.isHidden = (
messageStatusContainerView.isHidden = (
cellViewModel.variant != .standardOutgoing ||
cellViewModel.variant == .infoCall ||
(
@ -403,6 +434,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
!cellViewModel.isLast
)
)
messageStatusLabelPaddingView.isHidden = (
messageStatusContainerView.isHidden ||
cellViewModel.isLast
)
// Set the height of the underBubbleStackView to 0 if it has no content (need to do this
// otherwise it can randomly stretch)
@ -1095,11 +1130,15 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
return [:]
}
// Note: The 'String.count' value is based on actual character counts whereas
// NSAttributedString and NSRange are both based on UTF-16 encoded lengths, so
// in order to avoid strings which contain emojis breaking strings which end
// with URLs we need to use the 'String.utf16.count' value when creating the range
return detector
.matches(
in: attributedText.string,
options: [],
range: NSRange(location: 0, length: attributedText.string.count)
range: NSRange(location: 0, length: attributedText.string.utf16.count)
)
.reduce(into: [:]) { result, match in
guard

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client.\nDisappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -604,3 +604,7 @@
"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy";
"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages.";
"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected.";
"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending";
"MESSAGE_DELIVERY_STATUS_SENT" = "Sent";
"MESSAGE_DELIVERY_STATUS_READ" = "Read";
"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send";

@ -540,6 +540,7 @@ class NotificationActionHandler {
db,
interactionId: interaction.id,
threadId: thread.id,
threadVariant: thread.variant,
includingOlder: true,
trySendReadReceipt: true
)
@ -594,6 +595,7 @@ class NotificationActionHandler {
.asRequest(of: Int64.self)
.fetchOne(db),
threadId: thread.id,
threadVariant: thread.variant,
includingOlder: true,
trySendReadReceipt: true
)

@ -11,6 +11,7 @@ public enum SyncPushTokensJob: JobExecutor {
public static let maxFailureCount: Int = -1
public static let requiresThreadId: Bool = false
public static let requiresInteractionId: Bool = false
private static let maxFrequency: TimeInterval = (12 * 60 * 60)
public static func run(
_ job: Job,
@ -34,45 +35,44 @@ public enum SyncPushTokensJob: JobExecutor {
return
}
// Push tokens don't normally change while the app is launched, so checking once during launch is
// usually sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" and disabled
// "Background App Refresh" will not be able to obtain an APN token. Enabling those settings does not
// restart the app, so we check every activation for users who haven't yet registered.
guard job.behaviour != .recurringOnActive || !UIApplication.shared.isRegisteredForRemoteNotifications else {
// Push tokens don't normally change while the app is launched, so you would assume checking once
// during launch is sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications"
// and disabled "Background App Refresh" will not be able to obtain an APN token. Enabling those
// settings does not restart the app, so we check every activation for users who haven't yet
// registered.
//
// It's also possible for a device to successfully register for push notifications but fail to
// register with Session
//
// Due to the above we want to re-register at least once every ~12 hours to ensure users will
// continue to receive push notifications
//
// In addition to this if we are custom running the job (eg. by toggling the push notification
// setting) then we should run regardless of the other settings so users have a mechanism to force
// the registration to run
let lastPushNotificationSync: Date = UserDefaults.standard[.lastPushNotificationSync]
.defaulting(to: Date.distantPast)
guard
job.behaviour == .runOnce ||
!UIApplication.shared.isRegisteredForRemoteNotifications ||
Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency
else {
deferred(job) // Don't need to do anything if push notifications are already registered
return
}
Logger.info("Retrying remote notification registration since user hasn't registered yet.")
// Determine if we want to upload only if stale (Note: This should default to true, and be true if
// 'details' isn't provided)
let uploadOnlyIfStale: Bool = ((try? JSONDecoder().decode(Details.self, from: job.details ?? Data()))?.uploadOnlyIfStale ?? true)
// Get the app version info (used to determine if we want to update the push tokens)
let lastAppVersion: String? = AppVersion.sharedInstance().lastAppVersion
let currentAppVersion: String? = AppVersion.sharedInstance().currentAppVersion
Logger.info("Re-registering for remote notifications.")
// Perform device registration
PushRegistrationManager.shared.requestPushTokens()
.then(on: queue) { (pushToken: String, voipToken: String) -> Promise<Void> in
let lastPushToken: String? = Storage.shared[.lastRecordedPushToken]
let lastVoipToken: String? = Storage.shared[.lastRecordedVoipToken]
let shouldUploadTokens: Bool = (
!uploadOnlyIfStale || (
lastPushToken != pushToken ||
lastVoipToken != voipToken
) ||
lastAppVersion != currentAppVersion
)
guard shouldUploadTokens else { return Promise.value(()) }
let (promise, seal) = Promise<Void>.pending()
SyncPushTokensJob.registerForPushNotifications(
pushToken: pushToken,
voipToken: voipToken,
isForcedUpdate: shouldUploadTokens,
isForcedUpdate: true,
success: { seal.fulfill(()) },
failure: seal.reject
)
@ -94,6 +94,7 @@ public enum SyncPushTokensJob: JobExecutor {
public static func run(uploadOnlyIfStale: Bool) {
guard let job: Job = Job(
variant: .syncPushTokens,
behaviour: .runOnce,
details: SyncPushTokensJob.Details(
uploadOnlyIfStale: uploadOnlyIfStale
)

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import Reachability
import SessionUIKit
final class PathStatusView: UIView {
@ -42,6 +43,7 @@ final class PathStatusView: UIView {
// MARK: - Initialization
private let size: Size
private let reachability: Reachability = Reachability.forInternetConnection()
init(size: Size = .small) {
self.size = size
@ -73,15 +75,34 @@ final class PathStatusView: UIView {
self.set(.width, to: self.size.pointSize)
self.set(.height, to: self.size.pointSize)
setStatus(to: (!OnionRequestAPI.paths.isEmpty ? .connected : .connecting))
switch (reachability.isReachable(), OnionRequestAPI.paths.isEmpty) {
case (false, _): setStatus(to: .error)
case (true, true): setStatus(to: .connecting)
case (true, false): setStatus(to: .connected)
}
}
// MARK: - Functions
private func registerObservers() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(handleBuildingPathsNotification), name: .buildingPaths, object: nil)
notificationCenter.addObserver(self, selector: #selector(handlePathsBuiltNotification), name: .pathsBuilt, object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleBuildingPathsNotification),
name: .buildingPaths,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(handlePathsBuiltNotification),
name: .pathsBuilt,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(reachabilityChanged),
name: .reachabilityChanged,
object: nil
)
}
private func setStatus(to status: Status) {
@ -102,10 +123,34 @@ final class PathStatusView: UIView {
}
@objc private func handleBuildingPathsNotification() {
guard reachability.isReachable() else {
setStatus(to: .error)
return
}
setStatus(to: .connecting)
}
@objc private func handlePathsBuiltNotification() {
guard reachability.isReachable() else {
setStatus(to: .error)
return
}
setStatus(to: .connected)
}
@objc private func reachabilityChanged() {
guard Thread.isMainThread else {
DispatchQueue.main.async { [weak self] in self?.reachabilityChanged() }
return
}
guard reachability.isReachable() else {
setStatus(to: .error)
return
}
setStatus(to: (!OnionRequestAPI.paths.isEmpty ? .connected : .connecting))
}
}

@ -3,6 +3,7 @@
import Foundation
import Combine
import GRDB
import LocalAuthentication
import DifferenceKit
import SessionUIKit
import SessionMessagingKit
@ -99,7 +100,23 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
title: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE".localized(),
subtitle: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION".localized(),
rightAccessory: .toggle(.settingBool(key: .isScreenLockEnabled)),
onTap: {
onTap: { [weak self] in
// Make sure the device has a passcode set before allowing screen lock to
// be enabled (Note: This will always return true on a simulator)
guard LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) else {
self?.transitionToScreen(
ConfirmationModal(
info: ConfirmationModal.Info(
title: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE".localized(),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
),
transitionType: .present
)
return
}
Storage.shared.write { db in
db[.isScreenLockEnabled] = !db[.isScreenLockEnabled]
}

@ -104,7 +104,6 @@ public final class FullConversationCell: UITableViewCell {
let result: UIImageView = UIImageView()
result.clipsToBounds = true
result.contentMode = .scaleAspectFit
result.layer.cornerRadius = (FullConversationCell.statusIndicatorSize / 2)
return result
}()

@ -482,6 +482,7 @@ public extension Interaction {
_ db: Database,
interactionId: Int64?,
threadId: String,
threadVariant: SessionThread.Variant,
includingOlder: Bool,
trySendReadReceipt: Bool
) throws {
@ -518,8 +519,9 @@ public extension Interaction {
))
)
// If we want to send read receipts then try to add the 'SendReadReceiptsJob'
if trySendReadReceipt {
// If we want to send read receipts and it's a contact thread then try to add the
// 'SendReadReceiptsJob'
if trySendReadReceipt && threadVariant == .contact {
JobRunner.upsert(
db,
job: SendReadReceiptsJob.createOrUpdateIfNeeded(

@ -65,16 +65,37 @@ public struct RecipientState: Codable, Equatable, FetchableRecord, PersistableRe
}
}
public func statusIconInfo(variant: Interaction.Variant, hasAtLeastOneReadReceipt: Bool) -> (image: UIImage?, themeTintColor: ThemeValue) {
guard variant == .standardOutgoing else { return (nil, .textPrimary) }
public func statusIconInfo(variant: Interaction.Variant, hasAtLeastOneReadReceipt: Bool) -> (image: UIImage?, text: String?, themeTintColor: ThemeValue) {
guard variant == .standardOutgoing else { return (nil, nil, .textPrimary) }
switch (self, hasAtLeastOneReadReceipt) {
case (.sending, _): return (UIImage(systemName: "ellipsis.circle"), .textPrimary)
case (.sending, _):
return (
UIImage(systemName: "ellipsis.circle"),
"MESSAGE_DELIVERY_STATUS_SENDING".localized(),
.messageBubble_deliveryStatus
)
case (.sent, false), (.skipped, _):
return (UIImage(systemName: "checkmark.circle"), .textPrimary)
return (
UIImage(systemName: "checkmark.circle"),
"MESSAGE_DELIVERY_STATUS_SENT".localized(),
.messageBubble_deliveryStatus
)
case (.sent, true):
return (
UIImage(systemName: "eye.fill"),
"MESSAGE_DELIVERY_STATUS_READ".localized(),
.messageBubble_deliveryStatus
)
case (.sent, true): return (UIImage(systemName: "checkmark.circle.fill"), .textPrimary)
case (.failed, _): return (UIImage(systemName: "exclamationmark.circle"), .danger)
case (.failed, _):
return (
UIImage(systemName: "exclamationmark.triangle"),
"MESSAGE_DELIVERY_STATUS_FAILED".localized(),
.danger
)
}
}
}

@ -14,7 +14,10 @@ public final class FileServerAPI: NSObject {
public static let oldServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69"
@objc public static let server = "http://filev2.getsession.org"
public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59"
public static let maxFileSize = (10 * 1024 * 1024) // 10 MB
/// **Note:** The max file size is 10,000,000 bytes (rather than 10MiB which would be `(10 * 1024 * 1024)`), 10,000,000
/// exactly will be fine but a single byte more will result in an error
public static let maxFileSize = 10_000_000
/// Standard timeout is 10 seconds which is a little too short fir file upload/download with slightly larger files
public static let fileTimeout: TimeInterval = 30

@ -98,32 +98,24 @@ extension SendReadReceiptsJob {
// MARK: - Convenience
public extension SendReadReceiptsJob {
/// This method upserts a 'sendReadReceipts' job to include the timestamps for the specified `interactionIds`
///
/// **Note:** This method assumes that the provided `interactionIds` are valid and won't filter out any invalid ids so
/// ensure that is done correctly beforehand
@discardableResult static func createOrUpdateIfNeeded(_ db: Database, threadId: String, interactionIds: [Int64]) -> Job? {
guard db[.areReadReceiptsEnabled] == true else { return nil }
// Retrieve the timestampMs values for the specified interactions
let maybeTimestampMsValues: [Int64]? = try? Int64.fetchAll(
db,
Interaction
.select(.timestampMs)
.filter(interactionIds.contains(Interaction.Columns.id))
// Only `standardIncoming` incoming interactions should have read receipts sent
.filter(Interaction.Columns.variant == Interaction.Variant.standardIncoming)
.filter(Interaction.Columns.wasRead == false) // Only send for unread messages
.joining(
// Don't send read receipts in group threads
required: Interaction.thread
.filter(SessionThread.Columns.variant != SessionThread.Variant.closedGroup)
.filter(SessionThread.Columns.variant != SessionThread.Variant.openGroup)
)
.distinct()
)
let timestampMsValues: [Int64] = (try? Interaction
.select(.timestampMs)
.filter(interactionIds.contains(Interaction.Columns.id))
.distinct()
.asRequest(of: Int64.self)
.fetchAll(db))
.defaulting(to: [])
// If there are no timestamp values then do nothing
guard
let timestampMsValues: [Int64] = maybeTimestampMsValues,
!timestampMsValues.isEmpty
else { return nil }
guard !timestampMsValues.isEmpty else { return nil }
// Try to get an existing job (if there is one that's not running)
if

@ -19,7 +19,12 @@ extension MessageReceiver {
guard
let interactionId: Int64 = maybeInteraction?.id,
let interaction: Interaction = maybeInteraction
let interaction: Interaction = maybeInteraction,
let threadVariant: SessionThread.Variant = try SessionThread
.filter(id: interaction.threadId)
.select(.variant)
.asRequest(of: SessionThread.Variant.self)
.fetchOne(db)
else { return }
// Mark incoming messages as read and remove any of their notifications
@ -28,6 +33,7 @@ extension MessageReceiver {
db,
interactionId: interactionId,
threadId: interaction.threadId,
threadVariant: threadVariant,
includingOlder: false,
trySendReadReceipt: false
)

@ -393,6 +393,7 @@ extension MessageReceiver {
db,
interactionId: interactionId,
threadId: thread.id,
threadVariant: thread.variant,
includingOlder: true,
trySendReadReceipt: true
)

@ -66,7 +66,6 @@ public final class MessageSender {
) throws -> Promise<Void> {
let (promise, seal) = Promise<Void>.pending()
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false)
let messageSendTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
// Set the timestamp, sender and recipient
@ -261,6 +260,8 @@ public final class MessageSender {
behaviour: .runOnce,
details: NotifyPushServerJob.Details(message: snodeMessage)
)
let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive])
.defaulting(to: false)
if isMainAppActive {
JobRunner.add(db, job: job)

@ -34,6 +34,7 @@ internal enum Theme_ClassicDark: ThemeColors {
.messageBubble_outgoingText: .classicDark0,
.messageBubble_incomingText: .classicDark6,
.messageBubble_overlay: .black_06,
.messageBubble_deliveryStatus: .classicDark5,
// MenuButton
.menuButton_background: .primary,

@ -34,6 +34,7 @@ internal enum Theme_ClassicLight: ThemeColors {
.messageBubble_outgoingText: .classicLight0,
.messageBubble_incomingText: .classicLight0,
.messageBubble_overlay: .black_06,
.messageBubble_deliveryStatus: .classicLight1,
// MenuButton
.menuButton_background: .primary,

@ -34,6 +34,7 @@ internal enum Theme_OceanDark: ThemeColors {
.messageBubble_outgoingText: .oceanDark0,
.messageBubble_incomingText: .oceanDark7,
.messageBubble_overlay: .black_06,
.messageBubble_deliveryStatus: .oceanDark5,
// MenuButton
.menuButton_background: .primary,

@ -34,6 +34,7 @@ internal enum Theme_OceanLight: ThemeColors {
.messageBubble_outgoingText: .oceanLight1,
.messageBubble_incomingText: .oceanLight1,
.messageBubble_overlay: .black_06,
.messageBubble_deliveryStatus: .oceanLight2,
// MenuButton
.menuButton_background: .primary,

@ -122,6 +122,7 @@ public indirect enum ThemeValue: Hashable {
case messageBubble_outgoingText
case messageBubble_incomingText
case messageBubble_overlay
case messageBubble_deliveryStatus
// MenuButton
case menuButton_background

@ -44,6 +44,7 @@ public enum SNUserDefaults {
case lastOpenGroupImageUpdate
case lastOpen
case lastGarbageCollection
case lastPushNotificationSync
}
public enum Double: Swift.String {

@ -120,6 +120,7 @@ public final class JobRunner {
fileprivate static var perSessionJobsCompleted: Atomic<Set<Int64>> = Atomic([])
private static var hasCompletedInitialBecomeActive: Atomic<Bool> = Atomic(false)
private static var shutdownBackgroundTask: Atomic<OWSBackgroundTask?> = Atomic(nil)
fileprivate static var canStartQueues: Atomic<Bool> = Atomic(false)
// MARK: - Configuration
@ -170,6 +171,9 @@ public final class JobRunner {
queues.wrappedValue[job.variant]?.upsert(job, canStartJob: canStartJob)
// Don't start the queue if the job can't be started
guard canStartJob else { return }
// Start the job runner if needed
db.afterNextTransaction { _ in
queues.wrappedValue[job.variant]?.start()
@ -197,15 +201,13 @@ public final class JobRunner {
queues.wrappedValue[updatedJob.variant]?.insert(updatedJob, before: otherJob)
// Start the job runner if needed
db.afterNextTransaction { _ in
queues.wrappedValue[updatedJob.variant]?.start()
}
return (jobId, updatedJob)
}
public static func appDidFinishLaunching() {
// Flag that the JobRunner can start it's queues
JobRunner.canStartQueues.mutate { $0 = true }
// Note: 'appDidBecomeActive' will run on first launch anyway so we can
// leave those jobs out and can wait until then to start the JobRunner
let jobsToRun: (blocking: [Job], nonBlocking: [Job]) = Storage.shared
@ -251,6 +253,9 @@ public final class JobRunner {
}
public static func appDidBecomeActive() {
// Flag that the JobRunner can start it's queues
JobRunner.canStartQueues.mutate { $0 = true }
// If we have a running "sutdownBackgroundTask" then we want to cancel it as otherwise it
// can result in the database being suspended and us being unable to interact with it at all
shutdownBackgroundTask.mutate {
@ -300,6 +305,11 @@ public final class JobRunner {
exceptForVariant: Job.Variant? = nil,
onComplete: (() -> ())? = nil
) {
// Inform the JobRunner that it can't start any queues (this is to prevent queues from
// rescheduling themselves while in the background, when the app restarts or becomes active
// the JobRunenr will update this flag)
JobRunner.canStartQueues.mutate { $0 = false }
// Stop all queues except for the one containing the `exceptForVariant`
queues.wrappedValue
.values
@ -643,6 +653,7 @@ private final class JobQueue {
fileprivate func start(force: Bool = false) {
// We only want the JobRunner to run in the main app
guard CurrentAppContext().isMainApp else { return }
guard JobRunner.canStartQueues.wrappedValue else { return }
guard force || !isRunning.wrappedValue else { return }
// The JobRunner runs synchronously we need to ensure this doesn't start
@ -863,8 +874,9 @@ private final class JobQueue {
.fetchOne(db)
}
// If there are no remaining jobs the trigger the 'onQueueDrained' callback and stop
guard let nextJobTimestamp: TimeInterval = nextJobTimestamp else {
// If there are no remaining jobs or the JobRunner isn't allowed to start any queues then trigger
// the 'onQueueDrained' callback and stop
guard let nextJobTimestamp: TimeInterval = nextJobTimestamp, JobRunner.canStartQueues.wrappedValue else {
if executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty {
self.onQueueDrained?()
}
@ -1075,6 +1087,8 @@ private final class JobQueue {
queue = queue.filter { !dependantJobIds.contains($0.id ?? -1) }
}
}
performCleanUp(for: job, result: .failed)
return
}
@ -1093,7 +1107,7 @@ private final class JobQueue {
try job.dependantJobs
.updateAll(
db,
Job.Columns.failureCount.set(to: job.failureCount),
Job.Columns.failureCount.set(to: (job.failureCount + 1)),
Job.Columns.nextRunTimestamp.set(to: (nextRunTimestamp + (1 / 1000)))
)

Loading…
Cancel
Save