Merge pull request #777 from mpretty-cyro/fix/url-detection

Fixed a number of bugs
This commit is contained in:
Morgan Pretty 2023-02-06 13:09:18 +11:00 committed by GitHub
commit cd9a6eb7a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 141 additions and 34 deletions

View file

@ -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
)

View file

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

View file

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

View file

@ -737,11 +737,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()

View file

@ -197,7 +197,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,
@ -405,6 +415,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
@ -413,6 +424,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
db,
interactionId: targetInteractionId,
threadId: threadId,
threadVariant: threadVariant,
includingOlder: true,
trySendReadReceipt: trySendReadReceipt
)

View file

@ -165,6 +165,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
return result
}()
internal lazy var messageStatusLabelPaddingView: UIView = UIView()
// MARK: - Settings
@ -252,6 +254,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
underBubbleStackView.addArrangedSubview(reactionContainerView)
underBubbleStackView.addArrangedSubview(messageStatusContainerView)
underBubbleStackView.addArrangedSubview(messageStatusLabelPaddingView)
messageStatusContainerView.addSubview(messageStatusLabel)
messageStatusContainerView.addSubview(messageStatusImageView)
@ -267,6 +270,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
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() {
@ -429,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)
@ -1121,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

View file

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

View file

@ -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))
}
}

View file

@ -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]
}

View file

@ -445,6 +445,7 @@ public extension Interaction {
_ db: Database,
interactionId: Int64?,
threadId: String,
threadVariant: SessionThread.Variant,
includingOlder: Bool,
trySendReadReceipt: Bool
) throws {
@ -480,8 +481,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(

View file

@ -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

View file

@ -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
)

View file

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