Create contact view.

pull/1/head
Matthew Chen 7 years ago
parent 9c661b220a
commit 2738bcbc58

@ -230,6 +230,7 @@
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; };
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */; };
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */; };
34E88D262098C5AE00A608F4 /* ContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E88D252098C5AE00A608F4 /* ContactViewController.swift */; };
34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E8A8D02085238900B272B1 /* ProtoParsingTest.m */; };
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; };
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */; };
@ -881,6 +882,7 @@
34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIDiskUsage.m; sourceTree = "<group>"; };
34E3EF0E1EFC2684007F6822 /* DebugUIPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIPage.h; sourceTree = "<group>"; };
34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIPage.m; sourceTree = "<group>"; };
34E88D252098C5AE00A608F4 /* ContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewController.swift; sourceTree = "<group>"; };
34E8A8D02085238900B272B1 /* ProtoParsingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProtoParsingTest.m; sourceTree = "<group>"; };
34F308A01ECB469700BB7697 /* OWSBezierPathView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBezierPathView.h; sourceTree = "<group>"; };
34F308A11ECB469700BB7697 /* OWSBezierPathView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBezierPathView.m; sourceTree = "<group>"; };
@ -1624,6 +1626,7 @@
34B3F83B1E8DF1700035BE1A /* CallViewController.swift */,
34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */,
34B3F83F1E8DF1700035BE1A /* ContactsPicker.xib */,
34E88D252098C5AE00A608F4 /* ContactViewController.swift */,
3448BFC01EDF0EA7005B2D69 /* ConversationView */,
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */,
34D8C0221ED3673300188D7C /* DebugUI */,
@ -1631,6 +1634,7 @@
34BECE2C1F7ABCE000D7438D /* GifPicker */,
34386A4C207D0C01009F5D9C /* HomeView */,
34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */,
3496744E2076ACCE00080B5F /* LongTextViewController.swift */,
45B9EE9A200E91FB005D2F2D /* MediaDetailViewController.h */,
45B9EE9B200E91FB005D2F2D /* MediaDetailViewController.m */,
452EC6DE205E9E30000E787C /* MediaGalleryViewController.swift */,
@ -1641,7 +1645,6 @@
34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */,
34B3F8541E8DF1700035BE1A /* NewGroupViewController.h */,
34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */,
3496744E2076ACCE00080B5F /* LongTextViewController.swift */,
34A55F3620485464002CC6DE /* OWS2FARegistrationViewController.h */,
34A55F3520485464002CC6DE /* OWS2FARegistrationViewController.m */,
45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */,
@ -3255,6 +3258,7 @@
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
457F671B20746193000EABCD /* QuotedReplyPreview.swift in Sources */,
34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */,
34E88D262098C5AE00A608F4 /* ContactViewController.swift in Sources */,
FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */,
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */,

