From d010aa74cd85f37c9e300a808af1ddba56e082a3 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 17 Dec 2024 13:52:55 +1100 Subject: [PATCH 1/9] Updated versioning --- Session.xcodeproj/project.pbxproj | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 583066b73..a8c0d2a1a 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -7677,7 +7677,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 507; + CURRENT_PROJECT_VERSION = 514; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -7714,7 +7714,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ""; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.8.3; + MARKETING_VERSION = 2.8.4; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "-fobjc-arc-exceptions", @@ -7756,7 +7756,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 507; + CURRENT_PROJECT_VERSION = 514; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -7788,7 +7788,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ""; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.8.3; + MARKETING_VERSION = 2.8.4; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "-DNS_BLOCK_ASSERTIONS=1", @@ -7819,7 +7819,6 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 513; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7857,7 +7856,6 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.8.4; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -7890,7 +7888,6 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 513; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7928,7 +7925,6 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.8.4; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From 13fabbb3059837959c6afa67f2366a9e489a58ea Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 18 Dec 2024 10:04:40 +1100 Subject: [PATCH 2/9] Fixed an issue where sharing attachments could lose filename and extension --- .../ConversationVC+Interaction.swift | 6 +-- .../GIFs/GifPickerViewController.swift | 2 +- .../PhotoCapture.swift | 2 +- .../PhotoLibrary.swift | 2 +- Session/Settings/HelpViewModel.swift | 16 +++--- .../Database/Models/Attachment.swift | 7 ++- .../Database/Models/LinkPreview.swift | 8 ++- .../Attachments/SignalAttachment.swift | 8 +-- .../ShareNavController.swift | 10 ++-- SessionUtilitiesKit/Media/DataSource.swift | 21 ++++++-- .../Media/UTType+Utilities.swift | 52 ++++++++++++------- .../AttachmentApprovalViewController.swift | 2 +- 12 files changed, 84 insertions(+), 52 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 103158663..c09fed7dc 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -378,7 +378,7 @@ extension ConversationVC: } let fileName = urlResourceValues.name ?? "attachment".localized() - guard let dataSource = DataSourcePath(fileUrl: url, shouldDeleteOnDeinit: false) else { + guard let dataSource = DataSourcePath(fileUrl: url, sourceFilename: urlResourceValues.name, shouldDeleteOnDeinit: false) else { DispatchQueue.main.async { [weak self] in self?.viewModel.showToast(text: "attachmentsErrorLoad".localized()) } @@ -412,7 +412,7 @@ extension ConversationVC: func showAttachmentApprovalDialogAfterProcessingVideo(at url: URL, with fileName: String) { ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: true, message: nil) { [weak self, dependencies = viewModel.dependencies] modalActivityIndicator in - let dataSource = DataSourcePath(fileUrl: url, shouldDeleteOnDeinit: false)! + let dataSource = DataSourcePath(fileUrl: url, sourceFilename: fileName, shouldDeleteOnDeinit: false)! dataSource.sourceFilename = fileName SignalAttachment @@ -2594,7 +2594,7 @@ extension ConversationVC: } // Get data - let dataSourceOrNil = DataSourcePath(fileUrl: audioRecorder.url, shouldDeleteOnDeinit: true) + let dataSourceOrNil = DataSourcePath(fileUrl: audioRecorder.url, sourceFilename: nil, shouldDeleteOnDeinit: true) self.audioRecorder = nil guard let dataSource = dataSourceOrNil else { return SNLog("Couldn't load recorded data.") } diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index 56fedbab5..c7c389ecd 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -380,7 +380,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect return } - let dataSource = DataSourcePath(filePath: asset.filePath, shouldDeleteOnDeinit: false) + let dataSource = DataSourcePath(filePath: asset.filePath, sourceFilename: URL(fileURLWithPath: asset.filePath).pathExtension, shouldDeleteOnDeinit: false) let attachment = SignalAttachment.attachment(dataSource: dataSource, type: rendition.type, imageQuality: .medium) self?.dismiss(animated: true) { diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index 33c0bc223..291ab6875 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -441,7 +441,7 @@ extension PhotoCapture: CaptureOutputDelegate { Log.debug("[PhotoCapture] Ignoring error, since capture succeeded.") } - let dataSource = DataSourcePath(fileUrl: outputFileURL, shouldDeleteOnDeinit: true) + let dataSource = DataSourcePath(fileUrl: outputFileURL, sourceFilename: nil, shouldDeleteOnDeinit: true) let attachment = SignalAttachment.attachment(dataSource: dataSource, type: .mpeg4Movie) delegate?.photoCapture(self, didFinishProcessingAttachment: attachment) } diff --git a/Session/Media Viewing & Editing/PhotoLibrary.swift b/Session/Media Viewing & Editing/PhotoLibrary.swift index e31f1c604..a6617060e 100644 --- a/Session/Media Viewing & Editing/PhotoLibrary.swift +++ b/Session/Media Viewing & Editing/PhotoLibrary.swift @@ -196,7 +196,7 @@ class PhotoCollectionContents { guard exportSession?.status == .completed, - let dataSource = DataSourcePath(fileUrl: exportURL, shouldDeleteOnDeinit: true) + let dataSource = DataSourcePath(fileUrl: exportURL, sourceFilename: nil, shouldDeleteOnDeinit: true) else { resolver(Result.failure(PhotoLibraryError.assertionError(description: "Failed to build data source for exported video URL"))) return diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index 2530fd329..f7ba3506d 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -169,13 +169,15 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa cancelTitle: "Share", cancelStyle: .alert_text, onConfirm: { _ in UIPasteboard.general.string = latestLogFilePath }, - onCancel: { _ in - HelpViewModel.shareLogsInternal( - viewControllerToDismiss: viewControllerToDismiss, - targetView: targetView, - animated: animated, - onShareComplete: onShareComplete - ) + onCancel: { modal in + modal.dismiss(animated: true) { + HelpViewModel.shareLogsInternal( + viewControllerToDismiss: viewControllerToDismiss, + targetView: targetView, + animated: animated, + onShareComplete: onShareComplete + ) + } } ) ) diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index 6e4263274..ebc24ffd6 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -638,7 +638,10 @@ extension Attachment { // If the filename has not file extension, deduce one // from the MIME type. if targetFileExtension.isEmpty { - targetFileExtension = (UTType(sessionMimeType: mimeType)?.sessionFileExtension ?? UTType.fileExtensionDefault) + targetFileExtension = ( + UTType(sessionMimeType: mimeType)?.sessionFileExtension(sourceFilename: sourceFilename) ?? + UTType.fileExtensionDefault + ) } targetFileExtension = targetFileExtension.lowercased() @@ -657,7 +660,7 @@ extension Attachment { } let targetFileExtension: String = ( - UTType(sessionMimeType: mimeType)?.sessionFileExtension ?? + UTType(sessionMimeType: mimeType)?.sessionFileExtension(sourceFilename: sourceFilename) ?? UTType.fileExtensionDefault ).lowercased() diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index 05d57c2f3..9db5f25c9 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -132,12 +132,16 @@ public extension LinkPreview { static func generateAttachmentIfPossible(imageData: Data?, type: UTType) throws -> Attachment? { guard let imageData: Data = imageData, !imageData.isEmpty else { return nil } - guard let fileExtension: String = type.sessionFileExtension else { return nil } + guard let fileExtension: String = type.sessionFileExtension(sourceFilename: nil) else { return nil } guard let mimeType: String = type.preferredMIMEType else { return nil } let filePath = FileSystem.temporaryFilePath(fileExtension: fileExtension) try imageData.write(to: NSURL.fileURL(withPath: filePath), options: .atomicWrite) - let dataSource: DataSourcePath = DataSourcePath(filePath: filePath, shouldDeleteOnDeinit: true) + let dataSource: DataSourcePath = DataSourcePath( + filePath: filePath, + sourceFilename: nil, + shouldDeleteOnDeinit: true + ) return Attachment(contentType: mimeType, dataSource: dataSource) } diff --git a/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift b/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift index a7f7c3c39..ce8531edb 100644 --- a/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift +++ b/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift @@ -269,7 +269,7 @@ public class SignalAttachment: Equatable { // can be identified. public var mimeType: String { guard - let fileExtension: String = sourceFilename.map({ $0 as NSString })?.pathExtension, + let fileExtension: String = sourceFilename.map({ URL(fileURLWithPath: $0) })?.pathExtension, !fileExtension.isEmpty, let fileExtensionMimeType: String = UTType(sessionFileExtension: fileExtension)?.preferredMIMEType else { return (dataType.preferredMIMEType ?? UTType.mimeTypeDefault) } @@ -306,9 +306,9 @@ public class SignalAttachment: Equatable { // can be identified. public var fileExtension: String? { guard - let fileExtension: String = sourceFilename.map({ $0 as NSString })?.pathExtension, + let fileExtension: String = sourceFilename.map({ URL(fileURLWithPath: $0) })?.pathExtension, !fileExtension.isEmpty - else { return dataType.sessionFileExtension } + else { return dataType.sessionFileExtension(sourceFilename: sourceFilename) } return fileExtension.filteredFilename } @@ -803,7 +803,7 @@ public class SignalAttachment: Equatable { let baseFilename = dataSource.sourceFilename let mp4Filename = baseFilename?.filenameWithoutExtension.appendingFileExtension("mp4") - guard let dataSource = DataSourcePath(fileUrl: exportURL, shouldDeleteOnDeinit: true) else { + guard let dataSource = DataSourcePath(fileUrl: exportURL, sourceFilename: baseFilename, shouldDeleteOnDeinit: true) else { let attachment = SignalAttachment(dataSource: DataSourceValue.empty, dataType: type) attachment.error = .couldNotConvertToMpeg4 resolver(Result.success(attachment)) diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index f34d1ec15..964ca1743 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -274,16 +274,14 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { // // NOTE: SharingThreadPickerViewController will try to unpack them // and send them as normal text messages if possible. - case (_, true): return DataSourcePath(fileUrl: url, shouldDeleteOnDeinit: false) + case (_, true): + return DataSourcePath(fileUrl: url, sourceFilename: customFileName, shouldDeleteOnDeinit: false) default: - guard let dataSource = DataSourcePath(fileUrl: url, shouldDeleteOnDeinit: false) else { + guard let dataSource = DataSourcePath(fileUrl: url, sourceFilename: customFileName, shouldDeleteOnDeinit: false) else { return nil } - // Fallback to the last part of the URL - dataSource.sourceFilename = (customFileName ?? url.lastPathComponent) - return dataSource } } @@ -429,7 +427,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { switch value { case let data as Data: let customFileName = "Contact.vcf" // stringlint:ignore - let customFileExtension: String? = srcType.sessionFileExtension + let customFileExtension: String? = srcType.sessionFileExtension(sourceFilename: nil) guard let tempFilePath = try? FileSystem.write(data: data, toTemporaryFileWithExtension: customFileExtension) else { resolver( diff --git a/SessionUtilitiesKit/Media/DataSource.swift b/SessionUtilitiesKit/Media/DataSource.swift index 1cc3bf20d..e603b8434 100644 --- a/SessionUtilitiesKit/Media/DataSource.swift +++ b/SessionUtilitiesKit/Media/DataSource.swift @@ -97,7 +97,7 @@ public class DataSourceValue: DataSource { } public convenience init?(data: Data?, dataType: UTType) { - guard let fileExtension: String = dataType.sessionFileExtension else { return nil } + guard let fileExtension: String = dataType.sessionFileExtension(sourceFilename: nil) else { return nil } self.init(data: data, fileExtension: fileExtension) } @@ -210,15 +210,28 @@ public class DataSourcePath: DataSource { // MARK: - Initialization - public init(filePath: String, shouldDeleteOnDeinit: Bool) { + public init( + filePath: String, + sourceFilename: String?, + shouldDeleteOnDeinit: Bool + ) { self.filePath = filePath + self.sourceFilename = sourceFilename self.shouldDeleteOnDeinit = shouldDeleteOnDeinit } - public convenience init?(fileUrl: URL?, shouldDeleteOnDeinit: Bool) { + public convenience init?( + fileUrl: URL?, + sourceFilename: String?, + shouldDeleteOnDeinit: Bool + ) { guard let fileUrl: URL = fileUrl, fileUrl.isFileURL else { return nil } - self.init(filePath: fileUrl.path, shouldDeleteOnDeinit: shouldDeleteOnDeinit) + self.init( + filePath: fileUrl.path, + sourceFilename: (sourceFilename ?? fileUrl.lastPathComponent), + shouldDeleteOnDeinit: shouldDeleteOnDeinit + ) } deinit { diff --git a/SessionUtilitiesKit/Media/UTType+Utilities.swift b/SessionUtilitiesKit/Media/UTType+Utilities.swift index 64aef20b7..59353ead1 100644 --- a/SessionUtilitiesKit/Media/UTType+Utilities.swift +++ b/SessionUtilitiesKit/Media/UTType+Utilities.swift @@ -107,26 +107,6 @@ public extension UTType { var isMicrosoftDoc: Bool { UTType.supportedMicrosoftDocTypes.contains(self) } var isVisualMedia: Bool { isImage || isVideo || isAnimated } - var sessionFileExtension: String? { - // Special-case the "aac" filetype we use for voice messages (for legacy reasons) - // to use a .m4a file extension, not .aac, since AVAudioPlayer can't handle .aac - // properly. Doesn't affect file contents. - guard identifier != "public.aac-audio" else { return "m4a" } - - // Try to deduce the file extension by using a lookup table. - // - // This should be more accurate than deducing the file extension by - // converting to a UTI type. For example, .m4a files will have a - // UTI type of kUTTypeMPEG4Audio which incorrectly yields the file - // extension .mp4 instead of .m4a. - guard - let mimeType: String = preferredMIMEType, - let fileExtension: String = UTType.genericMimeTypesToExtensionTypes[mimeType] - else { return preferredFilenameExtension } - - return fileExtension - } - // MARK: - Initialization init?(sessionFileExtension: String) { @@ -188,6 +168,38 @@ public extension UTType { return (UTType(sessionMimeType: mimeType) ?? .invalid).isVisualMedia } + func sessionFileExtension(sourceFilename: String?) -> String? { + // First try to check if the file extension on `sourceFilename` is valid + // for the `preferredMIMEType` as we want to keep that if so (eg. use `.log` + // instead of `.txt`) + if + let sourceFileExtension: String = sourceFilename.map({ URL(fileURLWithPath: $0) })?.pathExtension, + let mimeType: String = preferredMIMEType, + let sourceExtensionMimeType: String = UTType.genericMimeTypesToExtensionTypes[sourceFileExtension], + UTType(mimeType: sourceExtensionMimeType)?.preferredMIMEType == mimeType + { + return sourceFileExtension + } + + // Special-case the "aac" filetype we use for voice messages (for legacy reasons) + // to use a .m4a file extension, not .aac, since AVAudioPlayer can't handle .aac + // properly. Doesn't affect file contents. + guard identifier != "public.aac-audio" else { return "m4a" } + + // Try to deduce the file extension by using a lookup table. + // + // This should be more accurate than deducing the file extension by + // converting to a UTI type. For example, .m4a files will have a + // UTI type of kUTTypeMPEG4Audio which incorrectly yields the file + // extension .mp4 instead of .m4a. + guard + let mimeType: String = preferredMIMEType, + let fileExtension: String = UTType.genericMimeTypesToExtensionTypes[mimeType] + else { return preferredFilenameExtension } + + return fileExtension + } + // MARK: - Lookup Table static func sessionMimeType(for fileExtension: String) -> String? { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift index 904a26bf9..63f40c158 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift @@ -533,7 +533,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC // Rewrite the filename's extension to reflect the output file format. var filename: String? = attachmentItem.attachment.sourceFilename if let sourceFilename = attachmentItem.attachment.sourceFilename { - if let fileExtension: String = dataType.sessionFileExtension { + if let fileExtension: String = dataType.sessionFileExtension(sourceFilename: sourceFilename) { filename = (sourceFilename as NSString).deletingPathExtension.appendingFileExtension(fileExtension) } } From bb7a1378fbf7c0bd79bd6022a26a33dfaa2aecec Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 18 Dec 2024 10:22:28 +1100 Subject: [PATCH 3/9] Updated build number --- Session.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index a8c0d2a1a..1e0ac6e22 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -7677,7 +7677,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 514; + CURRENT_PROJECT_VERSION = 516; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -7756,7 +7756,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 514; + CURRENT_PROJECT_VERSION = 516; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; From 465bd55111b4227d92b9e9271c9798e522086459 Mon Sep 17 00:00:00 2001 From: Bilb <1544279+Bilb@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:41:35 +0000 Subject: [PATCH 4/9] [Automated] Update translations from Crowdin --- .../Meta/Translations/Localizable.xcstrings | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Session/Meta/Translations/Localizable.xcstrings b/Session/Meta/Translations/Localizable.xcstrings index ec8c5a80a..09d8c91ec 100644 --- a/Session/Meta/Translations/Localizable.xcstrings +++ b/Session/Meta/Translations/Localizable.xcstrings @@ -12563,6 +12563,17 @@ } } }, + "adminPromotionNotSent" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Promotion not sent" + } + } + } + }, "adminPromotionSent" : { "extractionState" : "manual", "localizations" : { @@ -193854,6 +193865,17 @@ } } }, + "groupInviteNotSent" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invite not sent" + } + } + } + }, "groupInviteReinvite" : { "extractionState" : "manual", "localizations" : { From b20abd1ce81f0dc241f6fae7985ff5d4dddef2fe Mon Sep 17 00:00:00 2001 From: mpretty-cyro <15862619+mpretty-cyro@users.noreply.github.com> Date: Wed, 18 Dec 2024 05:19:42 +0000 Subject: [PATCH 5/9] [Automated] Update translations from Crowdin --- .../Meta/Translations/Localizable.xcstrings | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Session/Meta/Translations/Localizable.xcstrings b/Session/Meta/Translations/Localizable.xcstrings index 09d8c91ec..13816aa3f 100644 --- a/Session/Meta/Translations/Localizable.xcstrings +++ b/Session/Meta/Translations/Localizable.xcstrings @@ -13059,6 +13059,17 @@ } } }, + "adminPromotionStatusUnknown" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Promotion status unknown" + } + } + } + }, "adminRemove" : { "extractionState" : "manual", "localizations" : { @@ -194598,6 +194609,17 @@ } } }, + "groupInviteStatusUnknown" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invite status unknown" + } + } + } + }, "groupInviteSuccessful" : { "extractionState" : "manual", "localizations" : { From 1fa9f2271a59fefec7bfc5c3784154c34620b6d3 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 18 Dec 2024 16:45:46 +1100 Subject: [PATCH 6/9] Fixed an issue where new legacy groups would have invalid state --- .../MessageReceiver+ClosedGroups.swift | 42 ++++++++++++++----- .../MessageSender+ClosedGroups.swift | 37 ++++++++-------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 79750f55c..d5a00dc5c 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -142,6 +142,7 @@ extension MessageReceiver { // approved contact (to prevent spam via closed groups getting around message requests if users are // on old or modified clients) var hasApprovedAdmin: Bool = false + let receivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) for adminId in admins { if let contact: Contact = try? Contact.fetchOne(db, id: adminId), contact.isApproved { @@ -155,6 +156,36 @@ extension MessageReceiver { // antoher device) guard hasApprovedAdmin || configTriggeringChange != nil else { return } + // Create the disappearing config + let disappearingConfig: DisappearingMessagesConfiguration = DisappearingMessagesConfiguration + .defaultWith(groupPublicKey) + .with( + isEnabled: (expirationTimer > 0), + durationSeconds: TimeInterval(expirationTimer), + type: (expirationTimer > 0 ? .disappearAfterSend : .unknown) + ) + + /// Update `libSession` first + /// + /// **Note:** This **MUST** happen before we call `SessionThread.upsert` as we won't add the group + /// if it already exists in `libSession` and upserting the thread results in an update to `libSession` to set + /// the `priority` + if configTriggeringChange == nil { + try? LibSession.add( + db, + groupPublicKey: groupPublicKey, + name: name, + joinedAt: (TimeInterval(formationTimestampMs) / 1000), + latestKeyPairPublicKey: Data(encryptionKeyPair.publicKey), + latestKeyPairSecretKey: Data(encryptionKeyPair.secretKey), + latestKeyPairReceivedTimestamp: receivedTimestamp, + disappearingConfig: disappearingConfig, + members: members.asSet(), + admins: admins.asSet(), + using: dependencies + ) + } + // Create the group let thread: SessionThread = try SessionThread.upsert( db, @@ -198,20 +229,11 @@ extension MessageReceiver { } // Update the DisappearingMessages config - var disappearingConfig = DisappearingMessagesConfiguration.defaultWith(thread.id) if (try? thread.disappearingMessagesConfiguration.fetchOne(db)) == nil { - let isEnabled: Bool = (expirationTimer > 0) - disappearingConfig = try disappearingConfig - .with( - isEnabled: isEnabled, - durationSeconds: TimeInterval(expirationTimer), - type: isEnabled ? .disappearAfterSend : .unknown - ) - .saved(db) + try disappearingConfig.upsert(db) } // Store the key pair if it doesn't already exist - let receivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair( threadId: groupPublicKey, publicKey: Data(encryptionKeyPair.publicKey), diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index 2d08ddc96..cf3c75b81 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -39,6 +39,25 @@ extension MessageSender { let adminsAsData: [Data] = admins.map { Data(hex: $0) } let formationTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + /// Update `libSession` first + /// + /// **Note:** This **MUST** happen before we call `SessionThread.upsert` as we won't add the group + /// if it already exists in `libSession` and upserting the thread results in an update to `libSession` to set + /// the `priority` + try LibSession.add( + db, + groupPublicKey: groupPublicKey, + name: name, + joinedAt: formationTimestamp, + latestKeyPairPublicKey: Data(encryptionKeyPair.publicKey), + latestKeyPairSecretKey: Data(encryptionKeyPair.secretKey), + latestKeyPairReceivedTimestamp: formationTimestamp, + disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey), + members: members, + admins: admins, + using: dependencies + ) + // Create the relevant objects in the database let thread: SessionThread = try SessionThread.upsert( db, @@ -55,12 +74,11 @@ extension MessageSender { ).insert(db) // Store the key pair - let latestKeyPairReceivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) try ClosedGroupKeyPair( threadId: groupPublicKey, publicKey: Data(encryptionKeyPair.publicKey), secretKey: Data(encryptionKeyPair.secretKey), - receivedTimestamp: latestKeyPairReceivedTimestamp + receivedTimestamp: formationTimestamp ).insert(db) // Create the member objects @@ -82,21 +100,6 @@ extension MessageSender { ).save(db) } - // Update libSession - try LibSession.add( - db, - groupPublicKey: groupPublicKey, - name: name, - joinedAt: formationTimestamp, - latestKeyPairPublicKey: Data(encryptionKeyPair.publicKey), - latestKeyPairSecretKey: Data(encryptionKeyPair.secretKey), - latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp, - disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey), - members: members, - admins: admins, - using: dependencies - ) - let memberSendData: [MessageSender.PreparedSendData] = try members .map { memberId -> MessageSender.PreparedSendData in try MessageSender.preparedSendData( From 15aaa8332db3e854ad87b895fcbc6a9917d837fb Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 18 Dec 2024 16:59:20 +1100 Subject: [PATCH 7/9] Fixed an issue where shared messages and community invites wouldn't disappear --- .../Settings/ThreadSettingsViewModel.swift | 15 ++++++++------- Session/Notifications/AppNotifications.swift | 11 +++++++++-- SessionShareExtension/ThreadPickerVC.swift | 9 ++++++++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 49ce12702..73ce37bdd 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -792,18 +792,19 @@ class ThreadSettingsViewModel: SessionTableViewModel, NavigationItemSource, Navi ) .save(db) + let sentTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() + let destinationDisappearingMessagesConfiguration: DisappearingMessagesConfiguration? = try? DisappearingMessagesConfiguration + .filter(id: userId) + .filter(DisappearingMessagesConfiguration.Columns.isEnabled == true) + .fetchOne(db) let interaction: Interaction = try Interaction( threadId: thread.id, threadVariant: thread.variant, authorId: currentUserSessionId, variant: .standardOutgoing, - timestampMs: SnodeAPI.currentOffsetTimestampMs(), - expiresInSeconds: try? DisappearingMessagesConfiguration - .select(.durationSeconds) - .filter(id: userId) - .filter(DisappearingMessagesConfiguration.Columns.isEnabled == true) - .asRequest(of: TimeInterval.self) - .fetchOne(db), + timestampMs: sentTimestampMs, + expiresInSeconds: destinationDisappearingMessagesConfiguration?.durationSeconds, + expiresStartedAtMs: (destinationDisappearingMessagesConfiguration?.type == .disappearAfterSend ? Double(sentTimestampMs) : nil), linkPreviewUrl: communityUrl ) .inserted(db) diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 1e60c809e..1402d38e9 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -512,14 +512,21 @@ class NotificationActionHandler { return Storage.shared .writePublisher { db in + let sentTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() + let destinationDisappearingMessagesConfiguration: DisappearingMessagesConfiguration? = try? DisappearingMessagesConfiguration + .filter(id: threadId) + .filter(DisappearingMessagesConfiguration.Columns.isEnabled == true) + .fetchOne(db) let interaction: Interaction = try Interaction( threadId: threadId, threadVariant: thread.variant, authorId: getUserHexEncodedPublicKey(db), variant: .standardOutgoing, body: replyText, - timestampMs: SnodeAPI.currentOffsetTimestampMs(), - hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: replyText) + timestampMs: sentTimestampMs, + hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: replyText), + expiresInSeconds: destinationDisappearingMessagesConfiguration?.durationSeconds, + expiresStartedAtMs: (destinationDisappearingMessagesConfiguration?.type == .disappearAfterSend ? Double(sentTimestampMs) : nil) ).inserted(db) try Interaction.markAsRead( diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 8c9e9add1..18e686e68 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -280,14 +280,21 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView } // Create the interaction + let sentTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() + let destinationDisappearingMessagesConfiguration: DisappearingMessagesConfiguration? = try? DisappearingMessagesConfiguration + .filter(id: threadId) + .filter(DisappearingMessagesConfiguration.Columns.isEnabled == true) + .fetchOne(db) let interaction: Interaction = try Interaction( threadId: threadId, threadVariant: threadVariant, authorId: getUserHexEncodedPublicKey(db), variant: .standardOutgoing, body: body, - timestampMs: SnodeAPI.currentOffsetTimestampMs(), + timestampMs: sentTimestampMs, hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: body), + expiresInSeconds: destinationDisappearingMessagesConfiguration?.durationSeconds, + expiresStartedAtMs: (destinationDisappearingMessagesConfiguration?.type == .disappearAfterSend ? Double(sentTimestampMs) : nil), linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil) ).inserted(db) From ba1664f71fc8073f9338a4233029cb16f5b43986 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 18 Dec 2024 16:59:43 +1100 Subject: [PATCH 8/9] Updated version number --- Session.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1e0ac6e22..427f6b665 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -7677,7 +7677,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 516; + CURRENT_PROJECT_VERSION = 517; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -7756,7 +7756,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 516; + CURRENT_PROJECT_VERSION = 517; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; From c56e57663e4c9acb8d690917c11bcb5859666410 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO <> Date: Mon, 6 Jan 2025 10:37:08 +1100 Subject: [PATCH 9/9] add accessibility id for broken media --- .../Conversations/Message Cells/Content Views/MediaView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index c47a2c394..ef70084c8 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -372,6 +372,8 @@ public class MediaView: UIView { return } icon = asset + self.isAccessibilityElement = true + self.accessibilityIdentifier = "Media retry" case .invalid: guard let asset = UIImage(named: "media_invalid") else { @@ -379,6 +381,8 @@ public class MediaView: UIView { return } icon = asset + self.isAccessibilityElement = true + self.accessibilityIdentifier = "Media invalid" case .missing: return }