226 lines
7.8 KiB
Swift
226 lines
7.8 KiB
Swift
//
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Contacts
|
|
import SignalServiceKit
|
|
import SignalUI
|
|
|
|
#if USE_DEBUG_UI
|
|
|
|
class DebugContactsUtils: Dependencies {
|
|
|
|
// MARK: Random Contacts
|
|
|
|
static func randomPhoneNumber() -> String {
|
|
var result: String
|
|
var numberOfDigits: UInt
|
|
if Bool.random() {
|
|
// Generate a US phone number.
|
|
result = "+1"
|
|
numberOfDigits = 10
|
|
} else {
|
|
// Generate a UK phone number.
|
|
result = "+441"
|
|
numberOfDigits = 9
|
|
}
|
|
for _ in 0..<numberOfDigits {
|
|
result += String(Int.random(in: 0...9))
|
|
}
|
|
return result
|
|
}
|
|
|
|
static func createRandomContacts(
|
|
_ count: UInt,
|
|
contactHandler: ((CNContact, Int, inout Bool) async -> Void)? = nil
|
|
) async {
|
|
guard count > 0 else { return }
|
|
|
|
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
|
|
guard authorizationStatus != .denied && authorizationStatus != .restricted else {
|
|
OWSActionSheets.showErrorAlert(message: "No Contacts access.")
|
|
return
|
|
}
|
|
|
|
// Request access
|
|
guard authorizationStatus == .authorized else {
|
|
let store = CNContactStore()
|
|
let granted: Bool
|
|
do {
|
|
granted = try await store.requestAccess(for: .contacts)
|
|
} catch {
|
|
granted = false
|
|
}
|
|
guard granted else {
|
|
DispatchQueue.main.async {
|
|
OWSActionSheets.showErrorAlert(message: "No Contacts access.")
|
|
}
|
|
return
|
|
}
|
|
await createRandomContacts(count, contactHandler: contactHandler)
|
|
return
|
|
}
|
|
|
|
let maxBatchSize: UInt = 10
|
|
let batchSize = min(maxBatchSize, count)
|
|
let remainder = count - batchSize
|
|
await createRandomContactsBatch(batchSize, contactHandler: contactHandler)
|
|
guard remainder > 0 else { return }
|
|
Task { @MainActor in
|
|
await createRandomContacts(remainder, contactHandler: contactHandler)
|
|
}
|
|
}
|
|
|
|
private static func createRandomContactsBatch(
|
|
_ count: UInt,
|
|
contactHandler: ((CNContact, Int, inout Bool) async -> Void)?
|
|
) async {
|
|
Logger.debug("createRandomContactsBatch: \(count)")
|
|
|
|
var contacts = [CNContact]()
|
|
|
|
// 50% chance of fake contact having an avatar
|
|
let percentWithAvatar = 50
|
|
let percentWithLargeAvatar = 25
|
|
let minimumAvatarDiameter: UInt = 200
|
|
let maximimAvatarDiameter: UInt = 800
|
|
|
|
let saveRequest = CNSaveRequest()
|
|
for _ in 0..<count { autoreleasepool {
|
|
let contact = CNMutableContact()
|
|
contact.familyName = "Rando-" + CommonGenerator.lastName()
|
|
contact.givenName = CommonGenerator.firstName()
|
|
|
|
let homePhone = CNLabeledValue(label: CNLabelHome, value: CNPhoneNumber(stringValue: randomPhoneNumber()))
|
|
contact.phoneNumbers = [ homePhone ]
|
|
|
|
let avatarSeed = UInt.random(in: 0...99)
|
|
if avatarSeed < percentWithAvatar {
|
|
let shouldUseLargeAvatar = avatarSeed < percentWithLargeAvatar
|
|
let avatarDiameter: UInt = {
|
|
if shouldUseLargeAvatar {
|
|
return maximimAvatarDiameter
|
|
}
|
|
return UInt.random(in: minimumAvatarDiameter...maximimAvatarDiameter)
|
|
}()
|
|
let avatarImage: UIImage?
|
|
if shouldUseLargeAvatar {
|
|
avatarImage = AvatarBuilder.buildNoiseAvatar(diameterPoints: avatarDiameter)
|
|
} else {
|
|
avatarImage = AvatarBuilder.buildRandomAvatar(diameterPoints: avatarDiameter)
|
|
}
|
|
if let avatarImageData = avatarImage?.jpegData(compressionQuality: 0.9) {
|
|
contact.imageData = avatarImageData
|
|
Logger.debug("avatar size: \(avatarImageData.count) bytes")
|
|
}
|
|
}
|
|
|
|
contacts.append(contact)
|
|
saveRequest.add(contact, toContainerWithIdentifier: nil)
|
|
}}
|
|
|
|
Logger.info("Saving fake contacts: \(contacts.count)")
|
|
do {
|
|
try CNContactStore().execute(saveRequest)
|
|
if let contactHandler {
|
|
for (index, contact) in contacts.enumerated() {
|
|
var stop = false
|
|
await contactHandler(contact, index, &stop)
|
|
if stop {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
owsFailDebug("Error saving fake contacts: \(error)")
|
|
DispatchQueue.main.async {
|
|
OWSActionSheets.showErrorAlert(message: error.userErrorDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Contact Deletion
|
|
|
|
static func deleteAllContacts() {
|
|
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
|
|
guard authorizationStatus != .denied && authorizationStatus != .restricted else {
|
|
OWSActionSheets.showErrorAlert(message: "No Contacts access.")
|
|
return
|
|
}
|
|
|
|
// Request access
|
|
guard authorizationStatus == .authorized else {
|
|
let store = CNContactStore()
|
|
store.requestAccess(for: .contacts) { granted, error in
|
|
guard granted && error == nil else {
|
|
DispatchQueue.main.async {
|
|
OWSActionSheets.showErrorAlert(message: "No Contacts access.")
|
|
}
|
|
return
|
|
}
|
|
DispatchQueue.main.async {
|
|
deleteAllContacts()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
deleteContactsWithFilter { _ in return true }
|
|
}
|
|
|
|
static func deleteRandomContacts() {
|
|
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
|
|
guard authorizationStatus != .denied && authorizationStatus != .restricted else {
|
|
OWSActionSheets.showErrorAlert(message: "No Contacts access.")
|
|
return
|
|
}
|
|
|
|
// Request access
|
|
guard authorizationStatus == .authorized else {
|
|
let store = CNContactStore()
|
|
store.requestAccess(for: .contacts) { granted, error in
|
|
guard granted && error == nil else {
|
|
DispatchQueue.main.async {
|
|
OWSActionSheets.showErrorAlert(message: "No Contacts access.")
|
|
}
|
|
return
|
|
}
|
|
DispatchQueue.main.async {
|
|
deleteRandomContacts()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
deleteContactsWithFilter { contact in
|
|
return contact.familyName.hasPrefix("Rando-")
|
|
}
|
|
}
|
|
|
|
private static func deleteContactsWithFilter(_ filter: (CNContact) -> Bool) {
|
|
let keysToFetch: [CNKeyDescriptor] = [
|
|
CNContactIdentifierKey as CNKeyDescriptor,
|
|
CNContactGivenNameKey as CNKeyDescriptor,
|
|
CNContactFamilyNameKey as CNKeyDescriptor,
|
|
CNContactFormatter.descriptorForRequiredKeys(for: .fullName) as CNKeyDescriptor
|
|
]
|
|
|
|
let contactStore = CNContactStore()
|
|
let saveRequest = CNSaveRequest()
|
|
do {
|
|
try contactStore.enumerateContacts(with: CNContactFetchRequest(keysToFetch: keysToFetch)) { contact, _ in
|
|
if filter(contact) {
|
|
saveRequest.delete(contact.mutableCopy() as! CNMutableContact)
|
|
}
|
|
}
|
|
try contactStore.execute(saveRequest)
|
|
} catch {
|
|
Logger.error("\(error)")
|
|
OWSActionSheets.showErrorAlert(message: error.userErrorDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|