Merge branch 'charlesmchen/editContactShareView'

This commit is contained in:
Matthew Chen 2018-05-07 09:48:32 -04:00
commit 4bf08280dc
13 changed files with 1142 additions and 80 deletions

View File

@ -48,6 +48,8 @@
34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; };
34382266209A4E400094FEB7 /* ApproveContactShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34382265209A4E3F0094FEB7 /* ApproveContactShareViewController.swift */; };
3438226A209B63500094FEB7 /* EditContactShareNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34382269209B63500094FEB7 /* EditContactShareNameViewController.swift */; };
34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34386A4D207D0C01009F5D9C /* HomeViewController.m */; };
34386A52207D0C01009F5D9C /* HomeViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34386A50207D0C01009F5D9C /* HomeViewCell.m */; };
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; };
@ -161,6 +163,7 @@
3478506C1FD9B78A007B8332 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */; };
347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 3478506F1FDAEB16007B8332 /* OWSUserProfile.m */; };
347850721FDAEB17007B8332 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 347850701FDAEB16007B8332 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; };
348BB254209CD4B80047AEC2 /* ContactFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB253209CD4B80047AEC2 /* ContactFieldView.swift */; };
3496744D2076768700080B5F /* OWSMessageBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496744C2076768700080B5F /* OWSMessageBubbleView.m */; };
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496744E2076ACCE00080B5F /* LongTextViewController.swift */; };
34A55F3720485465002CC6DE /* OWS2FARegistrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A55F3520485464002CC6DE /* OWS2FARegistrationViewController.m */; };
@ -630,6 +633,8 @@
34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ElegantIcons.ttf; sourceTree = "<group>"; };
34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = "<group>"; };
34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = "<group>"; };
34382265209A4E3F0094FEB7 /* ApproveContactShareViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ApproveContactShareViewController.swift; path = SignalMessaging/attachments/ApproveContactShareViewController.swift; sourceTree = SOURCE_ROOT; };
34382269209B63500094FEB7 /* EditContactShareNameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EditContactShareNameViewController.swift; path = SignalMessaging/attachments/EditContactShareNameViewController.swift; sourceTree = SOURCE_ROOT; };
34386A4D207D0C01009F5D9C /* HomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewController.m; sourceTree = "<group>"; };
34386A4E207D0C01009F5D9C /* HomeViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewCell.h; sourceTree = "<group>"; };
34386A4F207D0C01009F5D9C /* HomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewController.h; sourceTree = "<group>"; };
@ -755,6 +760,7 @@
347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = "<group>"; };
3478506F1FDAEB16007B8332 /* OWSUserProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUserProfile.m; sourceTree = "<group>"; };
347850701FDAEB16007B8332 /* OWSUserProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUserProfile.h; sourceTree = "<group>"; };
348BB253209CD4B80047AEC2 /* ContactFieldView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContactFieldView.swift; path = SignalMessaging/attachments/ContactFieldView.swift; sourceTree = SOURCE_ROOT; };
348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceSleepManager.swift; sourceTree = "<group>"; };
3495BC911F1426B800B478F5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = translations/ar.lproj/Localizable.strings; sourceTree = "<group>"; };
3496744B2076768600080B5F /* OWSMessageBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageBubbleView.h; sourceTree = "<group>"; };
@ -1551,12 +1557,12 @@
346129A11FD1F09100532771 /* contacts */ = {
isa = PBXGroup;
children = (
3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */,
3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */,
346129A21FD1F09100532771 /* OWSContactsManager.h */,
346129A31FD1F09100532771 /* OWSContactsManager.m */,
34612A041FD7238500532771 /* OWSContactsSyncing.h */,
34612A051FD7238500532771 /* OWSContactsSyncing.m */,
3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */,
3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */,
346129AE1FD1F5D900532771 /* SystemContactsFetcher.swift */,
);
path = contacts;
@ -1596,9 +1602,12 @@
346129DB1FD5C02900532771 /* viewControllers */ = {
isa = PBXGroup;
children = (
34382265209A4E3F0094FEB7 /* ApproveContactShareViewController.swift */,
344F2498200FD03200CFB4F4 /* AttachmentApprovalViewController.swift */,
348BB253209CD4B80047AEC2 /* ContactFieldView.swift */,
344F248220069E9B00CFB4F4 /* CountryCodeViewController.h */,
344F248320069E9B00CFB4F4 /* CountryCodeViewController.m */,
34382269209B63500094FEB7 /* EditContactShareNameViewController.swift */,
346129DC1FD5C02900532771 /* LockInteractionController.h */,
346129DD1FD5C02900532771 /* LockInteractionController.m */,
344F2496200FD03200CFB4F4 /* MessageApprovalViewController.swift */,
@ -3051,6 +3060,7 @@
346129E61FD5C0C600532771 /* OWSDatabaseMigrationRunner.m in Sources */,
346129AB1FD1F0EE00532771 /* OWSFormat.m in Sources */,
344F249C200FD03300CFB4F4 /* AttachmentApprovalViewController.swift in Sources */,
348BB254209CD4B80047AEC2 /* ContactFieldView.swift in Sources */,
451F8A461FD715BA005CB9DA /* OWSGroupAvatarBuilder.m in Sources */,
347850591FD9972E007B8332 /* SwiftSingletons.swift in Sources */,
344F248720069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift in Sources */,
@ -3093,7 +3103,9 @@
451F8A3F1FD71455005CB9DA /* OWSTableViewController.m in Sources */,
451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */,
451F8A481FD715BA005CB9DA /* OWSContactAvatarBuilder.m in Sources */,
34382266209A4E400094FEB7 /* ApproveContactShareViewController.swift in Sources */,
4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */,
3438226A209B63500094FEB7 /* EditContactShareNameViewController.swift in Sources */,
346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */,
4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */,
346129D21FD2085A00532771 /* CommonStrings.swift in Sources */,

View File

@ -14,7 +14,7 @@ class TappableView: UIView {
// MARK: - Initializers
@available(*, unavailable, message: "use init(call:) constructor instead.")
@available(*, unavailable, message: "use other constructor instead.")
required init?(coder aDecoder: NSCoder) {
fatalError("Unimplemented")
}
@ -367,48 +367,14 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
private func createFieldsView() -> UIView {
SwiftAssertIsOnMainThread(#function)
let fieldsView = UIView.container()
fieldsView.layoutMargins = .zero
fieldsView.preservesSuperviewLayoutMargins = false
var lastRow: UIView?
let addSpacerRow = {
guard let prevRow = lastRow else {
owsFail("\(self.logTag) missing last row")
return
}
let row = UIView()
row.backgroundColor = UIColor(rgbHex: 0xdedee1)
fieldsView.addSubview(row)
row.autoSetDimension(.height, toSize: 1)
row.autoPinLeadingToSuperviewMargin(withInset: self.hMargin)
row.autoPinTrailingToSuperviewMargin()
row.autoPinEdge(.top, to: .bottom, of: prevRow)
lastRow = row
}
let addRow: ((UIView) -> Void) = { (row) in
if lastRow != nil {
addSpacerRow()
}
fieldsView.addSubview(row)
row.autoPinLeadingToSuperviewMargin()
row.autoPinTrailingToSuperviewMargin()
if let lastRow = lastRow {
row.autoPinEdge(.top, to: .bottom, of: lastRow)
} else {
row.autoPinEdge(toSuperviewEdge: .top)
}
lastRow = row
}
var rows = [UIView]()
if viewMode == .nonSystemContact {
addRow(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT",
rows.append(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT",
comment: "Label for 'new contact' button in conversation settings view."),
action: #selector(didPressCreateNewContact)))
addRow(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
rows.append(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
comment: "Label for 'new contact' button in conversation settings view."),
action: #selector(didPressAddToExistingContact)))
}
@ -424,7 +390,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
for phoneNumber in contact.phoneNumbers {
let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber)
addRow(createNameValueRow(name: phoneNumber.localizedLabel(),
rows.append(createNameValueRow(name: phoneNumber.localizedLabel(),
value: formattedPhoneNumber,
actionBlock: {
guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else {
@ -436,7 +402,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
}
for email in contact.emails {
addRow(createNameValueRow(name: email.localizedLabel(),
rows.append(createNameValueRow(name: email.localizedLabel(),
value: email.email,
actionBlock: {
guard let url = NSURL(string: "mailto:\(email.email)") else {
@ -449,9 +415,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
// TODO: Should we present addresses here too? How?
lastRow?.autoPinEdge(toSuperviewEdge: .bottom)
return fieldsView
return ContactFieldView(rows: rows, hMargin: hMargin)
}
private let hMargin = CGFloat(16)
@ -690,10 +654,10 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate
contactViewController.allowsActions = false
contactViewController.allowsEditing = true
contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton, style: .plain, target: self, action: #selector(didFinishEditingContact))
contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton,
style: .plain,
target: self,
action: #selector(didFinishEditingContact))
contactViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CommonStrings.cancelButton,
style: .plain,
target: self,
action: #selector(didFinishEditingContact))
self.navigationController?.pushViewController(contactViewController, animated: true)

View File

@ -60,7 +60,8 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactThumbnailImageDataKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor
CNContactEmailAddressesKey as CNKeyDescriptor,
CNContactPostalAddressesKey as CNKeyDescriptor
]
// MARK: - Lifecycle Methods

View File

@ -117,6 +117,7 @@ typedef enum : NSUInteger {
#pragma mark -
@interface ConversationViewController () <AttachmentApprovalViewControllerDelegate,
ApproveContactShareViewControllerDelegate,
AVAudioPlayerDelegate,
CNContactViewControllerDelegate,
ContactEditingDelegate,
@ -2522,6 +2523,7 @@ typedef enum : NSUInteger {
}
#pragma mark - Attachment Picking: Contacts
- (void)chooseContactForSending
{
ContactsPicker *contactsPicker =
@ -2946,6 +2948,26 @@ typedef enum : NSUInteger {
}
}
- (void)sendContactShare:(OWSContact *)contactShare
{
OWSAssertIsOnMainThread();
OWSAssert(contactShare);
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];
if (didAddToProfileWhitelist) {
[self ensureDynamicInteractions];
}
}
- (NSURL *)videoTempFolder
{
NSString *temporaryDirectory = NSTemporaryDirectory();
@ -4915,10 +4937,27 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
- (void)contactsPicker:(ContactsPicker *)contactsPicker didSelectContact:(Contact *)contact
{
OWSAssert(contact);
OWSAssert(contact.cnContact);
DDLogDebug(@"%@ in %s with contact: %@", self.logTag, __PRETTY_FUNCTION__, contact);
// TODO actually build contact message.
self.inputToolbar.messageText = contact.fullName;
OWSContact *_Nullable contactShare = [OWSContacts contactForSystemContact:contact.cnContact];
if (!contactShare) {
DDLogError(@"%@ Could not convert system contact.", self.logTag);
return;
}
// TODO: We should probably show this in the same navigation view controller.
ApproveContactShareViewController *approveContactShare =
[[ApproveContactShareViewController alloc] initWithContactShare:contactShare
contactsManager:self.contactsManager
delegate:self];
UINavigationController *navigationController =
[[UINavigationController alloc] initWithRootViewController:approveContactShare];
[self dismissKeyBoard];
[self presentViewController:navigationController animated:YES completion:nil];
}
- (void)contactsPicker:(ContactsPicker *)contactsPicker didSelectMultipleContacts:(NSArray<Contact *> *)contacts
@ -4932,6 +4971,27 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
return YES;
}
#pragma mark - ApproveContactShareViewControllerDelegate
- (void)approveContactShare:(ApproveContactShareViewController *)approveContactShare
didApproveContactShare:(OWSContact *)contactShare
{
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
[self dismissViewControllerAnimated:YES
completion:^{
[self sendContactShare:contactShare];
}];
}
- (void)approveContactShare:(ApproveContactShareViewController *)approveContactShare
didCancelContactShare:(OWSContact *)contactShare
{
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -120,9 +120,6 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
scrollView.autoPinWidthToSuperview(withMargin: 0)
scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
// See notes on how to use UIScrollView with iOS Auto Layout:
//
// https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-6_0/
let contentView = UIView.container()
self.contentView = contentView
scrollView.addSubview(contentView)

View File

@ -418,9 +418,48 @@
/* No comment provided by engineer. */
"CONTACT_DETAIL_COMM_TYPE_INSECURE" = "Unregistered Number";
/* Label for the 'edit name' button in the contact share approval view. */
"CONTACT_EDIT_NAME_BUTTON" = "Edit";
/* Label for a contact's email address. */
"CONTACT_EMAIL" = "Email";
/* Label for the 'city' field of a contact's address. */
"CONTACT_FIELD_ADDRESS_CITY" = "City";
/* Label for the 'country' field of a contact's address. */
"CONTACT_FIELD_ADDRESS_COUNTRY" = "Country";
/* Label for the 'neighborhood' field of a contact's address. */
"CONTACT_FIELD_ADDRESS_NEIGHBORHOOD" = "Neighborhood";
/* Label for the 'pobox' field of a contact's address. */
"CONTACT_FIELD_ADDRESS_POBOX" = "P.O. Box";
/* Label for the 'postcode' field of a contact's address. */
"CONTACT_FIELD_ADDRESS_POSTCODE" = "Post Code";
/* Label for the 'region' field of a contact's address. */
"CONTACT_FIELD_ADDRESS_REGION" = "Region";
/* Label for the 'street' field of a contact's address. */
"CONTACT_FIELD_ADDRESS_STREET" = "Street";
/* Label for the 'family name' field of a contact. */
"CONTACT_FIELD_FAMILY_NAME" = "Family Name";
/* Label for the 'given name' field of a contact. */
"CONTACT_FIELD_GIVEN_NAME" = "Given Name";
/* Label for the 'middle name' field of a contact. */
"CONTACT_FIELD_MIDDLE_NAME" = "Middle Name";
/* Label for the 'name prefix' field of a contact. */
"CONTACT_FIELD_NAME_PREFIX" = "Prefix";
/* Label for the 'name suffix' field of a contact. */
"CONTACT_FIELD_NAME_SUFFIX" = "Suffix";
/* Label for a contact's phone number. */
"CONTACT_PHONE" = "Phone";
@ -433,6 +472,12 @@
/* navbar title for contact picker when sharing a contact */
"CONTACT_PICKER_TITLE" = "Select Contact";
/* Title for the 'Approve contact share' view. */
"CONTACT_SHARE_APPROVAL_VIEW_TITLE" = "Share Contact";
/* Title for the 'edit contact share name' view. */
"CONTACT_SHARE_EDIT_NAME_VIEW_TITLE" = "Edit Name";
/* Indicates that a contact has no name. */
"CONTACT_WITHOUT_NAME" = "Unnamed Contact";

View File

@ -0,0 +1,533 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalServiceKit
@objc
public protocol ApproveContactShareViewControllerDelegate: class {
func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didApproveContactShare contactShare: OWSContact)
func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didCancelContactShare contactShare: OWSContact)
}
protocol ContactShareField: class {
func localizedLabel() -> String
func isIncluded() -> Bool
func setIsIncluded(_ isIncluded: Bool)
func applyToContact(contact: OWSContact)
}
// MARK: -
class ContactShareFieldBase<ContactFieldType: OWSContactField>: NSObject, ContactShareField {
let value: ContactFieldType
private var isIncludedFlag = true
required init(_ value: ContactFieldType) {
self.value = value
super.init()
}
func localizedLabel() -> String {
return value.localizedLabel()
}
func isIncluded() -> Bool {
return isIncludedFlag
}
func setIsIncluded(_ isIncluded: Bool) {
isIncludedFlag = isIncluded
}
func applyToContact(contact: OWSContact) {
preconditionFailure("This method must be overridden")
}
}
// MARK: -
class ContactSharePhoneNumber: ContactShareFieldBase<OWSContactPhoneNumber> {
override func applyToContact(contact: OWSContact) {
assert(isIncluded())
var values = [OWSContactPhoneNumber]()
values += contact.phoneNumbers
values.append(value)
contact.phoneNumbers = values
}
}
// MARK: -
class ContactShareEmail: ContactShareFieldBase<OWSContactEmail> {
override func applyToContact(contact: OWSContact) {
assert(isIncluded())
var values = [OWSContactEmail]()
values += contact.emails
values.append(value)
contact.emails = values
}
}
// MARK: -
class ContactShareAddress: ContactShareFieldBase<OWSContactAddress> {
override func applyToContact(contact: OWSContact) {
assert(isIncluded())
var values = [OWSContactAddress]()
values += contact.addresses
values.append(value)
contact.addresses = values
}
}
// MARK: -
class ContactShareFieldView: UIView {
let field: ContactShareField
let previewViewBlock : (() -> UIView)
private var checkbox: UIButton!
// MARK: - Initializers
@available(*, unavailable, message: "use init(call:) constructor instead.")
required init?(coder aDecoder: NSCoder) {
fatalError("Unimplemented")
}
required init(field: ContactShareField, previewViewBlock : @escaping (() -> UIView)) {
self.field = field
self.previewViewBlock = previewViewBlock
super.init(frame: CGRect.zero)
self.isUserInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped)))
createContents()
}
let hSpacing = CGFloat(10)
let hMargin = CGFloat(0)
func createContents() {
self.layoutMargins.left = 0
self.layoutMargins.right = 0
let checkbox = UIButton(type: .custom)
self.checkbox = checkbox
// TODO: Use real assets.
checkbox.setTitle("", for: .normal)
checkbox.setTitle("", for: .selected)
checkbox.setTitleColor(UIColor.black, for: .normal)
checkbox.setTitleColor(UIColor.black, for: .selected)
checkbox.titleLabel?.font = UIFont.ows_dynamicTypeBody
checkbox.isSelected = field.isIncluded()
// Disable the checkbox; the entire row is hot.
checkbox.isUserInteractionEnabled = false
addSubview(checkbox)
checkbox.autoPinEdge(toSuperviewEdge: .leading, withInset: hMargin)
checkbox.autoVCenterInSuperview()
checkbox.setCompressionResistanceHigh()
checkbox.setContentHuggingHigh()
let nameLabel = UILabel()
nameLabel.text = field.localizedLabel()
nameLabel.font = UIFont.ows_dynamicTypeCaption1
nameLabel.textColor = UIColor.black
nameLabel.lineBreakMode = .byTruncatingTail
addSubview(nameLabel)
nameLabel.autoPinTopToSuperviewMargin()
nameLabel.autoPinLeading(toTrailingEdgeOf: checkbox, offset: hSpacing)
nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin)
let previewView = previewViewBlock()
addSubview(previewView)
previewView.autoPinEdge(.top, to: .bottom, of: nameLabel, withOffset: 3)
previewView.autoPinBottomToSuperviewMargin()
previewView.autoPinLeading(toTrailingEdgeOf: checkbox, offset: hSpacing)
previewView.autoPinTrailingToSuperviewMargin(withInset: hMargin)
}
func wasTapped(sender: UIGestureRecognizer) {
Logger.info("\(self.logTag) \(#function)")
guard sender.state == .recognized else {
return
}
field.setIsIncluded(!field.isIncluded())
checkbox.isSelected = field.isIncluded()
}
}
// MARK: -
@objc
public class ApproveContactShareViewController: OWSViewController, EditContactShareNameViewControllerDelegate {
weak var delegate: ApproveContactShareViewControllerDelegate?
let contactsManager: OWSContactsManager
var contactShare: OWSContact
var fieldViews = [ContactShareFieldView]()
var nameLabel: UILabel!
// MARK: Initializers
@available(*, unavailable, message:"use other constructor instead.")
required public init?(coder aDecoder: NSCoder) {
fatalError("unimplemented")
}
@objc
required public init(contactShare: OWSContact, contactsManager: OWSContactsManager, delegate: ApproveContactShareViewControllerDelegate) {
self.contactsManager = contactsManager
self.contactShare = contactShare
self.delegate = delegate
super.init(nibName: nil, bundle: nil)
buildFields()
}
func buildFields() {
var fieldViews = [ContactShareFieldView]()
// TODO: Avatar
for phoneNumber in contactShare.phoneNumbers {
let field = ContactSharePhoneNumber(phoneNumber)
let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in
guard let strongSelf = self else { return UIView() }
return strongSelf.previewView(forPhoneNumber: phoneNumber)
})
fieldViews.append(fieldView)
}
for email in contactShare.emails {
let field = ContactShareEmail(email)
let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in
guard let strongSelf = self else { return UIView() }
return strongSelf.previewView(forEmail: email)
})
fieldViews.append(fieldView)
}
for address in contactShare.addresses {
let field = ContactShareAddress(address)
let fieldView = ContactShareFieldView(field: field, previewViewBlock: { [weak self] _ in
guard let strongSelf = self else { return UIView() }
return strongSelf.previewView(forAddress: address)
})
fieldViews.append(fieldView)
}
self.fieldViews = fieldViews
}
// MARK: - View Lifecycle
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateNavigationBar()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override public func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override public func loadView() {
super.loadView()
self.navigationItem.title = NSLocalizedString("CONTACT_SHARE_APPROVAL_VIEW_TITLE",
comment: "Title for the 'Approve contact share' view.")
self.view.backgroundColor = UIColor.white
updateContent()
updateNavigationBar()
}
// TODO: Surface error with resolution to user if not.
func canShareContact() -> Bool {
return contactShare.ows_isValid()
}
func updateNavigationBar() {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
target: self,
action: #selector(didPressCancel))
if canShareContact() {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON",
comment: "Label for 'send' button in the 'attachment approval' dialog."),
style: .plain, target: self, action: #selector(didPressSendButton))
} else {
self.navigationItem.rightBarButtonItem = nil
}
}
private func updateContent() {
SwiftAssertIsOnMainThread(#function)
guard let rootView = self.view else {
owsFail("\(logTag) missing root view.")
return
}
for subview in rootView.subviews {
subview.removeFromSuperview()
}
let scrollView = UIScrollView()
scrollView.preservesSuperviewLayoutMargins = false
self.view.addSubview(scrollView)
scrollView.layoutMargins = .zero
scrollView.autoPinWidthToSuperview()
scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
scrollView.autoPinEdge(toSuperviewEdge: .bottom)
let fieldsView = createFieldsView()
scrollView.addSubview(fieldsView)
// Use layoutMarginsGuide for views inside UIScrollView
// that should have same width as scroll view.
fieldsView.autoPinLeadingToSuperviewMargin()
fieldsView.autoPinTrailingToSuperviewMargin()
fieldsView.autoPinEdge(toSuperviewEdge: .top)
fieldsView.autoPinEdge(toSuperviewEdge: .bottom)
fieldsView.setContentHuggingHorizontalLow()
}
private func createFieldsView() -> UIView {
SwiftAssertIsOnMainThread(#function)
var rows = [UIView]()
rows.append(createNameRow())
for fieldView in fieldViews {
rows.append(fieldView)
}
return ContactFieldView(rows: rows, hMargin: hMargin)
}
private let hMargin = CGFloat(16)
func createNameRow() -> UIView {
let nameVMargin = CGFloat(16)
let row = UIView()
row.layoutMargins = UIEdgeInsets(top: nameVMargin, left: 0, bottom: nameVMargin, right: 0)
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .center
stackView.layoutMargins = .zero
stackView.spacing = 10
row.addSubview(stackView)
stackView.autoPinEdgesToSuperviewMargins()
let nameLabel = UILabel()
self.nameLabel = nameLabel
nameLabel.text = contactShare.displayName
nameLabel.font = UIFont.ows_dynamicTypeBody
nameLabel.textColor = UIColor.ows_materialBlue
nameLabel.lineBreakMode = .byTruncatingTail
stackView.addArrangedSubview(nameLabel)
let editNameLabel = UILabel()
editNameLabel.text = NSLocalizedString("CONTACT_EDIT_NAME_BUTTON", comment: "Label for the 'edit name' button in the contact share approval view.")
editNameLabel.font = UIFont.ows_dynamicTypeCaption1
editNameLabel.textColor = UIColor.black
stackView.addArrangedSubview(editNameLabel)
editNameLabel.setContentHuggingHigh()
editNameLabel.setCompressionResistanceHigh()
// Icon
let iconName = (self.view.isRTL() ? "system_disclosure_indicator_rtl" : "system_disclosure_indicator")
guard let iconImage = UIImage(named: iconName) else {
owsFail("\(logTag) missing icon.")
return row
}
let iconView = UIImageView(image: iconImage.withRenderingMode(.alwaysTemplate))
iconView.contentMode = .scaleAspectFit
iconView.tintColor = UIColor.black.withAlphaComponent(0.6)
stackView.addArrangedSubview(iconView)
iconView.setContentHuggingHigh()
iconView.setCompressionResistanceHigh()
row.isUserInteractionEnabled = true
row.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didPressEditName)))
return row
}
func previewView(forPhoneNumber phoneNumber: OWSContactPhoneNumber) -> UIView {
let label = UILabel()
label.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber)
label.font = UIFont.ows_dynamicTypeCaption1
label.textColor = UIColor.ows_materialBlue
label.lineBreakMode = .byTruncatingTail
return label
}
func previewView(forEmail email: OWSContactEmail) -> UIView {
let label = UILabel()
label.text = email.email
label.font = UIFont.ows_dynamicTypeCaption1
label.textColor = UIColor.ows_materialBlue
label.lineBreakMode = .byTruncatingTail
return label
}
func previewView(forAddress address: OWSContactAddress) -> UIView {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .leading
stackView.spacing = 0
stackView.layoutMargins = .zero
let tryToAddNameValue: ((String, String?) -> Void) = { (name, value) in
guard let value = value else {
return
}
guard value.count > 0 else {
return
}
let row = UIView.container()
let nameLabel = UILabel()
nameLabel.text = name
nameLabel.font = UIFont.ows_dynamicTypeCaption1
nameLabel.textColor = UIColor.black
nameLabel.lineBreakMode = .byTruncatingTail
row.addSubview(nameLabel)
nameLabel.autoPinLeadingToSuperviewMargin()
nameLabel.autoPinHeightToSuperview()
nameLabel.setContentHuggingHigh()
nameLabel.setCompressionResistanceHigh()
let valueLabel = UILabel()
valueLabel.text = value
valueLabel.font = UIFont.ows_dynamicTypeCaption1
valueLabel.textColor = UIColor.ows_materialBlue
valueLabel.lineBreakMode = .byTruncatingTail
row.addSubview(valueLabel)
valueLabel.autoPinLeading(toTrailingEdgeOf: nameLabel, offset: 10)
valueLabel.autoPinTrailingToSuperviewMargin()
valueLabel.autoPinHeightToSuperview()
stackView.addArrangedSubview(row)
}
tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_STREET", comment: "Label for the 'street' field of a contact's address."),
address.street)
tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_POBOX", comment: "Label for the 'pobox' field of a contact's address."),
address.pobox)
tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_NEIGHBORHOOD", comment: "Label for the 'neighborhood' field of a contact's address."),
address.neighborhood)
tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_CITY", comment: "Label for the 'city' field of a contact's address."),
address.city)
tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_REGION", comment: "Label for the 'region' field of a contact's address."),
address.region)
tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_POSTCODE", comment: "Label for the 'postcode' field of a contact's address."),
address.postcode)
tryToAddNameValue(NSLocalizedString("CONTACT_FIELD_ADDRESS_COUNTRY", comment: "Label for the 'country' field of a contact's address."),
address.country)
return stackView
}
// MARK: -
func filteredContactShare() -> OWSContact {
let result = self.contactShare.newContact(withNamePrefix: self.contactShare.namePrefix,
givenName: self.contactShare.givenName,
middleName: self.contactShare.middleName,
familyName: self.contactShare.familyName,
nameSuffix: self.contactShare.nameSuffix)
for fieldView in fieldViews {
if fieldView.field.isIncluded() {
fieldView.field.applyToContact(contact: result)
}
}
return result
}
// MARK: -
func didPressSendButton() {
Logger.info("\(logTag) \(#function)")
guard let delegate = self.delegate else {
owsFail("\(logTag) missing delegate.")
return
}
let filteredContactShare = self.filteredContactShare()
assert(filteredContactShare.ows_isValid())
delegate.approveContactShare(self, didApproveContactShare: filteredContactShare)
}
func didPressCancel() {
Logger.info("\(logTag) \(#function)")
guard let delegate = self.delegate else {
owsFail("\(logTag) missing delegate.")
return
}
delegate.approveContactShare(self, didCancelContactShare: contactShare)
}
func didPressEditName() {
Logger.info("\(logTag) \(#function)")
let view = EditContactShareNameViewController(contactShare: contactShare, delegate: self)
self.navigationController?.pushViewController(view, animated: true)
}
// MARK: - EditContactShareNameViewControllerDelegate
public func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: OWSContact) {
self.contactShare = contactShare
nameLabel.text = contactShare.displayName
self.updateNavigationBar()
}
}

