mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Contact picking
// FREEBIE
This commit is contained in:
parent
0e858bc090
commit
5c0c01dea2
|
@ -248,7 +248,6 @@
|
|||
451166C01FD86B98000739BA /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451166BF1FD86B98000739BA /* AccountManager.swift */; };
|
||||
451573962061B49500803601 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451573952061B49500803601 /* GradientView.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 */; };
|
||||
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, ); }; };
|
||||
|
@ -900,7 +899,6 @@
|
|||
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; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
@ -2085,7 +2083,6 @@
|
|||
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */,
|
||||
34E3E5671EC4B19400495BAC /* AudioProgressView.swift */,
|
||||
451764291DE939FD00EDB8B9 /* ContactCell.swift */,
|
||||
451764281DE939FD00EDB8B9 /* ContactCell.xib */,
|
||||
4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */,
|
||||
45A663C41F92EC760027B59E /* GroupTableViewCell.swift */,
|
||||
45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */,
|
||||
|
@ -2698,7 +2695,6 @@
|
|||
B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */,
|
||||
34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */,
|
||||
34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */,
|
||||
4517642A1DE939FD00EDB8B9 /* ContactCell.xib in Resources */,
|
||||
AD83FF431A73426500B5C81A /* audio_play_button@2x.png in Resources */,
|
||||
45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */,
|
||||
34B3F8781E8DF1700035BE1A /* ContactsPicker.xib in Resources */,
|
||||
|
|
|
@ -11,50 +11,52 @@ import UIKit
|
|||
import Contacts
|
||||
import SignalServiceKit
|
||||
|
||||
public protocol ContactsPickerDelegate {
|
||||
func contactsPicker(_: ContactsPicker, didContactFetchFailed error: NSError)
|
||||
func contactsPicker(_: ContactsPicker, didCancel error: NSError)
|
||||
@objc
|
||||
public protocol ContactsPickerDelegate: class {
|
||||
func contactsPicker(_: ContactsPicker, contactFetchDidFail error: NSError)
|
||||
func contactsPickerDidCancel(_: ContactsPicker)
|
||||
func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact)
|
||||
func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact])
|
||||
func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
public enum SubtitleCellValue {
|
||||
case phoneNumber
|
||||
case email
|
||||
@objc
|
||||
public enum SubtitleCellValue: Int {
|
||||
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 searchBar: UISearchBar!
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
let TAG = "[ContactsPicker]"
|
||||
let contactCellReuseIdentifier = "contactCellReuseIdentifier"
|
||||
let contactsManager: OWSContactsManager
|
||||
let collation = UILocalizedIndexedCollation.current()
|
||||
let contactStore = CNContactStore()
|
||||
private let TAG = "[ContactsPicker]"
|
||||
private let contactCellReuseIdentifier = "contactCellReuseIdentifier"
|
||||
|
||||
private var contactsManager: OWSContactsManager {
|
||||
return Environment.current().contactsManager
|
||||
}
|
||||
|
||||
private let collation = UILocalizedIndexedCollation.current()
|
||||
private let contactStore = CNContactStore()
|
||||
|
||||
// Data Source State
|
||||
lazy var sections = [[CNContact]]()
|
||||
lazy var filteredSections = [[CNContact]]()
|
||||
lazy var selectedContacts = [Contact]()
|
||||
private lazy var sections = [[CNContact]]()
|
||||
private lazy var filteredSections = [[CNContact]]()
|
||||
private lazy var selectedContacts = [Contact]()
|
||||
|
||||
// Configuration
|
||||
open var contactsPickerDelegate: ContactsPickerDelegate?
|
||||
var subtitleCellValue = SubtitleCellValue.phoneNumber
|
||||
var multiSelectEnabled = false
|
||||
let allowedContactKeys: [CNKeyDescriptor] = [
|
||||
public weak var contactsPickerDelegate: ContactsPickerDelegate?
|
||||
private let subtitleCellValue: SubtitleCellValue
|
||||
private let multiSelectEnabled: Bool
|
||||
private let allowedContactKeys: [CNKeyDescriptor] = [
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
||||
CNContactThumbnailImageDataKey as CNKeyDescriptor,
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
|
@ -65,10 +67,9 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
|
|||
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
|
||||
|
||||
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 = []
|
||||
|
||||
// Auto size cells for dynamic type
|
||||
|
@ -77,6 +78,8 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
|
|||
|
||||
tableView.allowsMultipleSelection = multiSelectEnabled
|
||||
|
||||
tableView.separatorInset = UIEdgeInsets(top: 0, left: ContactCell.kSeparatorHInset, bottom: 0, right: 16)
|
||||
|
||||
registerContactCell()
|
||||
initializeBarButtons()
|
||||
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)
|
||||
}
|
||||
|
||||
func didChangePreferredContentSize() {
|
||||
@objc
|
||||
public func didChangePreferredContentSize() {
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
|
||||
func initializeBarButtons() {
|
||||
let cancelButton = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(onTouchCancelButton))
|
||||
private func initializeBarButtons() {
|
||||
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onTouchCancelButton))
|
||||
self.navigationItem.leftBarButtonItem = cancelButton
|
||||
|
||||
if multiSelectEnabled {
|
||||
|
@ -99,48 +103,33 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate func registerContactCell() {
|
||||
tableView.register(ContactCell.nib, forCellReuseIdentifier: contactCellReuseIdentifier)
|
||||
private func registerContactCell() {
|
||||
tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
|
||||
}
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
init() {
|
||||
contactsManager = Environment.current().contactsManager
|
||||
@objc
|
||||
required public init(delegate: ContactsPickerDelegate?, multiSelection: Bool, subtitleCellType: SubtitleCellValue) {
|
||||
multiSelectEnabled = multiSelection
|
||||
subtitleCellValue = subtitleCellType
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
contactsPickerDelegate = delegate
|
||||
}
|
||||
|
||||
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
|
||||
contactsPickerDelegate = delegate
|
||||
}
|
||||
|
||||
convenience public init(delegate: ContactsPickerDelegate?, multiSelection: Bool, subtitleCellType: SubtitleCellValue) {
|
||||
self.init()
|
||||
multiSelectEnabled = multiSelection
|
||||
contactsPickerDelegate = delegate
|
||||
subtitleCellValue = subtitleCellType
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - Contact Operations
|
||||
|
||||
open func reloadContacts() {
|
||||
private func reloadContacts() {
|
||||
getContacts( onError: { error in
|
||||
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) {
|
||||
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")
|
||||
|
@ -152,7 +141,7 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
|
|||
|
||||
let cancelAction = UIAlertAction(title: dismissText, style: .cancel, handler: { _ in
|
||||
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)
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
})
|
||||
|
@ -229,7 +218,7 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
|
|||
let cnContact = dataSource[indexPath.section][indexPath.row]
|
||||
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 })
|
||||
cell.isSelected = isSelected
|
||||
|
||||
|
@ -302,7 +291,7 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa
|
|||
// MARK: - Button Actions
|
||||
|
||||
func onTouchCancelButton() {
|
||||
contactsPickerDelegate?.contactsPicker(self, didCancel: NSError(domain: "contactsPickerErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey: "User Canceled Selection"]))
|
||||
contactsPickerDelegate?.contactsPickerDidCancel(self)
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
|
|
@ -118,9 +118,10 @@ typedef enum : NSUInteger {
|
|||
|
||||
@interface ConversationViewController () <AttachmentApprovalViewControllerDelegate,
|
||||
AVAudioPlayerDelegate,
|
||||
ContactsViewHelperDelegate,
|
||||
ContactEditingDelegate,
|
||||
CNContactViewControllerDelegate,
|
||||
ContactEditingDelegate,
|
||||
ContactsPickerDelegate,
|
||||
ContactsViewHelperDelegate,
|
||||
DisappearingTimerConfigurationViewDelegate,
|
||||
OWSConversationSettingsViewDelegate,
|
||||
ConversationHeaderViewDelegate,
|
||||
|
@ -1562,7 +1563,7 @@ typedef enum : NSUInteger {
|
|||
if (self.dynamicInteractions.unreadIndicatorPosition) {
|
||||
NSUInteger unreadIndicatorPosition
|
||||
= (NSUInteger)[self.dynamicInteractions.unreadIndicatorPosition longValue];
|
||||
|
||||
|
||||
// If there is an unread indicator, increase the initial load window
|
||||
// to include it.
|
||||
OWSAssert(unreadIndicatorPosition > 0);
|
||||
|
@ -1585,7 +1586,7 @@ typedef enum : NSUInteger {
|
|||
rangeLength = MIN(rangeLength, kYapDatabaseRangeMaxLength);
|
||||
|
||||
self.lastRangeLength = rangeLength;
|
||||
|
||||
|
||||
YapDatabaseViewRangeOptions *rangeOptions =
|
||||
[YapDatabaseViewRangeOptions flexibleRangeWithLength:rangeLength offset:0 from:YapDatabaseViewEnd];
|
||||
|
||||
|
@ -2495,6 +2496,20 @@ typedef enum : NSUInteger {
|
|||
#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
|
||||
|
||||
- (void)showAttachmentDocumentPickerMenu
|
||||
|
@ -2677,13 +2692,13 @@ typedef enum : NSUInteger {
|
|||
DDLogWarn(@"%@ camera permission denied.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
UIImagePickerController *picker = [UIImagePickerController new];
|
||||
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
|
||||
picker.mediaTypes = @[ (__bridge NSString *)kUTTypeImage, (__bridge NSString *)kUTTypeMovie ];
|
||||
picker.allowsEditing = NO;
|
||||
picker.delegate = self;
|
||||
|
||||
|
||||
[self dismissKeyBoard];
|
||||
[self presentViewController:picker animated:YES completion:[UIUtil modalCompletionBlock]];
|
||||
}];
|
||||
|
@ -2714,12 +2729,12 @@ typedef enum : NSUInteger {
|
|||
DDLogWarn(@"%@ Media Library permission denied.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
UIImagePickerController *picker = [UIImagePickerController new];
|
||||
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
||||
picker.delegate = self;
|
||||
picker.mediaTypes = @[ (__bridge NSString *)kUTTypeImage, (__bridge NSString *)kUTTypeMovie ];
|
||||
|
||||
|
||||
[self dismissKeyBoard];
|
||||
[self presentViewController:picker animated:YES completion:[UIUtil modalCompletionBlock]];
|
||||
}];
|
||||
|
@ -2954,8 +2969,7 @@ typedef enum : NSUInteger {
|
|||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Storage access
|
||||
#pragma mark - Storage access
|
||||
|
||||
- (YapDatabaseConnection *)uiDatabaseConnection
|
||||
{
|
||||
|
@ -3207,14 +3221,14 @@ typedef enum : NSUInteger {
|
|||
BOOL shouldAnimateUpdates = [self shouldAnimateRowUpdates:rowChanges oldViewItemCount:oldViewItemCount];
|
||||
void (^batchUpdatesCompletion)(BOOL) = ^(BOOL finished) {
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
|
||||
|
||||
|
||||
if (!finished) {
|
||||
DDLogInfo(@"%@ performBatchUpdates did not finish", self.logTag);
|
||||
}
|
||||
|
||||
|
||||
[self updateLastVisibleTimestamp];
|
||||
|
||||
|
||||
if (scrollToBottom) {
|
||||
[self scrollToBottomAnimated:shouldAnimateScrollToBottom && shouldAnimateUpdates];
|
||||
}
|
||||
|
@ -3334,13 +3348,13 @@ typedef enum : NSUInteger {
|
|||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (strongSelf.voiceMessageUUID != voiceMessageUUID) {
|
||||
// This voice message recording has been cancelled
|
||||
// before recording could begin.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (granted) {
|
||||
[strongSelf startRecordingVoiceMemo];
|
||||
} else {
|
||||
|
@ -3576,6 +3590,18 @@ typedef enum : NSUInteger {
|
|||
[gifAction setValue:gifImage forKey:@"image"];
|
||||
[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 presentViewController:actionSheetController animated:true completion:nil];
|
||||
}
|
||||
|
@ -4066,7 +4092,7 @@ typedef enum : NSUInteger {
|
|||
[self updateGroupModelTo:groupModel
|
||||
successCompletion:^{
|
||||
DDLogInfo(@"Group updated, removing group creation error.");
|
||||
|
||||
|
||||
[message remove];
|
||||
}];
|
||||
}
|
||||
|
@ -4327,9 +4353,8 @@ typedef enum : NSUInteger {
|
|||
// first item at or after the "view horizon". See the comments below which explain
|
||||
// the "view horizon".
|
||||
ConversationViewItem *_Nullable lastViewItem = self.viewItems.lastObject;
|
||||
BOOL hasAddedNewItems = (lastViewItem &&
|
||||
previousLastTimestamp &&
|
||||
lastViewItem.interaction.timestamp > previousLastTimestamp.unsignedLongLongValue);
|
||||
BOOL hasAddedNewItems = (lastViewItem && previousLastTimestamp
|
||||
&& lastViewItem.interaction.timestamp > previousLastTimestamp.unsignedLongLongValue);
|
||||
|
||||
DDLogInfo(@"%@ hasAddedNewItems: %d", self.logTag, hasAddedNewItems);
|
||||
if (hasAddedNewItems) {
|
||||
|
@ -4848,6 +4873,37 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
|
|||
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
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -122,6 +122,18 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos
|
|||
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
|
||||
|
||||
func messageAction() -> UIAlertAction? {
|
||||
|
@ -135,6 +147,7 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos
|
|||
Logger.debug("\(self.TAG) Chose message.")
|
||||
self.channel = .message
|
||||
let picker = ContactsPicker(delegate: self, multiSelection: true, subtitleCellType: .phoneNumber)
|
||||
picker.title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
|
||||
let navigationController = UINavigationController(rootViewController: picker)
|
||||
self.presentingViewController.present(navigationController, animated: true)
|
||||
}
|
||||
|
@ -197,6 +210,7 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos
|
|||
self.channel = .mail
|
||||
|
||||
let picker = ContactsPicker(delegate: self, multiSelection: true, subtitleCellType: .email)
|
||||
picker.title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
|
||||
let navigationController = UINavigationController(rootViewController: picker)
|
||||
self.presentingViewController.present(navigationController, animated: true)
|
||||
}
|
||||
|
|
|
@ -8,29 +8,53 @@ import SignalServiceKit
|
|||
|
||||
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!
|
||||
@IBOutlet weak var contactDetailTextLabel: UILabel!
|
||||
@IBOutlet weak var contactImageView: UIImageView!
|
||||
@IBOutlet weak var contactContainerView: UIView!
|
||||
static let kAvatarSpacing: CGFloat = 6
|
||||
static let kAvatarDiameter: UInt = 40
|
||||
|
||||
let contactImageView: AvatarImageView
|
||||
let textStackView: UIStackView
|
||||
let titleLabel: UILabel
|
||||
var subtitleLabel: UILabel
|
||||
|
||||
var contact: Contact?
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
|
||||
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
|
||||
|
||||
contactContainerView.layer.masksToBounds = true
|
||||
contactContainerView.layer.cornerRadius = contactContainerView.frame.size.width/2
|
||||
textStackView.axis = .vertical
|
||||
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)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
accessoryType = .none
|
||||
self.subtitleLabel.removeFromSuperview()
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
|
@ -39,20 +63,19 @@ class ContactCell: UITableViewCell {
|
|||
}
|
||||
|
||||
func didChangePreferredContentSize() {
|
||||
contactTextLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
contactDetailTextLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
|
||||
self.titleLabel.font = UIFont.ows_dynamicTypeBody
|
||||
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
|
||||
|
||||
if contactTextLabel != nil {
|
||||
contactTextLabel.attributedText = contact.cnContact?.formattedFullName(font: contactTextLabel.font)
|
||||
}
|
||||
titleLabel.attributedText = contact.cnContact?.formattedFullName(font: titleLabel.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
|
||||
if let signalId = contact.parsedPhoneNumbers.first?.toE164() {
|
||||
contactIdForDeterminingBackgroundColor = signalId
|
||||
|
@ -60,32 +83,35 @@ class ContactCell: UITableViewCell {
|
|||
contactIdForDeterminingBackgroundColor = contact.fullName
|
||||
}
|
||||
|
||||
let kAvatarWidth: UInt = 40
|
||||
let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contact.fullName,
|
||||
colorSeed: contactIdForDeterminingBackgroundColor,
|
||||
diameter: kAvatarWidth,
|
||||
diameter: ContactCell.kAvatarDiameter,
|
||||
contactsManager: contactsManager)
|
||||
|
||||
self.contactImageView?.image = avatarBuilder.buildDefaultImage()
|
||||
} else {
|
||||
self.contactImageView?.image = contact.image
|
||||
contactImageView.image = avatarBuilder.buildDefaultImage()
|
||||
}
|
||||
}
|
||||
|
||||
func updateSubtitleBasedonType(_ subtitleType: SubtitleCellValue, contact: Contact) {
|
||||
func updateSubtitle(subtitleType: SubtitleCellValue, contact: Contact) {
|
||||
switch subtitleType {
|
||||
case .none:
|
||||
assert(self.subtitleLabel.superview == nil)
|
||||
break
|
||||
case .phoneNumber:
|
||||
self.textStackView.addArrangedSubview(self.subtitleLabel)
|
||||
|
||||
case SubtitleCellValue.phoneNumber:
|
||||
if contact.userTextPhoneNumbers.count > 0 {
|
||||
self.contactDetailTextLabel.text = "\(contact.userTextPhoneNumbers[0])"
|
||||
if let firstPhoneNumber = contact.userTextPhoneNumbers.first {
|
||||
self.subtitleLabel.text = firstPhoneNumber
|
||||
} 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:
|
||||
if contact.emails.count > 0 {
|
||||
self.contactDetailTextLabel.text = "\(contact.emails[0])"
|
||||
case .email:
|
||||
self.textStackView.addArrangedSubview(self.subtitleLabel)
|
||||
|
||||
if let firstEmail = contact.emails.first {
|
||||
self.subtitleLabel.text = firstEmail
|
||||
} 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 */
|
||||
"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 */
|
||||
"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 */
|
||||
"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 */
|
||||
"CONVERSATION_SETTINGS" = "Conversation Settings";
|
||||
|
||||
|
|
Loading…
Reference in a new issue