diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 6941e6a98..ff258445f 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -164,6 +164,8 @@ 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 */; }; + 348BB25A209CF8E50047AEC2 /* TappableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB258209CF8E40047AEC2 /* TappableStackView.swift */; }; + 348BB25B209CF8E50047AEC2 /* TappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB259209CF8E50047AEC2 /* TappableView.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 */; }; @@ -762,6 +764,8 @@ 3478506F1FDAEB16007B8332 /* OWSUserProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUserProfile.m; sourceTree = ""; }; 347850701FDAEB16007B8332 /* OWSUserProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUserProfile.h; sourceTree = ""; }; 348BB253209CD4B80047AEC2 /* ContactFieldView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContactFieldView.swift; path = SignalMessaging/attachments/ContactFieldView.swift; sourceTree = SOURCE_ROOT; }; + 348BB258209CF8E40047AEC2 /* TappableStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableStackView.swift; path = SignalMessaging/Views/TappableStackView.swift; sourceTree = SOURCE_ROOT; }; + 348BB259209CF8E50047AEC2 /* TappableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableView.swift; path = SignalMessaging/Views/TappableView.swift; sourceTree = SOURCE_ROOT; }; 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceSleepManager.swift; sourceTree = ""; }; 3495BC911F1426B800B478F5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = translations/ar.lproj/Localizable.strings; sourceTree = ""; }; 3496744B2076768600080B5F /* OWSMessageBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageBubbleView.h; sourceTree = ""; }; @@ -1586,15 +1590,17 @@ isa = PBXGroup; children = ( 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */, - 76EB052E18170B33006006FC /* ContactTableViewCell.h */, - 76EB052F18170B33006006FC /* ContactTableViewCell.m */, + 346129D11FD2085A00532771 /* CommonStrings.swift */, 340CB2221EAC155C0001CAA1 /* ContactsViewHelper.h */, 340CB2231EAC155C0001CAA1 /* ContactsViewHelper.m */, - 346129D11FD2085A00532771 /* CommonStrings.swift */, + 76EB052E18170B33006006FC /* ContactTableViewCell.h */, + 76EB052F18170B33006006FC /* ContactTableViewCell.m */, 45BE4EA12012AD2000935E59 /* DisappearingTimerConfigurationView.swift */, 451573952061B49500803601 /* GradientView.swift */, 346129CF1FD207F200532771 /* OWSAlerts.swift */, 454A965E1FD60EA2008D2A0E /* OWSFlatButton.swift */, + 348BB258209CF8E40047AEC2 /* TappableStackView.swift */, + 348BB259209CF8E50047AEC2 /* TappableView.swift */, 3400C7971EAFB772008A8584 /* ThreadViewHelper.h */, 3400C7981EAFB772008A8584 /* ThreadViewHelper.m */, ); @@ -3083,6 +3089,8 @@ 451F8A461FD715BA005CB9DA /* OWSGroupAvatarBuilder.m in Sources */, 347850591FD9972E007B8332 /* SwiftSingletons.swift in Sources */, 344F248720069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift in Sources */, + 348BB25B209CF8E50047AEC2 /* TappableView.swift in Sources */, + 348BB25A209CF8E50047AEC2 /* TappableStackView.swift in Sources */, 346129961FD1E30000532771 /* OWSDatabaseMigration.m in Sources */, 344D6CEC20069E070042AF96 /* NewNonContactConversationViewController.m in Sources */, 346129FB1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.m in Sources */, diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index b8d51e1a8..e55f2f5d2 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -9,36 +9,6 @@ import Reachability import ContactsUI import MessageUI -class TappableView: UIView { - let actionBlock : (() -> Void) - - // MARK: - Initializers - - @available(*, unavailable, message: "use other constructor instead.") - required init?(coder aDecoder: NSCoder) { - fatalError("Unimplemented") - } - - required init(actionBlock : @escaping () -> Void) { - self.actionBlock = actionBlock - super.init(frame: CGRect.zero) - - self.isUserInteractionEnabled = true - self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped))) - } - - func wasTapped(sender: UIGestureRecognizer) { - Logger.info("\(logTag) \(#function)") - - guard sender.state == .recognized else { - return - } - actionBlock() - } -} - -// MARK: - - class ContactViewController: OWSViewController, CNContactViewControllerDelegate { enum ContactViewMode { @@ -260,7 +230,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate let nameLabel = UILabel() nameLabel.text = contactShare.displayName - nameLabel.font = UIFont.ows_dynamicTypeTitle2.ows_bold() + nameLabel.font = UIFont.ows_dynamicTypeTitle1 nameLabel.textColor = UIColor.black nameLabel.lineBreakMode = .byTruncatingTail nameLabel.textAlignment = .center @@ -271,10 +241,10 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate var lastView: UIView = nameLabel - if let firstPhoneNumber = contactShare.phoneNumbers.first { + for phoneNumber in systemContactsWithSignalAccountsForContact() { let phoneNumberLabel = UILabel() - phoneNumberLabel.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: firstPhoneNumber.phoneNumber) - phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption2 + phoneNumberLabel.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber) + phoneNumberLabel.font = UIFont.ows_dynamicTypeFootnote phoneNumberLabel.textColor = UIColor.black phoneNumberLabel.lineBreakMode = .byTruncatingTail phoneNumberLabel.textAlignment = .center @@ -317,7 +287,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate case .systemContactWithoutSignal: // Show invite button for system contacts without a Signal account. let inviteButton = createLargePillButton(text: NSLocalizedString("ACTION_INVITE", - comment: "Label for 'invite' button in contact view."), + comment: "Label for 'invite' button in contact view."), actionBlock: { [weak self] _ in guard let strongSelf = self else { return } strongSelf.didPressInvite() @@ -328,7 +298,18 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate inviteButton.autoPinTrailingToSuperviewMargin(withInset: 55) lastView = inviteButton case .nonSystemContact: - // Show no action buttons for contacts not in user's device contacts. + // Show "add to contacts" button for non-system contacts. + let addToContactsButton = createLargePillButton(text: NSLocalizedString("CONVERSATION_VIEW_ADD_TO_CONTACTS_OFFER", + comment: "Message shown in conversation view that offers to add an unknown user to your phone's contacts."), + actionBlock: { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.didPressAddToContacts() + }) + topView.addSubview(addToContactsButton) + addToContactsButton.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 20) + addToContactsButton.autoPinLeadingToSuperviewMargin(withInset: 55) + addToContactsButton.autoPinTrailingToSuperviewMargin(withInset: 55) + lastView = addToContactsButton break case .noPhoneNumber: // Show no action buttons for contacts without a phone number. @@ -352,16 +333,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate var rows = [UIView]() - if viewMode == .nonSystemContact { - rows.append(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT", - comment: "Label for 'new contact' button in conversation settings view."), - action: #selector(didPressCreateNewContact))) - - rows.append(createActionRow(labelText: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT", - comment: "Label for 'new contact' button in conversation settings view."), - action: #selector(didPressAddToExistingContact))) - } - // TODO: Not designed yet. // if viewMode == .systemContactWithSignal || // viewMode == .systemContactWithoutSignal { @@ -371,32 +342,37 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // } for phoneNumber in contactShare.phoneNumbers { - let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber) - - rows.append(createNameValueRow(name: phoneNumber.localizedLabel(), - value: formattedPhoneNumber, - actionBlock: { - guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else { - owsFail("\(ContactViewController.logTag) could not open phone number.") - return - } - UIApplication.shared.openURL(url as URL) + rows.append(ContactFieldView.contactFieldView(forPhoneNumber: phoneNumber, + layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin), + actionBlock: { + guard let url = NSURL(string: "tel:\(phoneNumber.phoneNumber)") else { + owsFail("\(ContactViewController.logTag) could not open phone number.") + return + } + UIApplication.shared.openURL(url as URL) })) } for email in contactShare.emails { - rows.append(createNameValueRow(name: email.localizedLabel(), - value: email.email, - actionBlock: { - guard let url = NSURL(string: "mailto:\(email.email)") else { - owsFail("\(ContactViewController.logTag) could not open email.") - return - } - UIApplication.shared.openURL(url as URL) + rows.append(ContactFieldView.contactFieldView(forEmail: email, + layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin), + actionBlock: { + guard let url = NSURL(string: "mailto:\(email.email)") else { + owsFail("\(ContactViewController.logTag) could not open email.") + return + } + UIApplication.shared.openURL(url as URL) })) } - // TODO: Should we present addresses here too? How? + for address in contactShare.addresses { + rows.append(ContactFieldView.contactFieldView(forAddress: address, + layoutMargins: UIEdgeInsets(top: 5, left: hMargin, bottom: 5, right: hMargin), + actionBlock: { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.didPressAddress(address: address) + })) + } return ContactFieldView(rows: rows, hMargin: hMargin) } @@ -424,37 +400,6 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return row } - private func createNameValueRow(name: String, value: String?, actionBlock : @escaping () -> Void) -> UIView { - let row = TappableView(actionBlock: actionBlock) - row.layoutMargins.left = 0 - row.layoutMargins.right = 0 - - let nameLabel = UILabel() - nameLabel.text = name - nameLabel.font = UIFont.ows_dynamicTypeCaption1 - nameLabel.textColor = UIColor.black - nameLabel.lineBreakMode = .byTruncatingTail - row.addSubview(nameLabel) - nameLabel.autoPinTopToSuperviewMargin() - nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) - nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) - - let valueLabel = UILabel() - valueLabel.text = value - valueLabel.font = UIFont.ows_dynamicTypeCaption1 - valueLabel.textColor = UIColor.ows_materialBlue - valueLabel.lineBreakMode = .byTruncatingTail - row.addSubview(valueLabel) - valueLabel.autoPinEdge(.top, to: .bottom, of: nameLabel, withOffset: 3) - valueLabel.autoPinBottomToSuperviewMargin() - valueLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) - valueLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) - - // TODO: Should there be a disclosure icon here? - - return row - } - // TODO: Use real assets. private func createCircleActionButton(text: String, actionBlock : @escaping () -> Void) -> UIView { let buttonSize = CGFloat(50) @@ -496,7 +441,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate let label = UILabel() label.text = text - label.font = UIFont.ows_dynamicTypeCaption1 + label.font = UIFont.ows_dynamicTypeBody label.textColor = UIColor.ows_materialBlue label.lineBreakMode = .byTruncatingTail label.textAlignment = .center @@ -510,21 +455,15 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return button } - func didPressCreateNewContact(sender: UIGestureRecognizer) { + func didPressCreateNewContact() { Logger.info("\(logTag) \(#function)") - guard sender.state == .recognized else { - return - } presentNewContactView() } - func didPressAddToExistingContact(sender: UIGestureRecognizer) { + func didPressAddToExistingContact() { Logger.info("\(logTag) \(#function)") - guard sender.state == .recognized else { - return - } presentSelectAddToExistingContactView() } @@ -593,6 +532,26 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate inviteFlow.sendSMSTo(phoneNumbers: phoneNumbers) } + func didPressAddToContacts() { + Logger.info("\(logTag) \(#function)") + + let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_NEW_CONTACT", + comment: "Label for 'new contact' button in conversation settings view."), + style: .default) { _ in + self.didPressCreateNewContact() + }) + actionSheet.addAction(UIAlertAction(title: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT", + comment: "Label for 'new contact' button in conversation settings view."), + style: .default) { _ in + self.didPressAddToExistingContact() + }) + actionSheet.addAction(OWSAlerts.cancelAction) + + self.present(actionSheet, animated: true) + } + private func showPhoneNumberPicker(phoneNumbers: [String], completion :@escaping ((String) -> Void)) { let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) @@ -614,6 +573,41 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate self.navigationController?.popViewController(animated: true) } + func didPressAddress(address: OWSContactAddress) { + Logger.info("\(self.logTag) \(#function)") + + // Open address in Apple Maps app. + var addressParts = [String]() + let addAddressPart: ((String?) -> Void) = { (part) in + guard let part = part else { + return + } + guard part.count > 0 else { + return + } + addressParts.append(part) + } + addAddressPart(address.street) + addAddressPart(address.neighborhood) + addAddressPart(address.city) + addAddressPart(address.region) + addAddressPart(address.postcode) + addAddressPart(address.country) + let mapAddress = addressParts.joined(separator: ", ") + guard let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { + owsFail("\(ContactViewController.logTag) could not open address.") + return + } + // Note that we use "q" (i.e. query) rather than "address" since we can't assume + // this is a well-formed address. + guard let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") else { + owsFail("\(ContactViewController.logTag) could not open address.") + return + } + + UIApplication.shared.openURL(url as URL) + } + // MARK: - private func presentNewContactView() { diff --git a/Signal/src/ViewControllers/ContactsPicker.swift b/Signal/src/ViewControllers/ContactsPicker.swift index 24c49bbd2..255c9c9bb 100644 --- a/Signal/src/ViewControllers/ContactsPicker.swift +++ b/Signal/src/ViewControllers/ContactsPicker.swift @@ -244,6 +244,8 @@ public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableView } open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + Logger.verbose("\(logTag) \(#function)") + let cell = tableView.cellForRow(at: indexPath) as! ContactCell let selectedContact = cell.contact! diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m index b462923dd..85299986d 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m @@ -134,7 +134,7 @@ NS_ASSUME_NONNULL_BEGIN - (CGFloat)audioIconHSpacing { - return 10.f; + return 8.f; } + (CGFloat)audioIconVMargin diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m index cfd9a4c47..21615e390 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m @@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN - (CGFloat)iconHSpacing { - return 10.f; + return 8.f; } + (CGFloat)iconVMargin diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.m index 75f463572..29c88ac4c 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.m @@ -48,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN - (CGFloat)iconHSpacing { - return 10.f; + return 8.f; } + (CGFloat)iconVMargin diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 619acde57..d76ab631a 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -3622,6 +3622,17 @@ typedef enum : NSUInteger { [chooseMediaAction setValue:chooseMediaImage forKey:@"image"]; [actionSheetController addAction:chooseMediaAction]; + UIAlertAction *gifAction = [UIAlertAction + actionWithTitle:NSLocalizedString(@"SELECT_GIF_BUTTON", @"Label for 'select GIF to attach' action sheet button") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + [self showGifPicker]; + }]; + UIImage *gifImage = [UIImage imageNamed:@"actionsheet_gif_black"]; + OWSAssert(gifImage); + [gifAction setValue:gifImage forKey:@"image"]; + [actionSheetController addAction:gifAction]; + UIAlertAction *chooseDocumentAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"MEDIA_FROM_DOCUMENT_PICKER_BUTTON", @"action sheet button title when choosing attachment type") @@ -3634,17 +3645,6 @@ typedef enum : NSUInteger { [chooseDocumentAction setValue:chooseDocumentImage forKey:@"image"]; [actionSheetController addAction:chooseDocumentAction]; - UIAlertAction *gifAction = [UIAlertAction - actionWithTitle:NSLocalizedString(@"SELECT_GIF_BUTTON", @"Label for 'select GIF to attach' action sheet button") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *_Nonnull action) { - [self showGifPicker]; - }]; - UIImage *gifImage = [UIImage imageNamed:@"actionsheet_gif_black"]; - OWSAssert(gifImage); - [gifAction setValue:gifImage forKey:@"image"]; - [actionSheetController addAction:gifAction]; - if (kIsSendingContactSharesEnabled) { UIAlertAction *chooseContactAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"ATTACHMENT_MENU_CONTACT_BUTTON", diff --git a/SignalMessaging/Views/TappableStackView.swift b/SignalMessaging/Views/TappableStackView.swift new file mode 100644 index 000000000..8fa9e212e --- /dev/null +++ b/SignalMessaging/Views/TappableStackView.swift @@ -0,0 +1,33 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +public class TappableStackView: UIStackView { + let actionBlock : (() -> Void) + + // MARK: - Initializers + + @available(*, unavailable, message: "use other constructor instead.") + public required init(coder aDecoder: NSCoder) { + fatalError("Unimplemented") + } + + public required init(actionBlock : @escaping () -> Void) { + self.actionBlock = actionBlock + super.init(frame: CGRect.zero) + + self.isUserInteractionEnabled = true + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped))) + } + + func wasTapped(sender: UIGestureRecognizer) { + Logger.info("\(logTag) \(#function)") + + guard sender.state == .recognized else { + return + } + actionBlock() + } +} diff --git a/SignalMessaging/Views/TappableView.swift b/SignalMessaging/Views/TappableView.swift new file mode 100644 index 000000000..e440e6554 --- /dev/null +++ b/SignalMessaging/Views/TappableView.swift @@ -0,0 +1,33 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +public class TappableView: UIView { + let actionBlock : (() -> Void) + + // MARK: - Initializers + + @available(*, unavailable, message: "use other constructor instead.") + public required init?(coder aDecoder: NSCoder) { + fatalError("Unimplemented") + } + + public required init(actionBlock : @escaping () -> Void) { + self.actionBlock = actionBlock + super.init(frame: CGRect.zero) + + self.isUserInteractionEnabled = true + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped))) + } + + func wasTapped(sender: UIGestureRecognizer) { + Logger.info("\(logTag) \(#function)") + + guard sender.state == .recognized else { + return + } + actionBlock() + } +} diff --git a/SignalMessaging/attachments/ApproveContactShareViewController.swift b/SignalMessaging/attachments/ApproveContactShareViewController.swift index 2b79a29f2..141466285 100644 --- a/SignalMessaging/attachments/ApproveContactShareViewController.swift +++ b/SignalMessaging/attachments/ApproveContactShareViewController.swift @@ -97,7 +97,7 @@ class ContactShareAddress: ContactShareFieldBase { // MARK: - -class ContactShareFieldView: UIView { +class ContactShareFieldView: UIStackView { let field: ContactShareField @@ -108,7 +108,7 @@ class ContactShareFieldView: UIView { // MARK: - Initializers @available(*, unavailable, message: "use init(call:) constructor instead.") - required init?(coder aDecoder: NSCoder) { + required init(coder aDecoder: NSCoder) { fatalError("Unimplemented") } @@ -125,11 +125,14 @@ class ContactShareFieldView: UIView { } let hSpacing = CGFloat(10) - let hMargin = CGFloat(0) + let hMargin = CGFloat(16) func createContents() { - self.layoutMargins.left = 0 - self.layoutMargins.right = 0 + self.axis = .horizontal + self.spacing = hSpacing + self.alignment = .center + self.layoutMargins = UIEdgeInsets(top: 0, left: hMargin, bottom: 0, right: hMargin) + self.isLayoutMarginsRelativeArrangement = true let checkbox = UIButton(type: .custom) self.checkbox = checkbox @@ -142,28 +145,12 @@ class ContactShareFieldView: UIView { 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() + self.addArrangedSubview(checkbox) 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) + self.addArrangedSubview(previewView) } func wasTapped(sender: UIGestureRecognizer) { @@ -179,6 +166,7 @@ class ContactShareFieldView: UIView { // MARK: - +// TODO: Rename to ContactShareApprovalViewController @objc public class ApproveContactShareViewController: OWSViewController, EditContactShareNameViewControllerDelegate { weak var delegate: ApproveContactShareViewControllerDelegate? @@ -214,27 +202,26 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh // TODO: Avatar + let previewInsets = UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 0) + 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) + let fieldView = ContactShareFieldView(field: field, previewViewBlock: { + return ContactFieldView.contactFieldView(forPhoneNumber: phoneNumber, layoutMargins: previewInsets, actionBlock: nil) }) 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) + let fieldView = ContactShareFieldView(field: field, previewViewBlock: { + return ContactFieldView.contactFieldView(forEmail: email, layoutMargins: previewInsets, actionBlock: nil) }) 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) + let fieldView = ContactShareFieldView(field: field, previewViewBlock: { + return ContactFieldView.contactFieldView(forAddress: address, layoutMargins: previewInsets, actionBlock: nil) }) fieldViews.append(fieldView) } @@ -346,126 +333,33 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh func createNameRow() -> UIView { let nameVMargin = CGFloat(16) - let row = UIView() - row.layoutMargins = UIEdgeInsets(top: nameVMargin, left: 0, bottom: nameVMargin, right: 0) + let stackView = TappableStackView(actionBlock: { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.didPressEditName() + }) - let stackView = UIStackView() stackView.axis = .horizontal stackView.alignment = .center - stackView.layoutMargins = .zero + stackView.layoutMargins = UIEdgeInsets(top: nameVMargin, left: hMargin, bottom: nameVMargin, right: hMargin) stackView.spacing = 10 - row.addSubview(stackView) - stackView.autoPinEdgesToSuperviewMargins() + stackView.isLayoutMarginsRelativeArrangement = true let nameLabel = UILabel() self.nameLabel = nameLabel nameLabel.text = contactShare.displayName - nameLabel.font = UIFont.ows_dynamicTypeBody - nameLabel.textColor = UIColor.ows_materialBlue + nameLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() + nameLabel.textColor = UIColor.black 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 + editNameLabel.font = UIFont.ows_dynamicTypeBody + editNameLabel.textColor = UIColor.ows_materialBlue 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 } diff --git a/SignalMessaging/attachments/ContactFieldView.swift b/SignalMessaging/attachments/ContactFieldView.swift index ac4ef71f1..0a64b0f63 100644 --- a/SignalMessaging/attachments/ContactFieldView.swift +++ b/SignalMessaging/attachments/ContactFieldView.swift @@ -44,8 +44,8 @@ public class ContactFieldView: UIView { addSpacerRow() } self.addSubview(row) - row.autoPinLeadingToSuperviewMargin(withInset: hMargin) - row.autoPinTrailingToSuperviewMargin(withInset: hMargin) + row.autoPinLeadingToSuperviewMargin() + row.autoPinTrailingToSuperviewMargin() if let lastRow = lastRow { row.autoPinEdge(.top, to: .bottom, of: lastRow, withOffset: 0) } else { @@ -60,4 +60,114 @@ public class ContactFieldView: UIView { lastRow?.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) } + + public class func contactFieldView(forPhoneNumber phoneNumber: OWSContactPhoneNumber, layoutMargins: UIEdgeInsets, actionBlock : (() -> Void)?) -> UIView { + let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber) + return simpleFieldView(name: phoneNumber.localizedLabel(), value: formattedPhoneNumber, layoutMargins: layoutMargins, actionBlock: actionBlock) + } + + public class func contactFieldView(forEmail email: OWSContactEmail, layoutMargins: UIEdgeInsets, actionBlock : (() -> Void)?) -> UIView { + return simpleFieldView(name: email.localizedLabel(), value: email.email, layoutMargins: layoutMargins, actionBlock: actionBlock) + } + + private class func simpleFieldView(name: String, value: String?, layoutMargins: UIEdgeInsets, actionBlock : (() -> Void)?) -> UIView { + var stackView: UIStackView + if let actionBlock = actionBlock { + stackView = TappableStackView(actionBlock: actionBlock) + } else { + stackView = UIStackView() + } + stackView.axis = .vertical + stackView.alignment = .leading + stackView.spacing = 3 + stackView.layoutMargins = layoutMargins + stackView.isLayoutMarginsRelativeArrangement = true + + let nameLabel = UILabel() + nameLabel.text = name + nameLabel.font = UIFont.ows_dynamicTypeSubheadline + nameLabel.textColor = UIColor.black + nameLabel.lineBreakMode = .byTruncatingTail + stackView.addArrangedSubview(nameLabel) + + let valueLabel = UILabel() + valueLabel.text = value + valueLabel.font = UIFont.ows_dynamicTypeBody + valueLabel.textColor = UIColor.ows_materialBlue + valueLabel.lineBreakMode = .byTruncatingTail + stackView.addArrangedSubview(valueLabel) + + return stackView + } + + public class func contactFieldView(forAddress address: OWSContactAddress, layoutMargins: UIEdgeInsets, actionBlock : (() -> Void)?) -> UIView { + var stackView: UIStackView + if let actionBlock = actionBlock { + stackView = TappableStackView(actionBlock: actionBlock) + } else { + stackView = UIStackView() + } + stackView.axis = .vertical + stackView.alignment = .leading + stackView.spacing = 3 + stackView.layoutMargins = layoutMargins + stackView.isLayoutMarginsRelativeArrangement = true + + let nameLabel = UILabel() + nameLabel.text = address.localizedLabel() + nameLabel.font = UIFont.ows_dynamicTypeSubheadline + nameLabel.textColor = UIColor.black + nameLabel.lineBreakMode = .byTruncatingTail + stackView.addArrangedSubview(nameLabel) + + let tryToAddNameValue: ((String, String?) -> Void) = { (propertyName, propertyValue) in + guard let propertyValue = propertyValue else { + return + } + guard propertyValue.count > 0 else { + return + } + + let row = UIStackView() + row.axis = .horizontal + row.alignment = .leading + row.spacing = 10 + row.layoutMargins = .zero + + let nameLabel = UILabel() + nameLabel.text = propertyName + nameLabel.font = UIFont.ows_dynamicTypeBody + nameLabel.textColor = UIColor.black + nameLabel.lineBreakMode = .byTruncatingTail + row.addArrangedSubview(nameLabel) + nameLabel.setContentHuggingHigh() + nameLabel.setCompressionResistanceHigh() + + let valueLabel = UILabel() + valueLabel.text = propertyValue + valueLabel.font = UIFont.ows_dynamicTypeBody + valueLabel.textColor = UIColor.ows_materialBlue + valueLabel.lineBreakMode = .byTruncatingTail + row.addArrangedSubview(valueLabel) + + 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 + } } diff --git a/SignalMessaging/categories/UIView+OWS.m b/SignalMessaging/categories/UIView+OWS.m index a31942e27..14e68d3da 100644 --- a/SignalMessaging/categories/UIView+OWS.m +++ b/SignalMessaging/categories/UIView+OWS.m @@ -459,12 +459,13 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) - (void)logFrameWithLabel:(NSString *)label { - DDLogVerbose(@"%@ %@ frame: %@, hidden: %d, opacity: %f", + DDLogVerbose(@"%@ %@ frame: %@, hidden: %d, opacity: %f, layoutMargins: %@", self.logTag, label, NSStringFromCGRect(self.frame), self.hidden, - self.layer.opacity); + self.layer.opacity, + NSStringFromUIEdgeInsets(self.layoutMargins)); } - (void)logFrameLater