diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index b8d51e1a8..bdcff4a4f 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -260,7 +260,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 +271,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 +317,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 +328,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 +363,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 { @@ -373,30 +374,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(createSimpleFieldRow(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) })) } 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(createSimpleFieldRow(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) })) } - // TODO: Should we present addresses here too? How? + for address in contactShare.addresses { + rows.append(createAddressFieldRow(name: address.localizedLabel(), + address: address, + actionBlock: { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.didPressAddress(address: address) + })) + } return ContactFieldView(rows: rows, hMargin: hMargin) } @@ -424,33 +432,108 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return row } - private func createNameValueRow(name: String, value: String?, actionBlock : @escaping () -> Void) -> UIView { + private func createSimpleFieldRow(name: String, value: String?, actionBlock : @escaping () -> Void) -> UIView { let row = TappableView(actionBlock: actionBlock) row.layoutMargins.left = 0 row.layoutMargins.right = 0 + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + stackView.spacing = 3 + row.addSubview(stackView) + stackView.autoPinTopToSuperviewMargin() + stackView.autoPinBottomToSuperviewMargin() + stackView.autoPinLeadingToSuperviewMargin(withInset: hMargin) + stackView.autoPinTrailingToSuperviewMargin(withInset: hMargin) + let nameLabel = UILabel() nameLabel.text = name - nameLabel.font = UIFont.ows_dynamicTypeCaption1 + nameLabel.font = UIFont.ows_dynamicTypeSubheadline nameLabel.textColor = UIColor.black nameLabel.lineBreakMode = .byTruncatingTail - row.addSubview(nameLabel) - nameLabel.autoPinTopToSuperviewMargin() - nameLabel.autoPinLeadingToSuperviewMargin(withInset: hMargin) - nameLabel.autoPinTrailingToSuperviewMargin(withInset: hMargin) + stackView.addArrangedSubview(nameLabel) let valueLabel = UILabel() valueLabel.text = value - valueLabel.font = UIFont.ows_dynamicTypeCaption1 + valueLabel.font = UIFont.ows_dynamicTypeBody 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) + stackView.addArrangedSubview(valueLabel) - // TODO: Should there be a disclosure icon here? + return row + } + + private func createAddressFieldRow(name: String, address: OWSContactAddress, actionBlock : @escaping () -> Void) -> UIView { + let row = TappableView(actionBlock: actionBlock) + row.layoutMargins.left = 0 + row.layoutMargins.right = 0 + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + stackView.spacing = 3 + stackView.layoutMargins = .zero + row.addSubview(stackView) + stackView.autoPinTopToSuperviewMargin() + stackView.autoPinBottomToSuperviewMargin() + stackView.autoPinLeadingToSuperviewMargin(withInset: hMargin) + stackView.autoPinTrailingToSuperviewMargin(withInset: hMargin) + + let nameLabel = UILabel() + nameLabel.text = name + 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 row } @@ -496,7 +579,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 +593,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 +670,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 +711,39 @@ 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 email.") + return + } + guard let url = NSURL(string: "http://maps.apple.com/?address=\(escapedMapAddress)") else { + owsFail("\(ContactViewController.logTag) could not open email.") + return + } + + UIApplication.shared.openURL(url as URL) + } + // MARK: - private func presentNewContactView() { 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/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index 8974bd59a..b1d1d2544 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -285,6 +285,10 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations } [self updateBarButtonItems]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self tableView:self.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; + }); } - (void)applyDefaultBackButton diff --git a/SignalMessaging/attachments/ApproveContactShareViewController.swift b/SignalMessaging/attachments/ApproveContactShareViewController.swift index 2b79a29f2..597c195cc 100644 --- a/SignalMessaging/attachments/ApproveContactShareViewController.swift +++ b/SignalMessaging/attachments/ApproveContactShareViewController.swift @@ -179,6 +179,7 @@ class ContactShareFieldView: UIView { // MARK: - +// TODO: Rename to ContactShareApprovalViewController @objc public class ApproveContactShareViewController: OWSViewController, EditContactShareNameViewControllerDelegate { weak var delegate: ApproveContactShareViewControllerDelegate?