@ -0,0 +1,303 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalServiceKit
import SignalMessaging
import Reachability
class ContactViewController: OWSViewController {
let TAG = "[ContactView]"
enum ContactViewMode {
case systemContactWithSignal,
systemContactWithoutSignal,
nonSystemContactWithSignal,
nonSystemContactWithoutSignal,
noPhoneNumber,
unknown
}
enum ContactLookupMode {
case notLookingUp,
lookingUp,
lookedUpNoAccount,
lookedUpHasAccount
}
private var hasLoadedView = false
private var viewMode = ContactViewMode.unknown {
didSet {
SwiftAssertIsOnMainThread(#function)
if oldValue != viewMode && hasLoadedView {
updateContent()
}
}
}
private var lookupMode = ContactLookupMode.notLookingUp {
didSet {
SwiftAssertIsOnMainThread(#function)
if oldValue != lookupMode && hasLoadedView {
updateContent()
}
}
}
let contactsManager: OWSContactsManager
var reachability: Reachability?
override public var canBecomeFirstResponder: Bool {
return true
}
private let contact: OWSContact
// MARK: - Initializers
@available(*, unavailable, message: "use init(call:) constructor instead.")
required init?(coder aDecoder: NSCoder) {
fatalError("Unimplemented")
}
required init(contact: OWSContact) {
contactsManager = Environment.current().contactsManager
self.contact = contact
super.init(nibName: nil, bundle: nil)
tryToDetermineMode()
NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.tryToDetermineMode()
}
reachability = Reachability.forInternetConnection()
NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.tryToDetermineMode()
}
}
// MARK: - View Lifecycle
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.becomeFirstResponder()
contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.tryToDetermineMode()
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.becomeFirstResponder()
}
override func loadView() {
super.loadView()
self.view.backgroundColor = UIColor.white
updateContent()
hasLoadedView = true
}
private func tryToDetermineMode() {
SwiftAssertIsOnMainThread(#function)
guard let firstPhoneNumber = contact.phoneNumbers?.first else {
viewMode = .noPhoneNumber
return
}
if contactsManager.hasSignalAccount(forRecipientId: firstPhoneNumber.phoneNumber) {
viewMode = .systemContactWithSignal
return
}
if contactsManager.allContactsMap[firstPhoneNumber.phoneNumber] != nil {
// We can infer that this is _not_ a signal user because
// all contacts in contactsManager.allContactsMap have
// already been looked up.
viewMode = .systemContactWithoutSignal
return
}
switch lookupMode {
case .notLookingUp:
lookupMode = .lookingUp
viewMode = .unknown
ContactsUpdater.shared().lookupIdentifiers([firstPhoneNumber.phoneNumber], success: { [weak self] (signalRecipients) in
guard let strongSelf = self else { return }
let hasSignalAccount = signalRecipients.filter({ (signalRecipient) -> Bool in
return signalRecipient.recipientId() == firstPhoneNumber.phoneNumber
}).count > 0
if hasSignalAccount {
strongSelf.lookupMode = .lookedUpHasAccount
strongSelf.tryToDetermineMode()
} else {
strongSelf.lookupMode = .lookedUpNoAccount
strongSelf.tryToDetermineMode()
}
}) { [weak self] (error) in
guard let strongSelf = self else { return }
Logger.error("\(strongSelf.logTag) error looking up contact: \(error)")
strongSelf.lookupMode = .notLookingUp
strongSelf.tryToDetermineModeRetry()
}
return
case .lookingUp:
viewMode = .unknown
return
case .lookedUpNoAccount:
viewMode = .nonSystemContactWithoutSignal
return
case .lookedUpHasAccount:
viewMode = .nonSystemContactWithSignal
return
}
}
private func tryToDetermineModeRetry() {
// Try again after a minute.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 60.0) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.tryToDetermineMode()
}
}
private func updateContent() {
SwiftAssertIsOnMainThread(#function)
for subview in self.view.subviews {
subview.removeFromSuperview()
}
// TODO: The design calls for no navigation bar, just a back button.
let topView = UIView.container()
topView.backgroundColor = UIColor(rgbHex: 0xefeff4)
topView.preservesSuperviewLayoutMargins = true
self.view.addSubview(topView)
topView.autoPinEdge(toSuperviewEdge: .top)
topView.autoPinWidthToSuperview()
// TODO: Use actual avatar.
let avatarSize = CGFloat(100)
let avatarView = UIView.container()
avatarView.backgroundColor = UIColor.ows_materialBlue
avatarView.layer.cornerRadius = avatarSize * 0.5
topView.addSubview(avatarView)
avatarView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
avatarView.autoHCenterInSuperview()
avatarView.autoSetDimension(.width, toSize: avatarSize)
avatarView.autoSetDimension(.height, toSize: avatarSize)
let nameLabel = UILabel()
nameLabel.text = contact.displayName
nameLabel.font = UIFont.ows_dynamicTypeTitle3
nameLabel.textColor = UIColor.black
nameLabel.lineBreakMode = .byTruncatingTail
nameLabel.textAlignment = .center
topView.addSubview(nameLabel)
nameLabel.autoPinEdge(.top, to: .bottom, of: avatarView, withOffset: 10)
nameLabel.autoPinLeadingToSuperviewMargin()
nameLabel.autoPinTrailingToSuperviewMargin()
var lastView: UIView = nameLabel
if let firstPhoneNumber = contact.phoneNumbers?.first {
let phoneNumberLabel = UILabel()
phoneNumberLabel.text = firstPhoneNumber.phoneNumber
phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption1
phoneNumberLabel.textColor = UIColor.black
phoneNumberLabel.lineBreakMode = .byTruncatingTail
phoneNumberLabel.textAlignment = .center
topView.addSubview(phoneNumberLabel)
phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10)
phoneNumberLabel.autoPinLeadingToSuperviewMargin()
phoneNumberLabel.autoPinTrailingToSuperviewMargin()
lastView = phoneNumberLabel
}
switch viewMode {
case .systemContactWithSignal:
break
case .systemContactWithoutSignal:
break
case .nonSystemContactWithSignal:
break
case .nonSystemContactWithoutSignal:
break
case .noPhoneNumber:
break
case .unknown:
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
topView.addSubview(activityIndicator)
activityIndicator.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10)
activityIndicator.autoHCenterInSuperview()
lastView = activityIndicator
break
}
lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 10)
}
// acceptIncomingButton = createButton(image: #imageLiteral(resourceName: "call-active-wide"),
// action: #selector(didPressAnswerCall))
// acceptIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_ACCEPT_INCOMING_CALL_LABEL",
// comment: "Accessibility label for accepting incoming calls")
// func createButton(image: UIImage, action: Selector) -> UIButton {
// let button = UIButton()
// button.setImage(image, for: .normal)
// button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(),
// left: buttonInset(),
// bottom: buttonInset(),
// right: buttonInset())
// button.addTarget(self, action: action, for: .touchUpInside)
// button.autoSetDimension(.width, toSize: buttonSize())
// button.autoSetDimension(.height, toSize: buttonSize())
// return button
// }
//
// // MARK: - Layout
//
//
// func didPressFlipCamera(sender: UIButton) {
// // toggle value
// sender.isSelected = !sender.isSelected
//
// let useBackCamera = sender.isSelected
// Logger.info("\(TAG) in \(#function) with useBackCamera: \(useBackCamera)")
//
// callUIAdapter.setCameraSource(call: call, useBackCamera: useBackCamera)
// }
//
// internal func dismissImmediately(completion: (() -> Void)?) {
// if ContactView.kShowCallViewOnSeparateWindow {
// OWSWindowManager.shared().endCall(self)
// completion?()
// } else {
// self.dismiss(animated: true, completion: completion)
// }
// }
//
}

