From 77fc5571fb2cc94f6f630ac8d46463d75ee0f687 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 4 May 2018 22:32:29 -0400 Subject: [PATCH] Implement avatar sending // FREEBIE --- Signal.xcodeproj/project.pbxproj | 16 ++- .../ContactViewController.swift | 41 +++--- .../ConversationViewController.m | 45 +++++-- .../ViewControllers/DebugUI/DebugUIMessages.m | 28 +---- Signal/src/ViewModels/ThreadViewModel.swift | 40 ++++++ .../ViewModels/ContactShareViewModel.swift | 119 ++++++++++++++++++ .../OWSQuotedReplyModel.h | 0 .../OWSQuotedReplyModel.m | 0 .../ApproveContactShareViewController.swift | 24 ++-- .../EditContactShareNameViewController.swift | 6 +- SignalMessaging/contacts/OWSContactsManager.h | 5 +- SignalMessaging/contacts/OWSContactsManager.m | 19 ++- SignalServiceKit/src/Contacts/TSThread.h | 10 -- .../src/Contacts/Threads/TSContactThread.m | 9 -- .../src/Messages/Interactions/OWSContact.h | 3 +- .../src/Messages/Interactions/OWSContact.m | 23 ++-- .../src/Messages/Interactions/TSMessage.h | 1 - .../src/Messages/Interactions/TSMessage.m | 7 -- .../src/Messages/OWSMessageSender.m | 4 - .../src/Protocols/ContactsManagerProtocol.h | 6 +- 20 files changed, 275 insertions(+), 131 deletions(-) create mode 100644 Signal/src/ViewModels/ThreadViewModel.swift create mode 100644 SignalMessaging/ViewModels/ContactShareViewModel.swift rename SignalMessaging/{Models => ViewModels}/OWSQuotedReplyModel.h (100%) rename SignalMessaging/{Models => ViewModels}/OWSQuotedReplyModel.m (100%) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index c216af538..6941e6a98 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -310,7 +310,7 @@ 45360B901F9527DA00FA666C /* SearcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8F1F9527DA00FA666C /* SearcherTest.swift */; }; 45360B911F952AA900FA666C /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; }; - 4541B71B209D2DAE0008608F /* ContactShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */; }; + 4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */; }; 4542DF52208B82E9007B4E76 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; }; 4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */; }; 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; }; @@ -1830,8 +1830,6 @@ 45194F911FD7214600333B2C /* Models */ = { isa = PBXGroup; children = ( - 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */, - 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */, 34C42D621F4734ED0072EC04 /* OWSContactOffersInteraction.h */, 34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */, 34C42D641F4734ED0072EC04 /* TSUnreadIndicatorInteraction.h */, @@ -1872,6 +1870,7 @@ 453518931FC63DBF00210559 /* SignalMessaging */ = { isa = PBXGroup; children = ( + 4541B71C209D3B4F0008608F /* ViewModels */, 45194F911FD7214600333B2C /* Models */, 451F8A361FD7115D005CB9DA /* ViewControllers */, 454A96571FD600B4008D2A0E /* attachments */, @@ -1893,7 +1892,16 @@ isa = PBXGroup; children = ( 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 4541B71C209D3B4F0008608F /* ViewModels */ = { + isa = PBXGroup; + children = ( 4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */, + 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */, + 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */, ); path = ViewModels; sourceTree = ""; @@ -3118,6 +3126,7 @@ 4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */, 3438226A209B63500094FEB7 /* EditContactShareNameViewController.swift in Sources */, 346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */, + 4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */, 4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */, 346129D21FD2085A00532771 /* CommonStrings.swift in Sources */, 45F59A082028E4FB00E8D2B0 /* OWSAudioSession.swift in Sources */, @@ -3192,7 +3201,6 @@ 34A55F3720485465002CC6DE /* OWS2FARegistrationViewController.m in Sources */, 340FC8AD204DAC8D007AEB0F /* OWSLinkedDevicesTableViewController.m in Sources */, 340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */, - 4541B71B209D2DAE0008608F /* ContactShareViewModel.swift in Sources */, 3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */, 34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */, 34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */, diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index cf429db84..4c8765f0e 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -65,8 +65,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate var reachability: Reachability? - // TODO rename this property to contactShare - private let contact: ContactShareViewModel + private let contactShare: ContactShareViewModel // MARK: - Initializers @@ -77,7 +76,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate required init(contactShare: ContactShareViewModel) { contactsManager = Environment.current().contactsManager - self.contact = contactShare + self.contactShare = contactShare super.init(nibName: nil, bundle: nil) @@ -141,7 +140,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func updateMode() { SwiftAssertIsOnMainThread(#function) - guard phoneNumbersForContact().count > 0 else { + guard contactShare.phoneNumberStrings.count > 0 else { viewMode = .noPhoneNumber return } @@ -160,7 +159,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func systemContactsWithSignalAccountsForContact() -> [String] { SwiftAssertIsOnMainThread(#function) - return phoneNumbersForContact().filter({ (phoneNumber) -> Bool in + return contactShare.phoneNumberStrings.filter({ (phoneNumber) -> Bool in return contactsManager.hasSignalAccount(forRecipientId: phoneNumber) }) } @@ -168,21 +167,11 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func systemContactsForContact() -> [String] { SwiftAssertIsOnMainThread(#function) - return phoneNumbersForContact().filter({ (phoneNumber) -> Bool in + return contactShare.phoneNumberStrings.filter({ (phoneNumber) -> Bool in return contactsManager.allContactsMap[phoneNumber] != nil }) } - private func phoneNumbersForContact() -> [String] { - SwiftAssertIsOnMainThread(#function) - - var result = [String]() - for phoneNumber in contact.phoneNumbers { - result.append(phoneNumber.phoneNumber) - } - return result - } - private func updateContent() { SwiftAssertIsOnMainThread(#function) @@ -265,8 +254,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate let avatarView = AvatarImageView() // TODO: What's the best colorSeed value to use? - let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contact.displayName, - colorSeed: contact.displayName, + let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contactShare.displayName, + colorSeed: contactShare.displayName, diameter: UInt(avatarSize), contactsManager: contactsManager) avatarView.image = avatarBuilder.build() @@ -277,7 +266,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate avatarView.autoSetDimension(.height, toSize: avatarSize) let nameLabel = UILabel() - nameLabel.text = contact.displayName + nameLabel.text = contactShare.displayName nameLabel.font = UIFont.ows_dynamicTypeTitle2.ows_bold() nameLabel.textColor = UIColor.black nameLabel.lineBreakMode = .byTruncatingTail @@ -289,7 +278,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate var lastView: UIView = nameLabel - if let firstPhoneNumber = contact.phoneNumbers.first { + if let firstPhoneNumber = contactShare.phoneNumbers.first { let phoneNumberLabel = UILabel() phoneNumberLabel.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: firstPhoneNumber.phoneNumber) phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption2 @@ -388,7 +377,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // action:#selector(didPressShareContact))) // } - for phoneNumber in contact.phoneNumbers { + for phoneNumber in contactShare.phoneNumbers { let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber) rows.append(createNameValueRow(name: phoneNumber.localizedLabel(), @@ -402,7 +391,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate })) } - for email in contact.emails { + for email in contactShare.emails { rows.append(createNameValueRow(name: email.localizedLabel(), value: email.email, actionBlock: { @@ -600,7 +589,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: "")) return } - let phoneNumbers = phoneNumbersForContact() + let phoneNumbers = contactShare.phoneNumberStrings guard phoneNumbers.count > 0 else { owsFail("\(logTag) no phone numbers.") return @@ -640,8 +629,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return } - guard let systemContact = OWSContacts.systemContact(for: contact.dbRecord) else { - owsFail("\(logTag) Could not derive system contact.") + guard let systemContact = OWSContacts.systemContact(for: contactShare.dbRecord) else { + owsFail("\(logTag) Could not derive system contactShare.") return } @@ -679,7 +668,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return } - guard let firstPhoneNumber = contact.phoneNumbers.first else { + guard let firstPhoneNumber = contactShare.phoneNumbers.first else { owsFail("\(logTag) Missing phone number.") return } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 0d252c815..ce3fe62ca 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -65,6 +65,7 @@ #import #import #import +#import #import #import #import @@ -2948,7 +2949,7 @@ typedef enum : NSUInteger { } } -- (void)sendContactShare:(OWSContact *)contactShare +- (void)sendContactShare:(ContactShareViewModel *)contactShare { OWSAssertIsOnMainThread(); OWSAssert(contactShare); @@ -2956,12 +2957,20 @@ typedef enum : NSUInteger { DDLogVerbose(@"%@ Sending contact share.", self.logTag); BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; - TSOutgoingMessage *message = [ThreadUtil sendMessageWithContactShare:contactShare - inThread:self.thread - messageSender:self.messageSender - completion:nil]; - [self messageWasSent:message]; + [self.editingDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + if (contactShare.avatarImage) { + [contactShare.dbRecord saveAvatarImage:contactShare.avatarImage transaction:transaction]; + } + } + completionBlock:^{ + TSOutgoingMessage *message = [ThreadUtil sendMessageWithContactShare:contactShare.dbRecord + inThread:self.thread + messageSender:self.messageSender + completion:nil]; + [self messageWasSent:message]; + }]; + if (didAddToProfileWhitelist) { [self ensureDynamicInteractions]; @@ -4942,12 +4951,28 @@ interactionControllerForAnimationController:(id 0 + } +} diff --git a/SignalMessaging/ViewModels/ContactShareViewModel.swift b/SignalMessaging/ViewModels/ContactShareViewModel.swift new file mode 100644 index 000000000..5cd0235d1 --- /dev/null +++ b/SignalMessaging/ViewModels/ContactShareViewModel.swift @@ -0,0 +1,119 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public class ContactShareViewModel: NSObject { + + public let dbRecord: OWSContact + public let avatarImage: UIImage? + + public required init(contactShareRecord: OWSContact, avatarImage: UIImage?) { + self.dbRecord = contactShareRecord + self.avatarImage = avatarImage + } + + public convenience init(contactShareRecord: OWSContact, transaction: YapDatabaseReadTransaction) { + if let avatarAttachment = contactShareRecord.avatarAttachment(with: transaction) as? TSAttachmentStream { + self.init(contactShareRecord: contactShareRecord, avatarImage: avatarAttachment.image()) + } else { + self.init(contactShareRecord: contactShareRecord, avatarImage: nil) + } + } + + // MARK: Delegated -> dbRecord + + public var addresses: [OWSContactAddress] { + get { + return dbRecord.addresses + } + set { + return dbRecord.addresses = newValue + } + } + + public var emails: [OWSContactEmail] { + get { + return dbRecord.emails + } + set { + dbRecord.emails = newValue + } + } + + public var phoneNumbers: [OWSContactPhoneNumber] { + get { + return dbRecord.phoneNumbers + } + set { + dbRecord.phoneNumbers = newValue + } + } + + public var phoneNumberStrings: [String] { + return phoneNumbers.map { $0.phoneNumber } + } + + public var displayName: String { + return dbRecord.displayName + } + + public var ows_isValid: Bool { + return dbRecord.ows_isValid() + } + + public var namePrefix: String? { + return dbRecord.namePrefix + } + + public var givenName: String? { + return dbRecord.givenName + } + + public var middleName: String? { + return dbRecord.middleName + } + + public var familyName: String? { + return dbRecord.familyName + } + + public var nameSuffix: String? { + return dbRecord.nameSuffix + } + + public var isProfileAvatar: Bool { + return dbRecord.isProfileAvatar + } + + public func copy(withNamePrefix namePrefix: String?, + givenName: String?, + middleName: String?, + familyName: String?, + nameSuffix: String?) -> ContactShareViewModel { + + // TODO move the `copy` logic into the view model? + let newDbRecord = dbRecord.copy(withNamePrefix: namePrefix, givenName: givenName, middleName: middleName, familyName: familyName, nameSuffix: nameSuffix) + + return ContactShareViewModel(contactShareRecord: newDbRecord, avatarImage: self.avatarImage) + } + + public func newContact(withNamePrefix namePrefix: String?, + givenName: String?, + middleName: String?, + familyName: String?, + nameSuffix: String?) -> ContactShareViewModel { + + // TODO move the `newContact` logic into the view model? + let newDbRecord = dbRecord.newContact(withNamePrefix: namePrefix, + givenName: givenName, + middleName: middleName, + familyName: familyName, + nameSuffix: nameSuffix) + + return ContactShareViewModel(contactShareRecord: newDbRecord, avatarImage: self.avatarImage) + } + +} diff --git a/SignalMessaging/Models/OWSQuotedReplyModel.h b/SignalMessaging/ViewModels/OWSQuotedReplyModel.h similarity index 100% rename from SignalMessaging/Models/OWSQuotedReplyModel.h rename to SignalMessaging/ViewModels/OWSQuotedReplyModel.h diff --git a/SignalMessaging/Models/OWSQuotedReplyModel.m b/SignalMessaging/ViewModels/OWSQuotedReplyModel.m similarity index 100% rename from SignalMessaging/Models/OWSQuotedReplyModel.m rename to SignalMessaging/ViewModels/OWSQuotedReplyModel.m diff --git a/SignalMessaging/attachments/ApproveContactShareViewController.swift b/SignalMessaging/attachments/ApproveContactShareViewController.swift index 5bddec249..71078ef29 100644 --- a/SignalMessaging/attachments/ApproveContactShareViewController.swift +++ b/SignalMessaging/attachments/ApproveContactShareViewController.swift @@ -7,8 +7,8 @@ import SignalServiceKit @objc public protocol ApproveContactShareViewControllerDelegate: class { - func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didApproveContactShare contactShare: OWSContact) - func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didCancelContactShare contactShare: OWSContact) + func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didApproveContactShare contactShare: ContactShareViewModel) + func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didCancelContactShare contactShare: ContactShareViewModel) } protocol ContactShareField: class { @@ -48,7 +48,7 @@ class ContactShareFieldBase: NSObject, Contac isIncludedFlag = isIncluded } - func applyToContact(contact: OWSContact) { + func applyToContact(contact: ContactShareViewModel) { preconditionFailure("This method must be overridden") } } @@ -57,7 +57,7 @@ class ContactShareFieldBase: NSObject, Contac class ContactSharePhoneNumber: ContactShareFieldBase { - override func applyToContact(contact: OWSContact) { + override func applyToContact(contact: ContactShareViewModel) { assert(isIncluded()) var values = [OWSContactPhoneNumber]() @@ -71,7 +71,7 @@ class ContactSharePhoneNumber: ContactShareFieldBase { class ContactShareEmail: ContactShareFieldBase { - override func applyToContact(contact: OWSContact) { + override func applyToContact(contact: ContactShareViewModel) { assert(isIncluded()) var values = [OWSContactEmail]() @@ -85,7 +85,7 @@ class ContactShareEmail: ContactShareFieldBase { class ContactShareAddress: ContactShareFieldBase { - override func applyToContact(contact: OWSContact) { + override func applyToContact(contact: ContactShareViewModel) { assert(isIncluded()) var values = [OWSContactAddress]() @@ -185,7 +185,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh let contactsManager: OWSContactsManager - var contactShare: OWSContact + var contactShare: ContactShareViewModel var fieldViews = [ContactShareFieldView]() @@ -199,7 +199,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh } @objc - required public init(contactShare: OWSContact, contactsManager: OWSContactsManager, delegate: ApproveContactShareViewControllerDelegate) { + required public init(contactShare: ContactShareViewModel, contactsManager: OWSContactsManager, delegate: ApproveContactShareViewControllerDelegate) { self.contactsManager = contactsManager self.contactShare = contactShare self.delegate = delegate @@ -277,7 +277,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh // TODO: Surface error with resolution to user if not. func canShareContact() -> Bool { - return contactShare.ows_isValid() + return contactShare.ows_isValid } func updateNavigationBar() { @@ -471,7 +471,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh // MARK: - - func filteredContactShare() -> OWSContact { + func filteredContactShare() -> ContactShareViewModel { let result = self.contactShare.newContact(withNamePrefix: self.contactShare.namePrefix, givenName: self.contactShare.givenName, middleName: self.contactShare.middleName, @@ -498,7 +498,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh } let filteredContactShare = self.filteredContactShare() - assert(filteredContactShare.ows_isValid()) + assert(filteredContactShare.ows_isValid) delegate.approveContactShare(self, didApproveContactShare: filteredContactShare) } @@ -523,7 +523,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh // MARK: - EditContactShareNameViewControllerDelegate - public func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: OWSContact) { + public func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: ContactShareViewModel) { self.contactShare = contactShare nameLabel.text = contactShare.displayName diff --git a/SignalMessaging/attachments/EditContactShareNameViewController.swift b/SignalMessaging/attachments/EditContactShareNameViewController.swift index b943b60c9..edf6db9fe 100644 --- a/SignalMessaging/attachments/EditContactShareNameViewController.swift +++ b/SignalMessaging/attachments/EditContactShareNameViewController.swift @@ -109,7 +109,7 @@ class ContactNameFieldView: UIView { @objc public protocol EditContactShareNameViewControllerDelegate: class { - func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: OWSContact) + func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: ContactShareViewModel) } // MARK: - @@ -118,7 +118,7 @@ public protocol EditContactShareNameViewControllerDelegate: class { public class EditContactShareNameViewController: OWSViewController, ContactNameFieldViewDelegate { weak var delegate: EditContactShareNameViewControllerDelegate? - let contactShare: OWSContact + let contactShare: ContactShareViewModel var namePrefixView: ContactNameFieldView! var givenNameView: ContactNameFieldView! @@ -136,7 +136,7 @@ public class EditContactShareNameViewController: OWSViewController, ContactNameF } @objc - required public init(contactShare: OWSContact, delegate: EditContactShareNameViewControllerDelegate) { + required public init(contactShare: ContactShareViewModel, delegate: EditContactShareNameViewControllerDelegate) { self.contactShare = contactShare self.delegate = delegate diff --git a/SignalMessaging/contacts/OWSContactsManager.h b/SignalMessaging/contacts/OWSContactsManager.h index 53c3c34b5..a26ca8b3d 100644 --- a/SignalMessaging/contacts/OWSContactsManager.h +++ b/SignalMessaging/contacts/OWSContactsManager.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import @@ -77,6 +77,9 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; - (nullable NSString *)nameFromSystemContactsForRecipientId:(NSString *)recipientId; - (NSString *)stringForConversationTitleWithPhoneIdentifier:(NSString *)recipientId; +- (nullable UIImage *)systemContactImageForPhoneIdentifier:(nullable NSString *)identifier; +- (nullable UIImage *)profileImageForPhoneIdentifier:(nullable NSString *)identifier; + - (nullable UIImage *)imageForPhoneIdentifier:(nullable NSString *)identifier; - (NSAttributedString *)formattedDisplayNameForSignalAccount:(SignalAccount *)signalAccount font:(UIFont *_Nonnull)font; - (NSAttributedString *)formattedFullNameForRecipientId:(NSString *)recipientId font:(UIFont *)font; diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 7cda1c4fa..27b998592 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -707,19 +707,32 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification return [self signalAccountForRecipientId:recipientId] != nil; } -- (UIImage *_Nullable)imageForPhoneIdentifier:(NSString *_Nullable)identifier + +- (UIImage *_Nullable)systemContactImageForPhoneIdentifier:(NSString *_Nullable)identifier { Contact *contact = self.allContactsMap[identifier]; if (!contact) { + // If we haven't loaded system contacts yet, we may have a cached + // copy in the db contact = [self signalAccountForRecipientId:identifier].contact; } + return contact.image; +} + +- (nullable UIImage *)profileImageForPhoneIdentifier:(nullable NSString *)identifier +{ + return [self.profileManager profileAvatarForRecipientId:identifier]; +} + +- (UIImage *_Nullable)imageForPhoneIdentifier:(NSString *_Nullable)identifier +{ // Prefer the contact image from the local address book if available - UIImage *_Nullable image = contact.image; + UIImage *_Nullable image = [self systemContactImageForPhoneIdentifier:identifier]; // Else try to use the image from their profile if (image == nil) { - image = [self.profileManager profileAvatarForRecipientId:identifier]; + image = [self profileImageForPhoneIdentifier:identifier]; } return image; diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h index 07b795bd9..fa616048e 100644 --- a/SignalServiceKit/src/Contacts/TSThread.h +++ b/SignalServiceKit/src/Contacts/TSThread.h @@ -43,16 +43,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) NSArray *recipientIdentifiers; -#if TARGET_OS_IOS - -/** - * Returns the image representing the thread. Nil if not available. - * - * @return UIImage of the thread, or nil. - */ -- (nullable UIImage *)image; -#endif - #pragma mark Interactions /** diff --git a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m index 9b4de20ed..2570a0c4e 100644 --- a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m +++ b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m @@ -120,15 +120,6 @@ NS_ASSUME_NONNULL_BEGIN return [[TextSecureKitEnv sharedEnv].contactsManager displayNameForPhoneIdentifier:self.contactIdentifier]; } -#if TARGET_OS_IPHONE - -- (nullable UIImage *)image -{ - UIImage *image = [[TextSecureKitEnv sharedEnv].contactsManager imageForPhoneIdentifier:self.contactIdentifier]; - return image; -} - -#endif + (NSString *)threadIdFromContactId:(NSString *)contactId { return [TSContactThreadPrefix stringByAppendingString:contactId]; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index 3eef6a437..0d4109f7d 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -112,10 +112,9 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value); @property (nonatomic, readonly) NSArray *emails; @property (nonatomic, readonly) NSArray *addresses; -// MJK @property (nonatomic, readonly, nullable) NSString *avatarAttachmentId; - (nullable TSAttachment *)avatarAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (void)setAvatarAttachmentStream:(TSAttachmentStream *)attachmentStream; +- (void)saveAvatarImage:(UIImage *)image transaction:(YapDatabaseReadWriteTransaction *)transaction; // "Profile" avatars should _not_ be saved to device contacts. @property (nonatomic, readonly) BOOL isProfileAvatar; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index 0a55f7e49..fe979a008 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.m @@ -3,12 +3,11 @@ // #import "OWSContact.h" -#import "NSString+SSK.h" OWSAttachmentInfo.h +#import "MimeTypeUtil.h" +#import "NSString+SSK.h" #import "OWSContact+Private.h" #import "OWSSignalServiceProtos.pb.h" #import "PhoneNumber.h" - -//#import "Contact.h" #import "TSAttachment.h" #import "TSAttachmentPointer.h" #import "TSAttachmentStream.h" @@ -459,18 +458,26 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) } #pragma mark - Avatar -// MJK - (nullable TSAttachment *)avatarAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction { return [TSAttachment fetchObjectWithUniqueID:self.avatarAttachmentId transaction:transaction]; } -- (void)setAvatarAttachmentStream:(TSAttachmentStream *)attachmentStream -{ - OWSAssert([attachmentStream isKindOfClass:[TSAttachmentStream class]]); - OWSAssert(self.avatarAttachmentId == nil); +- (void)saveAvatarImage:(UIImage *)image transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + NSData *imageData = UIImageJPEGRepresentation(image, (CGFloat)0.9); + + TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:OWSMimeTypeImageJpeg + byteCount:imageData.length + sourceFilename:nil]; + + NSError *error; + BOOL success = [attachmentStream writeData:imageData error:&error]; + OWSAssert(success && !error); + + [attachmentStream saveWithTransaction:transaction]; self.avatarAttachmentId = attachmentStream.uniqueId; } diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.h b/SignalServiceKit/src/Messages/Interactions/TSMessage.h index ce8677e4f..4c672c7e2 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.h @@ -44,7 +44,6 @@ NS_ASSUME_NONNULL_BEGIN - (nullable TSAttachment *)attachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; - (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream; -- (void)setContactShareAvatarAttachmentStream:(TSAttachmentStream *)attachmentStream; - (BOOL)shouldStartExpireTimer; - (BOOL)shouldStartExpireTimerWithTransaction:(YapDatabaseReadTransaction *)transaction; diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.m b/SignalServiceKit/src/Messages/Interactions/TSMessage.m index 636bb7aa4..68572ada4 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.m @@ -332,13 +332,6 @@ static const NSUInteger OWSMessageSchemaVersion = 4; [self.quotedMessage setThumbnailAttachmentStream:attachmentStream]; } -- (void)setContactShareAvatarAttachmentStream:(TSAttachmentStream *)attachmentStream -{ - OWSAssert(self.contactShare); - - [self.contactShare setAvatarAttachmentStream:attachmentStream]; -} - #pragma mark - Update With... Methods - (void)updateWithExpireStartedAt:(uint64_t)expireStartedAt transaction:(YapDatabaseReadWriteTransaction *)transaction diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 63da9f41c..5fc1d8230 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -314,11 +314,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [message.quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction:transaction]; } - // MJK TODO - don't generate the avatar attachment until here, when we've committed to sending it. if (message.contactShare.avatarAttachmentId != nil) { - // contactShareAvatarAttachment = [message.contactShare - // createAvatarForUploadWithTransaction:transaction]; - // TODO generate and save the attachment for upload here. TSAttachment *avatarAttachment = [message.contactShare avatarAttachmentWithTransaction:transaction]; if ([avatarAttachment isKindOfClass:[TSAttachmentStream class]]) { contactShareAvatarAttachment = (TSAttachmentStream *)avatarAttachment; diff --git a/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h b/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h index 26af046da..c20015d9f 100644 --- a/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h +++ b/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // @class Contact; @@ -12,8 +12,4 @@ - (NSString * _Nonnull)displayNameForPhoneIdentifier:(NSString * _Nullable)phoneNumber; - (NSArray * _Nonnull)signalAccounts; -#if TARGET_OS_IPHONE -- (UIImage * _Nullable)imageForPhoneIdentifier:(NSString * _Nullable)phoneNumber; -#endif - @end