249 lines
8.2 KiB
Swift
249 lines
8.2 KiB
Swift
//
|
|
// Copyright 2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
|
|
@objc
|
|
public extension TSThread {
|
|
|
|
var isGroupThread: Bool {
|
|
self is TSGroupThread
|
|
}
|
|
|
|
var isNonContactThread: Bool {
|
|
!(self is TSContactThread)
|
|
}
|
|
|
|
var usesSenderKey: Bool {
|
|
self is TSGroupThread || self is TSPrivateStoryThread
|
|
}
|
|
|
|
var isGroupV1Thread: Bool {
|
|
guard let groupThread = self as? TSGroupThread else {
|
|
return false
|
|
}
|
|
return groupThread.groupModel.groupsVersion == .V1
|
|
}
|
|
|
|
var isGroupV2Thread: Bool {
|
|
guard let groupThread = self as? TSGroupThread else {
|
|
return false
|
|
}
|
|
return groupThread.groupModel.groupsVersion == .V2
|
|
}
|
|
|
|
var groupModelIfGroupThread: TSGroupModel? {
|
|
guard let groupThread = self as? TSGroupThread else {
|
|
return nil
|
|
}
|
|
return groupThread.groupModel
|
|
}
|
|
|
|
var canSendReactionToThread: Bool {
|
|
guard !isGroupV1Thread else {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
var canSendNonChatMessagesToThread: Bool {
|
|
guard !isGroupV1Thread else {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
@available(swift, obsoleted: 1.0)
|
|
func canSendChatMessagesToThread() -> Bool {
|
|
canSendChatMessagesToThread(ignoreAnnouncementOnly: false)
|
|
}
|
|
|
|
func canSendChatMessagesToThread(ignoreAnnouncementOnly: Bool = false) -> Bool {
|
|
guard !isGroupV1Thread else {
|
|
return false
|
|
}
|
|
if !ignoreAnnouncementOnly {
|
|
guard !isBlockedByAnnouncementOnly else {
|
|
return false
|
|
}
|
|
}
|
|
if let groupThread = self as? TSGroupThread {
|
|
guard groupThread.isLocalUserFullMember else {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
var isBlockedByAnnouncementOnly: Bool {
|
|
guard let groupThread = self as? TSGroupThread else {
|
|
return false
|
|
}
|
|
guard let groupModel = groupThread.groupModel as? TSGroupModelV2 else {
|
|
return false
|
|
}
|
|
// In "announcement-only" groups, only admins can send messages and start group calls.
|
|
return (groupModel.isAnnouncementsOnly &&
|
|
!groupModel.groupMembership.isLocalUserFullMemberAndAdministrator)
|
|
}
|
|
|
|
var isAnnouncementOnlyGroupThread: Bool {
|
|
guard let groupThread = self as? TSGroupThread else {
|
|
return false
|
|
}
|
|
guard let groupModel = groupThread.groupModel as? TSGroupModelV2 else {
|
|
return false
|
|
}
|
|
return groupModel.isAnnouncementsOnly
|
|
}
|
|
|
|
func hasPendingMessageRequest(transaction: SDSAnyReadTransaction) -> Bool {
|
|
return ThreadFinder().hasPendingMessageRequest(thread: self, transaction: transaction)
|
|
}
|
|
|
|
@nonobjc
|
|
func isSystemContact(contactsManager: ContactManager, tx: SDSAnyReadTransaction) -> Bool {
|
|
guard let contactThread = self as? TSContactThread else { return false }
|
|
return contactsManager.fetchSignalAccount(for: contactThread.contactAddress, transaction: tx) != nil
|
|
}
|
|
|
|
// MARK: - Database Hooks
|
|
|
|
internal func _anyDidInsert(tx: SDSAnyWriteTransaction) {
|
|
let searchableNameIndexer = DependenciesBridge.shared.searchableNameIndexer
|
|
searchableNameIndexer.insert(self, tx: tx.asV2Write)
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public extension TSThread {
|
|
|
|
struct LastVisibleInteraction: Codable, Equatable {
|
|
public let sortId: UInt64
|
|
public let onScreenPercentage: CGFloat
|
|
|
|
public init(sortId: UInt64, onScreenPercentage: CGFloat) {
|
|
self.sortId = sortId
|
|
self.onScreenPercentage = onScreenPercentage
|
|
}
|
|
}
|
|
|
|
private static let lastVisibleInteractionStore = SDSKeyValueStore(collection: "lastVisibleInteractionStore")
|
|
|
|
@objc
|
|
func hasLastVisibleInteraction(transaction: SDSAnyReadTransaction) -> Bool {
|
|
nil != Self.lastVisibleInteraction(forThread: self, transaction: transaction)
|
|
}
|
|
|
|
@objc
|
|
func lastVisibleSortId(transaction: SDSAnyReadTransaction) -> NSNumber? {
|
|
guard let lastVisibleInteraction = lastVisibleInteraction(transaction: transaction) else {
|
|
return nil
|
|
}
|
|
return NSNumber(value: lastVisibleInteraction.sortId)
|
|
}
|
|
|
|
func lastVisibleInteraction(transaction: SDSAnyReadTransaction) -> LastVisibleInteraction? {
|
|
Self.lastVisibleInteraction(forThread: self, transaction: transaction)
|
|
}
|
|
|
|
static func lastVisibleInteraction(forThread thread: TSThread,
|
|
transaction: SDSAnyReadTransaction) -> LastVisibleInteraction? {
|
|
guard let data = lastVisibleInteractionStore.getData(thread.uniqueId, transaction: transaction) else {
|
|
return nil
|
|
}
|
|
do {
|
|
return try JSONDecoder().decode(LastVisibleInteraction.self, from: data)
|
|
} catch {
|
|
owsFailDebug("Error: \(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
@objc
|
|
func clearLastVisibleInteraction(transaction: SDSAnyWriteTransaction) {
|
|
Self.setLastVisibleInteraction(nil, forThread: self, transaction: transaction)
|
|
}
|
|
|
|
@objc
|
|
func setLastVisibleInteraction(sortId: UInt64,
|
|
onScreenPercentage: CGFloat,
|
|
transaction: SDSAnyWriteTransaction) {
|
|
let lastVisibleInteraction = LastVisibleInteraction(sortId: sortId, onScreenPercentage: onScreenPercentage)
|
|
Self.setLastVisibleInteraction(lastVisibleInteraction, forThread: self, transaction: transaction)
|
|
}
|
|
|
|
func setLastVisibleInteraction(_ lastVisibleInteraction: LastVisibleInteraction?,
|
|
transaction: SDSAnyWriteTransaction) {
|
|
Self.setLastVisibleInteraction(lastVisibleInteraction, forThread: self, transaction: transaction)
|
|
}
|
|
|
|
static func setLastVisibleInteraction(_ lastVisibleInteraction: LastVisibleInteraction?,
|
|
forThread thread: TSThread,
|
|
transaction: SDSAnyWriteTransaction) {
|
|
guard let lastVisibleInteraction = lastVisibleInteraction else {
|
|
lastVisibleInteractionStore.removeValue(forKey: thread.uniqueId, transaction: transaction)
|
|
return
|
|
}
|
|
let data: Data
|
|
do {
|
|
data = try JSONEncoder().encode(lastVisibleInteraction)
|
|
} catch {
|
|
owsFailDebug("Error: \(error)")
|
|
lastVisibleInteractionStore.removeValue(forKey: thread.uniqueId, transaction: transaction)
|
|
return
|
|
}
|
|
lastVisibleInteractionStore.setData(data, key: thread.uniqueId, transaction: transaction)
|
|
}
|
|
}
|
|
|
|
// MARK: - Drafts
|
|
|
|
extension TSThread {
|
|
|
|
@objc
|
|
public func currentDraft(transaction: SDSAnyReadTransaction) -> MessageBody? {
|
|
currentDraft(shouldFetchLatest: true, transaction: transaction)
|
|
}
|
|
|
|
@objc
|
|
public func currentDraft(shouldFetchLatest: Bool,
|
|
transaction: SDSAnyReadTransaction) -> MessageBody? {
|
|
if shouldFetchLatest {
|
|
guard let thread = TSThread.anyFetch(uniqueId: uniqueId, transaction: transaction) else {
|
|
return nil
|
|
}
|
|
return Self.draft(forThread: thread)
|
|
} else {
|
|
return Self.draft(forThread: self)
|
|
}
|
|
}
|
|
|
|
private static func draft(forThread thread: TSThread) -> MessageBody? {
|
|
guard let messageDraft = thread.messageDraft else {
|
|
return nil
|
|
}
|
|
let ranges: MessageBodyRanges = thread.messageDraftBodyRanges ?? .empty
|
|
return MessageBody(text: messageDraft, ranges: ranges)
|
|
}
|
|
|
|
@objc
|
|
public func editTarget(transaction: SDSAnyReadTransaction) -> TSOutgoingMessage? {
|
|
guard
|
|
let editTargetTimestamp = editTargetTimestamp?.uint64Value,
|
|
let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: transaction.asV2Read)?.aciAddress
|
|
else {
|
|
return nil
|
|
}
|
|
return InteractionFinder.findMessage(
|
|
withTimestamp: editTargetTimestamp,
|
|
threadId: uniqueId,
|
|
author: localAddress,
|
|
transaction: transaction
|
|
) as? TSOutgoingMessage
|
|
}
|
|
}
|