@ -40,6 +40,8 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
quotedReply:(OWSQuotedReplyModel *)quotedReply
failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer;
- (void)didTapContactShareViewItem:(ConversationViewItem *)viewItem;
@end
@interface OWSMessageBubbleView : UIView

@ -1203,7 +1203,7 @@ NS_ASSUME_NONNULL_BEGIN
break;
}
case OWSMessageCellType_ContactShare:
// TODO:
[self.delegate didTapContactShareViewItem:self.viewItem];
break;
}
}

@ -2073,6 +2073,17 @@ typedef enum : NSUInteger {
[self.navigationController pushViewController:view animated:YES];
}
- (void)didTapContactShareViewItem:(ConversationViewItem *)conversationItem
{
OWSAssertIsOnMainThread();
OWSAssert(conversationItem);
OWSAssert(conversationItem.contactShare);
OWSAssert([conversationItem.interaction isKindOfClass:[TSMessage class]]);
ContactViewController *view = [[ContactViewController alloc] initWithContact:conversationItem.contactShare];
[self.navigationController pushViewController:view animated:YES];
}
- (void)didTapFailedIncomingAttachment:(ConversationViewItem *)viewItem
attachmentPointer:(TSAttachmentPointer *)attachmentPointer
{

@ -615,6 +615,15 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView)
}
func didTapContactShare(_ viewItem: ConversationViewItem) {
guard let contact = viewItem.contactShare() else {
owsFail("\(logTag) missing contact.")
return
}
let contactViewController = ContactViewController(contact: contact)
self.navigationController?.pushViewController(contactViewController, animated: true)
}
var audioAttachmentPlayer: OWSAudioPlayer?
func didTapAudioViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream) {

Loading…
Cancel
Save