View File

@ -0,0 +1,63 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
public class ContactFieldView: UIView {
@available(*, unavailable, message: "use other constructor instead.")
public required init?(coder aDecoder: NSCoder) {
fatalError("Unimplemented")
}
public required init(rows: [UIView], hMargin: CGFloat) {
super.init(frame: CGRect.zero)
self.layoutMargins = .zero
self.preservesSuperviewLayoutMargins = false
addRows(rows: rows, hMargin: hMargin)
}
private func addRows(rows: [UIView], hMargin: CGFloat) {
var lastRow: UIView?
let addSpacerRow = {
guard let prevRow = lastRow else {
owsFail("\(self.logTag) missing last row")
return
}
let row = UIView()
row.backgroundColor = UIColor(rgbHex: 0xdedee1)
self.addSubview(row)
row.autoSetDimension(.height, toSize: 1)
row.autoPinLeadingToSuperviewMargin(withInset: hMargin)
row.autoPinTrailingToSuperviewMargin()
row.autoPinEdge(.top, to: .bottom, of: prevRow, withOffset: 0)
lastRow = row
}
let addRow: ((UIView) -> Void) = { (row) in
if lastRow != nil {
addSpacerRow()
}
self.addSubview(row)
row.autoPinLeadingToSuperviewMargin(withInset: hMargin)
row.autoPinTrailingToSuperviewMargin(withInset: hMargin)
if let lastRow = lastRow {
row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0)
} else {
row.autoPinEdge(toSuperviewEdge: .top, withInset: 0)
}
lastRow = row
}
for row in rows {
addRow(row)
}
lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0)
}
}

