diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 93daa679a..022d50341 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6044,7 +6044,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 394; + CURRENT_PROJECT_VERSION = 395; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6117,7 +6117,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 394; + CURRENT_PROJECT_VERSION = 395; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6183,7 +6183,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 394; + CURRENT_PROJECT_VERSION = 395; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6257,7 +6257,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 394; + CURRENT_PROJECT_VERSION = 395; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7185,7 +7185,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 394; + CURRENT_PROJECT_VERSION = 395; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7257,7 +7257,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 394; + CURRENT_PROJECT_VERSION = 395; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 380c9c610..a8ca98c6d 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1604,6 +1604,23 @@ extension ConversationVC: let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } + if + let quote = try? interaction.quote.fetchOne(db), + let quotedAttachment = try? quote.attachment.fetchOne(db), + quotedAttachment.isVisualMedia, + quotedAttachment.downloadUrl == Attachment.nonMediaQuoteFileId, + let quotedInteraction = try? quote.originalInteraction.fetchOne(db) + { + let attachment = try? quotedInteraction.attachments.fetchAll(db).first + try quote.with( + attachmentId: attachment?.cloneAsQuoteThumbnail()?.inserted(db).id + ).update(db) + } + + // Remove message sending jobs for the same interaction in database + // Prevent the same message being sent twice + try Job.filter(Job.Columns.interactionId == interaction.id).deleteAll(db) + try MessageSender.send( db, interaction: interaction, diff --git a/Session/Conversations/Message Cells/Content Views/QuoteView.swift b/Session/Conversations/Message Cells/Content Views/QuoteView.swift index 2ee2f9068..ebd726a1c 100644 --- a/Session/Conversations/Message Cells/Content Views/QuoteView.swift +++ b/Session/Conversations/Message Cells/Content Views/QuoteView.swift @@ -144,7 +144,7 @@ final class QuoteView: UIView { .messageBubble_outgoingText : .messageBubble_incomingText ) - case .draft: return .messageBubble_outgoingText + case .draft: return .textPrimary } }() imageView.contentMode = .center @@ -156,10 +156,7 @@ final class QuoteView: UIView { mainStackView.addArrangedSubview(imageView) if (body ?? "").isEmpty { - body = (attachment.isImage ? - "Image" : - (isAudio ? "Audio" : "Document") - ) + body = attachment.shortDescription } // Generate the thumbnail if needed @@ -223,10 +220,10 @@ final class QuoteView: UIView { } .defaulting( to: attachment.map { - NSAttributedString(string: MIMETypeUtil.isAudio($0.contentType) ? "Audio" : "Document") + NSAttributedString(string: $0.shortDescription, attributes: [ .foregroundColor: textColor ]) } ) - .defaulting(to: NSAttributedString(string: "Document")) + .defaulting(to: NSAttributedString(string: "QUOTED_MESSAGE_NOT_FOUND".localized(), attributes: [ .foregroundColor: textColor ])) } // Label stack view diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 94eb3d73e..591d3ade6 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -545,7 +545,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { let quoteView: QuoteView = QuoteView( for: .regular, authorId: quote.authorId, - quotedText: quote.body ?? "QUOTED_MESSAGE_NOT_FOUND".localized(), + quotedText: quote.body, threadVariant: cellViewModel.threadVariant, currentUserPublicKey: cellViewModel.currentUserPublicKey, currentUserBlindedPublicKey: cellViewModel.currentUserBlindedPublicKey, diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 25d35e095..21732ffda 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -206,6 +206,10 @@ class ThreadSettingsViewModel: SessionTableViewModel Data? { guard let filePath: String = self.originalFilePath else { return nil diff --git a/SessionMessagingKit/Database/Models/Quote.swift b/SessionMessagingKit/Database/Models/Quote.swift index af92ee454..1b702ecf1 100644 --- a/SessionMessagingKit/Database/Models/Quote.swift +++ b/SessionMessagingKit/Database/Models/Quote.swift @@ -76,6 +76,26 @@ public struct Quote: Codable, Equatable, Hashable, FetchableRecord, PersistableR } } +// MARK: - Mutation + +public extension Quote { + func with( + interactionId: Int64? = nil, + authorId: String? = nil, + timestampMs: Int64? = nil, + body: String? = nil, + attachmentId: String? = nil + ) -> Quote { + return Quote( + interactionId: interactionId ?? self.interactionId, + authorId: authorId ?? self.authorId, + timestampMs: timestampMs ?? self.timestampMs, + body: body ?? self.body, + attachmentId: attachmentId ?? self.attachmentId + ) + } +} + // MARK: - Protobuf public extension Quote { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 669e46de0..5a5321554 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -420,7 +420,7 @@ extension MessageReceiver { // Delete the members to remove try GroupMember .filter(GroupMember.Columns.groupId == id) - .filter(updatedMemberIds.contains(GroupMember.Columns.profileId)) + .filter(membersToRemove.map{ $0.profileId }.contains(GroupMember.Columns.profileId)) .deleteAll(db) if didAdminLeave || sender == userPublicKey { diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 22c3ec62a..02e614aeb 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -825,6 +825,10 @@ public extension SessionThreadViewModel { let profileIdColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.id.name) + let groupMemberProfileIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.profileId.name) + let groupMemberRoleColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.role.name) + let groupMemberGroupIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.groupId.name) + /// **Note:** The `numColumnsBeforeProfiles` value **MUST** match the number of fields before /// the `ViewModel.contactProfileKey` entry below otherwise the query will fail to /// parse and might throw @@ -851,7 +855,8 @@ public extension SessionThreadViewModel { \(ViewModel.closedGroupProfileBackFallbackKey).*, \(closedGroup[.name]) AS \(ViewModel.closedGroupNameKey), - (\(groupMember[.profileId]) IS NOT NULL) AS \(ViewModel.currentUserIsClosedGroupMemberKey), + (\(ViewModel.currentUserIsClosedGroupMemberKey).profileId IS NOT NULL) AS \(ViewModel.currentUserIsClosedGroupMemberKey), + (\(ViewModel.currentUserIsClosedGroupAdminKey).profileId IS NOT NULL) AS \(ViewModel.currentUserIsClosedGroupAdminKey), \(openGroup[.name]) AS \(ViewModel.openGroupNameKey), \(openGroup[.server]) AS \(ViewModel.openGroupServerKey), \(openGroup[.roomToken]) AS \(ViewModel.openGroupRoomTokenKey), @@ -865,10 +870,15 @@ public extension SessionThreadViewModel { LEFT JOIN \(Profile.self) AS \(ViewModel.contactProfileKey) ON \(ViewModel.contactProfileKey).\(profileIdColumnLiteral) = \(thread[.id]) LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id]) LEFT JOIN \(ClosedGroup.self) ON \(closedGroup[.threadId]) = \(thread[.id]) - LEFT JOIN \(GroupMember.self) ON ( - \(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)")) AND - \(groupMember[.groupId]) = \(closedGroup[.threadId]) AND - \(SQL("\(groupMember[.profileId]) = \(userPublicKey)")) + LEFT JOIN \(GroupMember.self) AS \(ViewModel.currentUserIsClosedGroupMemberKey) ON ( + \(SQL("\(ViewModel.currentUserIsClosedGroupMemberKey).\(groupMemberRoleColumnLiteral) != \(GroupMember.Role.zombie)")) AND + \(ViewModel.currentUserIsClosedGroupMemberKey).\(groupMemberGroupIdColumnLiteral) = \(closedGroup[.threadId]) AND + \(SQL("\(ViewModel.currentUserIsClosedGroupMemberKey).\(groupMemberProfileIdColumnLiteral) = \(userPublicKey)")) + ) + LEFT JOIN \(GroupMember.self) AS \(ViewModel.currentUserIsClosedGroupAdminKey) ON ( + \(SQL("\(ViewModel.currentUserIsClosedGroupAdminKey).\(groupMemberRoleColumnLiteral) = \(GroupMember.Role.admin)")) AND + \(ViewModel.currentUserIsClosedGroupAdminKey).\(groupMemberGroupIdColumnLiteral) = \(closedGroup[.threadId]) AND + \(SQL("\(ViewModel.currentUserIsClosedGroupAdminKey).\(groupMemberProfileIdColumnLiteral) = \(userPublicKey)")) ) LEFT JOIN \(Profile.self) AS \(ViewModel.closedGroupProfileFrontKey) ON (