commit
a53366d114
|
@ -6032,7 +6032,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 389;
|
CURRENT_PROJECT_VERSION = 391;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -6057,7 +6057,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.2.4;
|
MARKETING_VERSION = 2.2.6;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -6105,7 +6105,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 389;
|
CURRENT_PROJECT_VERSION = 391;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -6135,7 +6135,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.2.4;
|
MARKETING_VERSION = 2.2.6;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -6171,7 +6171,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 389;
|
CURRENT_PROJECT_VERSION = 391;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -6194,7 +6194,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.2.4;
|
MARKETING_VERSION = 2.2.6;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||||
|
@ -6245,7 +6245,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 389;
|
CURRENT_PROJECT_VERSION = 391;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -6273,7 +6273,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.2.4;
|
MARKETING_VERSION = 2.2.6;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||||
|
@ -7173,7 +7173,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 389;
|
CURRENT_PROJECT_VERSION = 391;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -7212,7 +7212,7 @@
|
||||||
"$(SRCROOT)",
|
"$(SRCROOT)",
|
||||||
);
|
);
|
||||||
LLVM_LTO = NO;
|
LLVM_LTO = NO;
|
||||||
MARKETING_VERSION = 2.2.5;
|
MARKETING_VERSION = 2.2.6;
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||||
|
@ -7245,7 +7245,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 389;
|
CURRENT_PROJECT_VERSION = 391;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -7284,7 +7284,7 @@
|
||||||
"$(SRCROOT)",
|
"$(SRCROOT)",
|
||||||
);
|
);
|
||||||
LLVM_LTO = NO;
|
LLVM_LTO = NO;
|
||||||
MARKETING_VERSION = 2.2.5;
|
MARKETING_VERSION = 2.2.6;
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||||
PRODUCT_NAME = Session;
|
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(
|
try Interaction.markAsRead(
|
||||||
db,
|
db,
|
||||||
interactionId: interaction.id,
|
interactionId: interaction.id,
|
||||||
threadId: interaction.threadId,
|
threadId: interaction.threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
includingOlder: false,
|
includingOlder: false,
|
||||||
trySendReadReceipt: false
|
trySendReadReceipt: false
|
||||||
)
|
)
|
||||||
|
|
|
@ -110,6 +110,7 @@ extension ContextMenuVC {
|
||||||
static func actions(
|
static func actions(
|
||||||
for cellViewModel: MessageViewModel,
|
for cellViewModel: MessageViewModel,
|
||||||
recentEmojis: [EmojiWithSkinTones],
|
recentEmojis: [EmojiWithSkinTones],
|
||||||
|
currentUserPublicKey: String,
|
||||||
currentUserIsOpenGroupModerator: Bool,
|
currentUserIsOpenGroupModerator: Bool,
|
||||||
currentThreadIsMessageRequest: Bool,
|
currentThreadIsMessageRequest: Bool,
|
||||||
delegate: ContextMenuActionDelegate?
|
delegate: ContextMenuActionDelegate?
|
||||||
|
@ -163,6 +164,7 @@ extension ContextMenuVC {
|
||||||
let canDelete: Bool = (
|
let canDelete: Bool = (
|
||||||
cellViewModel.threadVariant != .openGroup ||
|
cellViewModel.threadVariant != .openGroup ||
|
||||||
currentUserIsOpenGroupModerator ||
|
currentUserIsOpenGroupModerator ||
|
||||||
|
cellViewModel.authorId == currentUserPublicKey ||
|
||||||
cellViewModel.state == .failed
|
cellViewModel.state == .failed
|
||||||
)
|
)
|
||||||
let canBan: Bool = (
|
let canBan: Bool = (
|
||||||
|
|
|
@ -777,6 +777,7 @@ extension ConversationVC:
|
||||||
let actions: [ContextMenuVC.Action] = ContextMenuVC.actions(
|
let actions: [ContextMenuVC.Action] = ContextMenuVC.actions(
|
||||||
for: cellViewModel,
|
for: cellViewModel,
|
||||||
recentEmojis: (self.viewModel.threadData.recentReactionEmoji ?? []).compactMap { EmojiWithSkinTones(rawValue: $0) },
|
recentEmojis: (self.viewModel.threadData.recentReactionEmoji ?? []).compactMap { EmojiWithSkinTones(rawValue: $0) },
|
||||||
|
currentUserPublicKey: self.viewModel.threadData.currentUserPublicKey,
|
||||||
currentUserIsOpenGroupModerator: OpenGroupManager.isUserModeratorOrAdmin(
|
currentUserIsOpenGroupModerator: OpenGroupManager.isUserModeratorOrAdmin(
|
||||||
self.viewModel.threadData.currentUserPublicKey,
|
self.viewModel.threadData.currentUserPublicKey,
|
||||||
for: self.viewModel.threadData.openGroupRoomToken,
|
for: self.viewModel.threadData.openGroupRoomToken,
|
||||||
|
|
|
@ -737,11 +737,17 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
|
||||||
|
|
||||||
// Store the 'sentMessageBeforeUpdate' state locally
|
// Store the 'sentMessageBeforeUpdate' state locally
|
||||||
let didSendMessageBeforeUpdate: Bool = self.viewModel.sentMessageBeforeUpdate
|
let didSendMessageBeforeUpdate: Bool = self.viewModel.sentMessageBeforeUpdate
|
||||||
|
let wasOnlyUpdates: Bool = (
|
||||||
|
changeset.count == 1 &&
|
||||||
|
changeset[0].elementUpdated.count == changeset[0].changeCount
|
||||||
|
)
|
||||||
self.viewModel.sentMessageBeforeUpdate = false
|
self.viewModel.sentMessageBeforeUpdate = false
|
||||||
|
|
||||||
// When sending a message we want to reload the UI instantly (with any form of animation the message
|
// When sending a message, or if there were only cell updates (ie. read status changes) we want to
|
||||||
// sending feels somewhat unresponsive but an instant update feels snappy)
|
// reload the UI instantly (with any form of animation the message sending feels somewhat unresponsive
|
||||||
guard !didSendMessageBeforeUpdate else {
|
// 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.viewModel.updateInteractionData(updatedData)
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
|
|
||||||
|
|
|
@ -197,7 +197,17 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
||||||
|
|
||||||
return SQL("LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])")
|
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),
|
filterSQL: MessageViewModel.filterSQL(threadId: threadId),
|
||||||
groupSQL: MessageViewModel.groupSQL,
|
groupSQL: MessageViewModel.groupSQL,
|
||||||
|
@ -405,6 +415,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
let threadId: String = self.threadData.threadId
|
let threadId: String = self.threadData.threadId
|
||||||
|
let threadVariant: SessionThread.Variant = self.threadData.threadVariant
|
||||||
let trySendReadReceipt: Bool = (self.threadData.threadIsMessageRequest == false)
|
let trySendReadReceipt: Bool = (self.threadData.threadIsMessageRequest == false)
|
||||||
self.lastInteractionIdMarkedAsRead = targetInteractionId
|
self.lastInteractionIdMarkedAsRead = targetInteractionId
|
||||||
|
|
||||||
|
@ -413,6 +424,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
||||||
db,
|
db,
|
||||||
interactionId: targetInteractionId,
|
interactionId: targetInteractionId,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
includingOlder: true,
|
includingOlder: true,
|
||||||
trySendReadReceipt: trySendReadReceipt
|
trySendReadReceipt: trySendReadReceipt
|
||||||
)
|
)
|
||||||
|
|
|
@ -165,6 +165,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
internal lazy var messageStatusLabelPaddingView: UIView = UIView()
|
||||||
|
|
||||||
// MARK: - Settings
|
// MARK: - Settings
|
||||||
|
|
||||||
|
@ -252,6 +254,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
||||||
|
|
||||||
underBubbleStackView.addArrangedSubview(reactionContainerView)
|
underBubbleStackView.addArrangedSubview(reactionContainerView)
|
||||||
underBubbleStackView.addArrangedSubview(messageStatusContainerView)
|
underBubbleStackView.addArrangedSubview(messageStatusContainerView)
|
||||||
|
underBubbleStackView.addArrangedSubview(messageStatusLabelPaddingView)
|
||||||
|
|
||||||
messageStatusContainerView.addSubview(messageStatusLabel)
|
messageStatusContainerView.addSubview(messageStatusLabel)
|
||||||
messageStatusContainerView.addSubview(messageStatusImageView)
|
messageStatusContainerView.addSubview(messageStatusImageView)
|
||||||
|
@ -267,6 +270,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
||||||
messageStatusLabel.center(.vertical, in: messageStatusContainerView)
|
messageStatusLabel.center(.vertical, in: messageStatusContainerView)
|
||||||
messageStatusLabel.pin(.leading, to: .leading, of: messageStatusContainerView)
|
messageStatusLabel.pin(.leading, to: .leading, of: messageStatusContainerView)
|
||||||
messageStatusLabel.pin(.trailing, to: .leading, of: messageStatusImageView, withInset: -2)
|
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() {
|
override func setUpGestureRecognizers() {
|
||||||
|
@ -429,6 +434,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
||||||
!cellViewModel.isLast
|
!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
|
// Set the height of the underBubbleStackView to 0 if it has no content (need to do this
|
||||||
// otherwise it can randomly stretch)
|
// otherwise it can randomly stretch)
|
||||||
|
@ -1121,11 +1130,15 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
||||||
return [:]
|
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
|
return detector
|
||||||
.matches(
|
.matches(
|
||||||
in: attributedText.string,
|
in: attributedText.string,
|
||||||
options: [],
|
options: [],
|
||||||
range: NSRange(location: 0, length: attributedText.string.count)
|
range: NSRange(location: 0, length: attributedText.string.utf16.count)
|
||||||
)
|
)
|
||||||
.reduce(into: [:]) { result, match in
|
.reduce(into: [:]) { result, match in
|
||||||
guard
|
guard
|
||||||
|
|
|
@ -546,6 +546,7 @@ class NotificationActionHandler {
|
||||||
db,
|
db,
|
||||||
interactionId: interaction.id,
|
interactionId: interaction.id,
|
||||||
threadId: thread.id,
|
threadId: thread.id,
|
||||||
|
threadVariant: thread.variant,
|
||||||
includingOlder: true,
|
includingOlder: true,
|
||||||
trySendReadReceipt: true
|
trySendReadReceipt: true
|
||||||
)
|
)
|
||||||
|
@ -600,6 +601,7 @@ class NotificationActionHandler {
|
||||||
.asRequest(of: Int64.self)
|
.asRequest(of: Int64.self)
|
||||||
.fetchOne(db),
|
.fetchOne(db),
|
||||||
threadId: thread.id,
|
threadId: thread.id,
|
||||||
|
threadVariant: thread.variant,
|
||||||
includingOlder: true,
|
includingOlder: true,
|
||||||
trySendReadReceipt: true
|
trySendReadReceipt: true
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Reachability
|
||||||
import SessionUIKit
|
import SessionUIKit
|
||||||
|
|
||||||
final class PathStatusView: UIView {
|
final class PathStatusView: UIView {
|
||||||
|
@ -42,6 +43,7 @@ final class PathStatusView: UIView {
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
private let size: Size
|
private let size: Size
|
||||||
|
private let reachability: Reachability = Reachability.forInternetConnection()
|
||||||
|
|
||||||
init(size: Size = .small) {
|
init(size: Size = .small) {
|
||||||
self.size = size
|
self.size = size
|
||||||
|
@ -73,15 +75,34 @@ final class PathStatusView: UIView {
|
||||||
self.set(.width, to: self.size.pointSize)
|
self.set(.width, to: self.size.pointSize)
|
||||||
self.set(.height, 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
|
// MARK: - Functions
|
||||||
|
|
||||||
private func registerObservers() {
|
private func registerObservers() {
|
||||||
let notificationCenter = NotificationCenter.default
|
NotificationCenter.default.addObserver(
|
||||||
notificationCenter.addObserver(self, selector: #selector(handleBuildingPathsNotification), name: .buildingPaths, object: nil)
|
self,
|
||||||
notificationCenter.addObserver(self, selector: #selector(handlePathsBuiltNotification), name: .pathsBuilt, object: nil)
|
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) {
|
private func setStatus(to status: Status) {
|
||||||
|
@ -102,10 +123,34 @@ final class PathStatusView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleBuildingPathsNotification() {
|
@objc private func handleBuildingPathsNotification() {
|
||||||
|
guard reachability.isReachable() else {
|
||||||
|
setStatus(to: .error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setStatus(to: .connecting)
|
setStatus(to: .connecting)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handlePathsBuiltNotification() {
|
@objc private func handlePathsBuiltNotification() {
|
||||||
|
guard reachability.isReachable() else {
|
||||||
|
setStatus(to: .error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setStatus(to: .connected)
|
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 Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import GRDB
|
import GRDB
|
||||||
|
import LocalAuthentication
|
||||||
import DifferenceKit
|
import DifferenceKit
|
||||||
import SessionUIKit
|
import SessionUIKit
|
||||||
import SessionMessagingKit
|
import SessionMessagingKit
|
||||||
|
@ -99,7 +100,23 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
||||||
title: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE".localized(),
|
title: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE".localized(),
|
||||||
subtitle: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION".localized(),
|
subtitle: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION".localized(),
|
||||||
rightAccessory: .toggle(.settingBool(key: .isScreenLockEnabled)),
|
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
|
Storage.shared.write { db in
|
||||||
db[.isScreenLockEnabled] = !db[.isScreenLockEnabled]
|
db[.isScreenLockEnabled] = !db[.isScreenLockEnabled]
|
||||||
}
|
}
|
||||||
|
|
|
@ -445,6 +445,7 @@ public extension Interaction {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
includingOlder: Bool,
|
includingOlder: Bool,
|
||||||
trySendReadReceipt: Bool
|
trySendReadReceipt: Bool
|
||||||
) throws {
|
) throws {
|
||||||
|
@ -480,8 +481,9 @@ public extension Interaction {
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
|
|
||||||
// If we want to send read receipts then try to add the 'SendReadReceiptsJob'
|
// If we want to send read receipts and it's a contact thread then try to add the
|
||||||
if trySendReadReceipt {
|
// 'SendReadReceiptsJob'
|
||||||
|
if trySendReadReceipt && threadVariant == .contact {
|
||||||
JobRunner.upsert(
|
JobRunner.upsert(
|
||||||
db,
|
db,
|
||||||
job: SendReadReceiptsJob.createOrUpdateIfNeeded(
|
job: SendReadReceiptsJob.createOrUpdateIfNeeded(
|
||||||
|
|
|
@ -98,32 +98,24 @@ extension SendReadReceiptsJob {
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
public extension SendReadReceiptsJob {
|
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? {
|
@discardableResult static func createOrUpdateIfNeeded(_ db: Database, threadId: String, interactionIds: [Int64]) -> Job? {
|
||||||
guard db[.areReadReceiptsEnabled] == true else { return nil }
|
guard db[.areReadReceiptsEnabled] == true else { return nil }
|
||||||
|
|
||||||
// Retrieve the timestampMs values for the specified interactions
|
// Retrieve the timestampMs values for the specified interactions
|
||||||
let maybeTimestampMsValues: [Int64]? = try? Int64.fetchAll(
|
let timestampMsValues: [Int64] = (try? Interaction
|
||||||
db,
|
.select(.timestampMs)
|
||||||
Interaction
|
.filter(interactionIds.contains(Interaction.Columns.id))
|
||||||
.select(.timestampMs)
|
.distinct()
|
||||||
.filter(interactionIds.contains(Interaction.Columns.id))
|
.asRequest(of: Int64.self)
|
||||||
// Only `standardIncoming` incoming interactions should have read receipts sent
|
.fetchAll(db))
|
||||||
.filter(Interaction.Columns.variant == Interaction.Variant.standardIncoming)
|
.defaulting(to: [])
|
||||||
.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()
|
|
||||||
)
|
|
||||||
|
|
||||||
// If there are no timestamp values then do nothing
|
// If there are no timestamp values then do nothing
|
||||||
guard
|
guard !timestampMsValues.isEmpty else { return nil }
|
||||||
let timestampMsValues: [Int64] = maybeTimestampMsValues,
|
|
||||||
!timestampMsValues.isEmpty
|
|
||||||
else { return nil }
|
|
||||||
|
|
||||||
// Try to get an existing job (if there is one that's not running)
|
// Try to get an existing job (if there is one that's not running)
|
||||||
if
|
if
|
||||||
|
|
|
@ -19,7 +19,12 @@ extension MessageReceiver {
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let interactionId: Int64 = maybeInteraction?.id,
|
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 }
|
else { return }
|
||||||
|
|
||||||
// Mark incoming messages as read and remove any of their notifications
|
// Mark incoming messages as read and remove any of their notifications
|
||||||
|
@ -28,6 +33,7 @@ extension MessageReceiver {
|
||||||
db,
|
db,
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
threadId: interaction.threadId,
|
threadId: interaction.threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
includingOlder: false,
|
includingOlder: false,
|
||||||
trySendReadReceipt: false
|
trySendReadReceipt: false
|
||||||
)
|
)
|
||||||
|
|
|
@ -405,6 +405,7 @@ extension MessageReceiver {
|
||||||
db,
|
db,
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
threadId: thread.id,
|
threadId: thread.id,
|
||||||
|
threadVariant: thread.variant,
|
||||||
includingOlder: true,
|
includingOlder: true,
|
||||||
trySendReadReceipt: true
|
trySendReadReceipt: true
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue