Merge branch 'mkirk/contact-share'

pull/1/head
Michael Kirk 7 years ago
commit 424bcbf83e

@ -248,7 +248,6 @@
451166C01FD86B98000739BA /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451166BF1FD86B98000739BA /* AccountManager.swift */; }; 451166C01FD86B98000739BA /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451166BF1FD86B98000739BA /* AccountManager.swift */; };
451573962061B49500803601 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451573952061B49500803601 /* GradientView.swift */; }; 451573962061B49500803601 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451573952061B49500803601 /* GradientView.swift */; };
451686AB1F520CDA00AC3D4B /* MultiDeviceProfileKeyUpdateJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451686AA1F520CDA00AC3D4B /* MultiDeviceProfileKeyUpdateJob.swift */; }; 451686AB1F520CDA00AC3D4B /* MultiDeviceProfileKeyUpdateJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451686AA1F520CDA00AC3D4B /* MultiDeviceProfileKeyUpdateJob.swift */; };
4517642A1DE939FD00EDB8B9 /* ContactCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 451764281DE939FD00EDB8B9 /* ContactCell.xib */; };
4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451764291DE939FD00EDB8B9 /* ContactCell.swift */; }; 4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451764291DE939FD00EDB8B9 /* ContactCell.swift */; };
45194F8F1FD71FF500333B2C /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129BE1FD2068600532771 /* ThreadUtil.m */; }; 45194F8F1FD71FF500333B2C /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129BE1FD2068600532771 /* ThreadUtil.m */; };
45194F901FD7200000333B2C /* ThreadUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129BD1FD2068600532771 /* ThreadUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; 45194F901FD7200000333B2C /* ThreadUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129BD1FD2068600532771 /* ThreadUtil.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -900,7 +899,6 @@
451166BF1FD86B98000739BA /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; }; 451166BF1FD86B98000739BA /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
451573952061B49500803601 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GradientView.swift; path = SignalMessaging/Views/GradientView.swift; sourceTree = SOURCE_ROOT; }; 451573952061B49500803601 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GradientView.swift; path = SignalMessaging/Views/GradientView.swift; sourceTree = SOURCE_ROOT; };
451686AA1F520CDA00AC3D4B /* MultiDeviceProfileKeyUpdateJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiDeviceProfileKeyUpdateJob.swift; sourceTree = "<group>"; }; 451686AA1F520CDA00AC3D4B /* MultiDeviceProfileKeyUpdateJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiDeviceProfileKeyUpdateJob.swift; sourceTree = "<group>"; };
451764281DE939FD00EDB8B9 /* ContactCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactCell.xib; sourceTree = "<group>"; };
451764291DE939FD00EDB8B9 /* ContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = "<group>"; }; 451764291DE939FD00EDB8B9 /* ContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = "<group>"; };
451777C71FD61554001225FF /* ConversationSearcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationSearcher.swift; sourceTree = "<group>"; }; 451777C71FD61554001225FF /* ConversationSearcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationSearcher.swift; sourceTree = "<group>"; };
451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = CallNotificationsAdapter.swift; path = ../UserInterface/Notifications/CallNotificationsAdapter.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 451A13B01E13DED2000A50FD /* CallNotificationsAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = CallNotificationsAdapter.swift; path = ../UserInterface/Notifications/CallNotificationsAdapter.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
@ -2085,7 +2083,6 @@
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */, 452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */,
34E3E5671EC4B19400495BAC /* AudioProgressView.swift */, 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */,
451764291DE939FD00EDB8B9 /* ContactCell.swift */, 451764291DE939FD00EDB8B9 /* ContactCell.swift */,
451764281DE939FD00EDB8B9 /* ContactCell.xib */,
4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */, 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */,
45A663C41F92EC760027B59E /* GroupTableViewCell.swift */, 45A663C41F92EC760027B59E /* GroupTableViewCell.swift */,
45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */, 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */,
@ -2698,7 +2695,6 @@
B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */, B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */,
34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */, 34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */,
34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */, 34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */,
4517642A1DE939FD00EDB8B9 /* ContactCell.xib in Resources */,
AD83FF431A73426500B5C81A /* audio_play_button@2x.png in Resources */, AD83FF431A73426500B5C81A /* audio_play_button@2x.png in Resources */,
45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */, 45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */,
34B3F8781E8DF1700035BE1A /* ContactsPicker.xib in Resources */, 34B3F8781E8DF1700035BE1A /* ContactsPicker.xib in Resources */,

@ -11,50 +11,52 @@ import UIKit
import Contacts import Contacts
import SignalServiceKit import SignalServiceKit
public protocol ContactsPickerDelegate { @objc
func contactsPicker(_: ContactsPicker, didContactFetchFailed error: NSError) public protocol ContactsPickerDelegate: class {
func contactsPicker(_: ContactsPicker, didCancel error: NSError) func contactsPicker(_: ContactsPicker, contactFetchDidFail error: NSError)
func contactsPickerDidCancel(_: ContactsPicker)
func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact) func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact)
func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact]) func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact])
func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool
} }
public extension ContactsPickerDelegate { public extension ContactsPickerDelegate {
func contactsPicker(_: ContactsPicker, didContactFetchFailed error: NSError) { }
func contactsPicker(_: ContactsPicker, didCancel error: NSError) { }
func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact) { }
func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact]) { }
func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool { return true } func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool { return true }
} }
public enum SubtitleCellValue { @objc
case phoneNumber public enum SubtitleCellValue: Int {
case email case phoneNumber, email, none
} }
open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate { @objc
public class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
@IBOutlet var tableView: UITableView! @IBOutlet var tableView: UITableView!
@IBOutlet var searchBar: UISearchBar! @IBOutlet var searchBar: UISearchBar!
// MARK: - Properties // MARK: - Properties
let TAG = "[ContactsPicker]" private let TAG = "[ContactsPicker]"
let contactCellReuseIdentifier = "contactCellReuseIdentifier" private let contactCellReuseIdentifier = "contactCellReuseIdentifier"
let contactsManager: OWSContactsManager
let collation = UILocalizedIndexedCollation.current() private var contactsManager: OWSContactsManager {
let contactStore = CNContactStore() return Environment.current().contactsManager
}
private let collation = UILocalizedIndexedCollation.current()
private let contactStore = CNContactStore()
// Data Source State // Data Source State
lazy var sections = [[CNContact]]() private lazy var sections = [[CNContact]]()
lazy var filteredSections = [[CNContact]]() private lazy var filteredSections = [[CNContact]]()
lazy var selectedContacts = [Contact]() private lazy var selectedContacts = [Contact]()
// Configuration // Configuration
open var contactsPickerDelegate: ContactsPickerDelegate? public weak var contactsPickerDelegate: ContactsPickerDelegate?
var subtitleCellValue = SubtitleCellValue.phoneNumber private let subtitleCellValue: SubtitleCellValue
var multiSelectEnabled = false private let multiSelectEnabled: Bool
let allowedContactKeys: [CNKeyDescriptor] = [ private let allowedContactKeys: [CNKeyDescriptor] = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactThumbnailImageDataKey as CNKeyDescriptor, CNContactThumbnailImageDataKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor,
@ -65,10 +67,9 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
override open func viewDidLoad() { override open func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
searchBar.placeholder = NSLocalizedString("INVITE_FRIENDS_PICKER_SEARCHBAR_PLACEHOLDER", comment: "Search") searchBar.placeholder = NSLocalizedString("INVITE_FRIENDS_PICKER_SEARCHBAR_PLACEHOLDER", comment: "Search")
// Prevent content form going under the navigation bar // Prevent content from going under the navigation bar
self.edgesForExtendedLayout = [] self.edgesForExtendedLayout = []
// Auto size cells for dynamic type // Auto size cells for dynamic type
@ -77,6 +78,8 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
tableView.allowsMultipleSelection = multiSelectEnabled tableView.allowsMultipleSelection = multiSelectEnabled
tableView.separatorInset = UIEdgeInsets(top: 0, left: ContactCell.kSeparatorHInset, bottom: 0, right: 16)
registerContactCell() registerContactCell()
initializeBarButtons() initializeBarButtons()
reloadContacts() reloadContacts()
@ -85,12 +88,13 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
NotificationCenter.default.addObserver(self, selector: #selector(self.didChangePreferredContentSize), name: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.didChangePreferredContentSize), name: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil)
} }
func didChangePreferredContentSize() { @objc
public func didChangePreferredContentSize() {
self.tableView.reloadData() self.tableView.reloadData()
} }
func initializeBarButtons() { private func initializeBarButtons() {
let cancelButton = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(onTouchCancelButton)) let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onTouchCancelButton))
self.navigationItem.leftBarButtonItem = cancelButton self.navigationItem.leftBarButtonItem = cancelButton
if multiSelectEnabled { if multiSelectEnabled {
@ -99,48 +103,33 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
} }
} }
fileprivate func registerContactCell() { private func registerContactCell() {
tableView.register(ContactCell.nib, forCellReuseIdentifier: contactCellReuseIdentifier) tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
} }
// MARK: - Initializers // MARK: - Initializers
init() { @objc
contactsManager = Environment.current().contactsManager required public init(delegate: ContactsPickerDelegate?, multiSelection: Bool, subtitleCellType: SubtitleCellValue) {
super.init(nibName: nil, bundle: nil)
}
required public init?(coder aDecoder: NSCoder) {
contactsManager = Environment.current().contactsManager
super.init(coder: aDecoder)
}
convenience public init(delegate: ContactsPickerDelegate?) {
self.init(delegate: delegate, multiSelection: false)
}
convenience public init(delegate: ContactsPickerDelegate?, multiSelection: Bool) {
self.init()
multiSelectEnabled = multiSelection multiSelectEnabled = multiSelection
subtitleCellValue = subtitleCellType
super.init(nibName: nil, bundle: nil)
contactsPickerDelegate = delegate contactsPickerDelegate = delegate
} }
convenience public init(delegate: ContactsPickerDelegate?, multiSelection: Bool, subtitleCellType: SubtitleCellValue) { required public init?(coder aDecoder: NSCoder) {
self.init() fatalError("init(coder:) has not been implemented")
multiSelectEnabled = multiSelection
contactsPickerDelegate = delegate
subtitleCellValue = subtitleCellType
} }
// MARK: - Contact Operations // MARK: - Contact Operations
open func reloadContacts() { private func reloadContacts() {
getContacts( onError: { error in getContacts( onError: { error in
Logger.error("\(self.TAG) failed to reload contacts with error:\(error)") Logger.error("\(self.TAG) failed to reload contacts with error:\(error)")
}) })
} }
func getContacts(onError errorHandler: @escaping (_ error: Error) -> Void) { private func getContacts(onError errorHandler: @escaping (_ error: Error) -> Void) {
switch CNContactStore.authorizationStatus(for: CNEntityType.contacts) { switch CNContactStore.authorizationStatus(for: CNEntityType.contacts) {
case CNAuthorizationStatus.denied, CNAuthorizationStatus.restricted: case CNAuthorizationStatus.denied, CNAuthorizationStatus.restricted:
let title = NSLocalizedString("INVITE_FLOW_REQUIRES_CONTACT_ACCESS_TITLE", comment: "Alert title when contacts disabled while trying to invite contacts to signal") let title = NSLocalizedString("INVITE_FLOW_REQUIRES_CONTACT_ACCESS_TITLE", comment: "Alert title when contacts disabled while trying to invite contacts to signal")
@ -152,7 +141,7 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
let cancelAction = UIAlertAction(title: dismissText, style: .cancel, handler: { _ in let cancelAction = UIAlertAction(title: dismissText, style: .cancel, handler: { _ in
let error = NSError(domain: "contactsPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"]) let error = NSError(domain: "contactsPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"])
self.contactsPickerDelegate?.contactsPicker(self, didContactFetchFailed: error) self.contactsPickerDelegate?.contactsPicker(self, contactFetchDidFail: error)
errorHandler(error) errorHandler(error)
self.dismiss(animated: true, completion: nil) self.dismiss(animated: true, completion: nil)
}) })
@ -229,7 +218,7 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
let cnContact = dataSource[indexPath.section][indexPath.row] let cnContact = dataSource[indexPath.section][indexPath.row]
let contact = Contact(systemContact: cnContact) let contact = Contact(systemContact: cnContact)
cell.updateContactsinUI(contact, subtitleType: subtitleCellValue, contactsManager: self.contactsManager) cell.configure(contact: contact, subtitleType: subtitleCellValue, contactsManager: self.contactsManager)
let isSelected = selectedContacts.contains(where: { $0.uniqueId == contact.uniqueId }) let isSelected = selectedContacts.contains(where: { $0.uniqueId == contact.uniqueId })
cell.isSelected = isSelected cell.isSelected = isSelected
@ -302,7 +291,7 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
// MARK: - Button Actions // MARK: - Button Actions
func onTouchCancelButton() { func onTouchCancelButton() {
contactsPickerDelegate?.contactsPicker(self, didCancel: NSError(domain: "contactsPickerErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey: "User Canceled Selection"])) contactsPickerDelegate?.contactsPickerDidCancel(self)
dismiss(animated: true, completion: nil) dismiss(animated: true, completion: nil)
} }

@ -118,9 +118,10 @@ typedef enum : NSUInteger {
@interface ConversationViewController () <AttachmentApprovalViewControllerDelegate, @interface ConversationViewController () <AttachmentApprovalViewControllerDelegate,
AVAudioPlayerDelegate, AVAudioPlayerDelegate,
ContactsViewHelperDelegate,
ContactEditingDelegate,
CNContactViewControllerDelegate, CNContactViewControllerDelegate,
ContactEditingDelegate,
ContactsPickerDelegate,
ContactsViewHelperDelegate,
DisappearingTimerConfigurationViewDelegate, DisappearingTimerConfigurationViewDelegate,
OWSConversationSettingsViewDelegate, OWSConversationSettingsViewDelegate,
ConversationHeaderViewDelegate, ConversationHeaderViewDelegate,
@ -2495,6 +2496,20 @@ typedef enum : NSUInteger {
#endif #endif
} }
#pragma mark - Attachment Picking: Contacts
- (void)chooseContactForSending
{
ContactsPicker *contactsPicker =
[[ContactsPicker alloc] initWithDelegate:self multiSelection:NO subtitleCellType:SubtitleCellValueNone];
contactsPicker.title
= NSLocalizedString(@"CONTACT_PICKER_TITLE", @"navbar title for contact picker when sharing a contact");
UINavigationController *navigationController =
[[UINavigationController alloc] initWithRootViewController:contactsPicker];
[self dismissKeyBoard];
[self presentViewController:navigationController animated:YES completion:nil];
}
#pragma mark - Attachment Picking: Documents #pragma mark - Attachment Picking: Documents
- (void)showAttachmentDocumentPickerMenu - (void)showAttachmentDocumentPickerMenu
@ -2954,8 +2969,7 @@ typedef enum : NSUInteger {
}]; }];
} }
#pragma mark - Storage access
#pragma mark Storage access
- (YapDatabaseConnection *)uiDatabaseConnection - (YapDatabaseConnection *)uiDatabaseConnection
{ {
@ -3576,6 +3590,18 @@ typedef enum : NSUInteger {
[gifAction setValue:gifImage forKey:@"image"]; [gifAction setValue:gifImage forKey:@"image"];
[actionSheetController addAction:gifAction]; [actionSheetController addAction:gifAction];
UIAlertAction *chooseContactAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"ATTACHMENT_MENU_CONTACT_BUTTON", @"attachment menu option to send contact")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[self chooseContactForSending];
}];
// TODO - proper image
UIImage *chooseContactImage = [UIImage imageNamed:@"actionsheet_camera_black"];
OWSAssert(takeMediaImage);
[chooseContactAction setValue:chooseContactImage forKey:@"image"];
[actionSheetController addAction:chooseContactAction];
[self dismissKeyBoard]; [self dismissKeyBoard];
[self presentViewController:actionSheetController animated:true completion:nil]; [self presentViewController:actionSheetController animated:true completion:nil];
} }
@ -4327,9 +4353,8 @@ typedef enum : NSUInteger {
// first item at or after the "view horizon". See the comments below which explain // first item at or after the "view horizon". See the comments below which explain
// the "view horizon". // the "view horizon".
ConversationViewItem *_Nullable lastViewItem = self.viewItems.lastObject; ConversationViewItem *_Nullable lastViewItem = self.viewItems.lastObject;
BOOL hasAddedNewItems = (lastViewItem && BOOL hasAddedNewItems = (lastViewItem && previousLastTimestamp
previousLastTimestamp && && lastViewItem.interaction.timestamp > previousLastTimestamp.unsignedLongLongValue);
lastViewItem.interaction.timestamp > previousLastTimestamp.unsignedLongLongValue);
DDLogInfo(@"%@ hasAddedNewItems: %d", self.logTag, hasAddedNewItems); DDLogInfo(@"%@ hasAddedNewItems: %d", self.logTag, hasAddedNewItems);
if (hasAddedNewItems) { if (hasAddedNewItems) {
@ -4848,6 +4873,37 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
conversationViewCell.isCellVisible = NO; conversationViewCell.isCellVisible = NO;
} }
#pragma mark - ContactsPickerDelegate
- (void)contactsPickerDidCancel:(ContactsPicker *)contactsPicker
{
DDLogDebug(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
}
- (void)contactsPicker:(ContactsPicker *)contactsPicker contactFetchDidFail:(NSError *)error
{
DDLogDebug(@"%@ in %s with error %@", self.logTag, __PRETTY_FUNCTION__, error);
}
- (void)contactsPicker:(ContactsPicker *)contactsPicker didSelectContact:(Contact *)contact
{
DDLogDebug(@"%@ in %s with contact: %@", self.logTag, __PRETTY_FUNCTION__, contact);
// TODO actually build contact message.
self.inputToolbar.messageText = contact.fullName;
}
- (void)contactsPicker:(ContactsPicker *)contactsPicker didSelectMultipleContacts:(NSArray<Contact *> *)contacts
{
OWSFail(@"%@ in %s with contacts: %@", self.logTag, __PRETTY_FUNCTION__, contacts);
}
- (BOOL)contactsPicker:(ContactsPicker *)contactsPicker shouldSelectContact:(Contact *)contact
{
// Any reason to preclude contacts?
return YES;
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -122,6 +122,18 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos
return true return true
} }
func contactsPicker(_: ContactsPicker, contactFetchDidFail error: NSError) {
Logger.error("\(self.logTag) in \(#function) with error: \(error)")
}
func contactsPickerDidCancel(_: ContactsPicker) {
Logger.debug("\(self.logTag) in \(#function)")
}
func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact) {
owsFail("\(logTag) in \(#function) InviteFlow only supports multi-select")
}
// MARK: SMS // MARK: SMS
func messageAction() -> UIAlertAction? { func messageAction() -> UIAlertAction? {
@ -135,6 +147,7 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos
Logger.debug("\(self.TAG) Chose message.") Logger.debug("\(self.TAG) Chose message.")
self.channel = .message self.channel = .message
let picker = ContactsPicker(delegate: self, multiSelection: true, subtitleCellType: .phoneNumber) let picker = ContactsPicker(delegate: self, multiSelection: true, subtitleCellType: .phoneNumber)
picker.title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
let navigationController = UINavigationController(rootViewController: picker) let navigationController = UINavigationController(rootViewController: picker)
self.presentingViewController.present(navigationController, animated: true) self.presentingViewController.present(navigationController, animated: true)
} }
@ -197,6 +210,7 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos
self.channel = .mail self.channel = .mail
let picker = ContactsPicker(delegate: self, multiSelection: true, subtitleCellType: .email) let picker = ContactsPicker(delegate: self, multiSelection: true, subtitleCellType: .email)
picker.title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
let navigationController = UINavigationController(rootViewController: picker) let navigationController = UINavigationController(rootViewController: picker)
self.presentingViewController.present(navigationController, animated: true) self.presentingViewController.present(navigationController, animated: true)
} }

@ -8,29 +8,53 @@ import SignalServiceKit
class ContactCell: UITableViewCell { class ContactCell: UITableViewCell {
static let nib = UINib(nibName: "ContactCell", bundle: nil) public static let kSeparatorHInset: CGFloat = CGFloat(kAvatarDiameter) + 16 + 8
@IBOutlet weak var contactTextLabel: UILabel! static let kAvatarSpacing: CGFloat = 6
@IBOutlet weak var contactDetailTextLabel: UILabel! static let kAvatarDiameter: UInt = 40
@IBOutlet weak var contactImageView: UIImageView!
@IBOutlet weak var contactContainerView: UIView! let contactImageView: AvatarImageView
let textStackView: UIStackView
let titleLabel: UILabel
var subtitleLabel: UILabel
var contact: Contact? var contact: Contact?
override func awakeFromNib() { override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.awakeFromNib() self.contactImageView = AvatarImageView()
self.textStackView = UIStackView()
self.titleLabel = UILabel()
self.titleLabel.font = UIFont.ows_dynamicTypeBody
self.subtitleLabel = UILabel()
self.subtitleLabel.font = UIFont.ows_dynamicTypeSubheadline
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Initialization code
selectionStyle = UITableViewCellSelectionStyle.none selectionStyle = UITableViewCellSelectionStyle.none
contactContainerView.layer.masksToBounds = true textStackView.axis = .vertical
contactContainerView.layer.cornerRadius = contactContainerView.frame.size.width/2 textStackView.addArrangedSubview(titleLabel)
contactImageView.autoSetDimensions(to: CGSize(width: CGFloat(ContactCell.kAvatarDiameter), height: CGFloat(ContactCell.kAvatarDiameter)))
let contentColumns: UIStackView = UIStackView(arrangedSubviews: [contactImageView, textStackView])
contentColumns.axis = .horizontal
contentColumns.spacing = ContactCell.kAvatarSpacing
contentColumns.alignment = .center
self.contentView.addSubview(contentColumns)
contentColumns.autoPinEdgesToSuperviewMargins()
NotificationCenter.default.addObserver(self, selector: #selector(self.didChangePreferredContentSize), name: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.didChangePreferredContentSize), name: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil)
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() { override func prepareForReuse() {
accessoryType = .none accessoryType = .none
self.subtitleLabel.removeFromSuperview()
} }
override func setSelected(_ selected: Bool, animated: Bool) { override func setSelected(_ selected: Bool, animated: Bool) {
@ -39,20 +63,19 @@ class ContactCell: UITableViewCell {
} }
func didChangePreferredContentSize() { func didChangePreferredContentSize() {
contactTextLabel.font = UIFont.preferredFont(forTextStyle: .body) self.titleLabel.font = UIFont.ows_dynamicTypeBody
contactDetailTextLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) self.subtitleLabel.font = UIFont.ows_dynamicTypeSubheadline
} }
func updateContactsinUI(_ contact: Contact, subtitleType: SubtitleCellValue, contactsManager: OWSContactsManager) { func configure(contact: Contact, subtitleType: SubtitleCellValue, contactsManager: OWSContactsManager) {
self.contact = contact self.contact = contact
if contactTextLabel != nil { titleLabel.attributedText = contact.cnContact?.formattedFullName(font: titleLabel.font)
contactTextLabel.attributedText = contact.cnContact?.formattedFullName(font: contactTextLabel.font) updateSubtitle(subtitleType: subtitleType, contact: contact)
}
updateSubtitleBasedonType(subtitleType, contact: contact)
if contact.image == nil { if let contactImage = contact.image {
contactImageView.image = contactImage
} else {
let contactIdForDeterminingBackgroundColor: String let contactIdForDeterminingBackgroundColor: String
if let signalId = contact.parsedPhoneNumbers.first?.toE164() { if let signalId = contact.parsedPhoneNumbers.first?.toE164() {
contactIdForDeterminingBackgroundColor = signalId contactIdForDeterminingBackgroundColor = signalId
@ -60,32 +83,35 @@ class ContactCell: UITableViewCell {
contactIdForDeterminingBackgroundColor = contact.fullName contactIdForDeterminingBackgroundColor = contact.fullName
} }
let kAvatarWidth: UInt = 40
let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contact.fullName, let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contact.fullName,
colorSeed: contactIdForDeterminingBackgroundColor, colorSeed: contactIdForDeterminingBackgroundColor,
diameter: kAvatarWidth, diameter: ContactCell.kAvatarDiameter,
contactsManager: contactsManager) contactsManager: contactsManager)
self.contactImageView?.image = avatarBuilder.buildDefaultImage() contactImageView.image = avatarBuilder.buildDefaultImage()
} else {
self.contactImageView?.image = contact.image
} }
} }
func updateSubtitleBasedonType(_ subtitleType: SubtitleCellValue, contact: Contact) { func updateSubtitle(subtitleType: SubtitleCellValue, contact: Contact) {
switch subtitleType { switch subtitleType {
case .none:
case SubtitleCellValue.phoneNumber: assert(self.subtitleLabel.superview == nil)
if contact.userTextPhoneNumbers.count > 0 { break
self.contactDetailTextLabel.text = "\(contact.userTextPhoneNumbers[0])" case .phoneNumber:
self.textStackView.addArrangedSubview(self.subtitleLabel)
if let firstPhoneNumber = contact.userTextPhoneNumbers.first {
self.subtitleLabel.text = firstPhoneNumber
} else { } else {
self.contactDetailTextLabel.text = NSLocalizedString("CONTACT_PICKER_NO_PHONE_NUMBERS_AVAILABLE", comment: "table cell subtitle when contact card has no known phone number") self.subtitleLabel.text = NSLocalizedString("CONTACT_PICKER_NO_PHONE_NUMBERS_AVAILABLE", comment: "table cell subtitle when contact card has no known phone number")
} }
case SubtitleCellValue.email: case .email:
if contact.emails.count > 0 { self.textStackView.addArrangedSubview(self.subtitleLabel)
self.contactDetailTextLabel.text = "\(contact.emails[0])"
if let firstEmail = contact.emails.first {
self.subtitleLabel.text = firstEmail
} else { } else {
self.contactDetailTextLabel.text = NSLocalizedString("CONTACT_PICKER_NO_EMAILS_AVAILABLE", comment: "table cell subtitle when contact card has no email") self.subtitleLabel.text = NSLocalizedString("CONTACT_PICKER_NO_EMAILS_AVAILABLE", comment: "table cell subtitle when contact card has no email")
} }
} }
} }

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11542" systemVersion="15G1108" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11524"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="60" id="KGk-i7-Jjw" customClass="ContactCell" customModule="Signal" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="59"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bKn-WF-9hk">
<rect key="frame" x="0.0" y="0.0" width="320" height="59"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="59" id="z02-WI-o4t"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HUe-8f-NjY">
<rect key="frame" x="60" y="10" width="250" height="21"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<color key="textColor" red="0.33333333333333331" green="0.33333333333333331" blue="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tqx-Gt-ofy">
<rect key="frame" x="60" y="35" width="234" height="18"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="textColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Bvt-tZ-NrJ">
<rect key="frame" x="10" y="10" width="40" height="40"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mxT-DB-6lI">
<rect key="frame" x="0.0" y="0.0" width="40" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="0.7725490196" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="40" id="8v6-UT-2rU"/>
<constraint firstAttribute="height" constant="40" id="pAq-lQ-W6t"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="Bvt-tZ-NrJ" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="1V9-eI-crl"/>
<constraint firstAttribute="trailing" secondItem="Tqx-Gt-ofy" secondAttribute="trailing" constant="26" id="36W-0D-Qv3"/>
<constraint firstItem="HUe-8f-NjY" firstAttribute="leading" secondItem="Bvt-tZ-NrJ" secondAttribute="trailing" constant="10" id="8Ws-h4-oJp"/>
<constraint firstItem="bKn-WF-9hk" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="NDc-ZH-7qQ"/>
<constraint firstItem="Tqx-Gt-ofy" firstAttribute="leading" secondItem="Bvt-tZ-NrJ" secondAttribute="trailing" constant="10" id="PDp-yF-F2b"/>
<constraint firstItem="HUe-8f-NjY" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="R7t-dd-hcs"/>
<constraint firstItem="Tqx-Gt-ofy" firstAttribute="top" secondItem="HUe-8f-NjY" secondAttribute="bottom" constant="4" id="aNx-bp-Uj5"/>
<constraint firstAttribute="trailing" secondItem="HUe-8f-NjY" secondAttribute="trailing" constant="10" id="cOY-df-aFi"/>
<constraint firstItem="Bvt-tZ-NrJ" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="10" id="m7q-1p-kjc"/>
<constraint firstAttribute="bottom" secondItem="Tqx-Gt-ofy" secondAttribute="bottom" constant="6" id="mEe-il-eMD"/>
<constraint firstAttribute="bottom" secondItem="bKn-WF-9hk" secondAttribute="bottom" id="oHg-Fe-wLe"/>
<constraint firstAttribute="trailing" secondItem="bKn-WF-9hk" secondAttribute="trailing" id="p0G-Qg-21x"/>
<constraint firstItem="bKn-WF-9hk" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="q4R-qf-Bvi"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="contactContainerView" destination="Bvt-tZ-NrJ" id="qqg-f5-Xol"/>
<outlet property="contactDetailTextLabel" destination="Tqx-Gt-ofy" id="Jlj-TK-UJl"/>
<outlet property="contactImageView" destination="mxT-DB-6lI" id="aYl-FS-HAU"/>
<outlet property="contactTextLabel" destination="HUe-8f-NjY" id="Smr-wZ-MHr"/>
</connections>
<point key="canvasLocation" x="245" y="321"/>
</tableViewCell>
</objects>
</document>

@ -139,6 +139,9 @@
/* Accessibility label for attaching photos */ /* Accessibility label for attaching photos */
"ATTACHMENT_LABEL" = "Attachment"; "ATTACHMENT_LABEL" = "Attachment";
/* attachment menu option to send contact */
"ATTACHMENT_MENU_CONTACT_BUTTON" = "Contact";
/* Alert title when picking a document fails for an unknown reason */ /* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Failed to choose document."; "ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Failed to choose document.";
@ -403,6 +406,9 @@
/* table cell subtitle when contact card has no known phone number */ /* table cell subtitle when contact card has no known phone number */
"CONTACT_PICKER_NO_PHONE_NUMBERS_AVAILABLE" = "No phone number available."; "CONTACT_PICKER_NO_PHONE_NUMBERS_AVAILABLE" = "No phone number available.";
/* navbar title for contact picker when sharing a contact */
"CONTACT_PICKER_TITLE" = "Select Contact";
/* title for conversation settings screen */ /* title for conversation settings screen */
"CONVERSATION_SETTINGS" = "Conversation Settings"; "CONVERSATION_SETTINGS" = "Conversation Settings";

Loading…
Cancel
Save