Signal-iOS/Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController+Header.swift
2020-04-06 11:09:06 -03:00

227 lines
9.4 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
import UIKit
// TODO: We should describe which state updates & when it is committed.
extension ConversationSettingsViewController {
private var subtitlePointSize: CGFloat {
return 12
}
private var threadName: String {
var threadName = contactsManager.displayNameWithSneakyTransaction(thread: thread)
if let contactThread = thread as? TSContactThread {
if let phoneNumber = contactThread.contactAddress.phoneNumber,
phoneNumber == threadName {
threadName = PhoneNumber.bestEffortFormatPartialUserSpecifiedText(toLookLikeAPhoneNumber: phoneNumber)
}
}
return threadName
}
private struct HeaderBuilder {
let viewController: ConversationSettingsViewController
var subviews = [UIView]()
init(viewController: ConversationSettingsViewController) {
self.viewController = viewController
addFirstSubviews()
}
mutating func addFirstSubviews() {
let avatarView = buildAvatarView()
let avatarWrapper = UIView.container()
avatarWrapper.addSubview(avatarView)
avatarView.autoPinEdgesToSuperviewEdges()
if let groupThread = viewController.thread as? TSGroupThread,
groupThread.groupModel.groupAvatarData == nil,
viewController.canEditConversationAttributes {
let cameraButton = GroupAttributesEditorHelper.buildCameraButtonForCorner()
avatarWrapper.addSubview(cameraButton)
cameraButton.autoPinEdge(toSuperviewEdge: .trailing)
cameraButton.autoPinEdge(toSuperviewEdge: .bottom)
}
subviews.append(avatarWrapper)
subviews.append(UIView.spacer(withHeight: 8))
subviews.append(buildThreadNameLabel())
}
func buildAvatarView() -> UIView {
let avatarImage = OWSAvatarBuilder.buildImage(thread: viewController.thread,
diameter: kLargeAvatarSize)
let avatarView = AvatarImageView(image: avatarImage)
let avatarSize = CGFloat(kLargeAvatarSize)
avatarView.autoSetDimension(.width, toSize: avatarSize)
avatarView.autoSetDimension(.height, toSize: avatarSize)
// Track the most recent avatar view.
viewController.avatarView = avatarView
return avatarView
}
func buildThreadNameLabel() -> UILabel {
let label = UILabel()
label.text = viewController.threadName
label.textColor = Theme.primaryTextColor
label.font = .ows_dynamicTypeTitle2
label.lineBreakMode = .byTruncatingTail
return label
}
mutating func addSubtitleLabel(text: String, font: UIFont? = nil) {
addSubtitleLabel(attributedText: NSAttributedString(string: text), font: font)
}
mutating func addSubtitleLabel(attributedText: NSAttributedString, font: UIFont? = nil) {
subviews.append(UIView.spacer(withHeight: 2))
subviews.append(buildHeaderSubtitleLabel(attributedText: attributedText, font: font))
}
func buildHeaderSubtitleLabel(attributedText: NSAttributedString,
font: UIFont?) -> UILabel {
let label = UILabel()
label.attributedText = attributedText
label.textColor = Theme.secondaryTextAndIconColor
if let font = font {
label.font = font
} else {
label.font = UIFont.ows_regularFont(withSize: viewController.subtitlePointSize)
}
label.lineBreakMode = .byTruncatingTail
return label
}
mutating func addLastSubviews() {
// TODO Message Request: In order to debug the profile is getting shared in the right moments,
// display the thread whitelist state in settings. Eventually we can probably delete this.
#if DEBUG
let viewController = self.viewController
let isThreadInProfileWhitelist =
viewController.databaseStorage.uiRead { transaction in
return viewController.profileManager.isThread(inProfileWhitelist: viewController.thread,
transaction: transaction)
}
let hasSharedProfile = String(format: "Whitelisted: %@", isThreadInProfileWhitelist ? "Yes" : "No")
addSubtitleLabel(text: hasSharedProfile)
#endif
}
func build() -> UIView {
let header = UIStackView(arrangedSubviews: subviews)
header.axis = .vertical
header.alignment = .center
header.layoutMargins = UIEdgeInsets(top: 0, leading: 18, bottom: 16, trailing: 18)
header.isLayoutMarginsRelativeArrangement = true
if viewController.canEditConversationAttributes {
header.addGestureRecognizer(UITapGestureRecognizer(target: viewController, action: #selector(conversationNameTouched)))
}
header.isUserInteractionEnabled = true
header.accessibilityIdentifier = UIView.accessibilityIdentifier(in: viewController, name: "mainSectionHeader")
return header
}
}
private func buildHeaderForGroup(groupThread: TSGroupThread) -> UIView {
var builder = HeaderBuilder(viewController: self)
let groupMembersText = String(format: NSLocalizedString("CONVERSATION_SETTINGS_GROUP_MEMBER_COUNT_FORMAT",
comment: "Format for the 'group member count' indicator in conversation settings view. Embeds {the number of group members}."),
OWSFormat.formatInt(groupThread.groupModel.groupMembership.nonPendingMembers.count))
builder.addSubtitleLabel(text: groupMembersText,
font: .ows_dynamicTypeBody)
builder.addLastSubviews()
let header = builder.build()
// This will only appear in internal, qa & dev builds.
if DebugFlags.groupsV2showV2Indicator {
let indicatorLabel = UILabel()
indicatorLabel.text = thread.isGroupV2Thread ? "v2" : "v1"
indicatorLabel.textColor = Theme.secondaryTextAndIconColor
indicatorLabel.font = .ows_dynamicTypeBody
header.addSubview(indicatorLabel)
indicatorLabel.autoPinEdge(toSuperviewMargin: .trailing)
indicatorLabel.autoPinEdge(toSuperviewMargin: .bottom)
}
return header
}
private func buildHeaderForContact(contactThread: TSContactThread) -> UIView {
var builder = HeaderBuilder(viewController: self)
let threadName = contactsManager.displayNameWithSneakyTransaction(thread: contactThread)
let recipientAddress = contactThread.contactAddress
if let phoneNumber = recipientAddress.phoneNumber {
let formattedPhoneNumber =
PhoneNumber.bestEffortFormatPartialUserSpecifiedText(toLookLikeAPhoneNumber: phoneNumber)
if threadName != formattedPhoneNumber {
builder.addSubtitleLabel(text: formattedPhoneNumber)
}
}
if let username = (databaseStorage.uiRead { transaction in
return self.profileManager.username(for: recipientAddress, transaction: transaction)
}),
username.count > 0 {
if let formattedUsername = CommonFormats.formatUsername(username),
threadName != formattedUsername {
builder.addSubtitleLabel(text: formattedUsername)
}
}
if !RemoteConfig.messageRequests
&& !contactsManager.hasNameInSystemContacts(for: recipientAddress) {
if let profileName = contactsManager.formattedProfileName(for: recipientAddress) {
builder.addSubtitleLabel(text: profileName)
}
}
#if TESTABLE_BUILD
let uuidText = String(format: "UUID: %@", contactThread.contactAddress.uuid?.uuidString ?? "Unknown")
builder.addSubtitleLabel(text: uuidText)
#endif
let isVerified = identityManager.verificationState(for: recipientAddress) == .verified
if isVerified {
let subtitle = NSMutableAttributedString()
// "checkmark"
subtitle.append("\u{f00c} ",
attributes: [
.font: UIFont.ows_fontAwesomeFont(subtitlePointSize)
])
subtitle.append(NSLocalizedString("PRIVACY_IDENTITY_IS_VERIFIED_BADGE",
comment: "Badge indicating that the user is verified."))
builder.addSubtitleLabel(attributedText: subtitle)
}
builder.addLastSubviews()
return builder.build()
}
func buildMainHeader() -> UIView {
if let groupThread = thread as? TSGroupThread {
return buildHeaderForGroup(groupThread: groupThread)
} else if let contactThread = thread as? TSContactThread {
return buildHeaderForContact(contactThread: contactThread)
} else {
owsFailDebug("Invalid thread.")
return UIView()
}
}
}