View File

@ -0,0 +1,313 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalServiceKit
@objc
public protocol ContactNameFieldViewDelegate: class {
func nameFieldDidChange()
}
// MARK: -
class ContactNameFieldView: UIView {
weak var delegate: ContactNameFieldViewDelegate?
let name: String
let initialValue: String?
var valueView: UITextField!
var hasUnsavedChanges = false
// MARK: - Initializers
@available(*, unavailable, message: "use other constructor instead.")
required init?(coder aDecoder: NSCoder) {
fatalError("Unimplemented")
}
required init(name: String, value: String?, delegate: ContactNameFieldViewDelegate) {
self.name = name
self.initialValue = value
self.delegate = delegate
super.init(frame: CGRect.zero)
self.isUserInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped)))
createContents()
}
func createContents() {
let vMargin = CGFloat(10)
self.layoutMargins = UIEdgeInsets(top: vMargin, left: 0, bottom: vMargin, right: 0)
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .center
stackView.layoutMargins = .zero
stackView.spacing = 10
self.addSubview(stackView)
stackView.autoPinEdgesToSuperviewMargins()
let nameLabel = UILabel()
nameLabel.text = name
nameLabel.font = UIFont.ows_dynamicTypeBody
nameLabel.textColor = UIColor.ows_materialBlue
nameLabel.lineBreakMode = .byTruncatingTail
stackView.addArrangedSubview(nameLabel)
nameLabel.setContentHuggingHigh()
nameLabel.setCompressionResistanceHigh()
valueView = UITextField()
if let initialValue = initialValue {
valueView.text = initialValue
}
valueView.font = UIFont.ows_dynamicTypeBody
valueView.textColor = UIColor.black
stackView.addArrangedSubview(valueView)
valueView.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}
func wasTapped(sender: UIGestureRecognizer) {
Logger.info("\(self.logTag) \(#function)")
guard sender.state == .recognized else {
return
}
valueView.becomeFirstResponder()
}
func textFieldDidChange(sender: UITextField) {
Logger.info("\(self.logTag) \(#function)")
hasUnsavedChanges = true
guard let delegate = self.delegate else {
owsFail("\(logTag) missing delegate.")
return
}
delegate.nameFieldDidChange()
}
public func value() -> String {
guard let value = valueView.text else {
return ""
}
return value
}
}
// MARK: -
@objc
public protocol EditContactShareNameViewControllerDelegate: class {
func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: OWSContact)
}
// MARK: -
@objc
public class EditContactShareNameViewController: OWSViewController, ContactNameFieldViewDelegate {
weak var delegate: EditContactShareNameViewControllerDelegate?
let contactShare: OWSContact
var namePrefixView: ContactNameFieldView!
var givenNameView: ContactNameFieldView!
var middleNameView: ContactNameFieldView!
var familyNameView: ContactNameFieldView!
var nameSuffixView: ContactNameFieldView!
var fieldViews = [ContactNameFieldView]()
// MARK: Initializers
@available(*, unavailable, message:"use other constructor instead.")
required public init?(coder aDecoder: NSCoder) {
fatalError("unimplemented")
}
@objc
required public init(contactShare: OWSContact, delegate: EditContactShareNameViewControllerDelegate) {
self.contactShare = contactShare
self.delegate = delegate
super.init(nibName: nil, bundle: nil)
buildFields()
}
func buildFields() {
namePrefixView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_NAME_PREFIX", comment: "Label for the 'name prefix' field of a contact."),
value: contactShare.namePrefix, delegate: self)
givenNameView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_GIVEN_NAME", comment: "Label for the 'given name' field of a contact."),
value: contactShare.givenName, delegate: self)
middleNameView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_MIDDLE_NAME", comment: "Label for the 'middle name' field of a contact."),
value: contactShare.middleName, delegate: self)
familyNameView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_FAMILY_NAME", comment: "Label for the 'family name' field of a contact."),
value: contactShare.familyName, delegate: self)
nameSuffixView = ContactNameFieldView(name: NSLocalizedString("CONTACT_FIELD_NAME_SUFFIX", comment: "Label for the 'name suffix' field of a contact."),
value: contactShare.nameSuffix, delegate: self)
fieldViews = [
namePrefixView ,
givenNameView ,
middleNameView ,
familyNameView ,
nameSuffixView
]
}
override public var canBecomeFirstResponder: Bool {
return true
}
// MARK: - View Lifecycle
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateNavigationBar()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override public func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override public func loadView() {
super.loadView()
self.navigationItem.title = NSLocalizedString("CONTACT_SHARE_EDIT_NAME_VIEW_TITLE",
comment: "Title for the 'edit contact share name' view.")
self.view.preservesSuperviewLayoutMargins = false
self.view.backgroundColor = UIColor.white
updateContent()
updateNavigationBar()
}
func hasUnsavedChanges() -> Bool {
for fieldView in fieldViews {
if fieldView.hasUnsavedChanges {
return true
}
}
return false
}
func updateNavigationBar() {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
target: self,
action: #selector(didPressCancel))
if hasUnsavedChanges() {
self.navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .save,
target: self,
action: #selector(didPressSave))
} else {
self.navigationItem.rightBarButtonItem = nil
}
}
private func updateContent() {
SwiftAssertIsOnMainThread(#function)
guard let rootView = self.view else {
owsFail("\(logTag) missing root view.")
return
}
for subview in rootView.subviews {
subview.removeFromSuperview()
}
let scrollView = UIScrollView()
scrollView.preservesSuperviewLayoutMargins = false
self.view.addSubview(scrollView)
scrollView.layoutMargins = .zero
scrollView.autoPinWidthToSuperview()
scrollView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
scrollView.autoPinEdge(toSuperviewEdge: .bottom)
let fieldsView = createFieldsView()
scrollView.addSubview(fieldsView)
fieldsView.autoPinLeadingToSuperviewMargin()
fieldsView.autoPinTrailingToSuperviewMargin()
fieldsView.autoPinEdge(toSuperviewEdge: .top)
fieldsView.autoPinEdge(toSuperviewEdge: .bottom)
}
private func createFieldsView() -> UIView {
SwiftAssertIsOnMainThread(#function)
var rows = [UIView]()
for fieldView in fieldViews {
rows.append(fieldView)
}
return ContactFieldView(rows: rows, hMargin: hMargin)
}
private let hMargin = CGFloat(16)
// MARK: -
func didPressSave() {
Logger.info("\(logTag) \(#function)")
let modifiedContactShare = contactShare.copy(withNamePrefix: namePrefixView.value(),
givenName: givenNameView.value(),
middleName: middleNameView.value(),
familyName: familyNameView.value(),
nameSuffix: nameSuffixView.value())
guard let delegate = self.delegate else {
owsFail("\(logTag) missing delegate.")
return
}
delegate.editContactShareNameView(self, didEditContactShare: modifiedContactShare)
guard let navigationController = self.navigationController else {
owsFail("\(logTag) Missing navigationController.")
return
}
navigationController.popViewController(animated: true)
}
func didPressCancel() {
Logger.info("\(logTag) \(#function)")
guard let navigationController = self.navigationController else {
owsFail("\(logTag) Missing navigationController.")
return
}
navigationController.popViewController(animated: true)
}
// MARK: - ContactNameFieldViewDelegate
public func nameFieldDidChange() {
updateNavigationBar()
}
}

View File

@ -34,6 +34,7 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee {
CNContactThumbnailImageDataKey as CNKeyDescriptor, // TODO full image instead of thumbnail?
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor,
CNContactPostalAddressesKey as CNKeyDescriptor,
CNContactViewController.descriptorForRequiredKeys()
]

View File

@ -134,6 +134,10 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
{
OWSAssert(phoneNumber);
if (![phoneNumber hasPrefix:COUNTRY_CODE_PREFIX]) {
return phoneNumber;
}
PhoneNumber *_Nullable parsedPhoneNumber = [self tryParsePhoneNumberFromE164:phoneNumber];
if (!parsedPhoneNumber) {
DDLogWarn(@"%@ could not parse phone number.", self.logTag);
@ -356,6 +360,9 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
+ (PhoneNumber *)tryParsePhoneNumberFromE164:(NSString *)text {
OWSAssert(text != nil);
if (![text hasPrefix:COUNTRY_CODE_PREFIX]) {
return nil;
}
return [self phoneNumberFromE164:text];
}

View File

@ -24,13 +24,7 @@ typedef NS_ENUM(NSUInteger, OWSContactPhoneType) {
NSString *NSStringForContactPhoneType(OWSContactPhoneType value);
@interface OWSContactPhoneNumber : MTLModel
@property (nonatomic, readonly) OWSContactPhoneType phoneType;
// Applies in the OWSContactPhoneType_Custom case.
@property (nonatomic, readonly, nullable) NSString *label;
@property (nonatomic, readonly) NSString *phoneNumber;
@protocol OWSContactField <NSObject>
- (BOOL)ows_isValid;
@ -42,6 +36,18 @@ NSString *NSStringForContactPhoneType(OWSContactPhoneType value);
#pragma mark -
@interface OWSContactPhoneNumber : MTLModel <OWSContactField>
@property (nonatomic, readonly) OWSContactPhoneType phoneType;
// Applies in the OWSContactPhoneType_Custom case.
@property (nonatomic, readonly, nullable) NSString *label;
@property (nonatomic, readonly) NSString *phoneNumber;
@end
#pragma mark -
typedef NS_ENUM(NSUInteger, OWSContactEmailType) {
OWSContactEmailType_Home = 1,
OWSContactEmailType_Mobile,
@ -51,7 +57,7 @@ typedef NS_ENUM(NSUInteger, OWSContactEmailType) {
NSString *NSStringForContactEmailType(OWSContactEmailType value);
@interface OWSContactEmail : MTLModel
@interface OWSContactEmail : MTLModel <OWSContactField>
@property (nonatomic, readonly) OWSContactEmailType emailType;
// Applies in the OWSContactEmailType_Custom case.
@ -59,12 +65,6 @@ NSString *NSStringForContactEmailType(OWSContactEmailType value);
@property (nonatomic, readonly) NSString *email;
- (BOOL)ows_isValid;
- (NSString *)localizedLabel;
- (NSString *)debugDescription;
@end
#pragma mark -
@ -77,7 +77,7 @@ typedef NS_ENUM(NSUInteger, OWSContactAddressType) {
NSString *NSStringForContactAddressType(OWSContactAddressType value);
@interface OWSContactAddress : MTLModel
@interface OWSContactAddress : MTLModel <OWSContactField>
@property (nonatomic, readonly) OWSContactAddressType addressType;
// Applies in the OWSContactAddressType_Custom case.
@ -91,12 +91,6 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value);
@property (nonatomic, readonly, nullable) NSString *postcode;
@property (nonatomic, readonly, nullable) NSString *country;
- (BOOL)ows_isValid;
- (NSString *)localizedLabel;
- (NSString *)debugDescription;
@end
#pragma mark -
@ -128,6 +122,20 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value);
- (NSString *)debugDescription;
#pragma mark - Creation and Derivation
- (OWSContact *)newContactWithNamePrefix:(nullable NSString *)namePrefix
givenName:(nullable NSString *)givenName
middleName:(nullable NSString *)middleName
familyName:(nullable NSString *)familyName
nameSuffix:(nullable NSString *)nameSuffix;
- (OWSContact *)copyContactWithNamePrefix:(nullable NSString *)namePrefix
givenName:(nullable NSString *)givenName
middleName:(nullable NSString *)middleName
familyName:(nullable NSString *)familyName
nameSuffix:(nullable NSString *)nameSuffix;
@end
#pragma mark -

View File

@ -7,6 +7,8 @@
#import "OWSContact+Private.h"
#import "OWSSignalServiceProtos.pb.h"
#import "PhoneNumber.h"
//#import "Contact.h"
#import "TSAttachment.h"
#import <YapDatabase/YapDatabaseTransaction.h>
@ -45,8 +47,8 @@ NSString *NSStringForContactPhoneType(OWSContactPhoneType value)
- (BOOL)ows_isValid
{
if (![PhoneNumber tryParsePhoneNumberFromE164:self.phoneNumber]) {
DDLogWarn(@"%@ invalid phone number; not e164: %@.", self.logTag, self.phoneNumber);
if (self.phoneNumber.ows_stripped.length < 1) {
DDLogWarn(@"%@ invalid phone number: %@.", self.logTag, self.phoneNumber);
return NO;
}
return YES;
@ -122,7 +124,7 @@ NSString *NSStringForContactEmailType(OWSContactEmailType value)
DDLogWarn(@"%@ invalid email: %@.", self.logTag, self.email);
return NO;
}
return YES;
return YES;
}
- (NSString *)localizedLabel
@ -201,7 +203,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
DDLogWarn(@"%@ invalid address; empty.", self.logTag);
return NO;
}
return YES;
return YES;
}
- (NSString *)localizedLabel
@ -360,6 +362,13 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
}
}
- (void)updateDisplayName
{
_displayName = nil;
[self ensureDisplayName];
}
- (NSString *)debugDescription
{
NSMutableString *result = [NSMutableString new];
@ -398,6 +407,55 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
return result;
}
- (OWSContact *)newContactWithNamePrefix:(nullable NSString *)namePrefix
givenName:(nullable NSString *)givenName
middleName:(nullable NSString *)middleName
familyName:(nullable NSString *)familyName
nameSuffix:(nullable NSString *)nameSuffix
{
OWSContact *newContact = [OWSContact new];
[newContact setNamePrefix:namePrefix
givenName:givenName
middleName:middleName
familyName:familyName
nameSuffix:nameSuffix];
return newContact;
}
- (OWSContact *)copyContactWithNamePrefix:(nullable NSString *)namePrefix
givenName:(nullable NSString *)givenName
middleName:(nullable NSString *)middleName
familyName:(nullable NSString *)familyName
nameSuffix:(nullable NSString *)nameSuffix
{
OWSContact *contactCopy = [self copy];
[contactCopy setNamePrefix:namePrefix
givenName:givenName
middleName:middleName
familyName:familyName
nameSuffix:nameSuffix];
return contactCopy;
}
- (void)setNamePrefix:(nullable NSString *)namePrefix
givenName:(nullable NSString *)givenName
middleName:(nullable NSString *)middleName
familyName:(nullable NSString *)familyName
nameSuffix:(nullable NSString *)nameSuffix
{
self.namePrefix = namePrefix.ows_stripped;
self.givenName = givenName.ows_stripped;
self.middleName = middleName.ows_stripped;
self.familyName = familyName.ows_stripped;
self.nameSuffix = nameSuffix.ows_stripped;
[self updateDisplayName];
}
@end
#pragma mark -