Signal-iOS/Signal/src/ViewControllers/DebugUI/DebugUIMessages.swift

4288 lines
173 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import CoreServices
import LibSignalClient
import SignalCoreKit
import SignalServiceKit
import SignalUI
#if USE_DEBUG_UI
class DebugUIMessages: DebugUIPage, Dependencies {
private enum MessageContentType {
case normal
case longText
case shortText
}
let name = "Messages"
func section(thread: TSThread?) -> OWSTableSection? {
var items = [OWSTableItem]()
if let thread {
items += [
OWSTableItem(title: "Delete All Messages in Thread", actionBlock: {
self.databaseStorage.write { transaction in
thread.removeAllThreadInteractions(transaction: transaction)
}
})
]
items += DebugUIMessages.itemsForActions([
DebugUIMessages.fakeAllContactShareAction(thread: thread),
DebugUIMessages.sendMessageVariationsAction(thread: thread),
// Send Media
DebugUIMessages.sendAllMediaAction(thread: thread),
DebugUIMessages.sendRandomMediaAction(thread: thread),
// Fake Media
DebugUIMessages.fakeAllMediaAction(thread: thread),
DebugUIMessages.fakeRandomMediaAction(thread: thread),
// Fake Text
DebugUIMessages.fakeAllTextAction(thread: thread),
DebugUIMessages.fakeRandomTextAction(thread: thread),
// Sequences
DebugUIMessages.allFakeSequencesAction(thread: thread),
// Quoted Replies
DebugUIMessages.allQuotedReplyAction(thread: thread),
// Exemplary
DebugUIMessages.allFakeActionsAction(thread: thread),
DebugUIMessages.allFakeBackDatedAction(thread: thread)
])
items += [
// MARK: - Actions
OWSTableItem(title: "Send N text messages (1/sec.)", actionBlock: {
DebugUIMessages.sendNTextMessagesInThread(thread)
}),
OWSTableItem(title: "Receive UUID message", actionBlock: {
DebugUIMessages.receiveUUIDEnvelopeInNewThread()
}),
OWSTableItem(title: "Create UUID group", actionBlock: {
DebugUIMessages.createUUIDGroup()
}),
OWSTableItem(title: "Send Media Gallery", actionBlock: {
DebugUIMessages.sendMediaAlbumInThread(thread)
}),
OWSTableItem(title: "Send Exemplary Media Galleries", actionBlock: {
DebugUIMessages.sendExemplaryMediaGalleriesInThread(thread)
}),
OWSTableItem(title: "Select Fake", actionBlock: {
DebugUIMessages.selectFakeAction(thread: thread)
}),
OWSTableItem(title: "Select Send Media", actionBlock: {
DebugUIMessages.selectSendMediaAction(thread: thread)
}),
OWSTableItem(title: "Send All Contact Shares", actionBlock: {
DebugUIMessages.sendAllContacts(thread: thread)
}),
OWSTableItem(title: "Select Quoted Reply", actionBlock: {
DebugUIMessages.selectQuotedReplyAction(thread: thread)
}),
OWSTableItem(title: "Select Back-Dated", actionBlock: {
DebugUIMessages.selectBackDatedAction(thread: thread)
}),
// MARK: - Misc.
OWSTableItem(title: "Perform random actions", actionBlock: {
DebugUIMessages.askForQuantityWithTitle("How many actions?") { quantity in
DebugUIMessages.performRandomActions(quantity, inThread: thread)
}
}),
OWSTableItem(title: "Create Threads", actionBlock: {
DebugUIMessages.askForQuantityWithTitle("How many threads?") { threadQuantity in
DebugUIMessages.askForQuantityWithTitle("How many messages in each thread?") { messageQuantity in
DebugUIMessages.createFakeThreads(threadQuantity, withFakeMessages: messageQuantity)
}
}
}),
OWSTableItem(title: "Send text/x-signal-plain", actionBlock: {
DebugUIMessages.sendOversizeTextMessageInThread(thread)
}),
OWSTableItem(title: "Send unknown mimetype", actionBlock: {
DebugUIMessages.sendRandomAttachmentInThread(thread, uti: kUnknownTestAttachmentUTI)
}),
OWSTableItem(title: "Send pdf", actionBlock: {
DebugUIMessages.sendRandomAttachmentInThread(thread, uti: kUTTypePDF as String)
}),
OWSTableItem(title: "Create all system messages", actionBlock: {
DebugUIMessages.createSystemMessagesInThread(thread)
}),
OWSTableItem(title: "Create messages with variety of timestamps", actionBlock: {
DebugUIMessages.createTimestampMessagesInThread(thread)
}),
OWSTableItem(title: "Send text and system messages", actionBlock: {
DebugUIMessages.askForQuantityWithTitle("How many messages?") { quantity in
DebugUIMessages.sendTextAndSystemMessages(quantity, thread: thread)
}
}),
OWSTableItem(title: "Message with stalled timer", actionBlock: {
DebugUIMessages.createDisappearingMessagesWhichFailedToStartInThread(thread)
}),
OWSTableItem(title: "Inject fake incoming messages", actionBlock: {
DebugUIMessages.askForQuantityWithTitle("How many messages?") { quantity in
DebugUIMessages.injectFakeIncomingMessages(quantity, inThread: thread)
}
}),
OWSTableItem(title: "Test Indic Scripts", actionBlock: {
DebugUIMessages.testIndicScriptsInThread(thread)
}),
OWSTableItem(title: "Test Zalgo", actionBlock: {
DebugUIMessages.testZalgoTextInThread(thread)
}),
OWSTableItem(title: "Test Directional Filenames", actionBlock: {
DebugUIMessages.testDirectionalFilenamesInThread(thread)
}),
OWSTableItem(title: "Test Linkification", actionBlock: {
DebugUIMessages.testLinkificationInThread(thread)
})
]
}
if let contactThread = thread as? TSContactThread {
let recipientAddress = contactThread.contactAddress
items.append(OWSTableItem(title: "Create New Groups", actionBlock: {
DebugUIMessages.askForQuantityWithTitle("How many Groups?") { quantity in
DebugUIMessages.createNewGroups(count: quantity, recipientAddress: recipientAddress)
}
}))
}
if let groupThread = thread as? TSGroupThread {
items.append(OWSTableItem(title: "Send message to all members", actionBlock: {
DebugUIMessages.sendMessages(1, toAllMembersOfGroup: groupThread)
}
))
}
return OWSTableSection(title: name, items: items)
}
private static func itemsForActions(_ actions: [DebugUIMessagesAction]) -> [OWSTableItem] {
let items: [OWSTableItem] = actions.map { action in
OWSTableItem(
title: action.label,
actionBlock: {
// For "all in group" actions, do each subaction in the group
// exactly once, in a predictable order.
if let groupAction = action as? DebugUIMessagesGroupAction,
groupAction.mode == .ordered {
groupAction.prepareAndPerformNTimes(UInt(groupAction.subactions.count))
return
}
DebugUIMessages.performActionNTimes(action)
}
)
}
return items
}
// MARK: Infra
private static func askForQuantityWithTitle(_ title: String, completion: @escaping (UInt) -> Void) {
guard let fromViewController = UIApplication.shared.frontmostViewController else { return }
let actionSheet = ActionSheetController(title: title)
[ 1, 10, 25, 100, 1 * 1000, 10 * 1000 ].forEach { count in
actionSheet.addAction(ActionSheetAction(
title: String(count),
handler: { _ in
completion(UInt(count))
}
))
}
actionSheet.addAction(OWSActionSheets.cancelAction)
fromViewController.presentActionSheet(actionSheet)
}
private static func performActionNTimes(_ action: DebugUIMessagesAction) {
askForQuantityWithTitle("How many?") { quantity in
action.prepareAndPerformNTimes(quantity)
}
}
private static func selectActionUI(_ actions: [DebugUIMessagesAction], label: String) {
guard let fromViewController = UIApplication.shared.frontmostViewController else {
owsFailDebug("No frontmost view controller.")
return
}
let actionSheet = ActionSheetController(title: label)
for action in actions {
actionSheet.addAction(ActionSheetAction(
title: action.label,
handler: { _ in
self.performActionNTimes(action)
}
))
}
actionSheet.addAction(OWSActionSheets.cancelAction)
fromViewController.presentActionSheet(actionSheet)
}
private static func selectFakeAction(thread: TSThread) {
selectActionUI(allFakeActions(thread: thread, includeLabels: false), label: "Select Fake")
}
// MARK: Fake Media
private static func fakeAllMediaAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"All Fake Media",
subactions: allFakeMediaActions(thread: thread, includeLabels: true)
)
}
private static func fakeRandomMediaAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.randomGroupActionWithLabel(
"Random Fake Media",
subactions: allFakeMediaActions(thread: thread, includeLabels: false)
)
}
private static func allFakeMediaActions(thread: TSThread, includeLabels: Bool) -> [DebugUIMessagesAction] {
var actions = [DebugUIMessagesAction]()
// Outgoing
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Jpeg ⚠️"))
}
actions += [
fakeOutgoingJpegAction(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingJpegAction(thread: thread, messageState: .failed, hasCaption: true),
fakeOutgoingJpegAction(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingJpegAction(thread: thread, messageState: .sending, hasCaption: true),
fakeOutgoingJpegAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingJpegAction(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Gif ⚠️"))
}
actions += [
// Don't bother with multiple GIF states.
fakeOutgoingGifAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingLargeGifAction(thread: thread, messageState: .sent, hasCaption: false)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Mp3 ⚠️"))
}
actions += [
fakeOutgoingMp3Action(thread: thread, messageState: .sending, hasCaption: true),
fakeOutgoingMp3Action(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingMp3Action(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingMp3Action(thread: thread, messageState: .failed, hasCaption: true),
fakeOutgoingMp3Action(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingMp3Action(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Mp4 ⚠️"))
}
actions += [
fakeOutgoingMp4Action(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingMp4Action(thread: thread, messageState: .sending, hasCaption: true),
fakeOutgoingMp4Action(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingMp4Action(thread: thread, messageState: .failed, hasCaption: true),
fakeOutgoingMp4Action(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingMp4Action(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Compact Landscape Png ⚠️"))
}
actions += [
fakeOutgoingCompactLandscapePngAction(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingCompactLandscapePngAction(thread: thread, messageState: .sending, hasCaption: true),
fakeOutgoingCompactLandscapePngAction(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingCompactLandscapePngAction(thread: thread, messageState: .failed, hasCaption: true),
fakeOutgoingCompactLandscapePngAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingCompactLandscapePngAction(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Compact Portrait Png ⚠️"))
}
actions += [
fakeOutgoingCompactPortraitPngAction(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingCompactPortraitPngAction(thread: thread, messageState: .sending, hasCaption: true),
fakeOutgoingCompactPortraitPngAction(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingCompactPortraitPngAction(thread: thread, messageState: .failed, hasCaption: true),
fakeOutgoingCompactPortraitPngAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingCompactPortraitPngAction(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Wide Landscape Png ⚠️"))
}
actions += [
fakeOutgoingWideLandscapePngAction(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingWideLandscapePngAction(thread: thread, messageState: .sending, hasCaption: true),
fakeOutgoingWideLandscapePngAction(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingWideLandscapePngAction(thread: thread, messageState: .failed, hasCaption: true),
fakeOutgoingWideLandscapePngAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingWideLandscapePngAction(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Tall Portrait Png ⚠️"))
}
actions += [
fakeOutgoingTallPortraitPngAction(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingTallPortraitPngAction(thread: thread, messageState: .sending, hasCaption: true),
fakeOutgoingTallPortraitPngAction(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingTallPortraitPngAction(thread: thread, messageState: .failed, hasCaption: true),
fakeOutgoingTallPortraitPngAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingTallPortraitPngAction(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Large Png ⚠️"))
}
actions += [
fakeOutgoingLargePngAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingLargePngAction(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Tiny Png ⚠️"))
}
actions += [
fakeOutgoingTinyPngAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingTinyPngAction(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Reserved Color Png ⚠️"))
}
let bubbleColorIncoming = ConversationStyle.bubbleColorIncoming(hasWallpaper: false, isDarkThemeEnabled: Theme.isDarkThemeEnabled)
actions += [
fakeOutgoingPngAction(
thread: thread,
actionLabel: "Fake Outgoing White Png",
imageSize: .square(200),
backgroundColor: .white,
textColor: Theme.accentBlueColor,
imageLabel: "W",
messageState: .failed,
hasCaption: true
),
fakeOutgoingPngAction(
thread: thread,
actionLabel: "Fake Outgoing White Png",
imageSize: .square(200),
backgroundColor: .white,
textColor: Theme.accentBlueColor,
imageLabel: "W",
messageState: .sending,
hasCaption: true
),
fakeOutgoingPngAction(
thread: thread,
actionLabel: "Fake Outgoing White Png",
imageSize: .square(200),
backgroundColor: .white,
textColor: Theme.accentBlueColor,
imageLabel: "W",
messageState: .sent,
hasCaption: true
),
fakeOutgoingPngAction(
thread: thread,
actionLabel: "Fake Outgoing 'Outgoing' Png",
imageSize: .square(200),
backgroundColor: bubbleColorIncoming,
textColor: .white,
imageLabel: "W",
messageState: .failed,
hasCaption: true
),
fakeOutgoingPngAction(
thread: thread,
actionLabel: "Fake Outgoing 'Outgoing' Png",
imageSize: .square(200),
backgroundColor: bubbleColorIncoming,
textColor: .white,
imageLabel: "W",
messageState: .sending,
hasCaption: true
),
fakeOutgoingPngAction(
thread: thread,
actionLabel: "Fake Outgoing 'Outgoing' Png",
imageSize: .square(200),
backgroundColor: bubbleColorIncoming,
textColor: .white,
imageLabel: "W",
messageState: .sent,
hasCaption: true
)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Tiny Pdf ⚠️"))
}
actions += [
fakeOutgoingTinyPdfAction(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingTinyPdfAction(thread: thread, messageState: .sending, hasCaption: true),
fakeOutgoingTinyPdfAction(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingTinyPdfAction(thread: thread, messageState: .failed, hasCaption: true),
fakeOutgoingTinyPdfAction(thread: thread, messageState: .sent, hasCaption: false),
fakeOutgoingTinyPdfAction(thread: thread, messageState: .sent, hasCaption: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Large Pdf ⚠️"))
}
actions += [
fakeOutgoingLargePdfAction(thread: thread, messageState: .failed, hasCaption: false)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Missing Png ⚠️"))
}
actions += [
fakeOutgoingMissingPngAction(thread: thread, messageState: .failed, hasCaption: false)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Large Pdf ⚠️"))
}
actions += [
fakeOutgoingMissingPdfAction(thread: thread, messageState: .failed, hasCaption: false)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Oversize Text ⚠️"))
}
actions += [
fakeOutgoingOversizeTextAction(thread: thread, messageState: .failed, hasCaption: false),
fakeOutgoingOversizeTextAction(thread: thread, messageState: .sending, hasCaption: false),
fakeOutgoingOversizeTextAction(thread: thread, messageState: .sent, hasCaption: false)
]
// Incoming
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Jpg ⚠️"))
}
actions += [
fakeIncomingJpegAction(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingJpegAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingJpegAction(thread: thread, isAttachmentDownloaded: false, hasCaption: true),
fakeIncomingJpegAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Gif ⚠️"))
}
actions += [
fakeIncomingGifAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingLargeGifAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Mp3 ⚠️"))
}
actions += [
fakeIncomingMp3Action(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingMp3Action(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingMp3Action(thread: thread, isAttachmentDownloaded: false, hasCaption: true),
fakeIncomingMp3Action(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Mp4 ⚠️"))
}
actions += [
fakeIncomingMp4Action(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingMp4Action(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingMp4Action(thread: thread, isAttachmentDownloaded: false, hasCaption: true),
fakeIncomingMp4Action(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Compact Landscape Png ⚠️"))
}
actions += [
fakeIncomingCompactLandscapePngAction(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingCompactLandscapePngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingCompactLandscapePngAction(thread: thread, isAttachmentDownloaded: false, hasCaption: true),
fakeIncomingCompactLandscapePngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Compact Portrait Png ⚠️"))
}
actions += [
fakeIncomingCompactPortraitPngAction(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingCompactPortraitPngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingCompactPortraitPngAction(thread: thread, isAttachmentDownloaded: false, hasCaption: true),
fakeIncomingCompactPortraitPngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Wide Landscape Png ⚠️"))
}
actions += [
fakeIncomingWideLandscapePngAction(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingWideLandscapePngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingWideLandscapePngAction(thread: thread, isAttachmentDownloaded: false, hasCaption: true),
fakeIncomingWideLandscapePngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Tall Portrait Png ⚠️"))
}
actions += [
fakeIncomingTallPortraitPngAction(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingTallPortraitPngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingTallPortraitPngAction(thread: thread, isAttachmentDownloaded: false, hasCaption: true),
fakeIncomingTallPortraitPngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Large Png ⚠️"))
}
actions += [
fakeIncomingLargePngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingLargePngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Tiny Png ⚠️"))
}
actions += [
fakeIncomingTinyPngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingTinyPngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Reserved Color Png ⚠️"))
}
actions += [
fakeIncomingPngAction(
thread: thread,
actionLabel: "Fake Incoming White Png",
imageSize: .square(200),
backgroundColor: .white,
textColor: Theme.accentBlueColor,
imageLabel: "W",
isAttachmentDownloaded: true,
hasCaption: true
),
fakeIncomingPngAction(
thread: thread,
actionLabel: "Fake Incoming White Png",
imageSize: .square(200),
backgroundColor: .white,
textColor: Theme.accentBlueColor,
imageLabel: "W",
isAttachmentDownloaded: false,
hasCaption: true),
fakeIncomingPngAction(
thread: thread,
actionLabel: "Fake Incoming 'Incoming' Png",
imageSize: .square(200),
backgroundColor: Theme.accentBlueColor,
textColor: .white,
imageLabel: "W",
isAttachmentDownloaded: true,
hasCaption: true
),
fakeIncomingPngAction(
thread: thread,
actionLabel: "Fake Incoming 'Incoming' Png",
imageSize: .square(200),
backgroundColor: Theme.accentBlueColor,
textColor: .white,
imageLabel: "W",
isAttachmentDownloaded: true,
hasCaption: true
),
fakeIncomingPngAction(
thread: thread,
actionLabel: "Fake Incoming 'Incoming' Png",
imageSize: .square(200),
backgroundColor: Theme.accentBlueColor,
textColor: .white,
imageLabel: "W",
isAttachmentDownloaded: false,
hasCaption: true
),
fakeIncomingPngAction(
thread: thread,
actionLabel: "Fake Incoming 'Incoming' Png",
imageSize: .square(200),
backgroundColor: Theme.accentBlueColor,
textColor: .white,
imageLabel: "W",
isAttachmentDownloaded: false,
hasCaption: true
)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Tiny Pdf ⚠️"))
}
actions += [
fakeIncomingTinyPdfAction(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingTinyPdfAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingTinyPdfAction(thread: thread, isAttachmentDownloaded: false, hasCaption: true),
fakeIncomingTinyPdfAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Large Pdf ⚠️"))
}
actions += [
fakeIncomingLargePdfAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Missing Png ⚠️"))
}
actions += [
fakeIncomingMissingPngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingMissingPngAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Missing Pdf ⚠️"))
}
actions += [
fakeIncomingMissingPdfAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false),
fakeIncomingMissingPdfAction(thread: thread, isAttachmentDownloaded: true, hasCaption: true)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Oversize Text ⚠️"))
}
actions += [
fakeIncomingOversizeTextAction(thread: thread, isAttachmentDownloaded: false, hasCaption: false),
fakeIncomingOversizeTextAction(thread: thread, isAttachmentDownloaded: true, hasCaption: false)
]
return actions
}
// MARK: Fake Outgoing Media
private static func fakeOutgoingJpegAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Jpeg",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.jpegInstance,
thread: thread
)
}
private static func fakeOutgoingGifAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Gif",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.gifInstance,
thread: thread
)
}
private static func fakeOutgoingLargeGifAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Large Gif",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.largeGifInstance,
thread: thread
)
}
private static func fakeOutgoingMp3Action(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Mp3",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.mp3Instance,
thread: thread
)
}
private static func fakeOutgoingMp4Action(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Mp4",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.mp4Instance,
thread: thread
)
}
private static func fakeOutgoingCompactPortraitPngAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Portrait Png",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.compactLandscapePngInstance,
thread: thread
)
}
private static func fakeOutgoingCompactLandscapePngAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Landscape Png",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.compactPortraitPngInstance,
thread: thread
)
}
private static func fakeOutgoingTallPortraitPngAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Tall Portrait Png",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
thread: thread
)
}
private static func fakeOutgoingWideLandscapePngAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Wide Landscape Png",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
thread: thread
)
}
private static func fakeOutgoingLargePngAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Large Png",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.largePngInstance,
thread: thread
)
}
private static func fakeOutgoingTinyPngAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Tiny Png",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.tinyPngInstance,
thread: thread
)
}
private static func fakeOutgoingPngAction(
thread: TSThread,
actionLabel: String,
imageSize: CGSize,
backgroundColor: UIColor,
textColor: UIColor,
imageLabel: String,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: actionLabel,
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.pngInstance(
size: imageSize,
backgroundColor: backgroundColor,
textColor: textColor,
label: imageLabel),
thread: thread
)
}
private static func fakeOutgoingTinyPdfAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Tiny Pdf",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.tinyPdfInstance,
thread: thread
)
}
private static func fakeOutgoingLargePdfAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(label: "Fake Outgoing Large Pdf",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.largePdfInstance,
thread: thread
)
}
private static func fakeOutgoingMissingPngAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Missing Png",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.missingPngInstance,
thread: thread
)
}
private static func fakeOutgoingMissingPdfAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Missing Pdf",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.missingPdfInstance,
thread: thread
)
}
private static func fakeOutgoingOversizeTextAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeOutgoingMediaAction(
label: "Fake Outgoing Oversize Text",
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.oversizeTextInstance,
thread: thread
)
}
private static func fakeOutgoingMediaAction(
label labelParam: String,
messageState: TSOutgoingMessageState,
hasCaption: Bool,
fakeAssetLoader: DebugUIMessagesAssetLoader,
thread: TSThread
) -> DebugUIMessagesAction {
let label = labelParam + actionLabelForHasCaption(hasCaption, outgoingMessageState: messageState, isDelivered: false, isRead: false)
return DebugUIMessagesSingleAction(
label: label,
unstaggeredAction: { index, transaction in
owsAssertDebug(!fakeAssetLoader.filePath.isEmptyOrNil)
createFakeOutgoingMedia(
index: index,
messageState: messageState,
hasCaption: hasCaption,
fakeAssetLoader: fakeAssetLoader,
thread: thread,
transaction: transaction
)
},
prepare: fakeAssetLoader.prepare
)
}
private static func createFakeOutgoingMedia(
index: UInt,
messageState: TSOutgoingMessageState,
hasCaption: Bool,
fakeAssetLoader: DebugUIMessagesAssetLoader,
thread: TSThread,
transaction: SDSAnyWriteTransaction
) {
owsAssertDebug(!fakeAssetLoader.filePath.isEmptyOrNil)
var messageBody: String?
if hasCaption {
// We want a message body that is "more than one line on all devices,
// using all dynamic type sizes."
let sampleText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, " +
"consectetur adipiscing elit."
messageBody = "\(index) " + sampleText
messageBody? += actionLabelForHasCaption(
hasCaption,
outgoingMessageState: messageState,
isDelivered: false,
isRead: false
)
}
let message = createFakeOutgoingMessage(
thread: thread,
messageBody: messageBody,
fakeAssetLoader: fakeAssetLoader,
messageState: messageState,
isDelivered: true,
transaction: transaction
)
// This is a hack to "back-date" the message.
let timestamp = Date.ows_millisecondTimestamp()
message.replaceTimestamp(timestamp, transaction: transaction)
}
// MARK: Fake Incoming Media
private static func fakeIncomingJpegAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Jpeg",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.jpegInstance,
thread: thread
)
}
private static func fakeIncomingGifAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Gif",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.gifInstance,
thread: thread
)
}
private static func fakeIncomingLargeGifAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Large Gif",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.largeGifInstance,
thread: thread
)
}
private static func fakeIncomingMp3Action(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Mp3",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.mp3Instance,
thread: thread
)
}
private static func fakeIncomingMp4Action(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Mp4",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.mp4Instance,
thread: thread
)
}
private static func fakeIncomingCompactPortraitPngAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Portrait Png",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.compactPortraitPngInstance,
thread: thread
)
}
private static func fakeIncomingCompactLandscapePngAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Landscape Png",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.compactLandscapePngInstance,
thread: thread
)
}
private static func fakeIncomingTallPortraitPngAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Tall Portrait Png",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
thread: thread
)
}
private static func fakeIncomingWideLandscapePngAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Wide Landscape Png",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
thread: thread
)
}
private static func fakeIncomingLargePngAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Large Png",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.largePngInstance,
thread: thread
)
}
private static func fakeIncomingTinyPngAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Tiny Incoming Large Png",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.tinyPngInstance,
thread: thread
)
}
private static func fakeIncomingPngAction(
thread: TSThread,
actionLabel: String,
imageSize: CGSize,
backgroundColor: UIColor,
textColor: UIColor,
imageLabel: String,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: actionLabel,
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.pngInstance(
size: imageSize,
backgroundColor: backgroundColor,
textColor: textColor,
label: imageLabel
),
thread: thread
)
}
private static func fakeIncomingTinyPdfAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Tiny Pdf",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.tinyPdfInstance,
thread: thread
)
}
private static func fakeIncomingLargePdfAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Large Pdf",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.largePdfInstance,
thread: thread
)
}
private static func fakeIncomingMissingPngAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Missing Png",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.missingPngInstance,
thread: thread
)
}
private static func fakeIncomingMissingPdfAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Missing Pdf",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.missingPdfInstance,
thread: thread
)
}
private static func fakeIncomingOversizeTextAction(
thread: TSThread,
isAttachmentDownloaded: Bool,
hasCaption: Bool
) -> DebugUIMessagesAction {
return fakeIncomingMediaAction(
label: "Fake Incoming Oversize Text",
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.oversizeTextInstance,
thread: thread
)
}
private static func fakeIncomingMediaAction(
label labelParam: String,
isAttachmentDownloaded: Bool,
hasCaption: Bool,
fakeAssetLoader: DebugUIMessagesAssetLoader,
thread: TSThread
) -> DebugUIMessagesAction {
var label = labelParam
if hasCaption {
label += " 🔤"
}
if isAttachmentDownloaded {
label += " 👍"
}
return DebugUIMessagesSingleAction(
label: label,
unstaggeredAction: { index, transaction in
owsAssertDebug(!fakeAssetLoader.filePath.isEmptyOrNil)
createFakeIncomingMedia(
index: index,
isAttachmentDownloaded: isAttachmentDownloaded,
hasCaption: hasCaption,
fakeAssetLoader: fakeAssetLoader,
thread: thread,
transaction: transaction
)
},
prepare: fakeAssetLoader.prepare
)
}
@discardableResult
private static func createFakeIncomingMedia(
index: UInt,
isAttachmentDownloaded: Bool,
hasCaption: Bool,
fakeAssetLoader: DebugUIMessagesAssetLoader,
thread: TSThread,
transaction: SDSAnyWriteTransaction
) -> TSIncomingMessage {
let caption: String?
if hasCaption {
// We want a message body that is "more than one line on all devices,
// using all dynamic type sizes."
caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, " +
"consectetur adipiscing elit."
} else {
caption = nil
}
return createFakeIncomingMedia(
index: index,
isAttachmentDownloaded: isAttachmentDownloaded,
caption: caption,
fakeAssetLoader: fakeAssetLoader,
thread: thread,
transaction: transaction
)
}
private static func createFakeIncomingMedia(
index: UInt,
isAttachmentDownloaded: Bool,
caption: String?,
fakeAssetLoader: DebugUIMessagesAssetLoader,
thread: TSThread,
transaction: SDSAnyWriteTransaction
) -> TSIncomingMessage {
owsAssertDebug(!fakeAssetLoader.filePath.isEmptyOrNil)
var messageBody: String?
if let caption {
messageBody = "\(index) " + caption + " 🔤"
if isAttachmentDownloaded {
messageBody? += " 👍"
}
}
return createFakeIncomingMessage(
thread: thread,
messageBody: messageBody,
fakeAssetLoader: fakeAssetLoader,
isAttachmentDownloaded: isAttachmentDownloaded,
transaction: transaction
)
}
// MARK: Fake Text Messages
private static func fakeAllTextAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"All Fake Text",
subactions: allFakeTextActions(thread: thread, includeLabels: true)
)
}
private static func fakeRandomTextAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.randomGroupActionWithLabel(
"Random Fake Text",
subactions: allFakeTextActions(thread: thread, includeLabels: false)
)
}
private static func allFakeTextActions(thread: TSThread, includeLabels: Bool) -> [DebugUIMessagesAction] {
let messageBodies = [
"Hi",
"1",
"1⃣2",
"1⃣2⃣3",
"",
""
]
var actions = [DebugUIMessagesAction]()
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Incoming Message Bodies ⚠️"))
}
actions.append(fakeShortIncomingTextMessageAction(thread: thread))
actions += messageBodies.map { messageBody in
fakeIncomingTextMessageAction(thread: thread, text: messageBody)
}
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Statuses ⚠️"))
}
actions += [
fakeShortOutgoingTextMessageAction(thread: thread, messageState: .failed),
fakeShortOutgoingTextMessageAction(thread: thread, messageState: .sending),
fakeShortOutgoingTextMessageAction(thread: thread, messageState: .sent),
fakeShortOutgoingTextMessageAction(thread: thread, messageState: .sent, isDelivered: true, isRead: false),
fakeShortOutgoingTextMessageAction(thread: thread, messageState: .sent, isDelivered: true, isRead: true)
]
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Outgoing Message Bodies ⚠️"))
}
actions += messageBodies.map { messageBody in
fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: messageBody)
}
return actions
}
private static func fakeOutgoingTextMessageAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
text: String
) -> DebugUIMessagesAction {
return DebugUIMessagesSingleAction(
label: "Fake Outgoing Text Message (\(text)",
unstaggeredAction: { index, transaction in
let messageBody = "\(index) " + text
createFakeOutgoingMessage(
thread: thread,
messageBody: messageBody,
messageState: messageState,
transaction: transaction
)
}
)
}
private static func fakeShortOutgoingTextMessageAction(
thread: TSThread,
messageState: TSOutgoingMessageState,
isDelivered: Bool = false,
isRead: Bool = false
) -> DebugUIMessagesAction {
return fakeShortOutgoingTextMessageAction(
thread: thread,
text: randomText(),
messageState: messageState,
isDelivered: isDelivered,
isRead: isRead
)
}
private static func fakeShortOutgoingTextMessageAction(
thread: TSThread,
text: String,
messageState: TSOutgoingMessageState,
isDelivered: Bool,
isRead: Bool
) -> DebugUIMessagesAction {
let label = "Fake Short Incoming Text Message".appending(actionLabelForHasCaption(
true,
outgoingMessageState: messageState,
isDelivered: isDelivered,
isRead: isRead
))
return DebugUIMessagesSingleAction(
label: label,
unstaggeredAction: { index, transaction in
let messageBody = "\(index) " + text
createFakeOutgoingMessage(
thread: thread,
messageBody: messageBody,
fakeAssetLoader: nil,
messageState: messageState,
isDelivered: isDelivered,
isRead: isRead,
transaction: transaction
)
}
)
}
private static func fakeIncomingTextMessageAction(
thread: TSThread,
text: String
) -> DebugUIMessagesAction {
return DebugUIMessagesSingleAction(
label: "Fake Incoming Text Message \(text)",
unstaggeredAction: { index, transaction in
let messageBody = "\(index) " + text
createFakeIncomingMessage(
thread: thread,
messageBody: messageBody,
fakeAssetLoader: nil,
transaction: transaction
)
}
)
}
private static func fakeShortIncomingTextMessageAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesSingleAction(
label: "Fake Short Incoming Text Message",
unstaggeredAction: { index, transaction in
let messageBody = "\(index) " + randomText()
createFakeIncomingMessage(
thread: thread,
messageBody: messageBody,
fakeAssetLoader: nil,
isAttachmentDownloaded: false,
transaction: transaction
)
}
)
}
// MARK: Sequences
private static func allFakeSequenceActions(thread: TSThread, includeLabels: Bool) -> [DebugUIMessagesAction] {
var actions = [DebugUIMessagesAction]()
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Short Message Sequences ⚠️"))
}
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming"))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "Outgoing"))
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming 1"))
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming 2"))
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming 3"))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .failed, text: "Outgoing Unsent 1"))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .failed, text: "Outgoing Unsent 2"))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sending, text: "Outgoing Sending 1"))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sending, text: "Outgoing Sending 2"))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "Outgoing Sent 1"))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "Outgoing Sent 2"))
actions.append(fakeShortOutgoingTextMessageAction(
thread: thread,
text: "Outgoing Delivered 1",
messageState: .sent,
isDelivered: true,
isRead: false
))
actions.append(fakeShortOutgoingTextMessageAction(
thread: thread,
text: "Outgoing Delivered 2",
messageState: .sent,
isDelivered: true,
isRead: false
))
actions.append(fakeShortOutgoingTextMessageAction(
thread: thread,
text: "Outgoing Read 1",
messageState: .sent,
isDelivered: true,
isRead: true
))
actions.append(fakeShortOutgoingTextMessageAction(
thread: thread,
text: "Outgoing Read 2",
messageState: .sent,
isDelivered: true,
isRead: true
))
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming"))
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Long Message Sequences ⚠️"))
}
let longText = "\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla " +
"vitae pretium hendrerit, tellus turpis pharetra libero..."
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming " + longText))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "Outgoing" + longText))
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming 1" + longText))
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming 2" + longText))
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming 3" + longText))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .failed, text: "Outgoing Unsent 1" + longText))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .failed, text: "Outgoing Unsent 2" + longText))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sending, text: "Outgoing Sending 1" + longText))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sending, text: "Outgoing Sending 2" + longText))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "Outgoing Sent 1" + longText))
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "Outgoing Sent 2" + longText))
actions.append(fakeShortOutgoingTextMessageAction(
thread: thread,
text: "Outgoing Delivered 1" + longText,
messageState: .sent,
isDelivered: true,
isRead: false
))
actions.append(fakeShortOutgoingTextMessageAction(
thread: thread,
text: "Outgoing Delivered 2" + longText,
messageState: .sent,
isDelivered: true,
isRead: false
))
actions.append(fakeShortOutgoingTextMessageAction(
thread: thread,
text: "Outgoing Read 1" + longText,
messageState: .sent,
isDelivered: true,
isRead: true
))
actions.append(fakeShortOutgoingTextMessageAction(
thread: thread,
text: "Outgoing Read 2" + longText,
messageState: .sent,
isDelivered: true,
isRead: true
))
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "Incoming" + longText))
return actions
}
private static func allFakeSequencesAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"All Fake Sequences",
subactions: allFakeSequenceActions(thread: thread, includeLabels: true)
)
}
// MARK: Fake Quoted Replies
private typealias PrepareBlock = (@escaping DebugUIMessagesAction.Completion) -> Void
private static func fakeQuotedReplyAction(
thread: TSThread,
quotedMessageLabel: String,
isQuotedMessageIncoming: Bool,
// Optional. At least one of quotedMessageBody and quotedMessageAssetLoader should be non-nil.
quotedMessageBody quotedMessageBodyParam: String?,
// Optional. At least one of quotedMessageBody and quotedMessageAssetLoader should be non-nil.
quotedMessageAssetLoader quotedMessageAssetLoaderParam: DebugUIMessagesAssetLoader?,
replyLabel: String,
isReplyIncoming: Bool = false,
replyMessageBody replyMessageBodyParam: String?,
replyAssetLoader replyAssetLoaderParam: DebugUIMessagesAssetLoader? = nil,
// Only applies if !isReplyIncoming.
replyMessageState: TSOutgoingMessageState = .sent
) -> DebugUIMessagesAction {
// Use fixed values for properties that shouldn't matter much.
let quotedMessageIsDelivered = false
let quotedMessageIsRead = false
let quotedMessageMessageState = TSOutgoingMessageState.sent
let replyIsDelivered = false
let replyIsRead = false
// Seamlessly convert oversize text messages to oversize text attachments.
var quotedMessageAssetLoader = quotedMessageAssetLoaderParam
var quotedMessageBody = quotedMessageBodyParam
if let quotedMessageBodyParam, quotedMessageBodyParam.lengthOfBytes(using: .utf8) >= kOversizeTextMessageSizeThreshold {
owsAssertDebug(quotedMessageAssetLoaderParam == nil)
quotedMessageAssetLoader = DebugUIMessagesAssetLoader.oversizeTextInstance(text: quotedMessageBodyParam)
quotedMessageBody = nil
}
var replyAssetLoader = replyAssetLoaderParam
var replyMessageBody = replyMessageBodyParam
if let replyMessageBodyParam, replyMessageBodyParam.lengthOfBytes(using: .utf8) >= kOversizeTextMessageSizeThreshold {
owsAssertDebug(replyAssetLoaderParam == nil)
replyAssetLoader = DebugUIMessagesAssetLoader.oversizeTextInstance(text: replyMessageBodyParam)
replyMessageBody = nil
}
var label = "Quoted Reply (" + replyLabel
if !isReplyIncoming {
label += actionLabelForHasCaption(
false,
outgoingMessageState: replyMessageState,
isDelivered: replyIsDelivered,
isRead: replyIsRead
)
}
label += (") to (" + quotedMessageLabel)
if let quotedMessageAssetLoader {
label += " " + quotedMessageAssetLoader.labelEmoji
}
if !isQuotedMessageIncoming {
label += actionLabelForHasCaption(
!quotedMessageBody.isEmptyOrNil,
outgoingMessageState: quotedMessageMessageState,
isDelivered: quotedMessageIsDelivered,
isRead: quotedMessageIsRead
)
}
label += ")"
let prepareBlocks = NSMutableArray()
if let block = quotedMessageAssetLoader?.prepare {
prepareBlocks.add(block)
}
if let block = replyAssetLoader?.prepare {
prepareBlocks.add(block)
}
return DebugUIMessagesSingleAction(
label: label,
unstaggeredAction: { index, transaction in
let quotedMessageBodyWIndex: String?
if let quotedMessageBody {
quotedMessageBodyWIndex = "\(index) " + quotedMessageBody
} else {
quotedMessageBodyWIndex = nil
}
let messageToQuote: TSInteraction = {
if isQuotedMessageIncoming {
return createFakeIncomingMessage(
thread: thread,
messageBody: quotedMessageBodyWIndex,
fakeAssetLoader: quotedMessageAssetLoader,
isAttachmentDownloaded: true,
transaction: transaction
)
} else {
return createFakeOutgoingMessage(
thread: thread,
messageBody: quotedMessageBodyWIndex,
fakeAssetLoader: quotedMessageAssetLoader,
messageState: quotedMessageMessageState,
isDelivered: quotedMessageIsDelivered,
isRead: quotedMessageIsRead,
transaction: transaction
)
}
}()
let threadAssociatedData = createFakeThreadAssociatedData(thread: thread)
let containerView = UIView(frame: CGRect(origin: .zero, size: .square(100)))
let renderItem = CVLoader.debugui_buildStandaloneRenderItem(
interaction: messageToQuote,
thread: thread,
threadAssociatedData: threadAssociatedData,
containerView: containerView,
transaction: transaction
)
let itemViewModel = CVItemViewModelImpl(renderItem: renderItem!)
let draft = DependenciesBridge.shared.quotedReplyManager.buildDraftQuotedReply(
originalMessage: itemViewModel.interaction as! TSMessage,
tx: transaction.asV2Read
)!
let quotedMessageBuilder = DependenciesBridge.shared.quotedReplyManager.buildQuotedReplyForSending(
draft: draft,
threadUniqueId: thread.uniqueId,
tx: transaction.asV2Write
)
let replyMessageBodyWIndex: String?
if let replyMessageBody {
replyMessageBodyWIndex = "\(index) " + replyMessageBody
} else {
replyMessageBodyWIndex = nil
}
if isReplyIncoming {
createFakeIncomingMessage(
thread: thread,
messageBody: replyMessageBodyWIndex,
fakeAssetLoader: replyAssetLoader,
quotedMessageBuilder: quotedMessageBuilder,
transaction: transaction
)
} else {
createFakeOutgoingMessage(
thread: thread,
messageBody: replyMessageBodyWIndex,
fakeAssetLoader: replyAssetLoader,
messageState: replyMessageState,
isDelivered: replyIsDelivered,
isRead: replyIsRead,
quotedMessageBuilder: quotedMessageBuilder,
transaction: transaction
)
}
},
prepare: groupPrepareBlockWithPrepareBlocks(prepareBlocks)
)
}
// Recursively perform a group of "prepare blocks" in sequence, aborting if any fail.
private static func groupPrepareBlockWithPrepareBlocks(_ prepareBlocks: NSMutableArray) -> PrepareBlock {
return { completion in
groupPrepareBlockStepWithPrepareBlocks(prepareBlocks, completion: completion)
}
}
private static func groupPrepareBlockStepWithPrepareBlocks(
_ prepareBlocks: NSMutableArray,
completion: @escaping DebugUIMessagesAction.Completion
) {
guard prepareBlocks.count > 0 else {
completion(.success(()))
return
}
let nextPrepareBlock = prepareBlocks.lastObject as! PrepareBlock
prepareBlocks.removeLastObject()
nextPrepareBlock({ result in
switch result {
case .success:
groupPrepareBlockStepWithPrepareBlocks(prepareBlocks, completion: completion)
case .failure(let error):
completion(.failure(error))
}
})
}
static private func allFakeQuotedReplyActions(thread: TSThread, includeLabels: Bool) -> [DebugUIMessagesAction] {
let shortText = "Lorem ipsum"
let mediumText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, " +
"consectetur adipiscing elit."
let longText = randomOversizeText()
var actions = [DebugUIMessagesAction]()
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Quoted Replies (Message Lengths) ⚠️"))
}
actions += [
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Medium Text",
replyMessageBody: mediumText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Medium Text",
isQuotedMessageIncoming: false,
quotedMessageBody: mediumText,
quotedMessageAssetLoader: nil,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Medium Text",
isQuotedMessageIncoming: false,
quotedMessageBody: mediumText,
quotedMessageAssetLoader: nil,
replyLabel: "Medium Text",
replyMessageBody: mediumText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Long Text",
isQuotedMessageIncoming: false,
quotedMessageBody: longText,
quotedMessageAssetLoader: nil,
replyLabel: "Long Text",
replyMessageBody: longText,
replyMessageState: .sent
)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Quoted Replies (Attachment Types) ⚠️"))
}
actions += [
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Jpg",
isQuotedMessageIncoming: false,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.jpegInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Jpg",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.jpegInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Mp3",
isQuotedMessageIncoming: false,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.mp3Instance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Mp3",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.mp3Instance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Mp4",
isQuotedMessageIncoming: false,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.mp4Instance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Mp4",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.mp4Instance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Gif",
isQuotedMessageIncoming: false,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.gifInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Gif",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.gifInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Pdf",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tinyPdfInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Missing Pdf",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.missingPdfInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Tiny Png",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tinyPngInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Missing Png",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.missingPngInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Quoted Replies (Attachment Layout) ⚠️"))
}
actions += [
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Tall Portrait Png",
isQuotedMessageIncoming: false,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Tall Portrait Png",
isQuotedMessageIncoming: false,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
replyLabel: "Medium Text",
replyMessageBody: mediumText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Tall Portrait Png",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Wide Landscape Png",
isQuotedMessageIncoming: false,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Wide Landscape Png",
isQuotedMessageIncoming: false,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
replyLabel: "Medium Text",
replyMessageBody: mediumText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Wide Landscape Png",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
replyLabel: "Medium Text",
replyMessageBody: mediumText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Tiny Png",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tinyPngInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Tiny Png",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tinyPngInstance,
replyLabel: "Medium Text",
replyMessageBody: mediumText,
replyMessageState: .sent
)
]
let directionActions: (Bool, Bool) -> Void = { isQuotedMessageIncoming, isReplyIncoming in
actions.append(fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: isQuotedMessageIncoming,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Short Text",
isReplyIncoming: isReplyIncoming,
replyMessageBody: shortText,
replyMessageState: .sent
))
}
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Quoted Replies (Incoming v. Outgoing) ⚠️"))
}
directionActions(false, false)
directionActions(true, false)
directionActions(false, true)
directionActions(true, true)
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Quoted Replies (Message States) ⚠️"))
}
actions += [
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Jpg",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.jpegInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Mp3",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.mp3Instance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Mp4",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.mp4Instance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Gif",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.gifInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Pdf",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tinyPdfInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Missing Pdf",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.missingPdfInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Tiny Png",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tinyPngInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Missing Png",
isQuotedMessageIncoming: true,
quotedMessageBody: nil,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.missingPngInstance,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sending
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .sent
),
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Short Text",
replyMessageBody: shortText,
replyMessageState: .failed
)
]
if includeLabels {
actions.append(fakeIncomingTextMessageAction(thread: thread, text: "⚠️ Quoted Replies (Reply W. Attachment) ⚠️"))
}
actions += [
// Png + Text -> Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Tall Portrait Png",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
replyLabel: "Tall Portrait Png",
replyMessageBody: shortText,
replyAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
replyMessageState: .sent
),
// Text -> Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Tall Portrait Png",
replyMessageBody: shortText,
replyMessageState: .sent
),
// Text -> Png
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Tall Portrait Png",
replyMessageBody: nil,
replyAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
replyMessageState: .sent
),
// Png -> Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Tall Portrait Png",
replyMessageBody: shortText,
replyAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
replyMessageState: .sent
),
// Png -> Portrait Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Tall Portrait Png",
replyMessageBody: shortText,
replyAssetLoader: DebugUIMessagesAssetLoader.tallPortraitPngInstance,
replyMessageState: .sent
),
// Png -> Landscape Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Wide Landscape Png",
replyMessageBody: shortText,
replyAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
replyMessageState: .sent
),
// Png -> Landscape Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Wide Landscape Png + Short Text",
replyMessageBody: shortText,
replyAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
replyMessageState: .sent
),
// Png -> Landscape Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Wide Landscape Png + Short Text",
replyMessageBody: shortText,
replyAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
replyMessageState: .sent
),
// Png -> Landscape Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Wide Landscape Png + Medium Text",
replyMessageBody: mediumText,
replyAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
replyMessageState: .sent
),
// Png -> Landscape Png + Text
fakeQuotedReplyAction(
thread: thread,
quotedMessageLabel: "Short Text",
isQuotedMessageIncoming: false,
quotedMessageBody: shortText,
quotedMessageAssetLoader: nil,
replyLabel: "Wide Landscape Png + Medium Text",
replyMessageBody: mediumText,
replyAssetLoader: DebugUIMessagesAssetLoader.wideLandscapePngInstance,
replyMessageState: .sent
)
]
return actions
}
private static func selectQuotedReplyAction(thread: TSThread) {
selectActionUI(allFakeQuotedReplyActions(thread: thread, includeLabels: false), label: "Select QuotedReply")
}
private static func allQuotedReplyAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"All Quoted Reply",
subactions: allFakeQuotedReplyActions(thread: thread, includeLabels: true)
)
}
private static func randomQuotedReplyAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.randomGroupActionWithLabel(
"Random Quoted Reply",
subactions: allFakeQuotedReplyActions(thread: thread, includeLabels: false)
)
}
// MARK: Exemplary
private static func allFakeActionsAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"All Fake",
subactions: allFakeActions(thread: thread, includeLabels: true)
)
}
private static func allFakeActions(thread: TSThread, includeLabels: Bool) -> [DebugUIMessagesAction] {
var actions = [DebugUIMessagesAction]()
actions.append(contentsOf: allFakeMediaActions(thread: thread, includeLabels: includeLabels))
actions.append(contentsOf: allFakeTextActions(thread: thread, includeLabels: includeLabels))
actions.append(contentsOf: allFakeSequenceActions(thread: thread, includeLabels: includeLabels))
actions.append(contentsOf: allFakeQuotedReplyActions(thread: thread, includeLabels: includeLabels))
actions.append(contentsOf: allFakeBackDatedActions(thread: thread, includeLabels: includeLabels))
actions.append(contentsOf: allFakeContactShareActions(thread: thread, includeLabels: includeLabels))
return actions
}
// MARK: Back-dated
private static func allFakeBackDatedAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"All Fake Back-Dated",
subactions: allFakeBackDatedActions(thread: thread, includeLabels: true)
)
}
private static func allFakeBackDatedActions(thread: TSThread, includeLabels: Bool) -> [DebugUIMessagesAction] {
var actions = [DebugUIMessagesAction]()
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(thread: thread, messageState: .sent, text: "⚠️ Back-Dated ⚠️"))
}
actions.append(fakeBackDatedMessageAction(thread: thread, label: "One Minute Ago", dateOffset: -Int64(kMinuteInMs)))
actions.append(fakeBackDatedMessageAction(thread: thread, label: "One Hour Ago", dateOffset: -Int64(kHourInMs)))
actions.append(fakeBackDatedMessageAction(thread: thread, label: "One Day Ago", dateOffset: -Int64(kDayInMs)))
actions.append(fakeBackDatedMessageAction(thread: thread, label: "Two Days Ago", dateOffset: -Int64(kDayInMs) * 2))
actions.append(fakeBackDatedMessageAction(thread: thread, label: "Ten Days Ago", dateOffset: -Int64(kDayInMs) * 10))
actions.append(fakeBackDatedMessageAction(thread: thread, label: "5 Months Ago", dateOffset: -Int64(kDayInMs) * 30 * 5))
actions.append(fakeBackDatedMessageAction(thread: thread, label: "7 Months Ago", dateOffset: -Int64(kDayInMs) * 30 * 7))
actions.append(fakeBackDatedMessageAction(thread: thread, label: "400 Days Ago", dateOffset: -Int64(kDayInMs) * 400))
return actions
}
private static func fakeBackDatedMessageAction(thread: TSThread, label: String, dateOffset: Int64) -> DebugUIMessagesAction {
return DebugUIMessagesSingleAction(
label: "Fake Back-Date Message \(label)",
unstaggeredAction: { index, transaction in
let messageBody = ["\(index)", randomText(), label].joined(separator: " ")
let message = createFakeOutgoingMessage(
thread: thread,
messageBody: messageBody,
messageState: .sent,
transaction: transaction
)
let timestamp = Int64(Date.ows_millisecondTimestamp()) + dateOffset
message.replaceTimestamp(UInt64(timestamp), transaction: transaction)
message.replaceReceived(atTimestamp: UInt64(timestamp), transaction: transaction)
}
)
}
private static func selectBackDatedAction(thread: TSThread) {
selectActionUI(allFakeBackDatedActions(thread: thread, includeLabels: false), label: "Select Back-Dated")
}
// MARK: Contact Shares
private typealias CreateContactBlock = (SDSAnyWriteTransaction) -> OWSContact
private static func fakeAllContactShareAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"All Fake Contact Shares",
subactions: allFakeContactShareActions(thread: thread, includeLabels: true)
)
}
private static func allFakeContactShareActions(thread: TSThread, includeLabels: Bool) -> [DebugUIMessagesAction] {
var actions = [DebugUIMessagesAction]()
if includeLabels {
actions.append(fakeOutgoingTextMessageAction(
thread: thread,
messageState: .sent,
text: "⚠️ Share Contact ⚠️"
))
}
actions.append(fakeContactShareMessageAction(
thread: thread,
label: "Name & Number",
contact: { _ in
let contact = OWSContact(name: OWSContactName(givenName: "Alice"))
contact.phoneNumbers = [ OWSContactPhoneNumber(type: .home, phoneNumber: "+13213214321") ]
return contact
}
))
actions.append(fakeContactShareMessageAction(
thread: thread,
label: "Name & Email",
contact: { _ in
let contact = OWSContact(name: OWSContactName(givenName: "Bob"))
contact.emails = [ OWSContactEmail(type: .home, email: "a@b.com") ]
return contact
}
))
actions.append(
fakeContactShareMessageAction(
thread: thread,
label: "Complicated",
contact: { transaction in
let contact = OWSContact(name: OWSContactName(
givenName: "Alice",
familyName: "Carol",
namePrefix: "Ms.",
nameSuffix: "Esq.",
middleName: "Bob",
organizationName: "Falafel Hut"
))
contact.phoneNumbers = [
OWSContactPhoneNumber(type: .home, phoneNumber: "+13213215555"),
OWSContactPhoneNumber(type: .custom, label: "Carphone", phoneNumber: "+13332226666")
]
contact.emails = (0..<16).map { OWSContactEmail(type: .home, email: String(format: "a%zd@b.com", $0)) }
let address1 = OWSContactAddress(
type: .home,
street: "123 home st.",
pobox: nil,
neighborhood: "round the bend.",
city: "homeville",
region: "HO",
postcode: "12345",
country: "USA"
)
let address2 = OWSContactAddress(
type: .custom,
label: "Otra casa",
street: "123 casa calle",
pobox: "caja 123",
neighborhood: nil,
city: "barrio norte",
region: "AB",
postcode: "53421",
country: "MX"
)
contact.addresses = [ address1, address2 ]
let avatarData = AvatarBuilder
.buildRandomAvatar(diameterPoints: 200)!
.jpegData(compressionQuality: 0.9)!
let attachmentStream = TSAttachmentStream(
contentType: OWSMimeTypeImageJpeg,
byteCount: UInt32(avatarData.count),
sourceFilename: nil,
caption: nil,
attachmentType: .default,
albumMessageId: nil
)
try! attachmentStream.write(avatarData)
attachmentStream.anyInsert(transaction: transaction)
contact.setLegacyAvatarAttachmentId(attachmentStream.uniqueId)
return contact
}
)
)
actions.append(fakeContactShareMessageAction(
thread: thread,
label: "Long values",
contact: { _ in
let contact = OWSContact(name: OWSContactName(
givenName: "Bobasdjasdlkjasldkjas",
familyName: "Bobasdjasdlkjasldkjas"
))
contact.emails = [ OWSContactEmail(type: .mobile, email: "asdlakjsaldkjasldkjasdlkjasdlkjasdlkajsa@b.com") ]
return contact
}
))
actions.append(fakeContactShareMessageAction(
thread: thread,
label: "System Contact w/o Signal",
contact: { _ in
let contact = OWSContact(name: OWSContactName(givenName: "Add Me To Your Contacts"))
contact.phoneNumbers = [ OWSContactPhoneNumber(type: .work, phoneNumber: "+32460205391") ]
return contact
}
))
actions.append(fakeContactShareMessageAction(
thread: thread,
label: "System Contact w. Signal",
contact: { _ in
let contact = OWSContact(name: OWSContactName(givenName: "Add Me To Your Contacts"))
contact.phoneNumbers = [ OWSContactPhoneNumber(type: .work, phoneNumber: "+32460205392") ]
return contact
}
))
return actions
}
private static func fakeContactShareMessageAction(
thread: TSThread,
label: String,
contact: @escaping CreateContactBlock
) -> DebugUIMessagesAction {
return DebugUIMessagesSingleAction(
label: "Fake Contact Share \(label)",
unstaggeredAction: { index, transaction in
createFakeOutgoingMessage(
thread: thread,
messageBody: nil,
messageState: .sent,
contactShare: contact(transaction),
transaction: transaction
)
})
}
private static func sendAllContacts(thread: TSThread) {
let subactions = allFakeContactShareActions(thread: thread, includeLabels: false)
let action = DebugUIMessagesGroupAction.allGroupActionWithLabel("Send All Contact Shares", subactions: subactions)
action.prepareAndPerformNTimes(UInt(subactions.count))
}
// MARK: Send Media
private static func selectSendMediaAction(thread: TSThread) {
selectActionUI(allSendMediaActions(thread: thread), label: "Select Send Media")
}
private static func sendAllMediaAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"All Send Media",
subactions: allSendMediaActions(thread: thread)
)
}
private static func sendRandomMediaAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.randomGroupActionWithLabel(
"Random Send Media",
subactions: allSendMediaActions(thread: thread)
)
}
private static func allSendMediaActions(thread: TSThread) -> [DebugUIMessagesAction] {
return [
sendJpegAction(thread: thread, hasCaption: false),
sendJpegAction(thread: thread, hasCaption: true),
sendGifAction(thread: thread, hasCaption: false),
sendGifAction(thread: thread, hasCaption: true),
sendLargeGifAction(thread: thread, hasCaption: false),
sendLargeGifAction(thread: thread, hasCaption: true),
sendMp3Action(thread: thread, hasCaption: false),
sendMp3Action(thread: thread, hasCaption: true),
sendMp4Action(thread: thread, hasCaption: false),
sendMp4Action(thread: thread, hasCaption: true)
]
}
private static func sendJpegAction(thread: TSThread, hasCaption: Bool) -> DebugUIMessagesAction {
return sendMediaAction(
label: "Send Jpeg",
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.jpegInstance,
thread: thread
)
}
private static func sendGifAction(thread: TSThread, hasCaption: Bool) -> DebugUIMessagesAction {
return sendMediaAction(
label: "Send Gif",
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.gifInstance,
thread: thread
)
}
private static func sendLargeGifAction(thread: TSThread, hasCaption: Bool) -> DebugUIMessagesAction {
return sendMediaAction(
label: "Send Large Gif",
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.largeGifInstance,
thread: thread
)
}
private static func sendMp3Action(thread: TSThread, hasCaption: Bool) -> DebugUIMessagesAction {
return sendMediaAction(
label: "Send Mp3",
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.mp3Instance,
thread: thread
)
}
private static func sendMp4Action(thread: TSThread, hasCaption: Bool) -> DebugUIMessagesAction {
return sendMediaAction(
label: "Send Mp4",
hasCaption: hasCaption,
fakeAssetLoader: DebugUIMessagesAssetLoader.mp4Instance,
thread: thread
)
}
private static func sendMediaAction(
label labelParam: String,
hasCaption: Bool,
fakeAssetLoader: DebugUIMessagesAssetLoader,
thread: TSThread
) -> DebugUIMessagesAction {
let label = hasCaption ? labelParam.appending(" 🔤") : labelParam
return DebugUIMessagesSingleAction(
label: label,
staggeredAction: { index, transaction, completion in
DispatchQueue.main.async {
owsAssertDebug(!fakeAssetLoader.filePath.isEmptyOrNil)
self.sendAttachmentWithFileUrl(
URL(fileURLWithPath: fakeAssetLoader.filePath!),
thread: thread,
label: label,
hasCaption: hasCaption,
completion: completion
)
}
},
prepare: fakeAssetLoader.prepare
)
}
private static func sendAttachmentWithFileUrl(
_ fileUrl: URL,
thread: TSThread,
label: String,
hasCaption: Bool,
completion: (Result<Void, Error>) -> Void
) {
guard let utiType = MIMETypeUtil.utiType(forFileExtension: fileUrl.pathExtension) else {
completion(.failure(DebugUIError.unknownFileExtension))
return
}
let filename = fileUrl.lastPathComponent
let dataSource: DataSource
do {
dataSource = try DataSourcePath.dataSource(withFilePath: fileUrl.path, shouldDeleteOnDeallocation: false)
dataSource.sourceFilename = filename
} catch {
owsFailDebug("error while creating data source: \(error)")
completion(.failure(error))
return
}
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: utiType)
let messageText: String?
if hasCaption {
// We want a message body that is "more than one line on all devices,
// using all dynamic type sizes."
let sampleText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, " +
"consectetur adipiscing elit."
messageText = label.appending(" ").appending(sampleText).appending(" 🔤")
attachment.captionText = messageText
} else {
messageText = nil
}
if attachment.hasError {
Logger.error("attachment[\(String(describing: attachment.sourceFilename))]: \(String(describing: attachment.errorName))")
Logger.flush()
owsAssertDebug(false)
}
sendAttachment(attachment, thread: thread, messageText: messageText)
completion(.success(()))
}
private static func sendAttachment(
_ attachment: SignalAttachment?,
thread: TSThread,
messageText: String?
) {
databaseStorage.read { transaction in
let attachments: [SignalAttachment]
if let attachment {
attachments = [ attachment ]
} else {
attachments = []
}
let messageBody: MessageBody?
if let messageText {
messageBody = MessageBody(text: messageText, ranges: .empty)
} else {
messageBody = nil
}
ThreadUtil.enqueueMessage(
body: messageBody,
mediaAttachments: attachments,
thread: thread,
transaction: transaction
)
}
}
private static func sendRandomAttachmentInThread(_ thread: TSThread, uti: String, length: UInt32 = 256) {
guard let dataSource = DataSourceValue.dataSource(with: createRandomDataOfSize(length), utiType: uti) else {
owsFailDebug("Failed to create data source.")
return
}
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: uti)
if Bool.random() {
// give 1/2 our attachments captions, and add a hint that it's a caption since we
// style them indistinguishably from a separate text message.
attachment.captionText = randomCaptionText()
}
sendAttachment(attachment, thread: thread, messageText: nil)
}
// MARK: Media Albums
private static func sendExemplaryMediaGalleriesInThread(_ thread: TSThread) {
sendMediaAlbumInThread(thread, imageCount: 2, messageText: nil)
sendMediaAlbumInThread(thread, imageCount: 3, messageText: nil)
sendMediaAlbumInThread(thread, imageCount: 4, messageText: nil)
sendMediaAlbumInThread(thread, imageCount: 5, messageText: nil)
sendMediaAlbumInThread(thread, imageCount: 6, messageText: nil)
sendMediaAlbumInThread(thread, imageCount: 7, messageText: nil)
let messageText = "This is the media gallery title..."
sendMediaAlbumInThread(thread, imageCount: 2, messageText: messageText)
sendMediaAlbumInThread(thread, imageCount: 3, messageText: messageText)
sendMediaAlbumInThread(thread, imageCount: 4, messageText: messageText)
sendMediaAlbumInThread(thread, imageCount: 5, messageText: messageText)
sendMediaAlbumInThread(thread, imageCount: 6, messageText: messageText)
sendMediaAlbumInThread(thread, imageCount: 7, messageText: messageText)
}
private static func sendMediaAlbumInThread(_ thread: TSThread) {
let imageCount = UInt.random(in: 2...10)
let messageText: String? = Bool.random() ? "This is the media gallery title..." : nil
sendMediaAlbumInThread(thread, imageCount: imageCount, messageText: messageText)
}
private static func sendMediaAlbumInThread(_ thread: TSThread, imageCount: UInt, messageText: String?) {
let fakeAssetLoaders: [DebugUIMessagesAssetLoader] = [
DebugUIMessagesAssetLoader.jpegInstance,
DebugUIMessagesAssetLoader.largePngInstance,
DebugUIMessagesAssetLoader.tinyPngInstance,
DebugUIMessagesAssetLoader.gifInstance,
DebugUIMessagesAssetLoader.mp4Instance,
DebugUIMessagesAssetLoader.mediumFilesizePngInstance
]
DebugUIMessagesAssetLoader.prepareAssetLoaders(fakeAssetLoaders) { result in
switch result {
case .success:
sendMediaAlbumInThread(thread, imageCount: imageCount, messageText: messageText, fakeAssetLoaders: fakeAssetLoaders)
case .failure(let error):
Logger.error("Could not prepare fake asset loaders. [\(error)]")
}
}
}
private static func sendMediaAlbumInThread(
_ thread: TSThread,
imageCount: UInt,
messageText: String?,
fakeAssetLoaders: [DebugUIMessagesAssetLoader]
) {
let attachments: [SignalAttachment] = (0..<imageCount).compactMap { _ in
let fakeAssetLoader = fakeAssetLoaders.randomElement()!
owsAssertDebug(FileManager.default.fileExists(atPath: fakeAssetLoader.filePath!))
let fileExtension = fakeAssetLoader.filePath?.fileExtension!
let tempFilePath = OWSFileSystem.temporaryFilePath(fileExtension: fileExtension)
do {
try FileManager.default.copyItem(
at: URL(fileURLWithPath: fakeAssetLoader.filePath!),
to: URL(fileURLWithPath: tempFilePath)
)
} catch {
return nil
}
guard
let dataSource = try? DataSourcePath.dataSource(withFilePath: tempFilePath, shouldDeleteOnDeallocation: false),
let uti = MIMETypeUtil.utiType(forMIMEType: fakeAssetLoader.mimeType)
else {
return nil
}
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: uti)
if Bool.random() {
attachment.captionText = randomText()
}
return attachment
}
let messageBody: MessageBody?
if let messageText {
messageBody = MessageBody(text: messageText, ranges: .empty)
} else {
messageBody = nil
}
databaseStorage.read { transaction in
ThreadUtil.enqueueMessage(
body: messageBody,
mediaAttachments: attachments,
thread: thread,
transaction: transaction
)
}
}
// MARK: Send Text Messages
private static func sendMessageVariationsAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesGroupAction.allGroupActionWithLabel(
"Send Conversation Cell Variations",
subactions: [
sendShortTextMessageAction(thread: thread),
sendOversizeTextMessageAction(thread: thread)
]
)
}
private static func sendShortTextMessageAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesSingleAction(
label: "Send Short Text Message",
staggeredAction: { index, transaction, completion in
DispatchQueue.main.async {
self.sendTextMessageInThread(thread, counter: index)
}
}
)
}
private static func sendOversizeTextMessageAction(thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesSingleAction(
label: "Send Oversize Text Message",
staggeredAction: { index, transaction, completion in
DispatchQueue.main.async {
self.sendOversizeTextMessageInThread(thread)
}
}
)
}
private static func sendTextMessageInThread(_ thread: TSThread, counter: UInt) {
Logger.info("sendTextMessageInThread: \(counter)")
Logger.flush()
let text = "\(counter) " + randomText()
databaseStorage.write { transaction in
ThreadUtil.enqueueMessage(
body: MessageBody(text: text, ranges: .empty),
thread: thread,
transaction: transaction
)
}
}
private static func sendOversizeTextMessageInThread(_ thread: TSThread) {
sendAttachment(nil, thread: thread, messageText: randomOversizeText())
}
private static func sendNTextMessagesInThread(_ thread: TSThread) {
performActionNTimes(sendTextMessagesActionInThread(thread))
}
private static func sendTextMessagesActionInThread(_ thread: TSThread) -> DebugUIMessagesAction {
return DebugUIMessagesSingleAction(
label: "Send Text Message",
staggeredAction: { index, transaction, completion in
DispatchQueue.main.async {
sendTextMessageInThread(thread, counter: index)
completion(.success(()))
}
})
}
// MARK: System Messages
private static func createSystemMessageInThread(_ thread: TSThread) {
let message = unsavedSystemMessagesInThread(thread).randomElement()!
databaseStorage.write { transaction in
message.anyInsert(transaction: transaction)
}
}
private static func createSystemMessagesInThread(_ thread: TSThread) {
let messages = unsavedSystemMessagesInThread(thread)
databaseStorage.write { transaction in
for message in messages {
message.anyInsert(transaction: transaction)
}
}
}
private static func unsavedSystemMessagesInThread(_ thread: TSThread) -> [TSInteraction] {
let messages = databaseStorage.write { transaction in
return unsavedSystemMessagesInThread(thread, transaction: transaction)
}
return messages
}
private static func unsavedSystemMessagesInThread(_ thread: TSThread, transaction: SDSAnyWriteTransaction) -> [TSInteraction] {
guard let incomingSenderAddress = anyIncomingSenderAddress(forThread: thread) else {
owsFailDebug("Missing incomingSenderAddress.")
return []
}
var results = [TSInteraction]()
// Calls
if let contactThread = thread as? TSContactThread {
results += [
TSCall(
callType: .incoming,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
),
TSCall(
callType: .outgoing,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
),
TSCall(
callType: .incomingMissed,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
),
TSCall(
callType: .incomingMissedBecauseOfChangedIdentity,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
),
TSCall(
callType: .outgoingIncomplete,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
),
TSCall(
callType: .incomingIncomplete,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
),
TSCall(
callType: .incomingDeclined,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
),
TSCall(
callType: .outgoingMissed,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
),
TSCall(
callType: .incomingMissedBecauseOfDoNotDisturb,
offerType: .audio,
thread: contactThread,
sentAtTimestamp: Date.ows_millisecondTimestamp()
)
]
}
// Disappearing Messages
if let durationSeconds = OWSDisappearingMessagesConfiguration.presetDurationsSeconds().first?.uint32Value {
let dmConfigurationStore = DependenciesBridge.shared.disappearingMessagesConfigurationStore
let disappearingMessagesConfiguration = dmConfigurationStore
.fetchOrBuildDefault(for: .thread(thread), tx: transaction.asV2Read)
.copyAsEnabled(withDurationSeconds: durationSeconds)
results.append(OWSDisappearingConfigurationUpdateInfoMessage(
thread: thread,
configuration: disappearingMessagesConfiguration,
createdByRemoteName: "Alice",
createdInExistingGroup: false
))
results.append(OWSDisappearingConfigurationUpdateInfoMessage(
thread: thread,
configuration: disappearingMessagesConfiguration,
createdByRemoteName: nil,
createdInExistingGroup: true
))
}
if let durationSeconds = OWSDisappearingMessagesConfiguration.presetDurationsSeconds().last?.uint32Value {
let dmConfigurationStore = DependenciesBridge.shared.disappearingMessagesConfigurationStore
let disappearingMessagesConfiguration = dmConfigurationStore
.fetchOrBuildDefault(for: .thread(thread), tx: transaction.asV2Read)
.copyAsEnabled(withDurationSeconds: durationSeconds)
results.append(OWSDisappearingConfigurationUpdateInfoMessage(
thread: thread,
configuration: disappearingMessagesConfiguration,
createdByRemoteName: "Alice",
createdInExistingGroup: false
))
}
let dmConfigurationStore = DependenciesBridge.shared.disappearingMessagesConfigurationStore
let disappearingMessagesConfiguration = dmConfigurationStore
.fetchOrBuildDefault(for: .thread(thread), tx: transaction.asV2Read)
.copy(withIsEnabled: false)
results.append(OWSDisappearingConfigurationUpdateInfoMessage(
thread: thread,
configuration: disappearingMessagesConfiguration,
createdByRemoteName: "Alice",
createdInExistingGroup: false
))
results += [
TSInfoMessage.userNotRegisteredMessage(in: thread, address: incomingSenderAddress),
TSInfoMessage(thread: thread, messageType: .typeSessionDidEnd),
// TODO: customMessage?
TSInfoMessage(thread: thread, messageType: .typeGroupUpdate),
// TODO: customMessage?
TSInfoMessage(thread: thread, messageType: .typeGroupQuit),
OWSVerificationStateChangeMessage(
thread: thread,
recipientAddress: incomingSenderAddress,
verificationState: .default,
isLocalChange: true
),
OWSVerificationStateChangeMessage(
thread: thread,
recipientAddress: incomingSenderAddress,
verificationState: .verified,
isLocalChange: true
),
OWSVerificationStateChangeMessage(
thread: thread,
recipientAddress: incomingSenderAddress,
verificationState: .noLongerVerified,
isLocalChange: true
),
OWSVerificationStateChangeMessage(
thread: thread,
recipientAddress: incomingSenderAddress,
verificationState: .default,
isLocalChange: false
),
OWSVerificationStateChangeMessage(
thread: thread,
recipientAddress: incomingSenderAddress,
verificationState: .verified,
isLocalChange: false),
OWSVerificationStateChangeMessage(
thread: thread,
recipientAddress: incomingSenderAddress,
verificationState: .noLongerVerified,
isLocalChange: false
),
TSErrorMessage.missingSession(with: createEnvelopeForThread(thread), with: transaction),
TSErrorMessage.invalidKeyException(with: createEnvelopeForThread(thread), with: transaction),
TSErrorMessage.invalidVersion(with: createEnvelopeForThread(thread), with: transaction),
TSErrorMessage.nonblockingIdentityChange(in: thread, address: incomingSenderAddress, wasIdentityVerified: false),
TSErrorMessage.nonblockingIdentityChange(in: thread, address: incomingSenderAddress, wasIdentityVerified: true)
]
return results
}
private static func sendTextAndSystemMessages(_ counter: UInt, thread: TSThread) {
guard counter > 0 else { return }
if Bool.random() {
sendTextMessageInThread(thread, counter: counter)
} else {
createSystemMessageInThread(thread)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
sendTextAndSystemMessages(counter - 1, thread: thread)
}
}
private static func createTimestampMessagesInThread(_ thread: TSThread) {
let now = Date.ows_millisecondTimestamp()
let timestamps = [
now + 1 * kHourInMs,
now,
now - 1 * kHourInMs,
now - 12 * kHourInMs,
now - 1 * kDayInMs,
now - 2 * kDayInMs,
now - 3 * kDayInMs,
now - 6 * kDayInMs,
now - 7 * kDayInMs,
now - 8 * kDayInMs,
now - 2 * kWeekInMs,
now - 1 * 30 * kDayInMs,
now - 2 * 30 * kDayInMs
]
guard let incomingSenderAci = anyIncomingSenderAddress(forThread: thread)?.aci else {
owsFailDebug("Missing incomingSenderAci.")
return
}
databaseStorage.write { transaction in
for timestamp in timestamps {
let randomText = randomText()
// Legit usage of SenderTimestamp to backdate incoming sent messages for Debug
let incomingMessageBuilder = TSIncomingMessageBuilder(thread: thread, messageBody: randomText)
incomingMessageBuilder.timestamp = timestamp
incomingMessageBuilder.authorAci = AciObjC(incomingSenderAci)
let incomingMessage = incomingMessageBuilder.build()
incomingMessage.anyInsert(transaction: transaction)
incomingMessage.debugonly_markAsReadNow(transaction: transaction)
// MJK TODO - this might be the one place we actually use senderTimestamp
let outgoingMessageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: randomText)
outgoingMessageBuilder.timestamp = timestamp
let outgoingMessage = outgoingMessageBuilder.build(transaction: transaction)
outgoingMessage.anyInsert(transaction: transaction)
outgoingMessage.update(withFakeMessageState: .sent, transaction: transaction)
outgoingMessage.update(withSentRecipient: ServiceIdObjC.wrapValue(incomingSenderAci), wasSentByUD: false, transaction: transaction)
outgoingMessage.update(
withDeliveredRecipient: SignalServiceAddress(incomingSenderAci),
deviceId: 0,
deliveryTimestamp: timestamp,
context: PassthroughDeliveryReceiptContext(),
tx: transaction
)
outgoingMessage.update(
withReadRecipient: SignalServiceAddress(incomingSenderAci),
deviceId: 0,
readTimestamp: timestamp,
tx: transaction
)
}
}
}
private static func createEnvelopeForThread(_ thread: TSThread) -> SSKProtoEnvelope {
let source: SignalServiceAddress = {
if let groupThread = thread as? TSGroupThread {
return groupThread.groupModel.groupMembers.first!
} else if let contactThread = thread as? TSContactThread {
return contactThread.contactAddress
} else {
owsFail("Unknown thread type")
}
}()
let envelopeBuilder = SSKProtoEnvelope.builder(timestamp: NSDate.ows_millisecondTimeStamp())
envelopeBuilder.setType(.ciphertext)
envelopeBuilder.setSourceServiceID(source.aci!.serviceIdString)
envelopeBuilder.setSourceDevice(1)
let envelope = try! envelopeBuilder.build()
return envelope
}
// MARK: -
private static func receiveUUIDEnvelopeInNewThread() {
let senderClient = FakeSignalClient.generate(e164Identifier: nil)
let localClient = LocalSignalClient()
let runner = TestProtocolRunner()
let fakeService = FakeService(localClient: localClient, runner: runner)
databaseStorage.write { transaction in
try! runner.initialize(senderClient: senderClient,
recipientClient: localClient,
transaction: transaction)
}
let envelopeBuilder = try! fakeService.envelopeBuilder(fromSenderClient: senderClient)
envelopeBuilder.setSourceServiceID(senderClient.serviceId.serviceIdString)
let envelopeData = try! envelopeBuilder.buildSerializedData()
messageProcessor.processReceivedEnvelopeData(
envelopeData,
serverDeliveryTimestamp: 0,
envelopeSource: .debugUI
) { _ in }
}
private static func createUUIDGroup() {
let uuidMembers = (0...3).map { _ in CommonGenerator.address(hasPhoneNumber: false) }
let members = uuidMembers + [DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction!.aciAddress]
let groupName = "UUID Group"
Task {
_ = try? await GroupManager.localCreateNewGroup(
members: members,
name: groupName,
disappearingMessageToken: .disabledToken,
shouldSendMessage: true
)
}
}
// MARK: Fake Threads & Messages
private static func createFakeThreads(_ threadQuantity: UInt, withFakeMessages messageQuantity: UInt) {
DebugContactsUtils.createRandomContacts(threadQuantity) { contact, index, stop in
guard
let phoneNumberText = contact.phoneNumbers.first?.value.stringValue,
let e164 = phoneNumberUtil.parsePhoneNumber(userSpecifiedText: phoneNumberText)?.toE164()
else {
owsFailDebug("Invalid phone number")
return
}
databaseStorage.write { transaction in
let address = SignalServiceAddress(phoneNumber: e164)
let contactThread = TSContactThread.getOrCreateThread(withContactAddress: address, transaction: transaction)
profileManager.addThread(toProfileWhitelist: contactThread, transaction: transaction)
createFakeMessagesInBatches(messageQuantity, inThread: contactThread, messageContentType: .longText, transaction: transaction)
Logger.info("Created a fake thread for \(e164) with \(messageQuantity) messages")
}
}
}
private static func createFakeMessagesInBatches(
_ counter: UInt,
inThread thread: TSThread,
messageContentType: MessageContentType,
transaction: SDSAnyWriteTransaction
) {
let maxBatchSize: UInt = 200
var remainder = counter
while remainder > 0 {
autoreleasepool {
let batchSize = min(maxBatchSize, remainder)
createFakeMessages(
batchSize,
batchOffset: counter - remainder,
inThread: thread,
messageContentType: messageContentType,
transaction: transaction
)
remainder -= batchSize
Logger.info("createFakeMessages \(counter - remainder) / \(counter)")
}
}
}
private static func createFakeMessages(
_ counter: UInt,
batchOffset: UInt,
inThread thread: TSThread,
messageContentType: MessageContentType,
transaction: SDSAnyWriteTransaction
) {
Logger.info("createFakeMessages: \(counter)")
guard let incomingSenderAci = anyIncomingSenderAddress(forThread: thread)?.aci else {
owsFailDebug("Missing incomingSenderAci.")
return
}
for i in 0..<counter {
let randomText: String
if messageContentType == .shortText {
randomText = DebugUIMessages.randomShortText() + " \(i + 1 + batchOffset)"
} else {
randomText = DebugUIMessages.randomText() + " (sequence: \(i + 1 + batchOffset)"
}
let isTextOnly = messageContentType != .normal
let numberOfCases = isTextOnly ? 2 : 4
switch Int.random(in: 0..<numberOfCases) {
case 0:
let incomingMessageBuilder = TSIncomingMessageBuilder(thread: thread, messageBody: randomText)
incomingMessageBuilder.authorAci = AciObjC(incomingSenderAci)
let message = incomingMessageBuilder.build()
message.anyInsert(transaction: transaction)
message.debugonly_markAsReadNow(transaction: transaction)
case 1:
createFakeOutgoingMessage(
thread: thread,
messageBody: randomText,
messageState: .sent,
transaction: transaction
)
case 2:
let filesize: UInt32 = 64
let pointer = TSAttachmentPointer(
serverId: 237391539706350548,
cdnKey: "",
cdnNumber: 0,
key: createRandomDataOfSize(filesize),
digest: nil,
byteCount: filesize,
contentType: "image/jpg",
sourceFilename: "test.jpg",
caption: nil,
albumMessageId: nil,
attachmentType: .default,
mediaSize: .zero,
blurHash: nil,
uploadTimestamp: 0,
videoDuration: nil
)
pointer.setAttachmentPointerStateDebug(.failed)
pointer.anyInsert(transaction: transaction)
let incomingMessageBuilder = TSIncomingMessageBuilder(thread: thread)
incomingMessageBuilder.authorAci = AciObjC(incomingSenderAci)
incomingMessageBuilder.attachmentIds = [ pointer.uniqueId ]
let message = incomingMessageBuilder.build()
message.anyInsert(transaction: transaction)
message.debugonly_markAsReadNow(transaction: transaction)
case 3:
let conversationFactory = ConversationFactory()
// We want to produce a variety of album sizes, but favoring smaller albums
conversationFactory.attachmentCount = Int.random(in: 0...SignalAttachment.maxAttachmentsAllowed)
conversationFactory.threadCreator = { _ in return thread }
conversationFactory.createSentMessage(transaction: transaction)
default:
break
}
}
}
private static func injectFakeIncomingMessages(_ counter: UInt, inThread thread: TSThread) {
// Wait 5 seconds so debug user has time to navigate to another
// view before message processing occurs.
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
for i in 0..<counter {
injectIncomingMessageInThread(thread, counter: counter - i)
}
}
}
private static func injectIncomingMessageInThread(_ thread: TSThread, counter: UInt) {
Logger.info("injectIncomingMessageInThread: \(counter)")
var randomText = randomText()
randomText = randomText.appending(randomText).appending("\n")
randomText = randomText.appending(randomText).appending("\n")
randomText = randomText.appending(randomText).appending("\n")
randomText = randomText.appending(randomText).appending("\n")
randomText = randomText.appending(randomText).appending("\n")
let text = "\(counter) " + randomText
let dataMessageBuilder = SSKProtoDataMessage.builder()
dataMessageBuilder.setBody(text)
if let groupThread = thread as? TSGroupThread, groupThread.isGroupV2Thread {
let groupModel = groupThread.groupModel as! TSGroupModelV2
let groupContext = try! groupsV2.buildGroupContextV2Proto(groupModel: groupModel, changeActionsProtoData: nil)
dataMessageBuilder.setGroupV2(groupContext)
}
let payloadBuilder = SSKProtoContent.builder()
if let dataMessage = dataMessageBuilder.buildIgnoringErrors() {
payloadBuilder.setDataMessage(dataMessage)
}
let plaintextData = payloadBuilder.buildIgnoringErrors()!.serializedDataIgnoringErrors()!
// Try to use an arbitrary member of the current thread that isn't
// ourselves as the sender.
let address = thread.recipientAddressesWithSneakyTransaction.first!
let envelopeBuilder = SSKProtoEnvelope.builder(timestamp: NSDate.ows_millisecondTimeStamp())
envelopeBuilder.setType(.ciphertext)
envelopeBuilder.setSourceServiceID(address.aci!.serviceIdString)
envelopeBuilder.setSourceDevice(1)
envelopeBuilder.setContent(plaintextData)
envelopeBuilder.setServerTimestamp(NSDate.ows_millisecondTimeStamp())
let envelope = try! envelopeBuilder.build()
processDecryptedEnvelope(envelope, plaintextData: plaintextData)
}
// MARK: -
private static func deleteRandomMessages(_ count: UInt, inThread thread: TSThread, transaction: SDSAnyWriteTransaction) {
Logger.info("deleteRandomMessages: \(count)")
let interactionFinder = InteractionFinder(threadUniqueId: thread.uniqueId)
let uniqueIds = try! interactionFinder.fetchUniqueIds(
filter: .newest,
excludingPlaceholders: !DebugFlags.showFailedDecryptionPlaceholders.get(),
limit: 100_000,
tx: transaction
)
let interactions = InteractionFinder.interactions(
withInteractionIds: Set(uniqueIds.shuffled().prefix(Int(count))),
transaction: transaction
)
for interaction in interactions {
interaction.anyRemove(transaction: transaction)
}
}
private static func insertAndDeleteNewOutgoingMessages(_ count: UInt, inThread thread: TSThread, transaction: SDSAnyWriteTransaction) {
Logger.info("insertAndDeleteNewOutgoingMessages: \(count)")
let messages: [TSOutgoingMessage] = (1...count).map { _ in
let text = randomText()
let dmConfigurationStore = DependenciesBridge.shared.disappearingMessagesConfigurationStore
let expiresInSeconds = dmConfigurationStore.durationSeconds(for: thread, tx: transaction.asV2Read)
let message = TSOutgoingMessage(in: thread, messageBody: text, attachmentId: nil, expiresInSeconds: expiresInSeconds)
Logger.info("insertAndDeleteNewOutgoingMessages timestamp: \(message.timestamp)")
return message
}
for message in messages {
message.anyInsert(transaction: transaction)
}
for message in messages {
message.anyRemove(transaction: transaction)
}
}
private static func resurrectNewOutgoingMessages1(_ count: UInt, inThread thread: TSThread, transaction: SDSAnyWriteTransaction) {
Logger.info("resurrectNewOutgoingMessages1.1: \(count)")
let messages: [TSOutgoingMessage] = (1...count).map { _ in
let text = randomText()
let dmConfigurationStore = DependenciesBridge.shared.disappearingMessagesConfigurationStore
let expiresInSeconds = dmConfigurationStore.durationSeconds(for: thread, tx: transaction.asV2Read)
let message = TSOutgoingMessage(in: thread, messageBody: text, attachmentId: nil, expiresInSeconds: expiresInSeconds)
Logger.info("resurrectNewOutgoingMessages1 timestamp: \(message.timestamp)")
return message
}
for message in messages {
message.anyInsert(transaction: transaction)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
Logger.info("resurrectNewOutgoingMessages1.2: \(count)")
databaseStorage.write { t in
for message in messages {
message.anyRemove(transaction: t)
}
for message in messages {
message.anyInsert(transaction: t)
}
}
}
}
private static func resurrectNewOutgoingMessages2(_ count: UInt, inThread thread: TSThread, transaction: SDSAnyWriteTransaction) {
Logger.info("resurrectNewOutgoingMessages2.1: \(count)")
let messages: [TSOutgoingMessage] = (1...count).map { _ in
let text = randomText()
let dmConfigurationStore = DependenciesBridge.shared.disappearingMessagesConfigurationStore
let expiresInSeconds = dmConfigurationStore.durationSeconds(for: thread, tx: transaction.asV2Read)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: text)
messageBuilder.expiresInSeconds = expiresInSeconds
let message = messageBuilder.build(transaction: transaction)
Logger.info("resurrectNewOutgoingMessages2 timestamp: \(message.timestamp)")
return message
}
for message in messages {
message.update(withFakeMessageState: .sending, transaction: transaction)
message.anyInsert(transaction: transaction)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
Logger.info("resurrectNewOutgoingMessages2.2: \(count)")
databaseStorage.write { t in
for message in messages {
message.anyRemove(transaction: t)
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
Logger.info("resurrectNewOutgoingMessages2.3: \(count)")
databaseStorage.write { t in
for message in messages {
message.anyInsert(transaction: t)
}
}
}
}
}
// MARK: Disappearing Messages
private static func createDisappearingMessagesWhichFailedToStartInThread(_ thread: TSThread) {
guard let aci = thread.recipientAddressesWithSneakyTransaction.first?.aci else {
owsFailDebug("No recipient")
return
}
let now = Date.ows_millisecondTimestamp()
let messageBody = "Should disappear 60s after \(now)"
let incomingMessageBuilder = TSIncomingMessageBuilder.incomingMessageBuilder(thread: thread, messageBody: messageBody)
incomingMessageBuilder.authorAci = AciObjC(aci)
incomingMessageBuilder.expiresInSeconds = 60
let message = incomingMessageBuilder.build()
// private setter to avoid starting expire machinery.
message.wasRead = true
databaseStorage.write { transaction in
message.anyInsert(transaction: transaction)
}
}
// MARK: Groups
private static func sendMessages(_ count: UInt, toAllMembersOfGroup groupThread: TSGroupThread) {
for address in groupThread.groupModel.groupMembers {
let contactThread = TSContactThread.getOrCreateThread(contactAddress: address)
let sendMessagesAction = sendTextMessagesActionInThread(contactThread)
sendMessagesAction.prepareAndPerformNTimes(count)
}
}
private static func createNewGroups(count: UInt, recipientAddress: SignalServiceAddress) {
guard count > 0 else { return }
let completion: (TSGroupThread) -> Void = { groupThread in
databaseStorage.write { transaction in
ThreadUtil.enqueueMessage(
body: MessageBody(text: "\(count)", ranges: .empty),
thread: groupThread,
transaction: transaction
)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
createNewGroups(count: count - 1, recipientAddress: recipientAddress)
}
}
}
Task {
let groupName = randomShortText()
await createRandomGroupWithName(groupName, member: recipientAddress, completion: completion)
}
}
private static func createRandomGroupWithName(
_ groupName: String,
member: SignalServiceAddress,
completion: @escaping (TSGroupThread) -> Void
) async {
let members = [ member, DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction!.aciAddress ]
do {
let groupThread = try await GroupManager.localCreateNewGroup(
members: members,
disappearingMessageToken: .disabledToken,
shouldSendMessage: true
)
completion(groupThread)
} catch {
owsFailDebug("Error: \(error)")
}
}
// MARK: International
private static func testLinkificationInThread(_ thread: TSThread) {
let strings = [
"google.com",
"foo.google.com",
"https://foo.google.com",
"https://foo.google.com/some/path.html",
"http://кц.com",
"кц.com",
"http://asĸ.com",
"кц.рф",
"кц.рф/some/path",
"https://кц.рф/some/path",
"http://foo.кц.рф"
]
databaseStorage.write { transaction in
for string in strings {
// DO NOT log these strings with the debugger attached.
// OWSLogInfo(@"%@", string);
createFakeIncomingMessage(
thread: thread,
messageBody: string,
fakeAssetLoader: nil,
transaction: transaction
)
let member = SignalServiceAddress(Aci(fromUUID: UUID()))
Task {
await createRandomGroupWithName(string, member: member, completion: { _ in })
}
}
}
}
private static func testIndicScriptsInThread(_ thread: TSThread) {
let strings = [
"\u{0C1C}\u{0C4D}\u{0C1E}\u{200C}\u{0C3E}",
"\u{09B8}\u{09CD}\u{09B0}\u{200C}\u{09C1}",
"non-crashing string"
]
databaseStorage.write { transaction in
for string in strings {
// DO NOT log these strings with the debugger attached.
// OWSLogInfo(@"%@", string);
createFakeIncomingMessage(
thread: thread,
messageBody: string,
fakeAssetLoader: nil,
transaction: transaction
)
let member = SignalServiceAddress(Aci(fromUUID: UUID()))
Task {
await createRandomGroupWithName(string, member: member, completion: { _ in })
}
}
}
}
private static func testZalgoTextInThread(_ thread: TSThread) {
let strings = [
"Ṱ̴̤̺̣͚͚̭̰̤̮̑̓̀͂͘͡h̵̢̤͔̼̗̦̖̬͌̀͒̀͘i̴̮̤͎͎̝̖̻͓̅̆͆̓̎͘͡ͅŝ̡̡̳͔̓͗̾̀̇͒͘͢͢͡͡ ỉ̛̲̩̫̝͉̀̒͐͋̾͘͢͡͞s̶̨̫̞̜̹͛́̇͑̅̒̊̈ s̵͍̲̗̠̗͈̦̬̉̿͂̏̐͆̾͐͊̾ǫ̶͍̼̝̉͊̉͢͜͞͝ͅͅṁ̵̡̨̬̤̝͔̣̄̍̋͊̿̄͋̈ͅe̪̪̻̱͖͚͈̲̍̃͘͠͝ z̷̢̢̛̩̦̱̺̼͑́̉̾ą͕͎̠̮̹̱̓̔̓̈̈́̅̐͢l̵̨͚̜͉̟̜͉͎̃͆͆͒͑̍̈̚͜͞ğ͔̖̫̞͎͍̒̂́̒̿̽̆͟o̶̢̬͚̘̤̪͇̻̒̋̇̊̏͢͡͡͠ͅ t̡̛̥̦̪̮̅̓̑̈́̉̓̽͛͢͡ȩ̡̩͓͈̩͎͗̔͑̌̓͊͆͝x̫̦͓̤͓̘̝̪͊̆͌͊̽̃̏͒͘͘͢ẗ̶̢̨̛̰̯͕͔́̐͗͌͟͠.̷̩̼̼̩̞̘̪́͗̅͊̎̾̅̏̀̕͟ͅ",
"This is some normal text"
]
databaseStorage.write { transaction in
for string in strings {
Logger.info("sending zalgo")
createFakeIncomingMessage(
thread: thread,
messageBody: string,
fakeAssetLoader: nil,
transaction: transaction
)
let member = SignalServiceAddress(Aci(fromUUID: UUID()))
Task {
await createRandomGroupWithName(string, member: member, completion: { _ in })
}
}
}
}
private static func testDirectionalFilenamesInThread(_ thread: TSThread) {
var filenames = [
"a_test\u{202D}abc.exe",
"b_test\u{202E}abc.exe",
"c_testabc.exe"
]
var sendUnsafeFile: (() -> Void)?
sendUnsafeFile = {
guard let filename = filenames.popLast() else { return }
let utiType = kUTTypeData as String
let dataLength: UInt32 = 32
guard let dataSource = DataSourceValue.dataSource(with: createRandomDataOfSize(dataLength), utiType: utiType) else { return }
dataSource.sourceFilename = filename
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: utiType)
guard attachment.hasError else {
Logger.error("attachment[\(String(describing: attachment.sourceFilename))]: \(String(describing: attachment.errorName))")
return
}
sendAttachment(attachment, thread: thread, messageText: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
sendUnsafeFile?()
sendUnsafeFile = nil
}
}
}
// MARK: Random Actions
private static func performRandomActions(_ counter: UInt, inThread thread: TSThread) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
performRandomActionInThread(thread, counter: counter)
if counter > 0 {
performRandomActions(counter - 1, inThread: thread)
}
}
}
private static func performRandomActionInThread(_ thread: TSThread, counter: UInt) {
let actions: [(SDSAnyWriteTransaction) -> Void] = [ { transaction in
// injectIncomingMessageInThread doesn't take a transaction.
DispatchQueue.main.async {
injectIncomingMessageInThread(thread, counter: counter)
}
}, { _ in
// sendTextMessageInThread doesn't take a transaction.
DispatchQueue.main.async {
sendTextMessageInThread(thread, counter: counter)
}
}, { transaction in
let messageCount = UInt.random(in: 1...4)
createFakeMessages(messageCount, batchOffset: 0, inThread: thread, messageContentType: .normal, transaction: transaction)
}, { transaction in
let messageCount = UInt.random(in: 1...4)
deleteRandomMessages(messageCount, inThread: thread, transaction: transaction)
}, { transaction in
let messageCount = UInt.random(in: 1...4)
insertAndDeleteNewOutgoingMessages(messageCount, inThread: thread, transaction: transaction)
}, { transaction in
let messageCount = UInt.random(in: 1...4)
resurrectNewOutgoingMessages1(messageCount, inThread: thread, transaction: transaction)
}, { transaction in
let messageCount = UInt.random(in: 1...4)
resurrectNewOutgoingMessages2(messageCount, inThread: thread, transaction: transaction)
}
]
databaseStorage.write { transaction in
for _ in 1...Int.random(in: 1...4) {
if let action = actions.randomElement() {
action(transaction)
}
}
}
}
// MARK: Utility
@discardableResult
private static func createFakeOutgoingMessage(
thread: TSThread,
messageBody messageBodyParam: String?,
fakeAssetLoader fakeAssetLoaderParam: DebugUIMessagesAssetLoader? = nil,
messageState: TSOutgoingMessageState,
isDelivered: Bool = false,
isRead: Bool = false,
quotedMessageBuilder: OwnedAttachmentBuilder<TSQuotedMessage>? = nil,
contactShare: OWSContact? = nil,
linkPreview: OWSLinkPreview? = nil,
messageSticker: MessageSticker? = nil,
transaction: SDSAnyWriteTransaction
) -> TSOutgoingMessage {
// Seamlessly convert oversize text messages to oversize text attachments.
let messageBody: String?
let fakeAssetLoader: DebugUIMessagesAssetLoader?
if let messageBodyParam, messageBodyParam.lengthOfBytes(using: .utf8) >= kOversizeTextMessageSizeThreshold {
owsAssertDebug(fakeAssetLoaderParam == nil)
messageBody = nil
fakeAssetLoader = DebugUIMessagesAssetLoader.oversizeTextInstance(text: messageBodyParam)
} else {
messageBody = messageBodyParam
fakeAssetLoader = fakeAssetLoaderParam
}
owsAssertDebug(!messageBody.isEmptyOrNil || fakeAssetLoader != nil || contactShare != nil)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: messageBody)
messageBuilder.isVoiceMessage = false
messageBuilder.quotedMessage = quotedMessageBuilder?.info
messageBuilder.contactShare = contactShare
messageBuilder.linkPreview = linkPreview
messageBuilder.messageSticker = messageSticker
let message = messageBuilder.build(transaction: transaction)
message.anyInsert(transaction: transaction)
message.update(withFakeMessageState: messageState, transaction: transaction)
try? quotedMessageBuilder?.finalize(
owner: .quotedReplyAttachment(messageRowId: message.sqliteRowId!),
tx: transaction.asV2Write
)
let attachment: TSAttachment?
if let fakeAssetLoader {
attachment = createFakeAttachment(
fakeAssetLoader: fakeAssetLoader,
isAttachmentDownloaded: true,
albumMessageId: message.uniqueId,
transaction: transaction
)
owsAssertDebug(attachment != nil)
} else {
attachment = nil
}
if let attachment {
updateAttachment(attachment, albumMessage: message, transaction: transaction)
}
if isDelivered {
if let address = thread.recipientAddresses(with: transaction).last {
owsAssertDebug(address.isValid)
message.update(
withDeliveredRecipient: address,
deviceId: 0,
deliveryTimestamp: Date.ows_millisecondTimestamp(),
context: PassthroughDeliveryReceiptContext(),
tx: transaction
)
}
}
if isRead {
if let address = thread.recipientAddresses(with: transaction).last {
owsAssertDebug(address.isValid)
message.update(
withReadRecipient: address,
deviceId: 0,
readTimestamp: Date.ows_millisecondTimestamp(),
tx: transaction
)
}
}
return message
}
private static func createFakeAttachment(
fakeAssetLoader: DebugUIMessagesAssetLoader,
isAttachmentDownloaded: Bool,
albumMessageId: String,
transaction: SDSAnyWriteTransaction
) -> TSAttachment? {
owsAssertDebug(!fakeAssetLoader.filePath.isEmptyOrNil)
if isAttachmentDownloaded {
let dataSource: DataSource
do {
dataSource = try DataSourcePath.dataSource(withFilePath: fakeAssetLoader.filePath!, shouldDeleteOnDeallocation: false)
} catch {
owsFailDebug("Failed to create dataSource: \(error)")
return nil
}
guard let filename = dataSource.sourceFilename else {
owsFailDebug("Empty filename: \(dataSource)")
return nil
}
// To support "fake missing" attachments, we sometimes lie about the length of the data.
let nominalDataLength: UInt32 = UInt32(max(1, dataSource.dataLength))
let attachmentStream = TSAttachmentStream(
contentType: fakeAssetLoader.mimeType,
byteCount: nominalDataLength,
sourceFilename: filename,
caption: nil,
attachmentType: .default,
albumMessageId: albumMessageId
)
do {
try attachmentStream.write(dataSource.data)
attachmentStream.anyInsert(transaction: transaction)
} catch {
owsFailDebug("Failed to write data: \(error)")
return nil
}
return attachmentStream
} else {
let filesize: UInt32 = 64
let attachmentPointer = TSAttachmentPointer(
serverId: 237391539706350548,
cdnKey: "",
cdnNumber: 0,
key: createRandomDataOfSize(filesize),
digest: nil,
byteCount: filesize,
contentType: fakeAssetLoader.mimeType,
sourceFilename: fakeAssetLoader.filename,
caption: nil,
albumMessageId: albumMessageId,
attachmentType: .default,
mediaSize: .zero,
blurHash: nil,
uploadTimestamp: 0,
videoDuration: nil
)
attachmentPointer.setAttachmentPointerStateDebug(.failed)
attachmentPointer.anyInsert(transaction: transaction)
return attachmentPointer
}
}
private static func updateAttachment(
_ attachment: TSAttachment,
albumMessage: TSMessage,
transaction: SDSAnyWriteTransaction
) {
// This doesn't work anymore. deal with it.
}
private static func actionLabelForHasCaption(
_ hasCaption: Bool,
outgoingMessageState: TSOutgoingMessageState,
isDelivered: Bool = false,
isRead: Bool = false
) -> String {
var label = ""
if hasCaption {
label += " 🔤"
}
switch outgoingMessageState {
case .failed:
label += " (Unsent)"
case .sending:
label += " (Sending)"
case .sent:
if isRead {
label += " (Read)"
} else if isDelivered {
label += " (Delivered)"
} else {
label += " (Sent)"
}
default:
owsFailDebug("unknown message state.")
}
return label
}
@discardableResult
private static func createFakeIncomingMessage(
thread: TSThread,
messageBody: String?,
attachment: TSAttachment?,
filename: String? = nil,
isAttachmentDownloaded: Bool = false,
quotedMessage: TSQuotedMessage? = nil,
transaction: SDSAnyWriteTransaction
) -> TSIncomingMessage {
owsAssertDebug(!messageBody.isEmptyOrNil || attachment != nil)
let attachmentIds: [String]
if let attachmentId = attachment?.uniqueId {
attachmentIds = [attachmentId]
} else {
attachmentIds = []
}
let authorAci = DebugUIMessages.anyIncomingSenderAddress(forThread: thread)!.aci!
let incomingMessageBuilder = TSIncomingMessageBuilder(thread: thread, messageBody: messageBody)
incomingMessageBuilder.authorAci = AciObjC(authorAci)
incomingMessageBuilder.attachmentIds = attachmentIds
incomingMessageBuilder.quotedMessage = quotedMessage
let message = incomingMessageBuilder.build()
message.anyInsert(transaction: transaction)
message.debugonly_markAsReadNow(transaction: transaction)
if let attachment {
updateAttachment(attachment, albumMessage: message, transaction: transaction)
}
return message
}
@discardableResult
private static func createFakeIncomingMessage(
thread: TSThread,
messageBody messageBodyParam: String?,
fakeAssetLoader fakeAssetLoaderParam: DebugUIMessagesAssetLoader?,
isAttachmentDownloaded: Bool = false,
quotedMessageBuilder: OwnedAttachmentBuilder<TSQuotedMessage>? = nil,
transaction: SDSAnyWriteTransaction
) -> TSIncomingMessage {
owsAssertDebug(!messageBodyParam.isEmptyOrNil || fakeAssetLoaderParam != nil)
let authorAci = DebugUIMessages.anyIncomingSenderAddress(forThread: thread)!.aci!
// Seamlessly convert oversize text messages to oversize text attachments.
let messageBody: String?
let fakeAssetLoader: DebugUIMessagesAssetLoader?
if let messageBodyParam, messageBodyParam.lengthOfBytes(using: .utf8) >= kOversizeTextMessageSizeThreshold {
owsAssertDebug(fakeAssetLoaderParam == nil)
messageBody = nil
fakeAssetLoader = DebugUIMessagesAssetLoader.oversizeTextInstance(text: messageBodyParam)
} else {
messageBody = messageBodyParam
fakeAssetLoader = fakeAssetLoaderParam
}
let incomingMessageBuilder = TSIncomingMessageBuilder(thread: thread, messageBody: messageBody)
incomingMessageBuilder.authorAci = AciObjC(authorAci)
incomingMessageBuilder.quotedMessage = quotedMessageBuilder?.info
let message = incomingMessageBuilder.build()
message.anyInsert(transaction: transaction)
message.debugonly_markAsReadNow(transaction: transaction)
try? quotedMessageBuilder?.finalize(
owner: .quotedReplyAttachment(messageRowId: message.sqliteRowId!),
tx: transaction.asV2Write
)
let attachment: TSAttachment?
if let fakeAssetLoader {
attachment = createFakeAttachment(
fakeAssetLoader: fakeAssetLoader,
isAttachmentDownloaded: isAttachmentDownloaded,
albumMessageId: message.uniqueId,
transaction: transaction
)
} else {
attachment = nil
}
if let attachment {
updateAttachment(attachment, albumMessage: message, transaction: transaction)
}
return message
}
private static func createFakeThreadAssociatedData(thread: TSThread) -> ThreadAssociatedData {
return ThreadAssociatedData(
threadUniqueId: thread.uniqueId,
isArchived: false,
isMarkedUnread: false,
mutedUntilTimestamp: 0,
audioPlaybackRate: 1
)
}
private static func anyIncomingSenderAddress(forThread thread: TSThread) -> SignalServiceAddress? {
if let contactThread = thread as? TSContactThread {
return contactThread.contactAddress
} else if let groupThread = thread as? TSGroupThread {
guard let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction?.aciAddress else {
owsFailDebug("Missing localAddress.")
return nil
}
let members = groupThread.groupMembership.fullMembers
let otherMembers = members.filter { $0 != localAddress }.shuffled()
guard let anyOtherMember = otherMembers.first else {
owsFailDebug("No other members.")
return nil
}
return anyOtherMember
} else {
owsFailDebug("Invalid thread.")
return nil
}
}
private static func processDecryptedEnvelope(_ envelope: SSKProtoEnvelope, plaintextData: Data) {
databaseStorage.write { tx in
messageReceiver.processEnvelope(
envelope,
plaintextData: plaintextData,
wasReceivedByUD: false,
serverDeliveryTimestamp: 0,
shouldDiscardVisibleMessages: false,
localIdentifiers: DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx.asV2Read)!,
tx: tx
)
}
}
}
// MARK: - Random
extension DebugUIMessages {
private static func randomText() -> String {
let randomTexts = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Suspendisse rutrum, nulla vitae pretium hendrerit, tellus " +
"turpis pharetra libero, vitae sodales tortor ante vel sem.",
"In a time of universal deceit - telling the truth is a revolutionary act.",
"If you want a vision of the future, imagine a boot stamping on a human face - forever.",
"Who controls the past controls the future. Who controls the present controls the past.",
"All animals are equal, but some animals are more equal than others.",
"War is peace. Freedom is slavery. Ignorance is strength.",
"All the war-propaganda, all the screaming and lies and hatred, comes invariably from people who are not fighting.",
"Political language. . . is designed to make lies sound truthful and murder respectable, and to give an " +
"appearance of solidity to pure wind.",
"The nationalist not only does not disapprove of atrocities committed by his own side, but he has a " +
"remarkable capacity for not even hearing about them.",
"Every generation imagines itself to be more intelligent than the one that went before it, and wiser than the " +
"one that comes after it.",
"War against a foreign country only happens when the moneyed classes think they are going to profit from it.",
"People have only as much liberty as they have the intelligence to want and the courage to take.",
"You cannot buy the revolution. You cannot make the revolution. You can only be the revolution. It is in " +
"your spirit, or it is nowhere.",
"That is what I have always understood to be the essence of anarchism: the conviction that the burden of " +
"proof has to be placed on authority, and that it should be dismantled if that burden cannot be met.",
"Ask for work. If they don't give you work, ask for bread. If they do not give you work or bread, then take bread.",
"Every society has the criminals it deserves.",
"Anarchism is founded on the observation that since few men are wise enough to rule themselves, even fewer " +
"are wise enough to rule others.",
"If you would know who controls you see who you may not criticise.",
"At one time in the world there were woods that no one owned."
]
return randomTexts.randomElement()!
}
private static func randomOversizeText() -> String {
var message = String()
while message.lengthOfBytes(using: .utf8) < kOversizeTextMessageSizeThreshold {
message += """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla
vitae pretium hendrerit, tellus turpis pharetra libero, vitae sodales tortor ante vel
sem. Fusce sed nisl a lorem gravida tincidunt. Suspendisse efficitur non quam ac
sodales. Aenean ut velit maximus, posuere sem a, accumsan nunc. Donec ullamcorper
turpis lorem. Quisque dignissim purus eu placerat ultricies. Proin at urna eget mi
semper congue. Aenean non elementum ex. Praesent pharetra quam at sem vestibulum,
vestibulum ornare dolor elementum. Vestibulum massa tortor, scelerisque sit amet
pulvinar a, rhoncus vitae nisl. Sed mi nunc, tempus at varius in, malesuada vitae
dui. Vivamus efficitur pulvinar erat vitae congue. Proin vehicula turpis non felis
congue facilisis. Nullam aliquet dapibus ligula ac mollis. Etiam sit amet posuere
lorem, in rhoncus nisi.
"""
}
return message
}
private static func randomShortText() -> String {
let alphabet: [Character] = (97...122).map { (ascii: Int) in
Character(Unicode.Scalar(ascii)!)
}
let chars: [Character] = (0..<4).map { _ in
let index = UInt.random(in: 0..<UInt(alphabet.count))
return alphabet[Int(index)]
}
return String(chars)
}
private static func randomCaptionText() -> String {
return randomText() + " (caption)"
}
private static func createRandomDataOfSize(_ size: UInt32) -> Data {
owsAssertDebug(size % 4 == 0)
owsAssertDebug(size < Int.max)
return Randomness.generateRandomBytes(Int32(size))
}
}
#endif