From e2d813cf8dd2412eb5cb2e40e89eefa72061b563 Mon Sep 17 00:00:00 2001 From: Riya Abraham Date: Wed, 29 Apr 2020 17:17:48 -0300 Subject: [PATCH] Debug UI for screenshots without assets. --- Signal.xcodeproj/project.pbxproj | 10 + Signal/src/Signal-Bridging-Header.h | 1 + .../DebugUI/DebugUIScreenshots.h | 17 + .../DebugUI/DebugUIScreenshots.m | 62 + .../DebugUI/DebugUIScreenshots.swift | 1149 +++++++++++++++++ .../DebugUI/DebugUITableViewController.m | 12 +- .../translations/en.lproj/Localizable.strings | 135 ++ .../src/Messages/Interactions/TSInteraction.h | 4 + .../src/Messages/Interactions/TSInteraction.m | 6 + .../Interactions/TSOutgoingMessage.swift | 7 + .../src/Messages/OWSMessageSender.m | 2 +- 11 files changed, 1402 insertions(+), 3 deletions(-) create mode 100644 Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.h create mode 100644 Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.m create mode 100644 Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 59b0d1dbbb..99e4c8d98b 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -346,6 +346,8 @@ 34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */; }; 34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDB206939B100CB1A14 /* DebugUIMessagesAction.m */; }; 34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDC206939B200CB1A14 /* DebugUIMessagesAssetLoader.m */; }; + 34D3E57B245A189D00F5E4A1 /* DebugUIScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D3E579245A189D00F5E4A1 /* DebugUIScreenshots.swift */; }; + 34D3E57C245A189D00F5E4A1 /* DebugUIScreenshots.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D3E57A245A189D00F5E4A1 /* DebugUIScreenshots.m */; }; 34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */; }; 34D58730208E2C4200D2255A /* OWS109OutgoingMessageState.h in Headers */ = {isa = PBXBuildFile; fileRef = 34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; }; @@ -1261,6 +1263,9 @@ 34D2CCDD206939B200CB1A14 /* DebugUIMessagesAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAction.h; sourceTree = ""; }; 34D2CCDE206939B400CB1A14 /* DebugUIMessagesAssetLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAssetLoader.h; sourceTree = ""; }; 34D2CCE220693A1700CB1A14 /* DebugUIMessagesUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesUtils.h; sourceTree = ""; }; + 34D3E578245A189D00F5E4A1 /* DebugUIScreenshots.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIScreenshots.h; sourceTree = ""; }; + 34D3E579245A189D00F5E4A1 /* DebugUIScreenshots.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugUIScreenshots.swift; sourceTree = ""; }; + 34D3E57A245A189D00F5E4A1 /* DebugUIScreenshots.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIScreenshots.m; sourceTree = ""; }; 34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS109OutgoingMessageState.m; sourceTree = ""; }; 34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS109OutgoingMessageState.h; sourceTree = ""; }; 34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = ""; }; @@ -2621,6 +2626,9 @@ 34E3EF0E1EFC2684007F6822 /* DebugUIPage.h */, 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */, 4556FA671F54AA9500AF40DD /* DebugUIProfile.swift */, + 34D3E578245A189D00F5E4A1 /* DebugUIScreenshots.h */, + 34D3E57A245A189D00F5E4A1 /* DebugUIScreenshots.m */, + 34D3E579245A189D00F5E4A1 /* DebugUIScreenshots.swift */, 452037CF1EE84975004E4CDF /* DebugUISessionState.h */, 452037D01EE84975004E4CDF /* DebugUISessionState.m */, 34BECE291F74C12700D7438D /* DebugUIStress.h */, @@ -4546,6 +4554,7 @@ 348433DF243CA94600C7F64A /* ReplaceAdminViewController.swift in Sources */, 34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */, 887EEC1F23F0B20600F8C26D /* PinReminderMegaphone.swift in Sources */, + 34D3E57B245A189D00F5E4A1 /* DebugUIScreenshots.swift in Sources */, 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */, 340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */, 34995F1A2411838D00C70546 /* NewGroupViewController.m in Sources */, @@ -4571,6 +4580,7 @@ 88905E9E229CCA96004E4234 /* ExpirationNagView.swift in Sources */, 881D85B822D92C2B00E118DF /* OWSPinSetupViewController.swift in Sources */, 340FC8BA204DAC8D007AEB0F /* FingerprintViewScanController.m in Sources */, + 34D3E57C245A189D00F5E4A1 /* DebugUIScreenshots.m in Sources */, 8835DDF9230CEDC300DC6B66 /* RecipientPickerViewController.m in Sources */, 4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */, 4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */, diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 540b471311..7db65488dc 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -22,6 +22,7 @@ #import "DateUtil.h" #import "DebugUIMessages.h" #import "DebugUIPage.h" +#import "DebugUIScreenshots.h" #import "DebugUITableViewController.h" #import "FingerprintViewController.h" #import "MediaDetailViewController.h" diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.h b/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.h new file mode 100644 index 0000000000..b46c4a0483 --- /dev/null +++ b/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.h @@ -0,0 +1,17 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +#import "DebugUIPage.h" + +#ifdef DEBUG + +NS_ASSUME_NONNULL_BEGIN + +@interface DebugUIScreenshots : DebugUIPage + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.m b/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.m new file mode 100644 index 0000000000..c4bb008c62 --- /dev/null +++ b/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.m @@ -0,0 +1,62 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +#import "DebugUIScreenshots.h" +#import "DebugContactsUtils.h" +#import "DebugUIContacts.h" +#import "OWSTableViewController.h" +#import "Signal-Swift.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#ifdef DEBUG + +NS_ASSUME_NONNULL_BEGIN + +@implementation DebugUIScreenshots + +#pragma mark - Factory Methods + +- (NSString *)name +{ + return @"Screenshots"; +} + +- (nullable OWSTableSection *)sectionForThread:(nullable TSThread *)thread +{ + NSMutableArray *items = [NSMutableArray new]; + + [items addObjectsFromArray:@[ + [OWSTableItem itemWithTitle:@"Delete all threads" + actionBlock:^{ + [DebugUIScreenshots deleteAllThreads]; + }], + [OWSTableItem itemWithTitle:@"Make Threads for Screenshots" + actionBlock:^{ + [DebugUIScreenshots makeThreadsForScreenshots]; + }], + ]]; + + return [OWSTableSection sectionWithTitle:self.name items:items]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.swift b/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.swift new file mode 100644 index 0000000000..9df1f95186 --- /dev/null +++ b/Signal/src/ViewControllers/DebugUI/DebugUIScreenshots.swift @@ -0,0 +1,1149 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +import Foundation + +#if DEBUG + +public extension DebugUIScreenshots { + + // MARK: - Dependencies + + static var databaseStorage: SDSDatabaseStorage { + return SDSDatabaseStorage.shared + } + + static var profileManager: OWSProfileManager { + return OWSProfileManager.shared() + } + + static var contactsManager: OWSContactsManager { + return Environment.shared.contactsManager + } + + static var tsAccountManager: TSAccountManager { + return .sharedInstance() + } + + // MARK: - + + @objc + class func deleteAllThreads() { + databaseStorage.write { transaction in + TSInteraction.anyRemoveAllWithoutInstantation(transaction: transaction) + TSAttachment.anyRemoveAllWithoutInstantation(transaction: transaction) + TSThread.anyRemoveAllWithoutInstantation(transaction: transaction) + } + } + + @objc + class func makeThreadsForScreenshots() { + + // Get the address of the local user. + guard let localAddress = tsAccountManager.localAddress else { + owsFailDebug("missing local address") + return + } + + databaseStorage.asyncWrite { transaction in + + // Time to start + // Modify the macOS system clock to "set" this to a specific time + let startingDateMS = Date.ows_millisecondTimestamp() + + // Localizing content: + // + // 1. Add NS_LocalizedString() (remove '_' like below). + // Make sure the "key" (e.g. SCREENSHOT_USERNAME_1) is unique for each string. + // Make sure you add a comment for the translators. + // + // someString1 will always be "Alice." + // let someString1 = "Alice" + // someLocalizedString might be "Alice" in English and "Boris" in Russian, + // depending on your device/simulator's locale's language. + // let someLocalizedString = NSLocalizedString("SCREENSHOT_USERNAME_1", comment: "An example name for a user we use in the screenshots.") + // 2. Run the l10n string script in the terminal + // Scripts/translation/auto-genstrings + // This will make sure there's an entry in the _English_ localization for each key in the source. + // 3. Search for the key you just added - you'll find it in the English localization file. + // Add a English value. + // 4. The strings for the other languages won't be set until you sync your l10n strings. + // DON'T DO THIS without checking with the engineers. + // It can cause leave Transifex in the wrong state. + // It is done with this script: + // Scripts/translation/sync-translations + + // If you add more users, make sure they each have + // unique numbers and uuid strings. + // TODOs set local profile phone number per locale + self.setLocalProfile(givenName: NSLocalizedString("SCREENSHOT_NAME_LOCAL_PROFILE", + comment: "This appears in Signal > Settings. A female leadership/presidential/chairwoman position + female name Freyja or similar spelling. This will have a cat profile photo."), + familyName: "", + avatarBundleFilename: "address-chairwoman-freya.jpg") + let address1 = self.ensureAccount(phoneNumber: "+13213214301", + uuidString: "123e4567-e89b-12d3-a456-426655440001", + // Example usage of NS_LocalizedString(). + // givenName: NSLocalizedString("SCREENSHOT_USERNAME_1", comment: "An example name for a user we use in the screenshots."), + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_ONE", + comment: "This is a contact's name. A male leadership/presidential position + the sound a cat makes. This will have a cat profile photo."), + familyName: "", + avatarBundleFilename: "address-chairman-meow.jpg", + transaction: transaction) + let address2 = self.ensureAccount(phoneNumber: "+13213214302", + uuidString: "123e4567-e89b-12d3-a456-426655440002", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_TWO", + comment: "This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This should be a unique non-public figure's name. This profile photo will be either male or female."), + familyName: "", + avatarBundleFilename: "address-male-1.jpg", + transaction: transaction) + let address3 = self.ensureAccount(phoneNumber: "+13213214303", + uuidString: "123e4567-e89b-12d3-a456-426655440003", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_THREE", + comment: "This is a contact's name. Please keep a similar nickname for Nikola/Nikita/etc in your language and only post the last initial. This profile photo will be either male or female but mostly female."), + familyName: "", + avatarBundleFilename: "address-female-9.jpg", + transaction: transaction) + let address4 = self.ensureAccount(phoneNumber: "+13213214304", + uuidString: "123e4567-e89b-12d3-a456-426655440004", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_FOUR", + comment: "This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This should be a unique non-public figure's name. This profile photo will be either male or female. Choose a unisex name if possible."), + familyName: "", + avatarBundleFilename: "address-female-12.jpg", + transaction: transaction) + let address5 = self.ensureAccount(phoneNumber: "+13213214305", + uuidString: "123e4567-e89b-12d3-a456-426655440005", + //givenName: "Ali Smith", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_FIVE", + comment: "This is a contact's name. Please keep the nick name Ali and change the last name to a popular lastname in your language. This will have male profile photo."), + familyName: "", + avatarBundleFilename: "address-male-4.jpg", + transaction: transaction) + let address6 = self.ensureAccount(phoneNumber: "+13213214306", + uuidString: "123e4567-e89b-12d3-a456-426655440006", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_SIX", + comment: "This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This will have a male profile photo."), + familyName: "", + avatarBundleFilename: "address-male-3.jpg", + transaction: transaction) + let address7 = self.ensureAccount(phoneNumber: "+13213214307", + uuidString: "123e4567-e89b-12d3-a456-426655440007", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_SEVEN", + comment: "This is a contact's name. Please keep a similar unisex first name (Kai) if this name isn't common and only post the last initial. This will have a female profile photo."), + familyName: "", + avatarBundleFilename: "address-female-1.jpg", + transaction: transaction) + let address8 = self.ensureAccount(phoneNumber: "+13213214308", + uuidString: "123e4567-e89b-12d3-a456-426655440008", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_EIGHT", + comment: "This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This should be a unique non-public figure's name. This will have a female profile photo."), + familyName: "", + avatarBundleFilename: "address-female-2.jpg", + transaction: transaction) + let address9 = self.ensureAccount(phoneNumber: "+13213214309", + uuidString: "123e4567-e89b-12d3-a456-426655440009", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_NINE", + comment: "This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. Include two last names if that is represented in your locale. This should be a unique non-public figure's name. This will have a female profile photo."), + familyName: "", + avatarBundleFilename: "address-female-7.jpg", + transaction: transaction) + let address10 = self.ensureAccount(phoneNumber: "+13213214310", + uuidString: "123e4567-e89b-12d3-a456-426655440010", + givenName: NSLocalizedString("SCREENSHOT_NAME_CONTACT_TEN", + comment: "This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This should be a unique non-public figure's name. This will have a female profile photo."), + familyName: "", + avatarBundleFilename: "address-female-8.jpg", + transaction: transaction) + + // 1:1 website screenshot for thread + if true { + let otherAddress = address10 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + let timestampMessage0 = startingDateMS - (6 * kDayInMs + 55 * kMinuteInMs) + let attachmentMessage0 = buildAttachment(bundleFilename: "nature-5-cat.jpg", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let attachmentMessage1 = buildAttachment(bundleFilename: "nature-7-cat.jpg", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let attachmentMessage2 = buildAttachment(bundleFilename: "nature-6-cat.jpg", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let message0 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage0, + attachments: [attachmentMessage0, attachmentMessage1, attachmentMessage2], + transaction: transaction) + _ = message0.recordReaction(for: otherAddress, + emoji: "โค๏ธ", + sentAtTimestamp: Date.ows_millisecondTimestamp(), + receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), + transaction: transaction) + let timestampMessage1 = startingDateMS - (6 * kDayInMs + 30 * kMinuteInMs) + let message1 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_ONE_MESSAGE_ONE", + comment: "This is a message."), + timestamp: timestampMessage1, transaction: transaction) + message1.debugonly_markAsReadNow(transaction: transaction) + _ = message1.recordReaction(for: localAddress, + emoji: "๐Ÿ‘", + sentAtTimestamp: Date.ows_millisecondTimestamp(), + receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), + transaction: transaction) + let timestampMessage2 = startingDateMS - (6 * kDayInMs + 3 * kMinuteInMs) + let message2 = self.buildOutgoingMessage(thread: thread, messageBody: "โ˜บ๏ธ", + timestamp: timestampMessage2, transaction: transaction) + message2.update(withReadRecipient: otherAddress, readTimestamp: Date.ows_millisecondTimestamp(), transaction: transaction) + let timestampMessage3 = startingDateMS - (6 * kDayInMs + 3 * kMinuteInMs) + let message3 = self.buildOutgoingMessage(thread: thread, messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_ONE_MESSAGE_TWO", + comment: "This is a message."), + timestamp: timestampMessage3, transaction: transaction) + message3.update(withReadRecipient: otherAddress, readTimestamp: Date.ows_millisecondTimestamp(), transaction: transaction) + let timestampMessage4 = startingDateMS - (6 * kDayInMs + 3 * kMinuteInMs) + let message4 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage4, transaction: transaction) + // Q: Which pack? A: Bandit the Cat. + let packIdHex = "9acc9e8aba563d26a4994e69263e3b25" + let packKeyHex = "5a6dff3948c28efb9b7aaf93ecc375c69fc316e78077ed26867a14d10a0f6a12" + // Which sticker - the index in the pack. + let stickerId0: UInt32 = 4 + if let messageSticker = self.buildMessageSticker(packIdHex: packIdHex, + packKeyHex: packKeyHex, + stickerId: stickerId0, + transaction: transaction) { + message4.update(with: messageSticker, transaction: transaction) + } + } + + // 1:1 encryption animation for website + if true { + let otherAddress = address4 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + let timestampMessage1 = startingDateMS - (5 * kDayInMs + 12 * kHourInMs) + let message1 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_TWO_MESSAGE_ONE", + comment: "This is a message before an image of mountains + a lake."), + timestamp: timestampMessage1, transaction: transaction) + message1.debugonly_markAsReadNow(transaction: transaction) + let timestampMessage2 = startingDateMS - (5 * kDayInMs + 12 * kHourInMs) + let attachmentMessage2 = buildAttachment(bundleFilename: "nature-8-NewZealand.jpg", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let message2 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, + messageBody: nil, + timestamp: timestampMessage2, + attachments: [attachmentMessage2], transaction: transaction) + message2.debugonly_markAsReadNow(transaction: transaction) + let timestampMessage3 = startingDateMS - (5 * kDayInMs + 12 * kHourInMs) + let message3 = self.buildOutgoingMessage(thread: thread, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_TWO_MESSAGE_TWO", + comment: "This is a message after an image of mountains + a lake."), + timestamp: timestampMessage3, + transaction: transaction) + message3.update(withReadRecipient: otherAddress, readTimestamp: Date.ows_millisecondTimestamp(), transaction: transaction) + let timestampMessage4 = startingDateMS - (5 * kDayInMs + 12 * kHourInMs) + let message4 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_TWO_MESSAGE_THREE", + comment: "This is a message."), + timestamp: timestampMessage4, transaction: transaction) + message4.debugonly_markAsReadNow(transaction: transaction) + } + + // 1:1 outgoing + if true { + let otherAddress = address9 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + let timestampMessage1 = startingDateMS - (4 * kDayInMs) + let message1 = self.buildOutgoingMessage(thread: thread, messageBody: "๐Ÿคฃ๐Ÿคฃ๐Ÿคฃ", + timestamp: timestampMessage1, transaction: transaction) + message1.update(withReadRecipient: otherAddress, readTimestamp: Date.ows_millisecondTimestamp(), transaction: transaction) + } + + // 1:1 incoming sticker + if true { + let otherAddress = address6 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + let timestampMessage1 = startingDateMS - (3 * kDayInMs + 12 * kHourInMs) + let message1 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_THREE_MESSAGE_ONE", + comment: "Replace crepes with similar item that you bake or cook i.e. bread, croissants, naan."), + timestamp: timestampMessage1, transaction: transaction) + message1.debugonly_markAsReadNow(transaction: transaction) + } + + // Group sent attachment + if true { + let memberAddresses = [ + address8, + address9 + ] + + // avatarData should be PNG data. + let thread = try! GroupManager.createGroupForTests(members: memberAddresses, + name: NSLocalizedString("SCREENSHOT_NAME_GROUP_ONE", + comment: "This is for a group of people interested in discussing books they've read."), + avatarData: buildAvatarData(bundleFilename: "address-group-book.jpg"), + transaction: transaction) + let timestampMessage1 = startingDateMS - (3 * kDayInMs + 4 * kHourInMs) + let message1 = self.buildOutgoingMessage(thread: thread, messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_ONE_MESSAGE_ONE", + comment: "This is for a message in the 'Book Club' group chat"), + timestamp: timestampMessage1, transaction: transaction) + let timestampMessage2 = startingDateMS - (3 * kDayInMs + 4 * kMinuteInMs) + let attachmentMessage2 = buildAttachment(bundleFilename: "1984.txt", + mimeType: "text/plain", + sourceFilename: NSLocalizedString("SCREENSHOT_THREAD_GROUP_ONE_FILE_NAME", + comment: "1984 is the book title. The file extension is a text file."), + transaction: transaction) + let message2 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage2, + attachments: [attachmentMessage2], + transaction: transaction) + } + + // Group incoming attachment + caption + if true { + let memberAddresses = [ + address8, + address9 + ] + + // avatarData should be PNG data. + let thread = try! GroupManager.createGroupForTests(members: memberAddresses, + name: NSLocalizedString("SCREENSHOT_NAME_GROUP_TWO", + comment: "This is for a group chat for people who want weather updates."), + avatarData: buildAvatarData(bundleFilename: "nature-2-trees.JPG"), + transaction: transaction) + let timestampMessage1 = startingDateMS - (2 * kDayInMs + 8 * kHourInMs) + let message1 = self.buildOutgoingMessage(thread: thread, messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_TWO_MESSAGE_ONE", + comment: "This is a message. Please include the emoji if possible."), + timestamp: timestampMessage1, transaction: transaction) + let timestampMessage2 = startingDateMS - (2 * kDayInMs + 8 * kHourInMs + 1 * kMinuteInMs) + let attachmentMessage2 = buildAttachment(bundleFilename: "test-jpg-2.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let message2 = self.buildIncomingMessage(thread: thread, + authorAddress: address9, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_TWO_MESSAGE_TWO", + comment: "This is a message sent with an attachment."), + timestamp: timestampMessage2, + attachments: [attachmentMessage2], + transaction: transaction) + message2.debugonly_markAsReadNow(transaction: transaction) + } + + // 1:1 received text + if true { + let otherAddress = address3 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + let message1 = self.buildOutgoingMessage(thread: thread, messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_FOUR_MESSAGE_ONE", + comment: "This is a message."), + transaction: transaction) + // This marks the outgoing message as read + // as opposed to displaying one sent check mark + message1.update(withReadRecipient: otherAddress, readTimestamp: Date.ows_millisecondTimestamp(), transaction: transaction) + let timestampMessage2 = startingDateMS - (2 * kDayInMs + 4 * kHourInMs + 42 * kMinuteInMs) + let message2 = self.buildIncomingMessage(thread: thread, + authorAddress: otherAddress, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_FOUR_MESSAGE_TWO", + comment: "This is a message. Include 'Thanks' + a similar phrase with the :) emoji."), + timestamp: timestampMessage2, transaction: transaction) + // This marks the incoming message as read + // so the "New Messages" indicator/ "Today" is not displayed + message2.debugonly_markAsReadNow(transaction: transaction) + } + + // Group received text + if true { + let memberAddresses = [ + address6, + address7 + ] + + // avatarData should be PNG data. + let thread = try! GroupManager.createGroupForTests(members: memberAddresses, + name: NSLocalizedString("SCREENSHOT_NAME_GROUP_THREE", + comment: "Please include emoji. This is a group name for people who climb rocks/climb trees/hike mountains/outside mountaineering."), + avatarData: buildAvatarData(bundleFilename: "address-group-climbers.jpg"), + transaction: transaction) + let timestampMessage1 = startingDateMS - (2 * kDayInMs + 2 * kHourInMs) + let message1 = self.buildIncomingMessage(thread: thread, authorAddress: address6, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_THREE_MESSAGE_ONE", + comment: "This is a message in the 'Rock Climbers' group chat. Please translate to make sense for the translated group name. For example: Which way should we go?"), + timestamp: timestampMessage1, transaction: transaction) + message1.debugonly_markAsReadNow(transaction: transaction) + } + + // Group received + if true { + let memberAddresses = [ + address4, + address5 + ] + + // avatarData should be PNG data. + let thread = try! GroupManager.createGroupForTests(members: memberAddresses, + name: NSLocalizedString("SCREENSHOT_NAME_GROUP_FOUR", + comment: "Please include emoji. This is a group name/channel name for pictures of the sun in the sky."), + avatarData: buildAvatarData(bundleFilename: "nature-4-sunset.jpg"), + transaction: transaction) + let timestampMessage1 = startingDateMS - (1 * kDayInMs + 18 * kHourInMs) + let message1 = self.buildOutgoingMessage(thread: thread, messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_FOUR_MESSAGE_ONE", + comment: "This is a message in the Sunsets group chat."), + timestamp: timestampMessage1, transaction: transaction) + let timestampMessage2 = startingDateMS - (1 * kDayInMs + 18 * kHourInMs + 10 * kMinuteInMs) + let attachmentMessage2 = buildAttachment(bundleFilename: "nature-3-road.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let message2 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage2, + attachments: [attachmentMessage2], + isViewOnceMessage: true, + transaction: transaction) + } + + // Example of how to make a simple 1:1 thread. + // TODO check voice note format + if true { + let otherAddress = address7 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + let timestampMessage1 = startingDateMS - (9 * kHourInMs) + let attachmentMessage1 = buildAttachment(bundleFilename: "test-jpg-3.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let message1 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage1, + attachments: [attachmentMessage1], transaction: transaction) + let timestampMessage2 = startingDateMS - (8 * kHourInMs + 27 * kMinuteInMs) + let message2 = self.buildOutgoingMessage(thread: thread, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_FIVE_MESSAGE_ONE", + comment: "This is a message expressing support/happiness/awe/shock."), + timestamp: timestampMessage2, transaction: transaction) + GroupManager.remoteUpdateDisappearingMessages(withContactOrV1GroupThread: thread, + disappearingMessageToken: DisappearingMessageToken(isEnabled: true, durationSeconds: UInt32(24 * kHourInterval)), + groupUpdateSourceAddress: localAddress, + transaction: transaction) + let attachmentMessage3 = buildAttachment(bundleFilename: "sonarping.mp3", + mimeType: "audio/mp3", + transaction: transaction) + attachmentMessage3.attachmentType = .voiceMessage + attachmentMessage3.anyOverwritingUpdate(transaction: transaction) + let timestampMessage3 = startingDateMS - (3 * kHourInMs + 58 * kMinuteInMs) + let message3 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage3, + attachments: [attachmentMessage3], + expiresInSeconds: 10800, + transaction: transaction) + _ = message3.recordReaction(for: otherAddress, + emoji: "โค๏ธ", + sentAtTimestamp: Date.ows_millisecondTimestamp(), + receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), + transaction: transaction) + let timestampMessage4 = startingDateMS - (3 * kHourInMs + 20 * kMinuteInMs) + let message4 = self.buildIncomingMessage(thread: thread, + authorAddress: otherAddress, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_FIVE_MESSAGE_TWO", + comment: "This is a message. Please include the emoji."), + timestamp: timestampMessage4, + expiresInSeconds: 10800, + transaction: transaction) + message4.debugonly_markAsReadNow(transaction: transaction) + let attachmentMessage5 = buildAttachment(bundleFilename: "test-jpg-2.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let timestampMessage5 = startingDateMS - (2 * kHourInMs + 20 * kMinuteInMs) + let message5 = self.buildIncomingMessage(thread: thread, + authorAddress: otherAddress, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_FIVE_MESSAGE_THREE", + comment: "This is a message."), + timestamp: timestampMessage5, + attachments: [attachmentMessage5], + expiresInSeconds: 10800, + transaction: transaction) + message5.debugonly_markAsReadNow(transaction: transaction) + _ = message5.recordReaction(for: localAddress, + emoji: "๐Ÿ‘", + sentAtTimestamp: Date.ows_millisecondTimestamp(), + receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), + transaction: transaction) + let timestampMessage6 = startingDateMS - ( 2 * kHourInMs + 2 * kMinuteInMs) + let message6 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage6, + expiresInSeconds: 10800, transaction: transaction) + // Q: Which pack? A: Bandit the Cat. + let packIdHex = "9acc9e8aba563d26a4994e69263e3b25" + let packKeyHex = "5a6dff3948c28efb9b7aaf93ecc375c69fc316e78077ed26867a14d10a0f6a12" + // Which sticker - the index in the pack. + let stickerId0: UInt32 = 14 + if let messageSticker = self.buildMessageSticker(packIdHex: packIdHex, + packKeyHex: packKeyHex, + stickerId: stickerId0, + transaction: transaction) { + message6.update(with: messageSticker, transaction: transaction) + } + } + + // Group thread received text + if true { + let memberAddresses = [ + address1, + address2, + address7, + address6 + ] + // avatarData should be PNG data. + let thread = try! GroupManager.createGroupForTests(members: memberAddresses, + name: NSLocalizedString("SCREENSHOT_NAME_GROUP_FIVE", + comment: "This is a group chat of family members. Please keep Kirk or replace with a common last name in your locale. Translate 'Family'"), + avatarData: buildAvatarData(bundleFilename: "address-group-family.jpg"), + // avatarData: nil, + transaction: transaction) + let message1 = self.buildOutgoingMessage(thread: thread, messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_FIVE_MESSAGE_ONE", + comment: "This is a message in the group chat of family members."), + transaction: transaction) + let timestampMessage2 = startingDateMS - (1 * kHourInMs + 58 * kMinuteInMs) + let message2 = self.buildIncomingMessage(thread: thread, + authorAddress: address6, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_FIVE_MESSAGE_TWO", + comment: "This is a message in the group chat of family members."), + timestamp: timestampMessage2, transaction: transaction) + } + + // 1:1 sent media + if true { + let otherAddress = address5 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + let timestampMessage2 = startingDateMS - (33 * kMinuteInMs) + let message1 = self.buildOutgoingMessage(thread: thread, messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_SIX_MESSAGE_ONE", + comment: "This is a message."), + timestamp: timestampMessage2, transaction: transaction) + // This marks the outgoing message as read + // as opposed to displaying one sent check mark + message1.update(withReadRecipient: otherAddress, readTimestamp: Date.ows_millisecondTimestamp(), transaction: transaction) + let attachmentMessage2 = buildAttachment(bundleFilename: "test-jpg-3.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let message2 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage2, + attachments: [attachmentMessage2], transaction: transaction) + } + + // Group other file types -- in focus + //Shows how to send and receive attached images. + if true { + //Shows how to send and receive attached images. + // This file lives on disk in Signal/test/Assets. + // In the project it is in Signal/Signal/test/Assets. + // You'll need to temporarily add it to the Signal target + // before you can use it here. + // You can find files in the repo using something like this... + // find . | grep -i "test-jpg" + // ...where test-jpg is a partial file name. + let attachment1 = buildAttachment(bundleFilename: "test-jpg-2.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let attachment2 = buildAttachment(bundleFilename: "test-jpg-2.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + + let memberAddresses = [ + address1, + address4, + address5, + address6, + address8, + address9, + localAddress + ] + + // avatarData should be PNG data. + let thread = try! GroupManager.createGroupForTests(members: memberAddresses, + name: NSLocalizedString("SCREENSHOT_NAME_GROUP_SIX", + comment: "This is group chat name for members talking about cats. Please include the emoji."), + avatarData: buildAvatarData(bundleFilename: "address-group-cat.jpg"), + transaction: transaction) + let attachmentMessage1 = buildAttachment(bundleFilename: "nature-2-trees.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let timestampMessage1 = startingDateMS - (23 * kHourInMs + 9 * kMinuteInMs) + let message1 = self.buildIncomingMessage(thread: thread, + authorAddress: address8, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_ONE", + comment: "This is a message after seeing a picture."), + timestamp: timestampMessage1, + attachments: [attachmentMessage1], transaction: transaction) + message1.debugonly_markAsReadNow(transaction: transaction) + let attachmentMessage2 = buildAttachment(bundleFilename: "test-jpg-3.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let timestampMessage2 = startingDateMS - (16 * kHourInMs + 5 * kMinuteInMs) + let message2 = self.buildIncomingMessage(thread: thread, + authorAddress: address5, + messageBody: nil, + timestamp: timestampMessage2, + attachments: [attachmentMessage2], transaction: transaction) + message2.debugonly_markAsReadNow(transaction: transaction) + let timestampMessage3 = startingDateMS - (4 * kHourInMs + 7 * kMinuteInMs) + let message3 = self.buildIncomingMessage(thread: thread, + authorAddress: address9, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_TWO", comment: "This is a message."), + timestamp: timestampMessage3, + transaction: transaction) + message3.debugonly_markAsReadNow(transaction: transaction) + let attachmentMessage4 = buildAttachment(bundleFilename: "nature-1-sunrise.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let timestampMessage4 = startingDateMS - (4 * kHourInMs + 7 * kMinuteInMs) + let message4 = self.buildIncomingMessage(thread: thread, + authorAddress: address9, + messageBody: nil, + timestamp: timestampMessage4, + attachments: [attachmentMessage4], transaction: transaction) + message4.debugonly_markAsReadNow(transaction: transaction) + let timestampMessage5 = startingDateMS - (15 * kMinuteInMs) + let message5 = self.buildOutgoingMessage(thread: thread, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_THREE", + comment: "This is a message in the cat chat group."), + timestamp: timestampMessage5, + transaction: transaction) + _ = message5.recordReaction(for: address1, + emoji: "๐Ÿ˜‚", + sentAtTimestamp: Date.ows_millisecondTimestamp(), + receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), + transaction: transaction) + _ = message5.recordReaction(for: address4, + emoji: "๐Ÿ˜ฎ", + sentAtTimestamp: Date.ows_millisecondTimestamp(), + receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), + transaction: transaction) + _ = message5.recordReaction(for: address5, + emoji: "๐Ÿ˜ฎ", + sentAtTimestamp: Date.ows_millisecondTimestamp(), + receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), + transaction: transaction) + let timestampMessage6 = startingDateMS - (13 * kMinuteInMs) + let message6 = self.buildIncomingMessage(thread: thread, + authorAddress: address6, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_FOUR", comment: "This is a message in the cat chat group."), + timestamp: timestampMessage6, + transaction: transaction) + message6.debugonly_markAsReadNow(transaction: transaction) + let attachmentMessage7 = buildAttachment(bundleFilename: "test-jpg-3.JPG", + mimeType: OWSMimeTypeImageJpeg, + transaction: transaction) + let timestampMessage7 = startingDateMS - (9 * kMinuteInMs) + let message7 = self.buildOutgoingMessage(thread: thread, + messageBody: nil, + timestamp: timestampMessage7, + attachments: [attachmentMessage7], + isViewOnceMessage: true, + transaction: transaction) + let attachmentMessage8 = buildAttachment(bundleFilename: "certificate.PDF", + mimeType: "application/pdf", + sourceFilename: NSLocalizedString("SCREENSHOT_THREAD_GROUP_SIX_FILE_NAME", comment: "This is a file name 'Instructions' for the cat chat group."), + transaction: transaction) + let timestampMessage8 = startingDateMS - (9 * kMinuteInMs) + let message8 = self.buildIncomingMessage(thread: thread, + authorAddress: address4, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_FIVE", comment: "This is a message in the cat chat group."), + timestamp: timestampMessage8, + attachments: [attachmentMessage8], transaction: transaction) + message8.debugonly_markAsReadNow(transaction: transaction) + } + + // Third thread 1:1 received voice message + if true { + let otherAddress = address2 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + // This will be a build warning because you're not using the message1 variable. + // you can fix that by saying: + // _ = self.buildOutgoingMessage(...) + let timestampMessage7 = startingDateMS - (4 * kMinuteInMs) + let message1 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, messageBody: "1", timestamp: timestampMessage7, transaction: transaction) + let message2 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, messageBody: "2", timestamp: timestampMessage7, transaction: transaction) + let message3 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, messageBody: "3", timestamp: timestampMessage7, transaction: transaction) + let message4 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, messageBody: "4.", timestamp: timestampMessage7, transaction: transaction) + let message5 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, messageBody: "5", timestamp: timestampMessage7, transaction: transaction) + let message6 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, messageBody: "6", timestamp: timestampMessage7, transaction: transaction) + let attachmentMessage7 = buildAttachment(bundleFilename: "sonarping.mp3", + mimeType: "audio/mp3", + transaction: transaction) + attachmentMessage7.attachmentType = .voiceMessage + attachmentMessage7.anyOverwritingUpdate(transaction: transaction) + let message7 = self.buildIncomingMessage(thread: thread, + authorAddress: otherAddress, + messageBody: nil, + timestamp: timestampMessage7, + attachments: [attachmentMessage7], transaction: transaction) + } + + // Second Thread -- Missed call or emoji on iPad + if true { + let otherAddress = address1 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) + let timestampMessage1 = startingDateMS - (3 * kMinuteInMs) + let message1 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, + messageBody: "๐Ÿคฃ๐Ÿคฃ๐Ÿคฃ", + timestamp: timestampMessage1, transaction: transaction) + let timestampMessage2 = startingDateMS - (2 * kMinuteInMs) + let message2 = self.buildIncomingMessage(thread: thread, authorAddress: otherAddress, + messageBody: NSLocalizedString("SCREENSHOT_THREAD_DIRECT_SEVEN_MESSAGE_ONE", comment: "This is a message before a call."), + timestamp: timestampMessage1, transaction: transaction) + let timestampCall1 = startingDateMS + // Replace .incomingIncomplete with other values to create other record types. + // Note that you have to remove the obj-c enum prefix. + // + // typedef NS_ENUM(NSUInteger, RPRecentCallType) { + // RPRecentCallTypeIncoming = 1, + // RPRecentCallTypeOutgoing, + // RPRecentCallTypeIncomingMissed, + // // These call types are used until the call connects. + // RPRecentCallTypeOutgoingIncomplete, + // RPRecentCallTypeIncomingIncomplete, + // RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity, + // RPRecentCallTypeIncomingDeclined, + // RPRecentCallTypeOutgoingMissed, + // }; + let callRecord = TSCall(callType: .incomingMissed, + in: thread, + sentAtTimestamp: Date.ows_millisecondTimestamp()) + callRecord.anyInsert(transaction: transaction) + } + + // First message for disappearing message time set + // Adjust the time of alert or keep this first + if true { + let otherAddress = address8 + let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) +// let message1 = self.buildOutgoingMessage(thread: thread, messageBody: "Hi yup.", transaction: transaction) +// // This marks the outgoing message as read +// // as opposed to displaying one sent check mark +// message1.update(withReadRecipient: otherAddress, readTimestamp: Date.ows_millisecondTimestamp(), transaction: transaction) +// let timestampMessage2 = startingDateMS - (30 * kHourInMs + 42 * kMinuteInMs) +// let message2 = self.buildIncomingMessage(thread: thread, +// authorAddress: otherAddress, +// messageBody: "Thanks! What a wonderful message to read :)", transaction: transaction) +// // This marks the incoming message as read +// // so the "New Messages" indicator/ "Today" is not displayed +// message2.debugonly_markAsReadNow(transaction: transaction) + GroupManager.remoteUpdateDisappearingMessages(withContactOrV1GroupThread: thread, + disappearingMessageToken: DisappearingMessageToken(isEnabled: true, durationSeconds: UInt32(24 * kHourInterval)), + groupUpdateSourceAddress: localAddress, + transaction: transaction) + } + +// // Example of how to enable or disable disappearing messages. +// if true { +// let memberAddresses = [ +// address1, +// address2, +// ] +// // avatarData should be PNG data. +// let thread = try! GroupManager.createGroupForTests(members: memberAddresses, +// name: "DMs", +// avatarData: nil, +// transaction: transaction) +// let message1 = self.buildOutgoingMessage(thread: thread, messageBody: "", transaction: transaction) +// // Enable DMs. +// // +// // For the purposes of this debug UI, it's simplest to use the +// // "remote" flavor of this message which is sync. +// GroupManager.remoteUpdateDisappearingMessages(withContactOrV1GroupThread: thread, +// disappearingMessageToken: DisappearingMessageToken(isEnabled: true, durationSeconds: UInt32(24 * kHourInterval)), +// groupUpdateSourceAddress: localAddress, +// transaction: transaction) +// // Disable DMs. +// GroupManager.remoteUpdateDisappearingMessages(withContactOrV1GroupThread: thread, +// disappearingMessageToken: DisappearingMessageToken.disabledToken, +// groupUpdateSourceAddress: localAddress, +// transaction: transaction) +// } + +// // Example of how insert call messages. +// if true { +// let otherAddress = address1 +// let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) +// +// // I think this is a "missed incoming" call. +// // +// // Replace .incomingIncomplete with other values to create other record types. +// // Note that you have to remove the obj-c enum prefix. +// // +// // typedef NS_ENUM(NSUInteger, RPRecentCallType) { +// // RPRecentCallTypeIncoming = 1, +// // RPRecentCallTypeOutgoing, +// // RPRecentCallTypeIncomingMissed, +// // // These call types are used until the call connects. +// // RPRecentCallTypeOutgoingIncomplete, +// // RPRecentCallTypeIncomingIncomplete, +// // RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity, +// // RPRecentCallTypeIncomingDeclined, +// // RPRecentCallTypeOutgoingMissed, +// // }; +//// let callRecord = TSCall(callType: .incomingIncomplete, +//// in: thread, +//// sentAtTimestamp: Date.ows_millisecondTimestamp()) +// let callRecord = TSCall(callType: .incomingMissed, +// in: thread, +// sentAtTimestamp: Date.ows_millisecondTimestamp()) +// callRecord.anyInsert(transaction: transaction) +// } + +// // Example of how to insert reactions. +// if true { +// let memberAddresses = [ +// address1, +// address2, +// ] +// // avatarData should be PNG data. +// let thread = try! GroupManager.createGroupForTests(members: memberAddresses, +// name: "Reactions", +// avatarData: nil, +// transaction: transaction) +// let outgoingMessage = self.buildOutgoingMessage(thread: thread, messageBody: "something", transaction: transaction) +// let incomingMessage = self.buildIncomingMessage(thread: thread, +// authorAddress: address1, +// messageBody: "Welcome... 1", transaction: transaction) +// +// // Local user (for this device) likes a given message. +// // +// // Reference: ["โค๏ธ", "๐Ÿ‘", "๐Ÿ‘Ž", "๐Ÿ˜‚", "๐Ÿ˜ฎ", "๐Ÿ˜ข", "๐Ÿ˜ก"] +// let _ = incomingMessage.recordReaction(for: localAddress, +// emoji: "โค๏ธ", +// sentAtTimestamp: Date.ows_millisecondTimestamp(), +// receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), +// transaction: transaction) +// +// // Some other user reacts to a given message. +// let _ = outgoingMessage.recordReaction(for: address1, +// emoji: "๐Ÿ‘", +// sentAtTimestamp: Date.ows_millisecondTimestamp(), +// receivedAtTimestamp: NSDate.ows_millisecondTimeStamp(), +// transaction: transaction) +// } + +// // Example of how to insert stickers. +// if true { +// let memberAddresses = [ +// address1, +// address2, +// ] +// // avatarData should be PNG data. +// let thread = try! GroupManager.createGroupForTests(members: memberAddresses, +// name: "Stickers", +// avatarData: nil, +// transaction: transaction) +// let outgoingMessage = self.buildOutgoingMessage(thread: thread, messageBody: "", transaction: transaction) +// let incomingMessage = self.buildIncomingMessage(thread: thread, +// authorAddress: address1, +// messageBody: "Welcome... 1", transaction: transaction) +// +// // Q: Which pack? A: Bandit the Cat. +// let packIdHex = "9acc9e8aba563d26a4994e69263e3b25" +// let packKeyHex = "5a6dff3948c28efb9b7aaf93ecc375c69fc316e78077ed26867a14d10a0f6a12" +// // Which sticker - the index in the pack. +// let stickerId0: UInt32 = 0 +// let stickerId1: UInt32 = 1 +// +// if let messageSticker = self.buildMessageSticker(packIdHex: packIdHex, +// packKeyHex: packKeyHex, +// stickerId: stickerId0, +// transaction: transaction) { +// incomingMessage.update(with: messageSticker, transaction: transaction) +// } +// if let messageSticker = self.buildMessageSticker(packIdHex: packIdHex, +// packKeyHex: packKeyHex, +// stickerId: stickerId1, +// transaction: transaction) { +// outgoingMessage.update(with: messageSticker, transaction: transaction) +// } +// } + } + } + + private class func buildMessageSticker(packIdHex: String, packKeyHex: String, stickerId: UInt32, transaction: SDSAnyWriteTransaction) -> MessageSticker? { + guard let stickerPackInfo = StickerPackInfo.parsePackIdHex(packIdHex, packKeyHex: packKeyHex) else { + owsFailDebug("Invalid info") + return nil + } + let allPacks: [StickerPack] = StickerManager.installedStickerPacks(transaction: transaction) + // Find the pack with the matching pack id. + let packList = allPacks.filter { $0.info.packId == stickerPackInfo.packId } + guard let pack: StickerPack = packList.first else { + owsFailDebug("Sticker pack not installed?") + return nil + } + // Find the sticker with the matching stickerId. + let stickerInfos: [StickerInfo] = pack.stickerInfos.filter { $0.stickerId == stickerId } + guard let stickerInfo = stickerInfos.first else { + owsFailDebug("Couldn't find sticker info in pack.") + return nil + } + guard let filePath = StickerManager.filepathForInstalledSticker(stickerInfo: stickerInfo) else { + owsFailDebug("Couldn't find sticker file path.") + return nil + } + do { + let stickerData = try Data(contentsOf: URL(fileURLWithPath: filePath)) + let stickerDraft = MessageStickerDraft(info: stickerInfo, stickerData: stickerData) + let messageSticker = try MessageSticker.buildValidatedMessageSticker(fromDraft: stickerDraft, + transaction: transaction) + return messageSticker + } catch { + owsFailDebug("Couldn't create sticker: \(error).") + return nil + } + } + +// private class func ensureAccount(phoneNumber: String, +// uuidString: String, +// givenName: String, +// familyName: String?, +// transaction: SDSAnyWriteTransaction) -> SignalServiceAddress { +// let uuid = UUID(uuidString: uuidString)! +// let address = SignalServiceAddress(uuid: uuid, phoneNumber: phoneNumber) +// self.profileManager.setProfileGivenName(givenName, +// familyName: familyName, +// for: address, +// wasLocallyInitiated: false, +// transaction: transaction) +// +// let contact = self.buildContact(address: address, fullName: givenName, transaction: transaction) +// if let existingAccount = contactsManager.fetchSignalAccount(for: address, transaction: transaction) { +// // Do nothing. +// existingAccount.contact = contact +// existingAccount.anyOverwritingUpdate(transaction: transaction) +// } else { +// let newAccount = SignalAccount(contact: contact, +// contactAvatarHash: nil, +// contactAvatarJpegData: nil, +// multipleAccountLabelText: "", +// recipientPhoneNumber: phoneNumber, +// recipientUUID: uuidString) +// newAccount.anyInsert(transaction: transaction) +// } +// return address +// } + + private class func ensureAccount(phoneNumber: String, + uuidString: String, + givenName: String, + familyName: String?, + avatarBundleFilename: String? = nil, + transaction: SDSAnyWriteTransaction) -> SignalServiceAddress { + let uuid = UUID(uuidString: uuidString)! + let address = SignalServiceAddress(uuid: uuid, phoneNumber: phoneNumber) + self.profileManager.setProfileGivenName(givenName, + familyName: familyName, + for: address, + wasLocallyInitiated: false, + transaction: transaction) + + if let avatarBundleFilename = avatarBundleFilename { + let resourceUrl = Bundle.main.resourceURL! + let fileUrl = resourceUrl.appendingPathComponent(avatarBundleFilename) + let avatarData = try! Data(contentsOf: fileUrl) + let avatarFileName = UUID().uuidString + ".jpg" + try! avatarData.write(to: URL(fileURLWithPath: OWSUserProfile.profileAvatarFilepath(withFilename: avatarFileName)), options: .atomic) + let profile = OWSUserProfile.getOrBuild(for: address, transaction: transaction) + profile.update(withAvatarFileName: avatarFileName, transaction: transaction) + } + let contact = self.buildContact(address: address, fullName: givenName, transaction: transaction) + if let existingAccount = contactsManager.fetchSignalAccount(for: address, transaction: transaction) { + // Do nothing. + existingAccount.contact = contact + existingAccount.anyOverwritingUpdate(transaction: transaction) + } else { + let newAccount = SignalAccount(contact: contact, + contactAvatarHash: nil, + contactAvatarJpegData: nil, + multipleAccountLabelText: "", + recipientPhoneNumber: phoneNumber, + recipientUUID: uuidString) + newAccount.anyInsert(transaction: transaction) + } + return address + } + + private class func setLocalProfile(givenName: String, + familyName: String?, + avatarBundleFilename: String? = nil) { + let avatarData: Data? + if let avatarBundleFilename = avatarBundleFilename { + let resourceUrl = Bundle.main.resourceURL! + let fileUrl = resourceUrl.appendingPathComponent(avatarBundleFilename) + avatarData = try! Data(contentsOf: fileUrl) + } else { + avatarData = nil + } + + OWSProfileManager.updateLocalProfilePromise( + profileGivenName: givenName, + profileFamilyName: familyName, + profileAvatarData: avatarData + ).retainUntilComplete() + } + + private class func buildContact(address: SignalServiceAddress, fullName: String, transaction: SDSAnyWriteTransaction) -> Contact { + var userTextPhoneNumbers: [String] = [] + var phoneNumberNameMap: [String: String] = [:] + var parsedPhoneNumbers: [PhoneNumber] = [] + if let phoneNumber = address.phoneNumber, + let parsedPhoneNumber = PhoneNumber(fromE164: phoneNumber) { + userTextPhoneNumbers.append(phoneNumber) + parsedPhoneNumbers.append(parsedPhoneNumber) + phoneNumberNameMap[parsedPhoneNumber.toE164()] = CommonStrings.mainPhoneNumberLabel + } + + guard let serviceIdentifier = address.serviceIdentifier else { + owsFail("serviceIdentifier was unexpectedly nil") + } + + return Contact(uniqueId: serviceIdentifier, + cnContactId: nil, + firstName: nil, + lastName: nil, + fullName: fullName, + userTextPhoneNumbers: userTextPhoneNumbers, + phoneNumberNameMap: phoneNumberNameMap, + parsedPhoneNumbers: parsedPhoneNumbers, + emails: [], + imageDataToHash: nil) + } + + private class func buildOutgoingMessage(thread: TSThread, + messageBody: String?, + timestamp: UInt64? = nil, + attachments: [TSAttachmentStream]? = nil, + expiresInSeconds: UInt32? = nil, + isViewOnceMessage: Bool? = false, + transaction: SDSAnyWriteTransaction) -> TSOutgoingMessage { + // Manipulate when the message was sent here. + // + // e.g. "yesterday" would be: Date.ows_millisecondTimestamp() - kDayInMs + let builder = TSOutgoingMessageBuilder(thread: thread, messageBody: messageBody) + if let timestamp = timestamp { + builder.timestamp = timestamp + } + if let attachments = attachments { + for attachment in attachments { + builder.addAttachmentId(attachment.uniqueId) + } + } + if let expiresInSeconds = expiresInSeconds { + builder.expiresInSeconds = expiresInSeconds + } + if let isViewOnceMessage = isViewOnceMessage { + builder.isViewOnceMessage = isViewOnceMessage + } + let message = builder.build() + message.replaceReceived(atTimestamp: timestamp ?? Date.ows_millisecondTimestamp()) + message.anyInsert(transaction: transaction) + // Mark as sent. + message.update(withFakeMessageState: .sent, transaction: transaction) + + guard let localAddress = tsAccountManager.localAddress else { + owsFailDebug("missing local address.") + return message + } + // Find some recipient of the message (who isn't us). + guard let otherAddress = (thread.recipientAddresses.filter { $0 != localAddress }.first) else { + owsFailDebug("Couldn't find other address in thread.") + return message + } + // Mark as sent to someone. + message.update(withSentRecipient: otherAddress, wasSentByUD: false, transaction: transaction) + // Mark as delivered by someone. + message.update(withDeliveredRecipient: otherAddress, + deliveryTimestamp: nil, + transaction: transaction) + // Mark as read by someone. + message.update(withReadRecipient: otherAddress, + readTimestamp: Date.ows_millisecondTimestamp(), + transaction: transaction ) + + return message + } + + private class func buildIncomingMessage(thread: TSThread, + authorAddress: SignalServiceAddress, + messageBody: String?, + timestamp: UInt64? = nil, + attachments: [TSAttachmentStream]? = nil, + expiresInSeconds: UInt32? = nil, + isViewOnceMessage: Bool? = false, + transaction: SDSAnyWriteTransaction) -> TSIncomingMessage { + // Manipulate when the message was sent here. + // + // e.g. "yesterday" would be: Date.ows_millisecondTimestamp() - kDayInMs + let timestamp = timestamp ?? Date.ows_millisecondTimestamp() + var attachmentIds = [String]() + if let attachments = attachments { + attachmentIds += attachments.map { $0.uniqueId } + } + let expiresInSeconds = expiresInSeconds ?? 0 + let isViewOnceMessage = isViewOnceMessage ?? false + let message = TSIncomingMessage(incomingMessageWithTimestamp: timestamp, + in: thread, + authorAddress: authorAddress, + sourceDeviceId: 0, + messageBody: messageBody, + attachmentIds: attachmentIds, + expiresInSeconds: expiresInSeconds, + quotedMessage: nil, + contactShare: nil, + linkPreview: nil, + messageSticker: nil, + serverTimestamp: nil, + wasReceivedByUD: false, + isViewOnceMessage: isViewOnceMessage) + message.replaceReceived(atTimestamp: timestamp) + message.anyInsert(transaction: transaction) + return message + } + + private class func buildAvatarData(bundleFilename: String) -> Data { + let resourceUrl = Bundle.main.resourceURL! + let fileUrl = resourceUrl.appendingPathComponent(bundleFilename) + return try! Data(contentsOf: fileUrl) + } + + private class func buildAttachment(bundleFilename: String, + mimeType: String, + sourceFilename: String? = nil, + caption: String? = nil, + transaction: SDSAnyWriteTransaction) -> TSAttachmentStream { + + // Content types are MIME types + // See: MIMETypeUtil.h + let resourceUrl = Bundle.main.resourceURL! + let fileUrl = resourceUrl.appendingPathComponent(bundleFilename) + let fileSize = OWSFileSystem.fileSize(of: fileUrl)!.intValue + let attachment = TSAttachmentStream(contentType: mimeType, + byteCount: UInt32(fileSize), + // This doesn't matter much unless you export the file. + // You can leave it nil. + sourceFilename: sourceFilename, + caption: caption, + // If you want an album, make sure that they all have the same album id. + albumMessageId: nil) + + let fileData = try! Data(contentsOf: fileUrl) + try! attachment.write(fileData) + attachment.anyInsert(transaction: transaction) + attachment.updateAsUploaded(withEncryptionKey: Randomness.generateRandomBytes(1), + digest: Randomness.generateRandomBytes(1), + serverId: 1, + uploadTimestamp: Date.ows_millisecondTimestamp(), + transaction: transaction) + return attachment + } +} + +#endif diff --git a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m index 8c72c4fac9..627b6c6ae9 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m @@ -8,6 +8,7 @@ #import "DebugUIDiskUsage.h" #import "DebugUIMessages.h" #import "DebugUIMisc.h" +#import "DebugUIScreenshots.h" #import "DebugUISessionState.h" #import "DebugUIStress.h" #import "DebugUISyncMessages.h" @@ -94,8 +95,12 @@ NS_ASSUME_NONNULL_BEGIN [subsectionItems addObject:[self itemForSubsection:[DebugUIProfile new] viewController:viewController thread:thread]]; [subsectionItems addObject:[self itemForSubsection:[DebugUIStress new] viewController:viewController thread:thread]]; - [subsectionItems - addObject:[self itemForSubsection:[DebugUISyncMessages new] viewController:viewController thread:thread]]; + [subsectionItems addObject:[self itemForSubsection:[DebugUISyncMessages new] + viewController:viewController + thread:thread]]; + [subsectionItems addObject:[self itemForSubsection:[DebugUIScreenshots new] + viewController:viewController + thread:thread]]; OWSTableItem *sharedDataFileBrowserItem = [OWSTableItem disclosureItemWithText:@"๐Ÿ“ Shared Container" actionBlock:^{ @@ -153,6 +158,9 @@ NS_ASSUME_NONNULL_BEGIN addObject:[self itemForSubsection:[DebugUISyncMessages new] viewController:viewController thread:nil]]; [subsectionItems addObject:[self itemForSubsection:[DebugUIBackup new] viewController:viewController thread:nil]]; [subsectionItems addObject:[self itemForSubsection:[DebugUIGroupsV2 new] viewController:viewController thread:nil]]; + [subsectionItems addObject:[self itemForSubsection:[DebugUIScreenshots new] + viewController:viewController + thread:nil]]; [subsectionItems addObject:[self itemForSubsection:[DebugUIMisc new] viewController:viewController thread:nil]]; [contents addSection:[OWSTableSection sectionWithTitle:@"Sections" items:subsectionItems]]; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 3e9f4446eb..7e577877d8 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2978,6 +2978,141 @@ /* Label for button on lock screen that lets users unlock Signal. */ "SCREEN_LOCK_UNLOCK_SIGNAL" = "Unlock Signal"; +/* This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This should be a unique non-public figure's name. This will have a female profile photo. */ +"SCREENSHOT_NAME_CONTACT_EIGHT" = "Tina Ukuku"; + +/* This is a contact's name. Please keep the nick name Ali and change the last name to a popular lastname in your language. This will have male profile photo. */ +"SCREENSHOT_NAME_CONTACT_FIVE" = "Ali Smith"; + +/* This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This should be a unique non-public figure's name. This profile photo will be either male or female. Choose a unisex name if possible. */ +"SCREENSHOT_NAME_CONTACT_FOUR" = "Artemis Cheng"; + +/* This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. Include two last names if that is represented in your locale. This should be a unique non-public figure's name. This will have a female profile photo. */ +"SCREENSHOT_NAME_CONTACT_NINE" = "Zeus Lehtonen"; + +/* This is a contact's name. A male leadership/presidential position + the sound a cat makes. This will have a cat profile photo. */ +"SCREENSHOT_NAME_CONTACT_ONE" = "Chairman Meow"; + +/* This is a contact's name. Please keep a similar unisex first name (Kai) if this name isn't common and only post the last initial. This will have a female profile photo. */ +"SCREENSHOT_NAME_CONTACT_SEVEN" = "Jordan B."; + +/* This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This will have a male profile photo. */ +"SCREENSHOT_NAME_CONTACT_SIX" = "Michael Kirk"; + +/* This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This should be a unique non-public figure's name. This will have a female profile photo. */ +"SCREENSHOT_NAME_CONTACT_TEN" = "Maya Dorai"; + +/* This is a contact's name. Please keep a similar nickname for Nikola/Nikita/etc in your language and only post the last initial. This profile photo will be either male or female but mostly female. */ +"SCREENSHOT_NAME_CONTACT_THREE" = "Nikki Romanov"; + +/* This is a contact's name. Replace the name for a more common name in your locale if this sounds too foreign. This should be a unique non-public figure's name. This profile photo will be either male or female. */ +"SCREENSHOT_NAME_CONTACT_TWO" = "Myles Larson"; + +/* This is a group chat of family members. Please keep Kirk or replace with a common last name in your locale. Translate 'Family' */ +"SCREENSHOT_NAME_GROUP_FIVE" = "Kirk Family"; + +/* Please include emoji. This is a group name/channel name for pictures of the sun in the sky. */ +"SCREENSHOT_NAME_GROUP_FOUR" = "Sunsets ๐ŸŒ…"; + +/* This is for a group of people interested in discussing books they've read. */ +"SCREENSHOT_NAME_GROUP_ONE" = "Book Club"; + +/* This is group chat name for members talking about cats. Please include the emoji. */ +"SCREENSHOT_NAME_GROUP_SIX" = "Cat Chat ๐Ÿˆ ๐Ÿฑ"; + +/* Please include emoji. This is a group name for people who climb rocks/climb trees/hike mountains/outside mountaineering. */ +"SCREENSHOT_NAME_GROUP_THREE" = "๐Ÿง—๐Ÿฝโ€โ™€๏ธ Rock Climbers"; + +/* This is for a group chat for people who want weather updates. */ +"SCREENSHOT_NAME_GROUP_TWO" = "Weather Forecasts"; + +/* This appears in Signal > Settings. A female leadership/presidential/chairwoman position + female name Freyja or similar spelling. This will have a cat profile photo. */ +"SCREENSHOT_NAME_LOCAL_PROFILE" = "Chairwoman Freya"; + +/* This is a message expressing support/happiness/awe/shock. */ +"SCREENSHOT_THREAD_DIRECT_FIVE_MESSAGE_ONE" = "Congrats! I can't believe it!"; + +/* This is a message. */ +"SCREENSHOT_THREAD_DIRECT_FIVE_MESSAGE_THREE" = "See you tomorrow?"; + +/* This is a message. Please include the emoji. */ +"SCREENSHOT_THREAD_DIRECT_FIVE_MESSAGE_TWO" = "Thank you โ˜บ๏ธ"; + +/* This is a message. */ +"SCREENSHOT_THREAD_DIRECT_FOUR_MESSAGE_ONE" = "Your wisdom has saved me."; + +/* This is a message. Include 'Thanks' + a similar phrase with the :) emoji. */ +"SCREENSHOT_THREAD_DIRECT_FOUR_MESSAGE_TWO" = "Thanks! What a wonderful message to read :)"; + +/* This is a message. */ +"SCREENSHOT_THREAD_DIRECT_ONE_MESSAGE_ONE" = "The rain is pouring down and I'm sitting here just listening to it."; + +/* This is a message. */ +"SCREENSHOT_THREAD_DIRECT_ONE_MESSAGE_TWO" = "That's what I did this morning too."; + +/* This is a message before a call. */ +"SCREENSHOT_THREAD_DIRECT_SEVEN_MESSAGE_ONE" = "Free for a call?"; + +/* This is a message. */ +"SCREENSHOT_THREAD_DIRECT_SIX_MESSAGE_ONE" = "Yes!"; + +/* Replace crepes with similar item that you bake or cook i.e. bread, croissants, naan. */ +"SCREENSHOT_THREAD_DIRECT_THREE_MESSAGE_ONE" = "We're making crepes tomorrow"; + +/* This is a message before an image of mountains + a lake. */ +"SCREENSHOT_THREAD_DIRECT_TWO_MESSAGE_ONE" = "Hey check this out!"; + +/* This is a message. */ +"SCREENSHOT_THREAD_DIRECT_TWO_MESSAGE_THREE" = "New Zealand!"; + +/* This is a message after an image of mountains + a lake. */ +"SCREENSHOT_THREAD_DIRECT_TWO_MESSAGE_TWO" = "Whoa where are you!?"; + +/* This is a message in the group chat of family members. */ +"SCREENSHOT_THREAD_GROUP_FIVE_MESSAGE_ONE" = "Today is ..."; + +/* This is a message in the group chat of family members. */ +"SCREENSHOT_THREAD_GROUP_FIVE_MESSAGE_TWO" = "Happy birthday to you. Happy birthday to you!"; + +/* This is a message in the Sunsets group chat. */ +"SCREENSHOT_THREAD_GROUP_FOUR_MESSAGE_ONE" = "It's too cloudy here."; + +/* 1984 is the book title. The file extension is a text file. */ +"SCREENSHOT_THREAD_GROUP_ONE_FILE_NAME" = "1984.txt"; + +/* This is for a message in the 'Book Club' group chat */ +"SCREENSHOT_THREAD_GROUP_ONE_MESSAGE_ONE" = "Did you read this yet?"; + +/* This is a file name 'Instructions' for the cat chat group. */ +"SCREENSHOT_THREAD_GROUP_SIX_FILE_NAME" = "Instructions.PDF"; + +/* This is a message in the cat chat group. */ +"SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_FIVE" = "This is the instruction manual."; + +/* This is a message in the cat chat group. */ +"SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_FOUR" = "Pictures, please!"; + +/* This is a message after seeing a picture. */ +"SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_ONE" = "This is peaceful."; + +/* This is a message in the cat chat group. */ +"SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_THREE" = "Sheโ€™s walking a cat on a leash..."; + +/* This is a message. */ +"SCREENSHOT_THREAD_GROUP_SIX_MESSAGE_TWO" = "๐ŸŒ… Good Morning!"; + +/* This is a message in the 'Rock Climbers' group chat. Please translate to make sense for the translated group name. For example: Which way should we go? */ +"SCREENSHOT_THREAD_GROUP_THREE_MESSAGE_ONE" = "Which route should we take?"; + +/* This is a message. Please include the emoji if possible. */ +"SCREENSHOT_THREAD_GROUP_TWO_MESSAGE_ONE" = "See you all there ๐Ÿค—"; + +/* This is a message sent with an attachment. */ +"SCREENSHOT_THREAD_GROUP_TWO_MESSAGE_TWO" = "Raining all day"; + +/* An example name for a user we use in the screenshots. */ +"SCREENSHOT_USERNAME_1" = "SCREENSHOT_USERNAME_1"; + /* Placeholder text indicating the user can search for contacts by name, username, or phone number. */ "SEARCH_BY_NAME_OR_USERNAME_OR_NUMBER_PLACEHOLDER_TEXT" = "Name, username, or number"; diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction.h b/SignalServiceKit/src/Messages/Interactions/TSInteraction.h index 1b5635ccc9..1683622344 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction.h +++ b/SignalServiceKit/src/Messages/Interactions/TSInteraction.h @@ -118,6 +118,10 @@ NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:receivedAtTimestamp // NOTE: This is only for use by the YDB-to-GRDB legacy migration. - (void)replaceSortId:(uint64_t)sortId; +#if TESTABLE_BUILD +- (void)replaceReceivedAtTimestamp:(uint64_t)receivedAtTimestamp; +#endif + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m index 22385cf8e4..6243824bf1 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m +++ b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m @@ -365,6 +365,12 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value) _sortId = sortId; } +#if TESTABLE_BUILD +- (void)replaceReceivedAtTimestamp:(uint64_t)receivedAtTimestamp { + _receivedAtTimestamp = receivedAtTimestamp; +} +#endif + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.swift b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.swift index bc9e78b718..1dde5d319a 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.swift +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.swift @@ -144,4 +144,11 @@ public class TSOutgoingMessageBuilder: NSObject { hasBuilt = true return TSOutgoingMessage(outgoingMessageWithBuilder: self) } + + #if TESTABLE_BUILD + @objc + public func addAttachmentId(_ attachmentId: String) { + attachmentIds.add(attachmentId) + } + #endif } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 4b0a624fc2..af2f27d336 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -912,7 +912,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; - (nullable TSThread *)threadForMessage:(TSMessage *)message transaction:(SDSAnyWriteTransaction *)transaction { TSThread *_Nullable thread = [message threadWithTransaction:transaction]; - OWSAssertDebug(thread != nil); + // OWSAssertDebug(thread != nil); // For some legacy sync messages, thread may be nil. // In this case, we should try to use the "local" thread.