Merge pull request #888 from mpretty-cyro/fix/minor-outgoing-quote-bugs
Fix a couple of minor bugs and clean up query interface
This commit is contained in:
commit
6d990559b7
|
@ -635,6 +635,8 @@
|
||||||
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; };
|
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; };
|
||||||
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */; };
|
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */; };
|
||||||
FD559DF52A7368CB00C7C62A /* DispatchQueue+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */; };
|
FD559DF52A7368CB00C7C62A /* DispatchQueue+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */; };
|
||||||
|
FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */; };
|
||||||
|
FD5931AB2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5931AA2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift */; };
|
||||||
FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */; };
|
FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */; };
|
||||||
FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; };
|
FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; };
|
||||||
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; };
|
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; };
|
||||||
|
@ -1755,6 +1757,8 @@
|
||||||
FD52090828B59411006098F6 /* ScreenLockUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockUI.swift; sourceTree = "<group>"; };
|
FD52090828B59411006098F6 /* ScreenLockUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockUI.swift; sourceTree = "<group>"; };
|
||||||
FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = "<group>"; };
|
FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = "<group>"; };
|
||||||
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Utilities.swift"; sourceTree = "<group>"; };
|
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
|
FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SQLInterpolation+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
|
FD5931AA2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScopeAdapter+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = "<group>"; };
|
FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = "<group>"; };
|
||||||
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = "<group>"; };
|
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = "<group>"; };
|
||||||
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
|
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -3693,6 +3697,8 @@
|
||||||
FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */,
|
FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */,
|
||||||
FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */,
|
FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */,
|
||||||
FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */,
|
FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */,
|
||||||
|
FD5931AA2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift */,
|
||||||
|
FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */,
|
||||||
);
|
);
|
||||||
path = Utilities;
|
path = Utilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -5666,6 +5672,7 @@
|
||||||
C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */,
|
C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */,
|
||||||
FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */,
|
FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */,
|
||||||
FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */,
|
FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */,
|
||||||
|
FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */,
|
||||||
FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */,
|
FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */,
|
||||||
FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */,
|
FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */,
|
||||||
FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,
|
FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,
|
||||||
|
@ -5677,6 +5684,7 @@
|
||||||
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
|
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
|
||||||
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
|
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
|
||||||
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */,
|
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */,
|
||||||
|
FD5931AB2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift in Sources */,
|
||||||
FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */,
|
FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */,
|
||||||
FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */,
|
FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */,
|
||||||
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
|
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
|
||||||
|
|
|
@ -154,6 +154,7 @@ extension ConversationVC:
|
||||||
_ sendMediaNavigationController: SendMediaNavigationController,
|
_ sendMediaNavigationController: SendMediaNavigationController,
|
||||||
didApproveAttachments attachments: [SignalAttachment],
|
didApproveAttachments attachments: [SignalAttachment],
|
||||||
forThreadId threadId: String,
|
forThreadId threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
messageText: String?,
|
messageText: String?,
|
||||||
using dependencies: Dependencies
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
|
@ -180,7 +181,14 @@ extension ConversationVC:
|
||||||
|
|
||||||
// MARK: - AttachmentApprovalViewControllerDelegate
|
// MARK: - AttachmentApprovalViewControllerDelegate
|
||||||
|
|
||||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?, using dependencies: Dependencies) {
|
func attachmentApproval(
|
||||||
|
_ attachmentApproval: AttachmentApprovalViewController,
|
||||||
|
didApproveAttachments attachments: [SignalAttachment],
|
||||||
|
forThreadId threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
|
messageText: String?,
|
||||||
|
using dependencies: Dependencies
|
||||||
|
) {
|
||||||
sendMessage(text: (messageText ?? ""), attachments: attachments, using: dependencies)
|
sendMessage(text: (messageText ?? ""), attachments: attachments, using: dependencies)
|
||||||
resetMentions()
|
resetMentions()
|
||||||
|
|
||||||
|
@ -255,11 +263,13 @@ extension ConversationVC:
|
||||||
|
|
||||||
func handleLibraryButtonTapped() {
|
func handleLibraryButtonTapped() {
|
||||||
let threadId: String = self.viewModel.threadData.threadId
|
let threadId: String = self.viewModel.threadData.threadId
|
||||||
|
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||||
|
|
||||||
Permissions.requestLibraryPermissionIfNeeded { [weak self] in
|
Permissions.requestLibraryPermissionIfNeeded { [weak self] in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let sendMediaNavController = SendMediaNavigationController.showingMediaLibraryFirst(
|
let sendMediaNavController = SendMediaNavigationController.showingMediaLibraryFirst(
|
||||||
threadId: threadId
|
threadId: threadId,
|
||||||
|
threadVariant: threadVariant
|
||||||
)
|
)
|
||||||
sendMediaNavController.sendMediaNavDelegate = self
|
sendMediaNavController.sendMediaNavDelegate = self
|
||||||
sendMediaNavController.modalPresentationStyle = .fullScreen
|
sendMediaNavController.modalPresentationStyle = .fullScreen
|
||||||
|
@ -277,7 +287,10 @@ extension ConversationVC:
|
||||||
SNLog("Proceeding without microphone access. Any recorded video will be silent.")
|
SNLog("Proceeding without microphone access. Any recorded video will be silent.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let sendMediaNavController = SendMediaNavigationController.showingCameraFirst(threadId: self.viewModel.threadData.threadId)
|
let sendMediaNavController = SendMediaNavigationController.showingCameraFirst(
|
||||||
|
threadId: self.viewModel.threadData.threadId,
|
||||||
|
threadVariant: self.viewModel.threadData.threadVariant
|
||||||
|
)
|
||||||
sendMediaNavController.sendMediaNavDelegate = self
|
sendMediaNavController.sendMediaNavDelegate = self
|
||||||
sendMediaNavController.modalPresentationStyle = .fullScreen
|
sendMediaNavController.modalPresentationStyle = .fullScreen
|
||||||
|
|
||||||
|
@ -363,6 +376,7 @@ extension ConversationVC:
|
||||||
func showAttachmentApprovalDialog(for attachments: [SignalAttachment]) {
|
func showAttachmentApprovalDialog(for attachments: [SignalAttachment]) {
|
||||||
let navController = AttachmentApprovalViewController.wrappedInNavController(
|
let navController = AttachmentApprovalViewController.wrappedInNavController(
|
||||||
threadId: self.viewModel.threadData.threadId,
|
threadId: self.viewModel.threadData.threadId,
|
||||||
|
threadVariant: self.viewModel.threadData.threadVariant,
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
approvalDelegate: self
|
approvalDelegate: self
|
||||||
)
|
)
|
||||||
|
@ -647,6 +661,7 @@ extension ConversationVC:
|
||||||
|
|
||||||
let approvalVC = AttachmentApprovalViewController.wrappedInNavController(
|
let approvalVC = AttachmentApprovalViewController.wrappedInNavController(
|
||||||
threadId: self.viewModel.threadData.threadId,
|
threadId: self.viewModel.threadData.threadId,
|
||||||
|
threadVariant: self.viewModel.threadData.threadVariant,
|
||||||
attachments: [ attachment ],
|
attachments: [ attachment ],
|
||||||
approvalDelegate: self
|
approvalDelegate: self
|
||||||
)
|
)
|
||||||
|
|
|
@ -156,7 +156,7 @@ final class QuoteView: UIView {
|
||||||
if attachment.isVisualMedia {
|
if attachment.isVisualMedia {
|
||||||
attachment.thumbnail(
|
attachment.thumbnail(
|
||||||
size: .small,
|
size: .small,
|
||||||
success: { image, _ in
|
success: { [imageView] image, _ in
|
||||||
guard Thread.isMainThread else {
|
guard Thread.isMainThread else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
imageView.image = image
|
imageView.image = image
|
||||||
|
@ -234,8 +234,6 @@ final class QuoteView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label stack view
|
// Label stack view
|
||||||
let bodyLabelSize = bodyLabel.systemLayoutSizeFitting(availableSpace)
|
|
||||||
|
|
||||||
let isCurrentUser: Bool = [
|
let isCurrentUser: Bool = [
|
||||||
currentUserPublicKey,
|
currentUserPublicKey,
|
||||||
currentUserBlinded15PublicKey,
|
currentUserBlinded15PublicKey,
|
||||||
|
@ -288,9 +286,8 @@ final class QuoteView: UIView {
|
||||||
cancelButton.set(.height, to: cancelButtonSize)
|
cancelButton.set(.height, to: cancelButtonSize)
|
||||||
cancelButton.addTarget(self, action: #selector(cancel), for: UIControl.Event.touchUpInside)
|
cancelButton.addTarget(self, action: #selector(cancel), for: UIControl.Event.touchUpInside)
|
||||||
|
|
||||||
addSubview(cancelButton)
|
mainStackView.addArrangedSubview(cancelButton)
|
||||||
cancelButton.center(.vertical, in: self)
|
cancelButton.center(.vertical, in: self)
|
||||||
cancelButton.pin(.right, to: .right, of: self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
||||||
profilePictureView,
|
profilePictureView,
|
||||||
replyButton,
|
replyButton,
|
||||||
timerView,
|
timerView,
|
||||||
messageStatusImageView,
|
messageStatusContainerView,
|
||||||
reactionContainerView
|
reactionContainerView
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,11 @@ class GlobalSearchViewController: BaseVC, SessionUtilRespondingViewController, U
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
// Don't log the 'interrupt' error as that's just the user typing too fast
|
||||||
|
if (error as? DatabaseError)?.resultCode != DatabaseError.SQLITE_INTERRUPT {
|
||||||
|
SNLog("[GlobalSearch] Failed to find results due to error: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
return .failure(error)
|
return .failure(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,16 +199,18 @@ public class MediaGalleryViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Item: FetchableRecordWithRowId, Decodable, Identifiable, Differentiable, Equatable, Hashable {
|
public struct Item: FetchableRecordWithRowId, Decodable, Identifiable, Differentiable, Equatable, Hashable, ColumnExpressible {
|
||||||
fileprivate static let interactionIdKey: SQL = SQL(stringLiteral: CodingKeys.interactionId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
fileprivate static let interactionVariantKey: SQL = SQL(stringLiteral: CodingKeys.interactionVariant.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
fileprivate static let interactionAuthorIdKey: SQL = SQL(stringLiteral: CodingKeys.interactionAuthorId.stringValue)
|
case interactionId
|
||||||
fileprivate static let interactionTimestampMsKey: SQL = SQL(stringLiteral: CodingKeys.interactionTimestampMs.stringValue)
|
case interactionVariant
|
||||||
fileprivate static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
case interactionAuthorId
|
||||||
fileprivate static let attachmentKey: SQL = SQL(stringLiteral: CodingKeys.attachment.stringValue)
|
case interactionTimestampMs
|
||||||
fileprivate static let attachmentAlbumIndexKey: SQL = SQL(stringLiteral: CodingKeys.attachmentAlbumIndex.stringValue)
|
|
||||||
|
case rowId
|
||||||
fileprivate static let attachmentString: String = CodingKeys.attachment.stringValue
|
case attachmentAlbumIndex
|
||||||
|
case attachment
|
||||||
|
}
|
||||||
|
|
||||||
public var id: String { attachment.id }
|
public var id: String { attachment.id }
|
||||||
public var differenceIdentifier: String { attachment.id }
|
public var differenceIdentifier: String { attachment.id }
|
||||||
|
@ -306,7 +308,7 @@ public class MediaGalleryViewModel {
|
||||||
let finalFilterSQL: SQL = {
|
let finalFilterSQL: SQL = {
|
||||||
guard let customFilters: SQL = customFilters else {
|
guard let customFilters: SQL = customFilters else {
|
||||||
return """
|
return """
|
||||||
WHERE \(attachment.alias[Column.rowID]) IN \(rowIds)
|
WHERE \(attachment[.rowId]) IN \(rowIds)
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,14 +320,14 @@ public class MediaGalleryViewModel {
|
||||||
}()
|
}()
|
||||||
let request: SQLRequest<Item> = """
|
let request: SQLRequest<Item> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(interaction[.id]) AS \(Item.interactionIdKey),
|
\(interaction[.id]) AS \(Item.Columns.interactionId),
|
||||||
\(interaction[.variant]) AS \(Item.interactionVariantKey),
|
\(interaction[.variant]) AS \(Item.Columns.interactionVariant),
|
||||||
\(interaction[.authorId]) AS \(Item.interactionAuthorIdKey),
|
\(interaction[.authorId]) AS \(Item.Columns.interactionAuthorId),
|
||||||
\(interaction[.timestampMs]) AS \(Item.interactionTimestampMsKey),
|
\(interaction[.timestampMs]) AS \(Item.Columns.interactionTimestampMs),
|
||||||
|
|
||||||
\(attachment.alias[Column.rowID]) AS \(Item.rowIdKey),
|
\(attachment[.rowId]) AS \(Item.Columns.rowId),
|
||||||
\(interactionAttachment[.albumIndex]) AS \(Item.attachmentAlbumIndexKey),
|
\(interactionAttachment[.albumIndex]) AS \(Item.Columns.attachmentAlbumIndex),
|
||||||
\(Item.attachmentKey).*
|
\(attachment.allColumns)
|
||||||
FROM \(Attachment.self)
|
FROM \(Attachment.self)
|
||||||
\(joinSQL)
|
\(joinSQL)
|
||||||
\(finalFilterSQL)
|
\(finalFilterSQL)
|
||||||
|
@ -338,8 +340,8 @@ public class MediaGalleryViewModel {
|
||||||
Attachment.numberOfSelectedColumns(db)
|
Attachment.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(Item.self, [
|
||||||
Item.attachmentString: adapters[1]
|
.attachment: adapters[1]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,14 @@ class SendMediaNavigationController: UINavigationController {
|
||||||
static let bottomButtonsCenterOffset: CGFloat = -50
|
static let bottomButtonsCenterOffset: CGFloat = -50
|
||||||
|
|
||||||
private let threadId: String
|
private let threadId: String
|
||||||
|
private let threadVariant: SessionThread.Variant
|
||||||
private var disposables: Set<AnyCancellable> = Set()
|
private var disposables: Set<AnyCancellable> = Set()
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
init(threadId: String) {
|
init(threadId: String, threadVariant: SessionThread.Variant) {
|
||||||
self.threadId = threadId
|
self.threadId = threadId
|
||||||
|
self.threadVariant = threadVariant
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
}
|
}
|
||||||
|
@ -74,17 +76,15 @@ class SendMediaNavigationController: UINavigationController {
|
||||||
|
|
||||||
public weak var sendMediaNavDelegate: SendMediaNavDelegate?
|
public weak var sendMediaNavDelegate: SendMediaNavDelegate?
|
||||||
|
|
||||||
@objc
|
public class func showingCameraFirst(threadId: String, threadVariant: SessionThread.Variant) -> SendMediaNavigationController {
|
||||||
public class func showingCameraFirst(threadId: String) -> SendMediaNavigationController {
|
let navController = SendMediaNavigationController(threadId: threadId, threadVariant: threadVariant)
|
||||||
let navController = SendMediaNavigationController(threadId: threadId)
|
|
||||||
navController.viewControllers = [navController.captureViewController]
|
navController.viewControllers = [navController.captureViewController]
|
||||||
|
|
||||||
return navController
|
return navController
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
public class func showingMediaLibraryFirst(threadId: String, threadVariant: SessionThread.Variant) -> SendMediaNavigationController {
|
||||||
public class func showingMediaLibraryFirst(threadId: String) -> SendMediaNavigationController {
|
let navController = SendMediaNavigationController(threadId: threadId, threadVariant: threadVariant)
|
||||||
let navController = SendMediaNavigationController(threadId: threadId)
|
|
||||||
navController.viewControllers = [navController.mediaLibraryViewController]
|
navController.viewControllers = [navController.mediaLibraryViewController]
|
||||||
|
|
||||||
return navController
|
return navController
|
||||||
|
@ -233,6 +233,7 @@ class SendMediaNavigationController: UINavigationController {
|
||||||
let approvalViewController = AttachmentApprovalViewController(
|
let approvalViewController = AttachmentApprovalViewController(
|
||||||
mode: .sharedNavigation,
|
mode: .sharedNavigation,
|
||||||
threadId: self.threadId,
|
threadId: self.threadId,
|
||||||
|
threadVariant: self.threadVariant,
|
||||||
attachments: self.attachments
|
attachments: self.attachments
|
||||||
)
|
)
|
||||||
approvalViewController.approvalDelegate = self
|
approvalViewController.approvalDelegate = self
|
||||||
|
@ -431,8 +432,22 @@ extension SendMediaNavigationController: AttachmentApprovalViewControllerDelegat
|
||||||
attachmentDraftCollection.remove(attachment: attachment)
|
attachmentDraftCollection.remove(attachment: attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?, using dependencies: Dependencies) {
|
func attachmentApproval(
|
||||||
sendMediaNavDelegate?.sendMediaNav(self, didApproveAttachments: attachments, forThreadId: threadId, messageText: messageText, using: dependencies)
|
_ attachmentApproval: AttachmentApprovalViewController,
|
||||||
|
didApproveAttachments attachments: [SignalAttachment],
|
||||||
|
forThreadId threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
|
messageText: String?,
|
||||||
|
using dependencies: Dependencies
|
||||||
|
) {
|
||||||
|
sendMediaNavDelegate?.sendMediaNav(
|
||||||
|
self,
|
||||||
|
didApproveAttachments: attachments,
|
||||||
|
forThreadId: threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
|
messageText: messageText,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func attachmentApprovalDidCancel(_ attachmentApproval: AttachmentApprovalViewController) {
|
func attachmentApprovalDidCancel(_ attachmentApproval: AttachmentApprovalViewController) {
|
||||||
|
@ -765,7 +780,7 @@ private class DoneButton: UIView {
|
||||||
|
|
||||||
protocol SendMediaNavDelegate: AnyObject {
|
protocol SendMediaNavDelegate: AnyObject {
|
||||||
func sendMediaNavDidCancel(_ sendMediaNavigationController: SendMediaNavigationController?)
|
func sendMediaNavDidCancel(_ sendMediaNavigationController: SendMediaNavigationController?)
|
||||||
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?, using dependencies: Dependencies)
|
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, threadVariant: SessionThread.Variant, messageText: String?, using dependencies: Dependencies)
|
||||||
|
|
||||||
func sendMediaNavInitialMessageText(_ sendMediaNavigationController: SendMediaNavigationController) -> String?
|
func sendMediaNavInitialMessageText(_ sendMediaNavigationController: SendMediaNavigationController) -> String?
|
||||||
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didChangeMessageText newMessageText: String?)
|
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didChangeMessageText newMessageText: String?)
|
||||||
|
|
|
@ -258,11 +258,12 @@ class BlockedContactsViewModel: SessionTableViewModel<NoNav, BlockedContactsView
|
||||||
|
|
||||||
// MARK: - DataModel
|
// MARK: - DataModel
|
||||||
|
|
||||||
public struct DataModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable {
|
public struct DataModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable, ColumnExpressible {
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let profileKey: SQL = SQL(stringLiteral: CodingKeys.profile.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case rowId
|
||||||
public static let profileString: String = CodingKeys.profile.stringValue
|
case profile
|
||||||
|
}
|
||||||
|
|
||||||
public var differenceIdentifier: String { profile.id }
|
public var differenceIdentifier: String { profile.id }
|
||||||
public var id: String { profile.id }
|
public var id: String { profile.id }
|
||||||
|
@ -286,11 +287,11 @@ class BlockedContactsViewModel: SessionTableViewModel<NoNav, BlockedContactsView
|
||||||
|
|
||||||
let request: SQLRequest<DataModel> = """
|
let request: SQLRequest<DataModel> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(profile.alias[Column.rowID]) AS \(DataModel.rowIdKey),
|
\(profile[.rowId]) AS \(DataModel.Columns.rowId),
|
||||||
\(DataModel.profileKey).*
|
\(profile.allColumns)
|
||||||
|
|
||||||
FROM \(Profile.self)
|
FROM \(Profile.self)
|
||||||
WHERE \(profile.alias[Column.rowID]) IN \(rowIds)
|
WHERE \(profile[.rowId]) IN \(rowIds)
|
||||||
ORDER BY \(orderSQL)
|
ORDER BY \(orderSQL)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -300,8 +301,8 @@ class BlockedContactsViewModel: SessionTableViewModel<NoNav, BlockedContactsView
|
||||||
Profile.numberOfSelectedColumns(db)
|
Profile.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(DataModel.self, [
|
||||||
DataModel.profileString: adapters[1]
|
.profile: adapters[1]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -522,7 +522,7 @@ extension Attachment {
|
||||||
\(interaction[.id]) = \(interactionAttachment[.interactionId]) OR
|
\(interaction[.id]) = \(interactionAttachment[.interactionId]) OR
|
||||||
(
|
(
|
||||||
\(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND
|
\(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND
|
||||||
\(Interaction.linkPreviewFilterLiteral)
|
\(Interaction.linkPreviewFilterLiteral())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -568,7 +568,7 @@ extension Attachment {
|
||||||
\(interaction[.id]) = \(interactionAttachment[.interactionId]) OR
|
\(interaction[.id]) = \(interactionAttachment[.interactionId]) OR
|
||||||
(
|
(
|
||||||
\(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND
|
\(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND
|
||||||
\(Interaction.linkPreviewFilterLiteral)
|
\(Interaction.linkPreviewFilterLiteral())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,19 @@ public extension ClosedGroup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Search Queries
|
||||||
|
|
||||||
|
public extension ClosedGroup {
|
||||||
|
struct FullTextSearch: Decodable, ColumnExpressible {
|
||||||
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case name
|
||||||
|
}
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
public extension ClosedGroup {
|
public extension ClosedGroup {
|
||||||
|
|
|
@ -29,13 +29,14 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
|
||||||
/// Whenever using this `linkPreview` association make sure to filter the result using
|
/// Whenever using this `linkPreview` association make sure to filter the result using
|
||||||
/// `.filter(literal: Interaction.linkPreviewFilterLiteral)` to ensure the correct LinkPreview is returned
|
/// `.filter(literal: Interaction.linkPreviewFilterLiteral)` to ensure the correct LinkPreview is returned
|
||||||
public static let linkPreview = hasOne(LinkPreview.self, using: LinkPreview.interactionForeignKey)
|
public static let linkPreview = hasOne(LinkPreview.self, using: LinkPreview.interactionForeignKey)
|
||||||
public static var linkPreviewFilterLiteral: SQL = {
|
public static func linkPreviewFilterLiteral(
|
||||||
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
|
interaction: TypedTableAlias<Interaction> = TypedTableAlias(),
|
||||||
let linkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias()
|
linkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias()
|
||||||
|
) -> SQL {
|
||||||
let halfResolution: Double = LinkPreview.timstampResolution
|
let halfResolution: Double = LinkPreview.timstampResolution
|
||||||
|
|
||||||
return "(\(interaction[.timestampMs]) BETWEEN (\(linkPreview[.timestamp]) - \(halfResolution)) * 1000 AND (\(linkPreview[.timestamp]) + \(halfResolution)) * 1000)"
|
return "(\(interaction[.timestampMs]) BETWEEN (\(linkPreview[.timestamp]) - \(halfResolution)) * 1000 AND (\(linkPreview[.timestamp]) + \(halfResolution)) * 1000)"
|
||||||
}()
|
}
|
||||||
public static let recipientStates = hasMany(RecipientState.self, using: RecipientState.interactionForeignKey)
|
public static let recipientStates = hasMany(RecipientState.self, using: RecipientState.interactionForeignKey)
|
||||||
|
|
||||||
public typealias Columns = CodingKeys
|
public typealias Columns = CodingKeys
|
||||||
|
@ -695,6 +696,17 @@ public extension Interaction {
|
||||||
// MARK: - Search Queries
|
// MARK: - Search Queries
|
||||||
|
|
||||||
public extension Interaction {
|
public extension Interaction {
|
||||||
|
struct FullTextSearch: Decodable, ColumnExpressible {
|
||||||
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case threadId
|
||||||
|
case body
|
||||||
|
}
|
||||||
|
|
||||||
|
let threadId: String
|
||||||
|
let body: String
|
||||||
|
}
|
||||||
|
|
||||||
struct TimestampInfo: FetchableRecord, Codable {
|
struct TimestampInfo: FetchableRecord, Codable {
|
||||||
public let id: Int64
|
public let id: Int64
|
||||||
public let timestampMs: Int64
|
public let timestampMs: Int64
|
||||||
|
@ -710,8 +722,7 @@ public extension Interaction {
|
||||||
|
|
||||||
static func idsForTermWithin(threadId: String, pattern: FTS5Pattern) -> SQLRequest<TimestampInfo> {
|
static func idsForTermWithin(threadId: String, pattern: FTS5Pattern) -> SQLRequest<TimestampInfo> {
|
||||||
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
|
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
|
||||||
let interactionFullTextSearch: SQL = SQL(stringLiteral: Interaction.fullTextSearchTableName)
|
let interactionFullTextSearch: TypedTableAlias<FullTextSearch> = TypedTableAlias(name: Interaction.fullTextSearchTableName)
|
||||||
let threadIdLiteral: SQL = SQL(stringLiteral: Interaction.Columns.threadId.name)
|
|
||||||
|
|
||||||
let request: SQLRequest<TimestampInfo> = """
|
let request: SQLRequest<TimestampInfo> = """
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -719,9 +730,9 @@ public extension Interaction {
|
||||||
\(interaction[.timestampMs])
|
\(interaction[.timestampMs])
|
||||||
FROM \(Interaction.self)
|
FROM \(Interaction.self)
|
||||||
JOIN \(interactionFullTextSearch) ON (
|
JOIN \(interactionFullTextSearch) ON (
|
||||||
\(interactionFullTextSearch).rowid = \(interaction.alias[Column.rowID]) AND
|
\(interactionFullTextSearch[.rowId]) = \(interaction[.rowId]) AND
|
||||||
\(SQL("\(interactionFullTextSearch).\(threadIdLiteral) = \(threadId)")) AND
|
\(SQL("\(interactionFullTextSearch[.threadId]) = \(threadId)")) AND
|
||||||
\(interactionFullTextSearch).\(SQL(stringLiteral: Interaction.Columns.body.name)) MATCH \(pattern)
|
\(interactionFullTextSearch[.body]) MATCH \(pattern)
|
||||||
)
|
)
|
||||||
|
|
||||||
ORDER BY \(interaction[.timestampMs].desc)
|
ORDER BY \(interaction[.timestampMs].desc)
|
||||||
|
|
|
@ -215,6 +215,19 @@ public extension OpenGroup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Search Queries
|
||||||
|
|
||||||
|
public extension OpenGroup {
|
||||||
|
struct FullTextSearch: Decodable, ColumnExpressible {
|
||||||
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case name
|
||||||
|
}
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
public extension OpenGroup {
|
public extension OpenGroup {
|
||||||
|
|
|
@ -298,6 +298,21 @@ public extension Profile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Search Queries
|
||||||
|
|
||||||
|
public extension Profile {
|
||||||
|
struct FullTextSearch: Decodable, ColumnExpressible {
|
||||||
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case nickname
|
||||||
|
case name
|
||||||
|
}
|
||||||
|
|
||||||
|
let nickname: String?
|
||||||
|
let name: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
public extension Profile {
|
public extension Profile {
|
||||||
|
|
|
@ -365,7 +365,7 @@ public extension SessionThread {
|
||||||
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
||||||
|
|
||||||
return """
|
return """
|
||||||
SELECT \(thread.allColumns())
|
SELECT \(thread.allColumns)
|
||||||
FROM \(SessionThread.self)
|
FROM \(SessionThread.self)
|
||||||
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
|
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
|
||||||
WHERE (
|
WHERE (
|
||||||
|
|
|
@ -82,7 +82,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Interaction.self)
|
DELETE FROM \(Interaction.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(interaction.alias[Column.rowID])
|
SELECT \(interaction[.rowId])
|
||||||
FROM \(Interaction.self)
|
FROM \(Interaction.self)
|
||||||
JOIN \(SessionThread.self) ON (
|
JOIN \(SessionThread.self) ON (
|
||||||
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND
|
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND
|
||||||
|
@ -90,7 +90,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
)
|
)
|
||||||
JOIN (
|
JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(\(interaction.alias[Column.rowID])) AS interactionCount,
|
COUNT(\(interaction[.rowId])) AS interactionCount,
|
||||||
\(interaction[.threadId])
|
\(interaction[.threadId])
|
||||||
FROM \(Interaction.self)
|
FROM \(Interaction.self)
|
||||||
GROUP BY \(interaction[.threadId])
|
GROUP BY \(interaction[.threadId])
|
||||||
|
@ -112,7 +112,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Job.self)
|
DELETE FROM \(Job.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(job.alias[Column.rowID])
|
SELECT \(job[.rowId])
|
||||||
FROM \(Job.self)
|
FROM \(Job.self)
|
||||||
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(job[.threadId])
|
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(job[.threadId])
|
||||||
LEFT JOIN \(Interaction.self) ON \(interaction[.id]) = \(job[.interactionId])
|
LEFT JOIN \(Interaction.self) ON \(interaction[.id]) = \(job[.interactionId])
|
||||||
|
@ -139,11 +139,11 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(LinkPreview.self)
|
DELETE FROM \(LinkPreview.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(linkPreview.alias[Column.rowID])
|
SELECT \(linkPreview[.rowId])
|
||||||
FROM \(LinkPreview.self)
|
FROM \(LinkPreview.self)
|
||||||
LEFT JOIN \(Interaction.self) ON (
|
LEFT JOIN \(Interaction.self) ON (
|
||||||
\(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND
|
\(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND
|
||||||
\(Interaction.linkPreviewFilterLiteral)
|
\(Interaction.linkPreviewFilterLiteral())
|
||||||
)
|
)
|
||||||
WHERE \(interaction[.id]) IS NULL
|
WHERE \(interaction[.id]) IS NULL
|
||||||
)
|
)
|
||||||
|
@ -159,7 +159,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(OpenGroup.self)
|
DELETE FROM \(OpenGroup.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(openGroup.alias[Column.rowID])
|
SELECT \(openGroup[.rowId])
|
||||||
FROM \(OpenGroup.self)
|
FROM \(OpenGroup.self)
|
||||||
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(openGroup[.threadId])
|
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(openGroup[.threadId])
|
||||||
WHERE (
|
WHERE (
|
||||||
|
@ -178,7 +178,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Capability.self)
|
DELETE FROM \(Capability.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(capability.alias[Column.rowID])
|
SELECT \(capability[.rowId])
|
||||||
FROM \(Capability.self)
|
FROM \(Capability.self)
|
||||||
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.server]) = \(capability[.openGroupServer])
|
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.server]) = \(capability[.openGroupServer])
|
||||||
WHERE \(openGroup[.threadId]) IS NULL
|
WHERE \(openGroup[.threadId]) IS NULL
|
||||||
|
@ -195,7 +195,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(BlindedIdLookup.self)
|
DELETE FROM \(BlindedIdLookup.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(blindedIdLookup.alias[Column.rowID])
|
SELECT \(blindedIdLookup[.rowId])
|
||||||
FROM \(BlindedIdLookup.self)
|
FROM \(BlindedIdLookup.self)
|
||||||
LEFT JOIN \(SessionThread.self) ON (
|
LEFT JOIN \(SessionThread.self) ON (
|
||||||
\(thread[.id]) = \(blindedIdLookup[.blindedId]) OR
|
\(thread[.id]) = \(blindedIdLookup[.blindedId]) OR
|
||||||
|
@ -222,7 +222,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Contact.self)
|
DELETE FROM \(Contact.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(contact.alias[Column.rowID])
|
SELECT \(contact[.rowId])
|
||||||
FROM \(Contact.self)
|
FROM \(Contact.self)
|
||||||
LEFT JOIN \(BlindedIdLookup.self) ON (
|
LEFT JOIN \(BlindedIdLookup.self) ON (
|
||||||
\(blindedIdLookup[.blindedId]) = \(contact[.id]) AND
|
\(blindedIdLookup[.blindedId]) = \(contact[.id]) AND
|
||||||
|
@ -243,7 +243,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Attachment.self)
|
DELETE FROM \(Attachment.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(attachment.alias[Column.rowID])
|
SELECT \(attachment[.rowId])
|
||||||
FROM \(Attachment.self)
|
FROM \(Attachment.self)
|
||||||
LEFT JOIN \(Quote.self) ON \(quote[.attachmentId]) = \(attachment[.id])
|
LEFT JOIN \(Quote.self) ON \(quote[.attachmentId]) = \(attachment[.id])
|
||||||
LEFT JOIN \(LinkPreview.self) ON \(linkPreview[.attachmentId]) = \(attachment[.id])
|
LEFT JOIN \(LinkPreview.self) ON \(linkPreview[.attachmentId]) = \(attachment[.id])
|
||||||
|
@ -269,7 +269,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Profile.self)
|
DELETE FROM \(Profile.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(profile.alias[Column.rowID])
|
SELECT \(profile[.rowId])
|
||||||
FROM \(Profile.self)
|
FROM \(Profile.self)
|
||||||
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(profile[.id])
|
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(profile[.id])
|
||||||
LEFT JOIN \(Interaction.self) ON \(interaction[.authorId]) = \(profile[.id])
|
LEFT JOIN \(Interaction.self) ON \(interaction[.authorId]) = \(profile[.id])
|
||||||
|
@ -310,7 +310,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(SessionThread.self)
|
DELETE FROM \(SessionThread.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(thread.alias[Column.rowID])
|
SELECT \(thread[.rowId])
|
||||||
FROM \(SessionThread.self)
|
FROM \(SessionThread.self)
|
||||||
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
|
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
|
||||||
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id])
|
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id])
|
||||||
|
|
|
@ -786,14 +786,14 @@ public final class OpenGroupManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo {
|
if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo, let proto: SNProtoContent = processedMessage?.proto {
|
||||||
try MessageReceiver.handle(
|
try MessageReceiver.handle(
|
||||||
db,
|
db,
|
||||||
threadId: (lookup.sessionId ?? lookup.blindedId),
|
threadId: (lookup.sessionId ?? lookup.blindedId),
|
||||||
threadVariant: .contact, // Technically not open group messages
|
threadVariant: .contact, // Technically not open group messages
|
||||||
message: messageInfo.message,
|
message: messageInfo.message,
|
||||||
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
||||||
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
associatedWithProto: proto,
|
||||||
using: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
import GRDB
|
import GRDB
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
public struct MentionInfo: FetchableRecord, Decodable {
|
public struct MentionInfo: FetchableRecord, Decodable, ColumnExpressible {
|
||||||
fileprivate static let threadVariantKey: SQL = SQL(stringLiteral: CodingKeys.threadVariant.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
fileprivate static let openGroupServerKey: SQL = SQL(stringLiteral: CodingKeys.openGroupServer.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
fileprivate static let openGroupRoomTokenKey: SQL = SQL(stringLiteral: CodingKeys.openGroupRoomToken.stringValue)
|
case profile
|
||||||
|
case threadVariant
|
||||||
fileprivate static let profileString: String = CodingKeys.profile.stringValue
|
case openGroupServer
|
||||||
|
case openGroupRoomToken
|
||||||
|
}
|
||||||
|
|
||||||
public let profile: Profile
|
public let profile: Profile
|
||||||
public let threadVariant: SessionThread.Variant
|
public let threadVariant: SessionThread.Variant
|
||||||
|
@ -79,7 +81,7 @@ public extension MentionInfo {
|
||||||
return SQLRequest("""
|
return SQLRequest("""
|
||||||
SELECT
|
SELECT
|
||||||
\(Profile.self).*,
|
\(Profile.self).*,
|
||||||
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)"))
|
\(SQL("\(threadVariant) AS \(MentionInfo.Columns.threadVariant)"))
|
||||||
|
|
||||||
\(targetJoin)
|
\(targetJoin)
|
||||||
\(targetWhere) AND \(SQL("\(profile[.id]) = \(threadId)"))
|
\(targetWhere) AND \(SQL("\(profile[.id]) = \(threadId)"))
|
||||||
|
@ -89,7 +91,7 @@ public extension MentionInfo {
|
||||||
return SQLRequest("""
|
return SQLRequest("""
|
||||||
SELECT
|
SELECT
|
||||||
\(Profile.self).*,
|
\(Profile.self).*,
|
||||||
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)"))
|
\(SQL("\(threadVariant) AS \(MentionInfo.Columns.threadVariant)"))
|
||||||
|
|
||||||
\(targetJoin)
|
\(targetJoin)
|
||||||
JOIN \(GroupMember.self) ON (
|
JOIN \(GroupMember.self) ON (
|
||||||
|
@ -107,9 +109,9 @@ public extension MentionInfo {
|
||||||
SELECT
|
SELECT
|
||||||
\(Profile.self).*,
|
\(Profile.self).*,
|
||||||
MAX(\(interaction[.timestampMs])), -- Want the newest interaction (for sorting)
|
MAX(\(interaction[.timestampMs])), -- Want the newest interaction (for sorting)
|
||||||
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)")),
|
\(SQL("\(threadVariant) AS \(MentionInfo.Columns.threadVariant)")),
|
||||||
\(openGroup[.server]) AS \(MentionInfo.openGroupServerKey),
|
\(openGroup[.server]) AS \(MentionInfo.Columns.openGroupServer),
|
||||||
\(openGroup[.roomToken]) AS \(MentionInfo.openGroupRoomTokenKey)
|
\(openGroup[.roomToken]) AS \(MentionInfo.Columns.openGroupRoomToken)
|
||||||
|
|
||||||
\(targetJoin)
|
\(targetJoin)
|
||||||
JOIN \(Interaction.self) ON (
|
JOIN \(Interaction.self) ON (
|
||||||
|
@ -130,8 +132,8 @@ public extension MentionInfo {
|
||||||
Profile.numberOfSelectedColumns(db)
|
Profile.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(MentionInfo.self, [
|
||||||
MentionInfo.profileString: adapters[0]
|
.profile: adapters[0]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,42 +11,66 @@ fileprivate typealias AttachmentInteractionInfo = MessageViewModel.AttachmentInt
|
||||||
fileprivate typealias ReactionInfo = MessageViewModel.ReactionInfo
|
fileprivate typealias ReactionInfo = MessageViewModel.ReactionInfo
|
||||||
fileprivate typealias TypingIndicatorInfo = MessageViewModel.TypingIndicatorInfo
|
fileprivate typealias TypingIndicatorInfo = MessageViewModel.TypingIndicatorInfo
|
||||||
|
|
||||||
public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable {
|
public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable, ColumnExpressible {
|
||||||
public static let threadIdKey: SQL = SQL(stringLiteral: CodingKeys.threadId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let threadVariantKey: SQL = SQL(stringLiteral: CodingKeys.threadVariant.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
public static let threadIsTrustedKey: SQL = SQL(stringLiteral: CodingKeys.threadIsTrusted.stringValue)
|
case threadId
|
||||||
public static let threadHasDisappearingMessagesEnabledKey: SQL = SQL(stringLiteral: CodingKeys.threadHasDisappearingMessagesEnabled.stringValue)
|
case threadVariant
|
||||||
public static let threadOpenGroupServerKey: SQL = SQL(stringLiteral: CodingKeys.threadOpenGroupServer.stringValue)
|
case threadIsTrusted
|
||||||
public static let threadOpenGroupPublicKeyKey: SQL = SQL(stringLiteral: CodingKeys.threadOpenGroupPublicKey.stringValue)
|
case threadHasDisappearingMessagesEnabled
|
||||||
public static let threadContactNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.threadContactNameInternal.stringValue)
|
case threadOpenGroupServer
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
case threadOpenGroupPublicKey
|
||||||
public static let authorNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.authorNameInternal.stringValue)
|
case threadContactNameInternal
|
||||||
public static let stateKey: SQL = SQL(stringLiteral: CodingKeys.state.stringValue)
|
|
||||||
public static let hasAtLeastOneReadReceiptKey: SQL = SQL(stringLiteral: CodingKeys.hasAtLeastOneReadReceipt.stringValue)
|
// Interaction Info
|
||||||
public static let mostRecentFailureTextKey: SQL = SQL(stringLiteral: CodingKeys.mostRecentFailureText.stringValue)
|
|
||||||
public static let isTypingIndicatorKey: SQL = SQL(stringLiteral: CodingKeys.isTypingIndicator.stringValue)
|
case rowId
|
||||||
public static let isSenderOpenGroupModeratorKey: SQL = SQL(stringLiteral: CodingKeys.isSenderOpenGroupModerator.stringValue)
|
case id
|
||||||
public static let profileKey: SQL = SQL(stringLiteral: CodingKeys.profile.stringValue)
|
case openGroupServerMessageId
|
||||||
public static let quoteKey: SQL = SQL(stringLiteral: CodingKeys.quote.stringValue)
|
case variant
|
||||||
public static let quoteAttachmentKey: SQL = SQL(stringLiteral: CodingKeys.quoteAttachment.stringValue)
|
case timestampMs
|
||||||
public static let linkPreviewKey: SQL = SQL(stringLiteral: CodingKeys.linkPreview.stringValue)
|
case receivedAtTimestampMs
|
||||||
public static let linkPreviewAttachmentKey: SQL = SQL(stringLiteral: CodingKeys.linkPreviewAttachment.stringValue)
|
case authorId
|
||||||
public static let currentUserPublicKeyKey: SQL = SQL(stringLiteral: CodingKeys.currentUserPublicKey.stringValue)
|
case authorNameInternal
|
||||||
public static let cellTypeKey: SQL = SQL(stringLiteral: CodingKeys.cellType.stringValue)
|
case body
|
||||||
public static let authorNameKey: SQL = SQL(stringLiteral: CodingKeys.authorName.stringValue)
|
case rawBody
|
||||||
public static let canHaveProfileKey: SQL = SQL(stringLiteral: CodingKeys.canHaveProfile.stringValue)
|
case expiresStartedAtMs
|
||||||
public static let shouldShowProfileKey: SQL = SQL(stringLiteral: CodingKeys.shouldShowProfile.stringValue)
|
case expiresInSeconds
|
||||||
public static let shouldShowDateHeaderKey: SQL = SQL(stringLiteral: CodingKeys.shouldShowDateHeader.stringValue)
|
|
||||||
public static let positionInClusterKey: SQL = SQL(stringLiteral: CodingKeys.positionInCluster.stringValue)
|
case state
|
||||||
public static let isOnlyMessageInClusterKey: SQL = SQL(stringLiteral: CodingKeys.isOnlyMessageInCluster.stringValue)
|
case hasAtLeastOneReadReceipt
|
||||||
public static let isLastKey: SQL = SQL(stringLiteral: CodingKeys.isLast.stringValue)
|
case mostRecentFailureText
|
||||||
public static let isLastOutgoingKey: SQL = SQL(stringLiteral: CodingKeys.isLastOutgoing.stringValue)
|
case isSenderOpenGroupModerator
|
||||||
|
case isTypingIndicator
|
||||||
public static let profileString: String = CodingKeys.profile.stringValue
|
case profile
|
||||||
public static let quoteString: String = CodingKeys.quote.stringValue
|
case quote
|
||||||
public static let quoteAttachmentString: String = CodingKeys.quoteAttachment.stringValue
|
case quoteAttachment
|
||||||
public static let linkPreviewString: String = CodingKeys.linkPreview.stringValue
|
case linkPreview
|
||||||
public static let linkPreviewAttachmentString: String = CodingKeys.linkPreviewAttachment.stringValue
|
case linkPreviewAttachment
|
||||||
|
|
||||||
|
case currentUserPublicKey
|
||||||
|
|
||||||
|
// Post-Query Processing Data
|
||||||
|
|
||||||
|
case attachments
|
||||||
|
case reactionInfo
|
||||||
|
case cellType
|
||||||
|
case authorName
|
||||||
|
case senderName
|
||||||
|
case canHaveProfile
|
||||||
|
case shouldShowProfile
|
||||||
|
case shouldShowDateHeader
|
||||||
|
case containsOnlyEmoji
|
||||||
|
case glyphCount
|
||||||
|
case previousVariant
|
||||||
|
case positionInCluster
|
||||||
|
case isOnlyMessageInCluster
|
||||||
|
case isLast
|
||||||
|
case isLastOutgoing
|
||||||
|
case currentUserBlinded15PublicKey
|
||||||
|
case currentUserBlinded25PublicKey
|
||||||
|
case optimisticMessageId
|
||||||
|
}
|
||||||
|
|
||||||
public enum CellType: Int, Decodable, Equatable, Hashable, DatabaseValueConvertible {
|
public enum CellType: Int, Decodable, Equatable, Hashable, DatabaseValueConvertible {
|
||||||
case textOnlyMessage
|
case textOnlyMessage
|
||||||
|
@ -462,13 +486,13 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
|
||||||
// MARK: - AttachmentInteractionInfo
|
// MARK: - AttachmentInteractionInfo
|
||||||
|
|
||||||
public extension MessageViewModel {
|
public extension MessageViewModel {
|
||||||
struct AttachmentInteractionInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, Comparable {
|
struct AttachmentInteractionInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, Comparable, ColumnExpressible {
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let attachmentKey: SQL = SQL(stringLiteral: CodingKeys.attachment.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
public static let interactionAttachmentKey: SQL = SQL(stringLiteral: CodingKeys.interactionAttachment.stringValue)
|
case rowId
|
||||||
|
case attachment
|
||||||
public static let attachmentString: String = CodingKeys.attachment.stringValue
|
case interactionAttachment
|
||||||
public static let interactionAttachmentString: String = CodingKeys.interactionAttachment.stringValue
|
}
|
||||||
|
|
||||||
public let rowId: Int64
|
public let rowId: Int64
|
||||||
public let attachment: Attachment
|
public let attachment: Attachment
|
||||||
|
@ -491,13 +515,13 @@ public extension MessageViewModel {
|
||||||
// MARK: - ReactionInfo
|
// MARK: - ReactionInfo
|
||||||
|
|
||||||
public extension MessageViewModel {
|
public extension MessageViewModel {
|
||||||
struct ReactionInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, Comparable, Hashable, Differentiable {
|
struct ReactionInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, Comparable, Hashable, Differentiable, ColumnExpressible {
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let reactionKey: SQL = SQL(stringLiteral: CodingKeys.reaction.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
public static let profileKey: SQL = SQL(stringLiteral: CodingKeys.profile.stringValue)
|
case rowId
|
||||||
|
case reaction
|
||||||
public static let reactionString: String = CodingKeys.reaction.stringValue
|
case profile
|
||||||
public static let profileString: String = CodingKeys.profile.stringValue
|
}
|
||||||
|
|
||||||
public let rowId: Int64
|
public let rowId: Int64
|
||||||
public let reaction: Reaction
|
public let reaction: Reaction
|
||||||
|
@ -522,9 +546,12 @@ public extension MessageViewModel {
|
||||||
// MARK: - TypingIndicatorInfo
|
// MARK: - TypingIndicatorInfo
|
||||||
|
|
||||||
public extension MessageViewModel {
|
public extension MessageViewModel {
|
||||||
struct TypingIndicatorInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable {
|
struct TypingIndicatorInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, ColumnExpressible {
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let threadIdKey: SQL = SQL(stringLiteral: CodingKeys.threadId.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case rowId
|
||||||
|
case threadId
|
||||||
|
}
|
||||||
|
|
||||||
public let rowId: Int64
|
public let rowId: Int64
|
||||||
public let threadId: String
|
public let threadId: String
|
||||||
|
@ -776,59 +803,48 @@ public extension MessageViewModel {
|
||||||
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
||||||
let disappearingMessagesConfig: TypedTableAlias<DisappearingMessagesConfiguration> = TypedTableAlias()
|
let disappearingMessagesConfig: TypedTableAlias<DisappearingMessagesConfiguration> = TypedTableAlias()
|
||||||
let profile: TypedTableAlias<Profile> = TypedTableAlias()
|
let profile: TypedTableAlias<Profile> = TypedTableAlias()
|
||||||
|
let threadProfile: TypedTableAlias<Profile> = TypedTableAlias(name: "threadProfile")
|
||||||
let quote: TypedTableAlias<Quote> = TypedTableAlias()
|
let quote: TypedTableAlias<Quote> = TypedTableAlias()
|
||||||
|
let quoteInteraction: TypedTableAlias<Interaction> = TypedTableAlias(name: "quoteInteraction")
|
||||||
|
let quoteInteractionAttachment: TypedTableAlias<InteractionAttachment> = TypedTableAlias(
|
||||||
|
name: "quoteInteractionAttachment"
|
||||||
|
)
|
||||||
|
let quoteLinkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias(name: "quoteLinkPreview")
|
||||||
|
let quoteAttachment: TypedTableAlias<Attachment> = TypedTableAlias(name: ViewModel.CodingKeys.quoteAttachment.stringValue)
|
||||||
let linkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias()
|
let linkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias()
|
||||||
|
let linkPreviewAttachment: TypedTableAlias<Attachment> = TypedTableAlias(ViewModel.self, column: .linkPreviewAttachment)
|
||||||
let threadProfile: SQL = SQL(stringLiteral: "threadProfile")
|
let readReceipt: TypedTableAlias<RecipientState> = TypedTableAlias(name: "readReceipt")
|
||||||
let quoteInteraction: SQL = SQL(stringLiteral: "quoteInteraction")
|
|
||||||
let quoteInteractionAttachment: SQL = SQL(stringLiteral: "quoteInteractionAttachment")
|
|
||||||
let readReceipt: SQL = SQL(stringLiteral: "readReceipt")
|
|
||||||
let idColumn: SQL = SQL(stringLiteral: Interaction.Columns.id.name)
|
|
||||||
let interactionBodyColumn: SQL = SQL(stringLiteral: Interaction.Columns.body.name)
|
|
||||||
let profileIdColumn: SQL = SQL(stringLiteral: Profile.Columns.id.name)
|
|
||||||
let nicknameColumn: SQL = SQL(stringLiteral: Profile.Columns.nickname.name)
|
|
||||||
let nameColumn: SQL = SQL(stringLiteral: Profile.Columns.name.name)
|
|
||||||
let quoteBodyColumn: SQL = SQL(stringLiteral: Quote.Columns.body.name)
|
|
||||||
let quoteAttachmentIdColumn: SQL = SQL(stringLiteral: Quote.Columns.attachmentId.name)
|
|
||||||
let readReceiptInteractionIdColumn: SQL = SQL(stringLiteral: RecipientState.Columns.interactionId.name)
|
|
||||||
let readTimestampMsColumn: SQL = SQL(stringLiteral: RecipientState.Columns.readTimestampMs.name)
|
|
||||||
let timestampMsColumn: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name)
|
|
||||||
let authorIdColumn: SQL = SQL(stringLiteral: Interaction.Columns.authorId.name)
|
|
||||||
let attachmentIdColumn: SQL = SQL(stringLiteral: Attachment.Columns.id.name)
|
|
||||||
let interactionAttachmentInteractionIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.interactionId.name)
|
|
||||||
let interactionAttachmentAttachmentIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.attachmentId.name)
|
|
||||||
let interactionAttachmentAlbumIndexColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.albumIndex.name)
|
|
||||||
|
|
||||||
let numColumnsBeforeLinkedRecords: Int = 22
|
let numColumnsBeforeLinkedRecords: Int = 22
|
||||||
let finalGroupSQL: SQL = (groupSQL ?? "")
|
let finalGroupSQL: SQL = (groupSQL ?? "")
|
||||||
let request: SQLRequest<ViewModel> = """
|
let request: SQLRequest<ViewModel> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(thread[.id]) AS \(ViewModel.threadIdKey),
|
\(thread[.id]) AS \(ViewModel.Columns.threadId),
|
||||||
\(thread[.variant]) AS \(ViewModel.threadVariantKey),
|
\(thread[.variant]) AS \(ViewModel.Columns.threadVariant),
|
||||||
-- Default to 'true' for non-contact threads
|
-- Default to 'true' for non-contact threads
|
||||||
IFNULL(\(contact[.isTrusted]), true) AS \(ViewModel.threadIsTrustedKey),
|
IFNULL(\(contact[.isTrusted]), true) AS \(ViewModel.Columns.threadIsTrusted),
|
||||||
-- Default to 'false' when no contact exists
|
-- Default to 'false' when no contact exists
|
||||||
IFNULL(\(disappearingMessagesConfig[.isEnabled]), false) AS \(ViewModel.threadHasDisappearingMessagesEnabledKey),
|
IFNULL(\(disappearingMessagesConfig[.isEnabled]), false) AS \(ViewModel.Columns.threadHasDisappearingMessagesEnabled),
|
||||||
\(openGroup[.server]) AS \(ViewModel.threadOpenGroupServerKey),
|
\(openGroup[.server]) AS \(ViewModel.Columns.threadOpenGroupServer),
|
||||||
\(openGroup[.publicKey]) AS \(ViewModel.threadOpenGroupPublicKeyKey),
|
\(openGroup[.publicKey]) AS \(ViewModel.Columns.threadOpenGroupPublicKey),
|
||||||
IFNULL(\(threadProfile).\(nicknameColumn), \(threadProfile).\(nameColumn)) AS \(ViewModel.threadContactNameInternalKey),
|
IFNULL(\(threadProfile[.nickname]), \(threadProfile[.name])) AS \(ViewModel.Columns.threadContactNameInternal),
|
||||||
|
|
||||||
\(interaction.alias[Column.rowID]) AS \(ViewModel.rowIdKey),
|
\(interaction[.rowId]) AS \(ViewModel.Columns.rowId),
|
||||||
\(interaction[.id]),
|
\(interaction[.id]),
|
||||||
\(interaction[.openGroupServerMessageId]),
|
\(interaction[.openGroupServerMessageId]),
|
||||||
\(interaction[.variant]),
|
\(interaction[.variant]),
|
||||||
\(interaction[.timestampMs]),
|
\(interaction[.timestampMs]),
|
||||||
\(interaction[.receivedAtTimestampMs]),
|
\(interaction[.receivedAtTimestampMs]),
|
||||||
\(interaction[.authorId]),
|
\(interaction[.authorId]),
|
||||||
IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.authorNameInternalKey),
|
IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.Columns.authorNameInternal),
|
||||||
\(interaction[.body]),
|
\(interaction[.body]),
|
||||||
\(interaction[.expiresStartedAtMs]),
|
\(interaction[.expiresStartedAtMs]),
|
||||||
\(interaction[.expiresInSeconds]),
|
\(interaction[.expiresInSeconds]),
|
||||||
|
|
||||||
-- Default to 'sending' assuming non-processed interaction when null
|
-- Default to 'sending' assuming non-processed interaction when null
|
||||||
IFNULL(MIN(\(recipientState[.state])), \(SQL("\(RecipientState.State.sending)"))) AS \(ViewModel.stateKey),
|
IFNULL(MIN(\(recipientState[.state])), \(SQL("\(RecipientState.State.sending)"))) AS \(ViewModel.Columns.state),
|
||||||
(\(readReceipt).\(readTimestampMsColumn) IS NOT NULL) AS \(ViewModel.hasAtLeastOneReadReceiptKey),
|
(\(readReceipt[.readTimestampMs]) IS NOT NULL) AS \(ViewModel.Columns.hasAtLeastOneReadReceipt),
|
||||||
\(recipientState[.mostRecentFailureText]) AS \(ViewModel.mostRecentFailureTextKey),
|
\(recipientState[.mostRecentFailureText]) AS \(ViewModel.Columns.mostRecentFailureText),
|
||||||
|
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
|
@ -839,46 +855,46 @@ public extension MessageViewModel {
|
||||||
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND
|
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND
|
||||||
\(SQL("\(groupMember[.role]) IN \([GroupMember.Role.moderator, GroupMember.Role.admin])"))
|
\(SQL("\(groupMember[.role]) IN \([GroupMember.Role.moderator, GroupMember.Role.admin])"))
|
||||||
)
|
)
|
||||||
) AS \(ViewModel.isSenderOpenGroupModeratorKey),
|
) AS \(ViewModel.Columns.isSenderOpenGroupModerator),
|
||||||
|
|
||||||
\(ViewModel.profileKey).*,
|
\(profile.allColumns),
|
||||||
\(quote[.interactionId]),
|
\(quote[.interactionId]),
|
||||||
\(quote[.authorId]),
|
\(quote[.authorId]),
|
||||||
\(quote[.timestampMs]),
|
\(quote[.timestampMs]),
|
||||||
\(quoteInteraction).\(interactionBodyColumn) AS \(quoteBodyColumn),
|
\(quoteInteraction[.body]),
|
||||||
\(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) AS \(quoteAttachmentIdColumn),
|
\(quoteInteractionAttachment[.attachmentId]),
|
||||||
\(ViewModel.quoteAttachmentKey).*,
|
\(quoteAttachment.allColumns),
|
||||||
\(ViewModel.linkPreviewKey).*,
|
\(linkPreview.allColumns),
|
||||||
\(ViewModel.linkPreviewAttachmentKey).*,
|
\(linkPreviewAttachment.allColumns),
|
||||||
|
|
||||||
\(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey),
|
\(SQL("\(userPublicKey)")) AS \(ViewModel.Columns.currentUserPublicKey),
|
||||||
|
|
||||||
-- All of the below properties are set in post-query processing but to prevent the
|
-- All of the below properties are set in post-query processing but to prevent the
|
||||||
-- query from crashing when decoding we need to provide default values
|
-- query from crashing when decoding we need to provide default values
|
||||||
\(CellType.textOnlyMessage) AS \(ViewModel.cellTypeKey),
|
\(CellType.textOnlyMessage) AS \(ViewModel.Columns.cellType),
|
||||||
'' AS \(ViewModel.authorNameKey),
|
'' AS \(ViewModel.Columns.authorName),
|
||||||
false AS \(ViewModel.canHaveProfileKey),
|
false AS \(ViewModel.Columns.canHaveProfile),
|
||||||
false AS \(ViewModel.shouldShowProfileKey),
|
false AS \(ViewModel.Columns.shouldShowProfile),
|
||||||
false AS \(ViewModel.shouldShowDateHeaderKey),
|
false AS \(ViewModel.Columns.shouldShowDateHeader),
|
||||||
\(Position.middle) AS \(ViewModel.positionInClusterKey),
|
\(Position.middle) AS \(ViewModel.Columns.positionInCluster),
|
||||||
false AS \(ViewModel.isOnlyMessageInClusterKey),
|
false AS \(ViewModel.Columns.isOnlyMessageInCluster),
|
||||||
false AS \(ViewModel.isLastKey),
|
false AS \(ViewModel.Columns.isLast),
|
||||||
false AS \(ViewModel.isLastOutgoingKey)
|
false AS \(ViewModel.Columns.isLastOutgoing)
|
||||||
|
|
||||||
FROM \(Interaction.self)
|
FROM \(Interaction.self)
|
||||||
JOIN \(SessionThread.self) ON \(thread[.id]) = \(interaction[.threadId])
|
JOIN \(SessionThread.self) ON \(thread[.id]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId])
|
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(Profile.self) AS \(threadProfile) ON \(threadProfile).\(profileIdColumn) = \(interaction[.threadId])
|
LEFT JOIN \(threadProfile) ON \(threadProfile[.id]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(DisappearingMessagesConfiguration.self) ON \(disappearingMessagesConfig[.threadId]) = \(interaction[.threadId])
|
LEFT JOIN \(DisappearingMessagesConfiguration.self) ON \(disappearingMessagesConfig[.threadId]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(interaction[.threadId])
|
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])
|
LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])
|
||||||
LEFT JOIN \(Quote.self) ON \(quote[.interactionId]) = \(interaction[.id])
|
LEFT JOIN \(Quote.self) ON \(quote[.interactionId]) = \(interaction[.id])
|
||||||
LEFT JOIN \(Interaction.self) AS \(quoteInteraction) ON (
|
LEFT JOIN \(quoteInteraction) ON (
|
||||||
\(quoteInteraction).\(timestampMsColumn) = \(quote[.timestampMs]) AND (
|
\(quoteInteraction[.timestampMs]) = \(quote[.timestampMs]) AND (
|
||||||
\(quoteInteraction).\(authorIdColumn) = \(quote[.authorId]) OR (
|
\(quoteInteraction[.authorId]) = \(quote[.authorId]) OR (
|
||||||
-- A users outgoing message is stored in some cases using their standard id
|
-- A users outgoing message is stored in some cases using their standard id
|
||||||
-- but the quote will use their blinded id so handle that case
|
-- but the quote will use their blinded id so handle that case
|
||||||
\(quoteInteraction).\(authorIdColumn) = \(userPublicKey) AND
|
\(quoteInteraction[.authorId]) = \(userPublicKey) AND
|
||||||
(
|
(
|
||||||
\(quote[.authorId]) = \(blinded15PublicKey ?? "''") OR
|
\(quote[.authorId]) = \(blinded15PublicKey ?? "''") OR
|
||||||
\(quote[.authorId]) = \(blinded25PublicKey ?? "''")
|
\(quote[.authorId]) = \(blinded25PublicKey ?? "''")
|
||||||
|
@ -886,27 +902,38 @@ public extension MessageViewModel {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
LEFT JOIN \(InteractionAttachment.self) AS \(quoteInteractionAttachment) ON (
|
LEFT JOIN \(quoteInteractionAttachment) ON (
|
||||||
\(quoteInteractionAttachment).\(interactionAttachmentInteractionIdColumn) = \(quoteInteraction).\(idColumn) AND
|
\(quoteInteractionAttachment[.interactionId]) = \(quoteInteraction[.id]) AND
|
||||||
\(quoteInteractionAttachment).\(interactionAttachmentAlbumIndexColumn) = 0
|
\(quoteInteractionAttachment[.albumIndex]) = 0
|
||||||
|
)
|
||||||
|
LEFT JOIN \(quoteLinkPreview) ON (
|
||||||
|
\(quoteLinkPreview[.url]) = \(quoteInteraction[.linkPreviewUrl]) AND
|
||||||
|
\(Interaction.linkPreviewFilterLiteral(
|
||||||
|
interaction: quoteInteraction,
|
||||||
|
linkPreview: quoteLinkPreview
|
||||||
|
))
|
||||||
|
)
|
||||||
|
LEFT JOIN \(quoteAttachment) ON (
|
||||||
|
\(quoteAttachment[.id]) = \(quoteInteractionAttachment[.attachmentId]) OR
|
||||||
|
\(quoteAttachment[.id]) = \(quoteLinkPreview[.attachmentId]) OR
|
||||||
|
\(quoteAttachment[.id]) = \(quote[.attachmentId])
|
||||||
)
|
)
|
||||||
LEFT JOIN \(Attachment.self) AS \(ViewModel.quoteAttachmentKey) ON \(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn)
|
|
||||||
|
|
||||||
LEFT JOIN \(LinkPreview.self) ON (
|
LEFT JOIN \(LinkPreview.self) ON (
|
||||||
\(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND
|
\(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND
|
||||||
\(Interaction.linkPreviewFilterLiteral)
|
\(Interaction.linkPreviewFilterLiteral())
|
||||||
)
|
)
|
||||||
LEFT JOIN \(Attachment.self) AS \(ViewModel.linkPreviewAttachmentKey) ON \(ViewModel.linkPreviewAttachmentKey).\(attachmentIdColumn) = \(linkPreview[.attachmentId])
|
LEFT JOIN \(linkPreviewAttachment) ON \(linkPreviewAttachment[.id]) = \(linkPreview[.attachmentId])
|
||||||
LEFT JOIN \(RecipientState.self) ON (
|
LEFT JOIN \(RecipientState.self) ON (
|
||||||
-- Ignore 'skipped' states
|
-- Ignore 'skipped' states
|
||||||
\(SQL("\(recipientState[.state]) != \(RecipientState.State.skipped)")) AND
|
\(SQL("\(recipientState[.state]) != \(RecipientState.State.skipped)")) AND
|
||||||
\(recipientState[.interactionId]) = \(interaction[.id])
|
\(recipientState[.interactionId]) = \(interaction[.id])
|
||||||
)
|
)
|
||||||
LEFT JOIN \(RecipientState.self) AS \(readReceipt) ON (
|
LEFT JOIN \(readReceipt) ON (
|
||||||
\(readReceipt).\(readTimestampMsColumn) IS NOT NULL AND
|
\(readReceipt[.readTimestampMs]) IS NOT NULL AND
|
||||||
\(readReceipt).\(readReceiptInteractionIdColumn) = \(interaction[.id])
|
\(readReceipt[.interactionId]) = \(interaction[.id])
|
||||||
)
|
)
|
||||||
WHERE \(interaction.alias[Column.rowID]) IN \(rowIds)
|
WHERE \(interaction[.rowId]) IN \(rowIds)
|
||||||
\(finalGroupSQL)
|
\(finalGroupSQL)
|
||||||
ORDER BY \(orderSQL)
|
ORDER BY \(orderSQL)
|
||||||
"""
|
"""
|
||||||
|
@ -921,12 +948,12 @@ public extension MessageViewModel {
|
||||||
Attachment.numberOfSelectedColumns(db)
|
Attachment.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(ViewModel.self, [
|
||||||
ViewModel.profileString: adapters[1],
|
.profile: adapters[1],
|
||||||
ViewModel.quoteString: adapters[2],
|
.quote: adapters[2],
|
||||||
ViewModel.quoteAttachmentString: adapters[3],
|
.quoteAttachment: adapters[3],
|
||||||
ViewModel.linkPreviewString: adapters[4],
|
.linkPreview: adapters[4],
|
||||||
ViewModel.linkPreviewAttachmentString: adapters[5]
|
.linkPreviewAttachment: adapters[5]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -953,9 +980,9 @@ public extension MessageViewModel.AttachmentInteractionInfo {
|
||||||
let numColumnsBeforeLinkedRecords: Int = 1
|
let numColumnsBeforeLinkedRecords: Int = 1
|
||||||
let request: SQLRequest<AttachmentInteractionInfo> = """
|
let request: SQLRequest<AttachmentInteractionInfo> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(attachment.alias[Column.rowID]) AS \(AttachmentInteractionInfo.rowIdKey),
|
\(attachment[.rowId]) AS \(AttachmentInteractionInfo.Columns.rowId),
|
||||||
\(AttachmentInteractionInfo.attachmentKey).*,
|
\(attachment.allColumns),
|
||||||
\(AttachmentInteractionInfo.interactionAttachmentKey).*
|
\(interactionAttachment.allColumns)
|
||||||
FROM \(Attachment.self)
|
FROM \(Attachment.self)
|
||||||
JOIN \(InteractionAttachment.self) ON \(interactionAttachment[.attachmentId]) = \(attachment[.id])
|
JOIN \(InteractionAttachment.self) ON \(interactionAttachment[.attachmentId]) = \(attachment[.id])
|
||||||
\(finalFilterSQL)
|
\(finalFilterSQL)
|
||||||
|
@ -968,9 +995,9 @@ public extension MessageViewModel.AttachmentInteractionInfo {
|
||||||
InteractionAttachment.numberOfSelectedColumns(db)
|
InteractionAttachment.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(AttachmentInteractionInfo.self, [
|
||||||
AttachmentInteractionInfo.attachmentString: adapters[1],
|
.attachment: adapters[1],
|
||||||
AttachmentInteractionInfo.interactionAttachmentString: adapters[2]
|
.interactionAttachment: adapters[2]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1034,9 +1061,9 @@ public extension MessageViewModel.ReactionInfo {
|
||||||
let numColumnsBeforeLinkedRecords: Int = 1
|
let numColumnsBeforeLinkedRecords: Int = 1
|
||||||
let request: SQLRequest<ReactionInfo> = """
|
let request: SQLRequest<ReactionInfo> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(reaction.alias[Column.rowID]) AS \(ReactionInfo.rowIdKey),
|
\(reaction[.rowId]) AS \(ReactionInfo.Columns.rowId),
|
||||||
\(ReactionInfo.reactionKey).*,
|
\(reaction.allColumns),
|
||||||
\(ReactionInfo.profileKey).*
|
\(profile.allColumns)
|
||||||
FROM \(Reaction.self)
|
FROM \(Reaction.self)
|
||||||
LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(reaction[.authorId])
|
LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(reaction[.authorId])
|
||||||
\(finalFilterSQL)
|
\(finalFilterSQL)
|
||||||
|
@ -1049,9 +1076,9 @@ public extension MessageViewModel.ReactionInfo {
|
||||||
Profile.numberOfSelectedColumns(db)
|
Profile.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(ReactionInfo.self, [
|
||||||
ReactionInfo.reactionString: adapters[1],
|
.reaction: adapters[1],
|
||||||
ReactionInfo.profileString: adapters[2]
|
.profile: adapters[2]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1117,8 +1144,8 @@ public extension MessageViewModel.TypingIndicatorInfo {
|
||||||
}()
|
}()
|
||||||
let request: SQLRequest<MessageViewModel.TypingIndicatorInfo> = """
|
let request: SQLRequest<MessageViewModel.TypingIndicatorInfo> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(threadTypingIndicator.alias[Column.rowID]) AS \(MessageViewModel.TypingIndicatorInfo.rowIdKey),
|
\(threadTypingIndicator[.rowId]),
|
||||||
\(threadTypingIndicator[.threadId]) AS \(MessageViewModel.TypingIndicatorInfo.threadIdKey)
|
\(threadTypingIndicator[.threadId])
|
||||||
FROM \(ThreadTypingIndicator.self)
|
FROM \(ThreadTypingIndicator.self)
|
||||||
\(finalFilterSQL)
|
\(finalFilterSQL)
|
||||||
"""
|
"""
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -45,8 +45,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
|
||||||
.replacingMentions(for: thread.id))
|
.replacingMentions(for: thread.id))
|
||||||
.defaulting(to: "APN_Message".localized())
|
.defaulting(to: "APN_Message".localized())
|
||||||
|
|
||||||
var userInfo: [String: Any] = [ NotificationServiceExtension.isFromRemoteKey: true ]
|
let userInfo: [String: Any] = [
|
||||||
userInfo[NotificationServiceExtension.threadIdKey] = thread.id
|
NotificationServiceExtension.isFromRemoteKey: true,
|
||||||
|
NotificationServiceExtension.threadIdKey: thread.id,
|
||||||
|
NotificationServiceExtension.threadVariantRaw: thread.variant.rawValue
|
||||||
|
]
|
||||||
|
|
||||||
let notificationContent = UNMutableNotificationContent()
|
let notificationContent = UNMutableNotificationContent()
|
||||||
notificationContent.userInfo = userInfo
|
notificationContent.userInfo = userInfo
|
||||||
|
@ -145,8 +148,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
|
||||||
// Only notify missed calls
|
// Only notify missed calls
|
||||||
guard messageInfo.state == .missed || messageInfo.state == .permissionDenied else { return }
|
guard messageInfo.state == .missed || messageInfo.state == .permissionDenied else { return }
|
||||||
|
|
||||||
var userInfo: [String: Any] = [ NotificationServiceExtension.isFromRemoteKey: true ]
|
let userInfo: [String: Any] = [
|
||||||
userInfo[NotificationServiceExtension.threadIdKey] = thread.id
|
NotificationServiceExtension.isFromRemoteKey: true,
|
||||||
|
NotificationServiceExtension.threadIdKey: thread.id,
|
||||||
|
NotificationServiceExtension.threadVariantRaw: thread.variant.rawValue
|
||||||
|
]
|
||||||
|
|
||||||
let notificationContent = UNMutableNotificationContent()
|
let notificationContent = UNMutableNotificationContent()
|
||||||
notificationContent.userInfo = userInfo
|
notificationContent.userInfo = userInfo
|
||||||
|
@ -206,8 +212,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
|
||||||
default: notificationBody = NotificationStrings.incomingMessageBody
|
default: notificationBody = NotificationStrings.incomingMessageBody
|
||||||
}
|
}
|
||||||
|
|
||||||
var userInfo: [String: Any] = [ NotificationServiceExtension.isFromRemoteKey: true ]
|
let userInfo: [String: Any] = [
|
||||||
userInfo[NotificationServiceExtension.threadIdKey] = thread.id
|
NotificationServiceExtension.isFromRemoteKey: true,
|
||||||
|
NotificationServiceExtension.threadIdKey: thread.id,
|
||||||
|
NotificationServiceExtension.threadVariantRaw: thread.variant.rawValue
|
||||||
|
]
|
||||||
|
|
||||||
let notificationContent = UNMutableNotificationContent()
|
let notificationContent = UNMutableNotificationContent()
|
||||||
notificationContent.userInfo = userInfo
|
notificationContent.userInfo = userInfo
|
||||||
|
|
|
@ -18,6 +18,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
|
||||||
|
|
||||||
public static let isFromRemoteKey = "remote"
|
public static let isFromRemoteKey = "remote"
|
||||||
public static let threadIdKey = "Signal.AppNotificationsUserInfoKey.threadId"
|
public static let threadIdKey = "Signal.AppNotificationsUserInfoKey.threadId"
|
||||||
|
public static let threadVariantRaw = "Signal.AppNotificationsUserInfoKey.threadVariantRaw"
|
||||||
public static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter"
|
public static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter"
|
||||||
|
|
||||||
// MARK: Did receive a remote push notification request
|
// MARK: Did receive a remote push notification request
|
||||||
|
|
|
@ -36,10 +36,6 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
|
||||||
SetCurrentAppContext(appContext)
|
SetCurrentAppContext(appContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to manually trigger these since we don't have a "mainWindow" here and the current theme
|
|
||||||
// might have been changed since the share extension was last opened
|
|
||||||
ThemeManager.applySavedTheme()
|
|
||||||
|
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
|
|
||||||
_ = AppVersion.sharedInstance()
|
_ = AppVersion.sharedInstance()
|
||||||
|
@ -66,6 +62,11 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
|
||||||
case .failure: SNLog("[SessionShareExtension] Failed to complete migrations")
|
case .failure: SNLog("[SessionShareExtension] Failed to complete migrations")
|
||||||
case .success:
|
case .success:
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
// Need to manually trigger these since we don't have a "mainWindow" here
|
||||||
|
// and the current theme might have been changed since the share extension
|
||||||
|
// was last opened
|
||||||
|
ThemeManager.applySavedTheme()
|
||||||
|
|
||||||
// performUpdateCheck must be invoked after Environment has been initialized because
|
// performUpdateCheck must be invoked after Environment has been initialized because
|
||||||
// upgrade process may depend on Environment.
|
// upgrade process may depend on Environment.
|
||||||
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
|
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
|
||||||
|
|
|
@ -185,6 +185,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
||||||
|
|
||||||
let approvalVC: UINavigationController = AttachmentApprovalViewController.wrappedInNavController(
|
let approvalVC: UINavigationController = AttachmentApprovalViewController.wrappedInNavController(
|
||||||
threadId: strongSelf.viewModel.viewData[indexPath.row].threadId,
|
threadId: strongSelf.viewModel.viewData[indexPath.row].threadId,
|
||||||
|
threadVariant: strongSelf.viewModel.viewData[indexPath.row].threadVariant,
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
approvalDelegate: strongSelf
|
approvalDelegate: strongSelf
|
||||||
)
|
)
|
||||||
|
@ -197,6 +198,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
||||||
_ attachmentApproval: AttachmentApprovalViewController,
|
_ attachmentApproval: AttachmentApprovalViewController,
|
||||||
didApproveAttachments attachments: [SignalAttachment],
|
didApproveAttachments attachments: [SignalAttachment],
|
||||||
forThreadId threadId: String,
|
forThreadId threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
messageText: String?,
|
messageText: String?,
|
||||||
using dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
|
@ -221,78 +223,111 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
||||||
ModalActivityIndicatorViewController.present(fromViewController: shareNavController!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in
|
ModalActivityIndicatorViewController.present(fromViewController: shareNavController!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in
|
||||||
Storage.resumeDatabaseAccess()
|
Storage.resumeDatabaseAccess()
|
||||||
|
|
||||||
dependencies.storage
|
/// When we prepare the message we set the timestamp to be the `SnodeAPI.currentOffsetTimestampMs()`
|
||||||
.writePublisher { db -> MessageSender.PreparedSendData in
|
/// but won't actually have a value because the share extension won't have talked to a service node yet which can cause
|
||||||
guard
|
/// issues with Disappearing Messages, as a result we need to explicitly `getNetworkTime` in order to ensure it's accurate
|
||||||
let threadVariant: SessionThread.Variant = try SessionThread
|
Just(())
|
||||||
.filter(id: threadId)
|
.setFailureType(to: Error.self)
|
||||||
.select(.variant)
|
.flatMap { _ in
|
||||||
.asRequest(of: SessionThread.Variant.self)
|
// We may not have sufficient snodes, so rather than failing we try to load/fetch
|
||||||
.fetchOne(db)
|
// them if needed
|
||||||
else { throw MessageSenderError.noThread }
|
guard !SnodeAPI.hasCachedSnodesIncludingExpired() else {
|
||||||
|
return Just(())
|
||||||
// Create the interaction
|
.setFailureType(to: Error.self)
|
||||||
let interaction: Interaction = try Interaction(
|
.eraseToAnyPublisher()
|
||||||
threadId: threadId,
|
|
||||||
authorId: getUserHexEncodedPublicKey(db),
|
|
||||||
variant: .standardOutgoing,
|
|
||||||
body: body,
|
|
||||||
timestampMs: SnodeAPI.currentOffsetTimestampMs(),
|
|
||||||
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: body),
|
|
||||||
expiresInSeconds: try? DisappearingMessagesConfiguration
|
|
||||||
.select(.durationSeconds)
|
|
||||||
.filter(id: threadId)
|
|
||||||
.filter(DisappearingMessagesConfiguration.Columns.isEnabled == true)
|
|
||||||
.asRequest(of: TimeInterval.self)
|
|
||||||
.fetchOne(db),
|
|
||||||
linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil)
|
|
||||||
).inserted(db)
|
|
||||||
|
|
||||||
guard let interactionId: Int64 = interaction.id else {
|
|
||||||
throw StorageError.failedToSave
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user is sharing a Url, there is a LinkPreview and it doesn't match an existing
|
return SnodeAPI.getSnodePool()
|
||||||
// one then add it now
|
.map { _ in () }
|
||||||
if
|
.eraseToAnyPublisher()
|
||||||
isSharingUrl,
|
|
||||||
let linkPreviewDraft: LinkPreviewDraft = attachments.first?.linkPreviewDraft,
|
|
||||||
(try? interaction.linkPreview.isEmpty(db)) == true
|
|
||||||
{
|
|
||||||
try LinkPreview(
|
|
||||||
url: linkPreviewDraft.urlString,
|
|
||||||
title: linkPreviewDraft.title,
|
|
||||||
attachmentId: LinkPreview
|
|
||||||
.generateAttachmentIfPossible(
|
|
||||||
imageData: linkPreviewDraft.jpegImageData,
|
|
||||||
mimeType: OWSMimeTypeImageJpeg
|
|
||||||
)?
|
|
||||||
.inserted(db)
|
|
||||||
.id
|
|
||||||
).insert(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare any attachments
|
|
||||||
try Attachment.process(
|
|
||||||
db,
|
|
||||||
data: Attachment.prepare(attachments: finalAttachments),
|
|
||||||
for: interactionId
|
|
||||||
)
|
|
||||||
|
|
||||||
// Prepare the message send data
|
|
||||||
return try MessageSender
|
|
||||||
.preparedSendData(
|
|
||||||
db,
|
|
||||||
interaction: interaction,
|
|
||||||
threadId: threadId,
|
|
||||||
threadVariant: threadVariant,
|
|
||||||
using: dependencies
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
|
.flatMap { _ in
|
||||||
|
SnodeAPI
|
||||||
|
.getSwarm(
|
||||||
|
for: {
|
||||||
|
switch threadVariant {
|
||||||
|
case .contact, .legacyGroup, .group: return threadId
|
||||||
|
case .community: return getUserHexEncodedPublicKey(using: dependencies)
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
.tryFlatMapWithRandomSnode { SnodeAPI.getNetworkTime(from: $0, using: dependencies) }
|
||||||
|
.map { _ in () }
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
.flatMap { _ in
|
||||||
|
dependencies.storage.writePublisher { db -> MessageSender.PreparedSendData in
|
||||||
|
guard
|
||||||
|
let threadVariant: SessionThread.Variant = try SessionThread
|
||||||
|
.filter(id: threadId)
|
||||||
|
.select(.variant)
|
||||||
|
.asRequest(of: SessionThread.Variant.self)
|
||||||
|
.fetchOne(db)
|
||||||
|
else { throw MessageSenderError.noThread }
|
||||||
|
|
||||||
|
// Create the interaction
|
||||||
|
let interaction: Interaction = try Interaction(
|
||||||
|
threadId: threadId,
|
||||||
|
authorId: getUserHexEncodedPublicKey(db),
|
||||||
|
variant: .standardOutgoing,
|
||||||
|
body: body,
|
||||||
|
timestampMs: SnodeAPI.currentOffsetTimestampMs(),
|
||||||
|
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: body),
|
||||||
|
expiresInSeconds: try? DisappearingMessagesConfiguration
|
||||||
|
.select(.durationSeconds)
|
||||||
|
.filter(id: threadId)
|
||||||
|
.filter(DisappearingMessagesConfiguration.Columns.isEnabled == true)
|
||||||
|
.asRequest(of: TimeInterval.self)
|
||||||
|
.fetchOne(db),
|
||||||
|
linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil)
|
||||||
|
).inserted(db)
|
||||||
|
|
||||||
|
guard let interactionId: Int64 = interaction.id else {
|
||||||
|
throw StorageError.failedToSave
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user is sharing a Url, there is a LinkPreview and it doesn't match an existing
|
||||||
|
// one then add it now
|
||||||
|
if
|
||||||
|
isSharingUrl,
|
||||||
|
let linkPreviewDraft: LinkPreviewDraft = attachments.first?.linkPreviewDraft,
|
||||||
|
(try? interaction.linkPreview.isEmpty(db)) == true
|
||||||
|
{
|
||||||
|
try LinkPreview(
|
||||||
|
url: linkPreviewDraft.urlString,
|
||||||
|
title: linkPreviewDraft.title,
|
||||||
|
attachmentId: LinkPreview
|
||||||
|
.generateAttachmentIfPossible(
|
||||||
|
imageData: linkPreviewDraft.jpegImageData,
|
||||||
|
mimeType: OWSMimeTypeImageJpeg
|
||||||
|
)?
|
||||||
|
.inserted(db)
|
||||||
|
.id
|
||||||
|
).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare any attachments
|
||||||
|
try Attachment.process(
|
||||||
|
db,
|
||||||
|
data: Attachment.prepare(attachments: finalAttachments),
|
||||||
|
for: interactionId
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prepare the message send data
|
||||||
|
return try MessageSender
|
||||||
|
.preparedSendData(
|
||||||
|
db,
|
||||||
|
interaction: interaction,
|
||||||
|
threadId: threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
.flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0, using: dependencies) }
|
.flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0, using: dependencies) }
|
||||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { [weak self] result in
|
receiveCompletion: { [weak self] result in
|
||||||
|
|
|
@ -28,6 +28,7 @@ public class ThreadPickerViewModel {
|
||||||
.shareQuery(userPublicKey: userPublicKey)
|
.shareQuery(userPublicKey: userPublicKey)
|
||||||
.fetchAll(db)
|
.fetchAll(db)
|
||||||
}
|
}
|
||||||
|
.map { threads -> [SessionThreadViewModel] in threads.filter { $0.canWrite } } // Exclude unwritable threads
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.handleEvents(didFail: { SNLog("[ThreadPickerViewModel] Observation failed with error: \($0)") })
|
.handleEvents(didFail: { SNLog("[ThreadPickerViewModel] Observation failed with error: \($0)") })
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ public enum GetSnodePoolJob: JobExecutor {
|
||||||
// but we want to succeed this job immediately (since it's marked as blocking), this allows us
|
// but we want to succeed this job immediately (since it's marked as blocking), this allows us
|
||||||
// to block if we have no Snode pool and prevent other jobs from failing but avoids having to
|
// to block if we have no Snode pool and prevent other jobs from failing but avoids having to
|
||||||
// wait if we already have a potentially valid snode pool
|
// wait if we already have a potentially valid snode pool
|
||||||
guard !SnodeAPI.hasCachedSnodesInclusingExpired() else {
|
guard !SnodeAPI.hasCachedSnodesIncludingExpired() else {
|
||||||
SNLog("[GetSnodePoolJob] Has valid cached pool, running async instead")
|
SNLog("[GetSnodePoolJob] Has valid cached pool, running async instead")
|
||||||
SnodeAPI
|
SnodeAPI
|
||||||
.getSnodePool()
|
.getSnodePool()
|
||||||
|
|
|
@ -141,7 +141,7 @@ public final class SnodeAPI {
|
||||||
|
|
||||||
// MARK: - Public API
|
// MARK: - Public API
|
||||||
|
|
||||||
public static func hasCachedSnodesInclusingExpired() -> Bool {
|
public static func hasCachedSnodesIncludingExpired() -> Bool {
|
||||||
loadSnodePoolIfNeeded()
|
loadSnodePoolIfNeeded()
|
||||||
|
|
||||||
return !hasInsufficientSnodes
|
return !hasInsufficientSnodes
|
||||||
|
@ -1009,7 +1009,7 @@ public final class SnodeAPI {
|
||||||
|
|
||||||
// MARK: - Internal API
|
// MARK: - Internal API
|
||||||
|
|
||||||
private static func getNetworkTime(
|
public static func getNetworkTime(
|
||||||
from snode: Snode,
|
from snode: Snode,
|
||||||
using dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<UInt64, Error> {
|
) -> AnyPublisher<UInt64, Error> {
|
||||||
|
@ -1024,7 +1024,14 @@ public final class SnodeAPI {
|
||||||
using: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
.decoded(as: GetNetworkTimestampResponse.self, using: dependencies)
|
.decoded(as: GetNetworkTimestampResponse.self, using: dependencies)
|
||||||
.map { _, response in response.timestamp }
|
.map { _, response in
|
||||||
|
// Assume we've fetched the networkTime in order to send a message to the specified snode, in
|
||||||
|
// which case we want to update the 'clockOffsetMs' value for subsequent requests
|
||||||
|
let offset = (Int64(response.timestamp) - Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000)))
|
||||||
|
SnodeAPI.clockOffsetMs.mutate { $0 = offset }
|
||||||
|
|
||||||
|
return response.timestamp
|
||||||
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1351,18 +1351,24 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
|
||||||
|
|
||||||
// Fetch the inserted/updated rows
|
// Fetch the inserted/updated rows
|
||||||
let additionalFilters: SQL = SQL(rowIds.contains(Column.rowID))
|
let additionalFilters: SQL = SQL(rowIds.contains(Column.rowID))
|
||||||
let updatedItems: [T] = (try? dataQuery(additionalFilters)
|
|
||||||
.fetchAll(db))
|
|
||||||
.defaulting(to: [])
|
|
||||||
|
|
||||||
// If the inserted/updated rows we irrelevant (eg. associated to another thread, a quote or a link
|
do {
|
||||||
// preview) then trigger the update callback (if there were deletions) and stop here
|
let updatedItems: [T] = try dataQuery(additionalFilters)
|
||||||
guard !updatedItems.isEmpty else { return hasOtherChanges }
|
.fetchAll(db)
|
||||||
|
|
||||||
// Process the upserted data (assume at least one value changed)
|
// If the inserted/updated rows we irrelevant (eg. associated to another thread, a quote or a link
|
||||||
dataCache.mutate { $0 = $0.upserting(items: updatedItems) }
|
// preview) then trigger the update callback (if there were deletions) and stop here
|
||||||
|
guard !updatedItems.isEmpty else { return hasOtherChanges }
|
||||||
return true
|
|
||||||
|
// Process the upserted data (assume at least one value changed)
|
||||||
|
dataCache.mutate { $0 = $0.upserting(items: updatedItems) }
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
SNLog("[PagedDatabaseObserver] Error loading associated data: \(error)")
|
||||||
|
return hasOtherChanges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearCache(_ db: Database) {
|
public func clearCache(_ db: Database) {
|
||||||
|
|
|
@ -3,22 +3,65 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
||||||
public class TypedTableAlias<T> where T: TableRecord, T: ColumnExpressible {
|
public struct TypedTableAlias<T: ColumnExpressible> {
|
||||||
public let alias: TableAlias = TableAlias(name: T.databaseTableName)
|
public enum RowIdColumn {
|
||||||
|
case rowId
|
||||||
|
}
|
||||||
|
|
||||||
public init() {}
|
internal let name: String
|
||||||
|
internal let tableName: String?
|
||||||
|
internal let alias: TableAlias
|
||||||
|
|
||||||
|
public var allColumns: SQLSelection { alias[AllColumns().sqlSelection] }
|
||||||
|
public var never: NeverJoiningTypedTableAlias<T> { NeverJoiningTypedTableAlias<T>(alias: self) }
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
public init(name: String, tableName: String? = nil) {
|
||||||
|
self.name = name
|
||||||
|
self.tableName = tableName
|
||||||
|
self.alias = TableAlias(name: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(name: String) where T: TableRecord {
|
||||||
|
self.name = name
|
||||||
|
self.tableName = T.databaseTableName
|
||||||
|
self.alias = TableAlias(name: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() where T: TableRecord {
|
||||||
|
self = TypedTableAlias(name: T.databaseTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<VM: ColumnExpressible>(_ viewModel: VM.Type, column: VM.Columns, tableName: String?) {
|
||||||
|
self.name = column.name
|
||||||
|
self.tableName = tableName
|
||||||
|
self.alias = TableAlias(name: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<VM: ColumnExpressible>(_ viewModel: VM.Type, column: VM.Columns) where T: TableRecord {
|
||||||
|
self = TypedTableAlias(viewModel, column: column, tableName: T.databaseTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Functions
|
||||||
|
|
||||||
public subscript(_ column: T.Columns) -> SQLExpression {
|
public subscript(_ column: T.Columns) -> SQLExpression {
|
||||||
return alias[column.name]
|
return alias[column.name]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// **Warning:** For this to work you **MUST** call the '.aliased()' method when joining or it will
|
public subscript(_ column: RowIdColumn) -> SQLSelection {
|
||||||
/// throw when trying to decode
|
return alias[Column.rowID]
|
||||||
public func allColumns() -> SQLSelection {
|
|
||||||
return alias[AllColumns().sqlSelection]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - NeverJoiningTypedTableAlias
|
||||||
|
|
||||||
|
public struct NeverJoiningTypedTableAlias<T: ColumnExpressible> {
|
||||||
|
internal let alias: TypedTableAlias<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Extensions
|
||||||
|
|
||||||
extension QueryInterfaceRequest {
|
extension QueryInterfaceRequest {
|
||||||
public func aliased<T>(_ typedAlias: TypedTableAlias<T>) -> Self {
|
public func aliased<T>(_ typedAlias: TypedTableAlias<T>) -> Self {
|
||||||
return aliased(typedAlias.alias)
|
return aliased(typedAlias.alias)
|
||||||
|
@ -32,7 +75,5 @@ extension Association {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TableAlias {
|
extension TableAlias {
|
||||||
public func allColumns() -> SQLSelection {
|
public var allColumns: SQLSelection { self[AllColumns().sqlSelection] }
|
||||||
return self[AllColumns().sqlSelection]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
public extension SQLInterpolation {
|
||||||
|
/// Appends the table name of the record type.
|
||||||
|
///
|
||||||
|
/// // SELECT * FROM player
|
||||||
|
/// let player: TypedTableAlias<T> = TypedTableAlias()
|
||||||
|
/// let request: SQLRequest<Player> = "SELECT * FROM \(player)"
|
||||||
|
@_disfavoredOverload
|
||||||
|
mutating func appendInterpolation<T>(_ typedTableAlias: TypedTableAlias<T>) {
|
||||||
|
let name: String = typedTableAlias.name
|
||||||
|
|
||||||
|
guard let tableName: String = typedTableAlias.tableName else { return appendLiteral(name.quotedDatabaseIdentifier) }
|
||||||
|
guard name != tableName else { return appendLiteral(tableName.quotedDatabaseIdentifier) }
|
||||||
|
|
||||||
|
appendLiteral("\(tableName.quotedDatabaseIdentifier) AS \(name.quotedDatabaseIdentifier)")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a simple SQL query for use when we want a `LEFT JOIN` that will always fail
|
||||||
|
///
|
||||||
|
/// // SELECT * FROM player LEFT JOIN team AS testTeam ON false
|
||||||
|
/// let player: TypedTableAlias<Player> = TypedTableAlias()
|
||||||
|
/// let testTeam: TypedTableAlias<Team> = TypedTableAlias(name: "testTeam")
|
||||||
|
/// let request: SQLRequest<Player> = "SELECT * FROM \(player) LEFT JOIN \(testTeam.never)
|
||||||
|
@_disfavoredOverload
|
||||||
|
mutating func appendInterpolation<T: ColumnExpressible>(_ neverJoiningAlias: NeverJoiningTypedTableAlias<T>) where T: TableRecord {
|
||||||
|
guard let tableName: String = neverJoiningAlias.alias.tableName else {
|
||||||
|
appendLiteral("(SELECT \(generateSelection(for: T.self))) AS \(neverJoiningAlias.alias.name.quotedDatabaseIdentifier) ON false")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLiteral("\(tableName.quotedDatabaseIdentifier) AS \(neverJoiningAlias.alias.name.quotedDatabaseIdentifier) ON false")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a simple SQL query for use when we want a `LEFT JOIN` that will always fail
|
||||||
|
///
|
||||||
|
/// // SELECT * FROM player LEFT JOIN (SELECT 0 AS teamInfo.Column.A, 0 AS teamInfo.Column.B) AS teamInfo ON false
|
||||||
|
/// let player: TypedTableAlias<Player> = TypedTableAlias()
|
||||||
|
/// let teamInfo: TypedTableAlias<TeamInfo> = TypedTableAlias(name: "teamInfo")
|
||||||
|
/// let request: SQLRequest<Player> = "SELECT * FROM \(player) LEFT JOIN \(teamInfo.never)
|
||||||
|
@_disfavoredOverload
|
||||||
|
mutating func appendInterpolation<T: ColumnExpressible>(_ neverJoiningAlias: NeverJoiningTypedTableAlias<T>) where T.Columns: CaseIterable {
|
||||||
|
appendLiteral("(SELECT \(generateSelection(for: T.self))) AS \(neverJoiningAlias.alias.name.quotedDatabaseIdentifier) ON false")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a simple SQL query for use when we want a `LEFT JOIN` that will always fail
|
||||||
|
///
|
||||||
|
/// // SELECT * FROM player LEFT JOIN (SELECT 0 AS teamInfo.Column.A, 0 AS teamInfo.Column.B) AS teamInfo ON false
|
||||||
|
/// let player: TypedTableAlias<Player> = TypedTableAlias()
|
||||||
|
/// let teamInfo: TypedTableAlias<TeamInfo> = TypedTableAlias(name: "teamInfo")
|
||||||
|
/// let request: SQLRequest<Player> = "SELECT * FROM \(player) LEFT JOIN \(teamInfo.never)
|
||||||
|
@_disfavoredOverload
|
||||||
|
mutating func appendInterpolation<T: ColumnExpressible>(_ neverJoiningAlias: NeverJoiningTypedTableAlias<T>) {
|
||||||
|
appendLiteral("(SELECT \(generateSelection(for: T.self))) AS \(neverJoiningAlias.alias.name.quotedDatabaseIdentifier) ON false")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateSelection<T: ColumnExpressible>(for type: T.Type) -> String where T.Columns: CaseIterable {
|
||||||
|
return T.Columns.allCases
|
||||||
|
.map { "NULL AS \($0.name)" }
|
||||||
|
.joined(separator: ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateSelection<T: ColumnExpressible>(for type: T.Type) -> String {
|
||||||
|
return "SELECT 1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
public extension ScopeAdapter {
|
||||||
|
static func with<VM: ColumnExpressible>(
|
||||||
|
_ viewModel: VM.Type,
|
||||||
|
_ scopes: [VM.Columns: RowAdapter]
|
||||||
|
) -> ScopeAdapter {
|
||||||
|
return ScopeAdapter(scopes.reduce(into: [:]) { result, next in result[next.key.name] = next.value })
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ public protocol AttachmentApprovalViewControllerDelegate: AnyObject {
|
||||||
_ attachmentApproval: AttachmentApprovalViewController,
|
_ attachmentApproval: AttachmentApprovalViewController,
|
||||||
didApproveAttachments attachments: [SignalAttachment],
|
didApproveAttachments attachments: [SignalAttachment],
|
||||||
forThreadId threadId: String,
|
forThreadId threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
messageText: String?,
|
messageText: String?,
|
||||||
using dependencies: Dependencies
|
using dependencies: Dependencies
|
||||||
)
|
)
|
||||||
|
@ -59,6 +60,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
||||||
|
|
||||||
private let mode: Mode
|
private let mode: Mode
|
||||||
private let threadId: String
|
private let threadId: String
|
||||||
|
private let threadVariant: SessionThread.Variant
|
||||||
private let isAddMoreVisible: Bool
|
private let isAddMoreVisible: Bool
|
||||||
|
|
||||||
public weak var approvalDelegate: AttachmentApprovalViewControllerDelegate?
|
public weak var approvalDelegate: AttachmentApprovalViewControllerDelegate?
|
||||||
|
@ -128,11 +130,13 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
||||||
required public init(
|
required public init(
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
attachments: [SignalAttachment]
|
attachments: [SignalAttachment]
|
||||||
) {
|
) {
|
||||||
assert(attachments.count > 0)
|
assert(attachments.count > 0)
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.threadId = threadId
|
self.threadId = threadId
|
||||||
|
self.threadVariant = threadVariant
|
||||||
let attachmentItems = attachments.map { SignalAttachmentItem(attachment: $0 )}
|
let attachmentItems = attachments.map { SignalAttachmentItem(attachment: $0 )}
|
||||||
self.isAddMoreVisible = (mode == .sharedNavigation)
|
self.isAddMoreVisible = (mode == .sharedNavigation)
|
||||||
|
|
||||||
|
@ -162,10 +166,16 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
||||||
|
|
||||||
public class func wrappedInNavController(
|
public class func wrappedInNavController(
|
||||||
threadId: String,
|
threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
attachments: [SignalAttachment],
|
attachments: [SignalAttachment],
|
||||||
approvalDelegate: AttachmentApprovalViewControllerDelegate
|
approvalDelegate: AttachmentApprovalViewControllerDelegate
|
||||||
) -> UINavigationController {
|
) -> UINavigationController {
|
||||||
let vc = AttachmentApprovalViewController(mode: .modal, threadId: threadId, attachments: attachments)
|
let vc = AttachmentApprovalViewController(
|
||||||
|
mode: .modal,
|
||||||
|
threadId: threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
|
attachments: attachments
|
||||||
|
)
|
||||||
vc.approvalDelegate = approvalDelegate
|
vc.approvalDelegate = approvalDelegate
|
||||||
|
|
||||||
let navController = StyledNavigationController(rootViewController: vc)
|
let navController = StyledNavigationController(rootViewController: vc)
|
||||||
|
@ -674,7 +684,14 @@ extension AttachmentApprovalViewController: AttachmentTextToolbarDelegate {
|
||||||
attachmentTextToolbar.isUserInteractionEnabled = false
|
attachmentTextToolbar.isUserInteractionEnabled = false
|
||||||
attachmentTextToolbar.isHidden = true
|
attachmentTextToolbar.isHidden = true
|
||||||
|
|
||||||
approvalDelegate?.attachmentApproval(self, didApproveAttachments: attachments, forThreadId: threadId, messageText: attachmentTextToolbar.messageText, using: dependencies)
|
approvalDelegate?.attachmentApproval(
|
||||||
|
self,
|
||||||
|
didApproveAttachments: attachments,
|
||||||
|
forThreadId: threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
|
messageText: attachmentTextToolbar.messageText,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func attachmentTextToolbarDidChange(_ attachmentTextToolbar: AttachmentTextToolbar) {
|
func attachmentTextToolbarDidChange(_ attachmentTextToolbar: AttachmentTextToolbar) {
|
||||||
|
|
Loading…
Reference in New Issue