Signal-iOS/Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController+Contents.swift
2020-06-16 12:28:21 -07:00

812 lines
40 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
import UIKit
extension ConversationSettingsViewController {
// MARK: - Helpers
private var iconSpacingSmall: CGFloat {
return kContactCellAvatarTextMargin
}
private var iconSpacingLarge: CGFloat {
return OWSTableItem.iconSpacing
}
private var isContactThread: Bool {
return !thread.isGroupThread
}
private var hasExistingContact: Bool {
guard let contactThread = thread as? TSContactThread else {
owsFailDebug("Invalid thread.")
return false
}
return contactsManager.hasSignalAccount(for: contactThread.contactAddress)
}
private func buildCell(name: String, icon: ThemeIcon,
disclosureIconColor: UIColor? = nil,
accessibilityIdentifier: String? = nil) -> UITableViewCell {
let cell = OWSTableItem.buildCell(name: name, icon: icon, accessibilityIdentifier: accessibilityIdentifier)
if let disclosureIconColor = disclosureIconColor {
let accessoryView = OWSColorPickerAccessoryView(color: disclosureIconColor)
accessoryView.sizeToFit()
cell.accessoryView = accessoryView
}
return cell
}
// MARK: - Table
func updateTableContents() {
let contents = OWSTableContents()
contents.title = NSLocalizedString("CONVERSATION_SETTINGS", comment: "title for conversation settings screen")
let isNoteToSelf = thread.isNoteToSelf
// Main section.
let mainSection = OWSTableSection()
let header = buildMainHeader()
lastContentWidth = view.width
let headerHeight = header.systemLayoutSizeFitting(view.frame.size).height
mainSection.customHeaderView = header
mainSection.customHeaderHeight = NSNumber(value: Float(headerHeight))
addBasicItems(to: mainSection)
// TODO: We can remove this item once message requests are mandatory.
addProfileSharingItems(to: mainSection)
if shouldShowColorPicker {
addColorPickerItems(to: mainSection)
}
contents.addSection(mainSection)
buildDisappearingMessagesSection(to: mainSection)
if !isNoteToSelf {
contents.addSection(buildNotificationsSection())
}
if let groupModel = currentGroupModel {
if canEditConversationAccess,
let groupModelV2 = groupModel as? TSGroupModelV2 {
buildGroupAccessSections(groupModelV2: groupModelV2,
contents: contents)
}
contents.addSection(buildGroupMembershipSection(groupModel: groupModel))
if thread.isGroupV2Thread,
let localAddress = tsAccountManager.localAddress {
contents.addSection(buildPendingMembersSection(groupModel: groupModel,
localAddress: localAddress))
}
}
if !isNoteToSelf {
contents.addSection(buildBlockAndLeaveSection())
}
let emptySection = OWSTableSection()
emptySection.customFooterHeight = 24
contents.addSection(emptySection)
self.contents = contents
updateNavigationBar()
}
private func addBasicItems(to section: OWSTableSection) {
let isNoteToSelf = thread.isNoteToSelf
if let contactThread = thread as? TSContactThread,
contactsManager.supportsContactEditing && !hasExistingContact {
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
return OWSTableItem.buildDisclosureCell(name: NSLocalizedString("CONVERSATION_SETTINGS_ADD_TO_SYSTEM_CONTACTS",
comment: "button in conversation settings view."),
icon: .settingsAddToContacts,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "add_to_system_contacts"))
},
actionBlock: { [weak self] in
self?.showAddToSystemContactsActionSheet(contactThread: contactThread)
}))
}
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
return OWSTableItem.buildDisclosureCell(name: MediaStrings.allMedia,
icon: .settingsAllMedia,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "all_media"))
},
actionBlock: { [weak self] in
self?.showMediaGallery()
}))
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let title = NSLocalizedString("CONVERSATION_SETTINGS_SEARCH",
comment: "Table cell label in conversation settings which returns the user to the conversation with 'search mode' activated")
return OWSTableItem.buildDisclosureCell(name: title,
icon: .settingsSearch,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "search"))
},
actionBlock: { [weak self] in
self?.tappedConversationSearch()
}))
if !isNoteToSelf && !isGroupThread && thread.hasSafetyNumbers() {
// Safety Numbers
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
return OWSTableItem.buildDisclosureCell(name: NSLocalizedString("VERIFY_PRIVACY",
comment: "Label for button or row which allows users to verify the safety number of another user."),
icon: .settingsViewSafetyNumber,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "safety_numbers"))
},
actionBlock: { [weak self] in
self?.showVerificationView()
}))
}
// Indicate if the user is in the system contacts
if !isNoteToSelf && !isGroupThread && hasExistingContact {
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
return OWSTableItem.buildDisclosureCell(name: NSLocalizedString(
"CONVERSATION_SETTINGS_VIEW_IS_SYSTEM_CONTACT",
comment: "Indicates that user is in the system contacts list."),
icon: .settingsUserInContacts,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "is_in_contacts"))
},
actionBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return
}
if self.contactsManager.supportsContactEditing {
self.presentContactViewController()
}
}))
}
}
private func addProfileSharingItems(to section: OWSTableSection) {
guard !thread.isGroupV2Thread else {
return
}
let isLocalUserInConversation = self.isLocalUserInConversation
// For pre-message request threads, allow manually sharing your profile if the thread is not whitelisted.
let (isPreMessageRequestsThread, isThreadInProfileWhitelist) = databaseStorage.uiRead { transaction -> (Bool, Bool) in
return (
GRDBThreadFinder.isPreMessageRequestsThread(self.thread, transaction: transaction.unwrapGrdbRead),
self.profileManager.isThread(inProfileWhitelist: self.thread, transaction: transaction)
)
}
if isPreMessageRequestsThread && !isThreadInProfileWhitelist {
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let title =
(self.isGroupThread
? NSLocalizedString("CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE_WITH_GROUP",
comment: "Action that shares user profile with a group.")
: NSLocalizedString("CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE_WITH_USER",
comment: "Action that shares user profile with a user."))
let cell = OWSTableItem.buildDisclosureCell(name: title,
icon: .settingsProfile,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "share_profile"))
cell.isUserInteractionEnabled = isLocalUserInConversation
return cell
},
actionBlock: { [weak self] in
self?.showShareProfileAlert()
}))
}
}
private func buildDisappearingMessagesSection(to section: OWSTableSection) {
let canEditConversationAttributes = self.canEditConversationAttributes
let disappearingMessagesConfiguration: OWSDisappearingMessagesConfiguration = self.disappearingMessagesConfiguration
let switchAction = #selector(disappearingMessagesSwitchValueDidChange)
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cell = OWSTableItem.newCell()
cell.preservesSuperviewLayoutMargins = true
cell.contentView.preservesSuperviewLayoutMargins = true
cell.selectionStyle = .none
let icon: ThemeIcon = (disappearingMessagesConfiguration.isEnabled
? .settingsTimer
: .settingsTimerDisabled)
let iconView = OWSTableItem.imageView(forIcon: icon)
let rowLabel = UILabel()
rowLabel.text = NSLocalizedString(
"DISAPPEARING_MESSAGES", comment: "table cell label in conversation settings")
rowLabel.textColor = Theme.primaryTextColor
rowLabel.font = OWSTableItem.primaryLabelFont
rowLabel.lineBreakMode = .byTruncatingTail
let switchView = UISwitch()
switchView.isOn = disappearingMessagesConfiguration.isEnabled
switchView.addTarget(self, action: switchAction, for: .valueChanged)
switchView.isEnabled = canEditConversationAttributes
let topRow = UIStackView(arrangedSubviews: [ iconView, rowLabel, switchView ])
topRow.spacing = self.iconSpacingLarge
topRow.alignment = .center
cell.contentView.addSubview(topRow)
topRow.autoPinEdgesToSuperviewMargins()
switchView.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "disappearing_messages_switch")
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "disappearing_messages")
return cell
},
customRowHeight: UITableView.automaticDimension,
actionBlock: nil))
if disappearingMessagesConfiguration.isEnabled {
let sliderAction = #selector(durationSliderDidChange)
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cell = OWSTableItem.newCell()
cell.preservesSuperviewLayoutMargins = true
cell.contentView.preservesSuperviewLayoutMargins = true
cell.selectionStyle = .none
let iconView = OWSTableItem.imageView(forIcon: .settingsTimer)
let rowLabel = self.disappearingMessagesDurationLabel
self.updateDisappearingMessagesDurationLabel()
rowLabel.textColor = Theme.primaryTextColor
rowLabel.font = OWSTableItem.primaryLabelFont
// don't truncate useful duration info which is in the tail
rowLabel.lineBreakMode = .byTruncatingHead
let topRow = UIStackView(arrangedSubviews: [ iconView, rowLabel ])
topRow.spacing = self.iconSpacingLarge
topRow.alignment = .center
cell.contentView.addSubview(topRow)
topRow.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
let slider = UISlider()
slider.maximumValue
= Float(self.disappearingMessagesDurations.count - 1)
slider.minimumValue = 0
slider.isContinuous = true // NO fires change event only once you let go
slider.value = Float(self.disappearingMessagesConfiguration.durationIndex)
slider.addTarget(self, action: sliderAction, for: .valueChanged)
cell.contentView.addSubview(slider)
slider.autoPinEdge(.top, to: .bottom, of: topRow, withOffset: 6)
slider.autoPinEdge(.leading, to: .leading, of: rowLabel)
slider.autoPinTrailingToSuperviewMargin()
slider.autoPinBottomToSuperviewMargin()
slider.isEnabled = canEditConversationAttributes
slider.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "disappearing_messages_slider")
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "disappearing_messages_duration")
return cell
},
customRowHeight: UITableView.automaticDimension,
actionBlock: nil))
}
section.footerTitle = NSLocalizedString(
"DISAPPEARING_MESSAGES_DESCRIPTION", comment: "subheading in conversation settings")
}
private func addColorPickerItems(to section: OWSTableSection) {
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let colorName = self.thread.conversationColorName
let currentColor = OWSConversationColor.conversationColorOrDefault(colorName: colorName).themeColor
let title = NSLocalizedString("CONVERSATION_SETTINGS_CONVERSATION_COLOR",
comment: "Label for table cell which leads to picking a new conversation color")
return self.buildCell(name: title,
icon: .settingsColorPalette,
disclosureIconColor: currentColor,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "conversation_color"))
},
actionBlock: { [weak self] in
self?.showColorPicker()
}))
}
private func buildNotificationsSection() -> OWSTableSection {
let section = OWSTableSection()
section.customHeaderHeight = 14
section.customFooterHeight = 14
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let sound = OWSSounds.notificationSound(for: self.thread)
let cell = OWSTableItem.buildCellWithAccessoryLabel(icon: .settingsMessageSound,
itemName: NSLocalizedString("SETTINGS_ITEM_NOTIFICATION_SOUND",
comment: "Label for settings view that allows user to change the notification sound."),
accessoryText: OWSSounds.displayName(for: sound))
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "notifications")
return cell
},
customRowHeight: UITableView.automaticDimension,
actionBlock: { [weak self] in
self?.showSoundSettingsView()
}))
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
var muteStatus = NSLocalizedString("CONVERSATION_SETTINGS_MUTE_NOT_MUTED",
comment: "Indicates that the current thread is not muted.")
let now = Date()
if let mutedUntilDate = self.thread.mutedUntilDate,
mutedUntilDate > now {
let calendar = Calendar.current
let muteUntilComponents = calendar.dateComponents([.year, .month, .day], from: mutedUntilDate)
let nowComponents = calendar.dateComponents([.year, .month, .day], from: now)
let dateFormatter = DateFormatter()
if nowComponents.year != muteUntilComponents.year
|| nowComponents.month != muteUntilComponents.month
|| nowComponents.day != muteUntilComponents.day {
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .short
} else {
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
}
let formatString = NSLocalizedString("CONVERSATION_SETTINGS_MUTED_UNTIL_FORMAT",
comment: "Indicates that this thread is muted until a given date or time. Embeds {{The date or time which the thread is muted until}}.")
muteStatus = String(format: formatString,
dateFormatter.string(from: mutedUntilDate))
}
let cell = OWSTableItem.buildCellWithAccessoryLabel(icon: .settingsMuted,
itemName: NSLocalizedString("CONVERSATION_SETTINGS_MUTE_LABEL",
comment: "label for 'mute thread' cell in conversation settings"),
accessoryText: muteStatus)
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "mute")
return cell
},
customRowHeight: UITableView.automaticDimension,
actionBlock: { [weak self] in
self?.showMuteUnmuteActionSheet()
}))
return section
}
private func buildBlockAndLeaveSection() -> OWSTableSection {
let section = OWSTableSection()
section.customHeaderHeight = 14
section.footerTitle = isGroupThread
? NSLocalizedString("CONVERSATION_SETTINGS_BLOCK_AND_LEAVE_SECTION_FOOTER",
comment: "Footer text for the 'block and leave' section of group conversation settings view.")
: NSLocalizedString("CONVERSATION_SETTINGS_BLOCK_AND_LEAVE_SECTION_CONTACT_FOOTER",
comment: "Footer text for the 'block and leave' section of contact conversation settings view.")
if isGroupThread, isLocalUserInConversation {
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
return OWSTableItem.buildIconNameCell(icon: .settingsLeaveGroup,
itemName: NSLocalizedString("LEAVE_GROUP_ACTION",
comment: "table cell label in conversation settings"),
customColor: UIColor.ows_accentRed,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "leave_group"))
},
actionBlock: { [weak self] in
self?.didTapLeaveGroup()
}))
}
let isCurrentlyBlocked = blockingManager.isThreadBlocked(thread)
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cellTitle: String
var customColor: UIColor?
if isCurrentlyBlocked {
cellTitle =
(self.thread.isGroupThread
? NSLocalizedString("CONVERSATION_SETTINGS_UNBLOCK_GROUP",
comment: "Label for 'unblock group' action in conversation settings view.")
: NSLocalizedString("CONVERSATION_SETTINGS_UNBLOCK_USER",
comment: "Label for 'unblock user' action in conversation settings view."))
} else {
cellTitle =
(self.thread.isGroupThread
? NSLocalizedString("CONVERSATION_SETTINGS_BLOCK_GROUP",
comment: "Label for 'block group' action in conversation settings view.")
: NSLocalizedString("CONVERSATION_SETTINGS_BLOCK_USER",
comment: "Label for 'block user' action in conversation settings view."))
customColor = UIColor.ows_accentRed
}
let cell = OWSTableItem.buildIconNameCell(icon: .settingsBlock,
itemName: cellTitle,
customColor: customColor,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "block"))
return cell
},
actionBlock: { [weak self] in
if isCurrentlyBlocked {
self?.didTapUnblockGroup()
} else {
self?.didTapBlockGroup()
}
}))
return section
}
private func buildGroupAccessSections(groupModelV2: TSGroupModelV2,
contents: OWSTableContents) {
do {
let section = OWSTableSection()
section.customHeaderHeight = 14
section.footerTitle = NSLocalizedString("CONVERSATION_SETTINGS_ATTRIBUTES_ACCESS_SECTION_FOOTER",
comment: "Footer for the 'attributes access' section in conversation settings view.")
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let accessStatus = self.accessoryLabel(forAccess: groupModelV2.access.attributes)
let cell = OWSTableItem.buildCellWithAccessoryLabel(icon: .settingsEditGroupAccess,
itemName: NSLocalizedString("CONVERSATION_SETTINGS_EDIT_ATTRIBUTES_ACCESS",
comment: "Label for 'edit attributes access' action in conversation settings view."),
accessoryText: accessStatus)
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "edit_group_attributes_access")
return cell
},
actionBlock: { [weak self] in
self?.showGroupAttributesAccessView()
}))
contents.addSection(section)
}
if DebugFlags.groupsV2editMemberAccess {
let section = OWSTableSection()
section.customHeaderHeight = 14
section.footerTitle = NSLocalizedString("CONVERSATION_SETTINGS_MEMBERSHIP_ACCESS_SECTION_FOOTER",
comment: "Footer for the 'membership access' section in conversation settings view.")
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let accessStatus = self.accessoryLabel(forAccess: groupModelV2.access.members)
let cell = OWSTableItem.buildCellWithAccessoryLabel(icon: .settingsEditGroupAccess,
itemName: NSLocalizedString("CONVERSATION_SETTINGS_EDIT_MEMBERSHIP_ACCESS",
comment: "Label for 'edit membership access' action in conversation settings view."),
accessoryText: accessStatus)
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "edit_group_membership_access")
return cell
},
actionBlock: { [weak self] in
self?.showGroupMembershipAccessView()
}))
contents.addSection(section)
}
}
private func accessoryLabel(forAccess access: GroupV2Access) -> String {
switch access {
case .unknown, .any, .member:
if access != .member {
owsFailDebug("Invalid attributes access: \(access.rawValue)")
}
return NSLocalizedString("CONVERSATION_SETTINGS_ATTRIBUTES_ACCESS_MEMBER",
comment: "Label indicating that all group members can update the group's attributes: name, avatar, etc.")
case .administrator:
return NSLocalizedString("CONVERSATION_SETTINGS_ATTRIBUTES_ACCESS_ADMINISTRATOR",
comment: "Label indicating that only administrators can update the group's attributes: name, avatar, etc.")
}
}
private func buildGroupMembershipSection(groupModel: TSGroupModel) -> OWSTableSection {
let section = OWSTableSection()
section.customFooterHeight = 14
guard let localAddress = tsAccountManager.localAddress else {
owsFailDebug("Missing localAddress.")
return section
}
guard let helper = self.contactsViewHelper else {
owsFailDebug("Missing contactsViewHelper.")
return section
}
// "Add Members" cell.
if canEditConversationMembership {
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cell = OWSTableItem.newCell()
cell.preservesSuperviewLayoutMargins = true
cell.contentView.preservesSuperviewLayoutMargins = true
let iconView = OWSTableItem.buildIconInCircleView(icon: .settingsAddMembers,
iconSize: kSmallAvatarSize,
innerIconSize: 22)
let rowLabel = UILabel()
rowLabel.text = NSLocalizedString("CONVERSATION_SETTINGS_ADD_MEMBERS",
comment: "Label for 'add members' button in conversation settings view.")
rowLabel.textColor = Theme.accentBlueColor
rowLabel.font = OWSTableItem.primaryLabelFont
rowLabel.lineBreakMode = .byTruncatingTail
let contentRow = UIStackView(arrangedSubviews: [ iconView, rowLabel ])
contentRow.spacing = self.iconSpacingSmall
cell.contentView.addSubview(contentRow)
contentRow.autoPinEdgesToSuperviewMargins()
return cell
},
customRowHeight: UITableView.automaticDimension) { [weak self] in
self?.showAddMembersView()
})
}
let groupMembership = groupModel.groupMembership
let allMembers = groupMembership.nonPendingMembers
var allMembersSorted = [SignalServiceAddress]()
var verificationStateMap = [SignalServiceAddress: OWSVerificationState]()
databaseStorage.uiRead { transaction in
for memberAddress in allMembers {
verificationStateMap[memberAddress] = self.identityManager.verificationState(for: memberAddress,
transaction: transaction)
}
allMembersSorted = self.contactsManager.sortSignalServiceAddresses(Array(allMembers),
transaction: transaction)
}
var membersToRender = [SignalServiceAddress]()
if groupMembership.isNonPendingMember(localAddress) {
// Make sure local user is first.
membersToRender.insert(localAddress, at: 0)
}
// Admin users are second.
let adminMembers = allMembersSorted.filter { $0 != localAddress && groupMembership.isAdministrator($0) }
membersToRender += adminMembers
// Non-admin users are third.
let nonAdminMembers = allMembersSorted.filter { $0 != localAddress && !groupMembership.isAdministrator($0) }
membersToRender += nonAdminMembers
if membersToRender.count > 1 {
let headerFormat = NSLocalizedString("CONVERSATION_SETTINGS_MEMBERS_SECTION_TITLE_FORMAT",
comment: "Format for the section title of the 'members' section in conversation settings view. Embeds: {{ the number of group members }}.")
section.headerTitle = String(format: headerFormat,
OWSFormat.formatInt(membersToRender.count))
} else {
section.headerTitle = NSLocalizedString("CONVERSATION_SETTINGS_MEMBERS_SECTION_TITLE",
comment: "Section title of the 'members' section in conversation settings view.")
}
// TODO: Do we show pending members here? How?
var hasMoreMembers = false
for memberAddress in membersToRender {
let maxMembersToShow = 5
// Note that we use <= to account for the header cell.
guard isShowingAllGroupMembers || section.itemCount() <= maxMembersToShow else {
hasMoreMembers = true
break
}
guard let verificationState = verificationStateMap[memberAddress] else {
owsFailDebug("Missing verificationState.")
continue
}
let isLocalUser = memberAddress == localAddress
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cell = ContactTableViewCell()
cell.setUseSmallAvatars()
let isGroupAdmin = groupMembership.isAdministrator(memberAddress)
let isVerified = verificationState == .verified
let isNoLongerVerified = verificationState == .noLongerVerified
let isBlocked = helper.isSignalServiceAddressBlocked(memberAddress)
if isGroupAdmin {
cell.setAccessoryMessage(NSLocalizedString("GROUP_MEMBER_ADMIN_INDICATOR",
comment: "Label indicating that a group member is an admin."))
} else if isNoLongerVerified {
cell.setAccessoryMessage(NSLocalizedString("CONTACT_CELL_IS_NO_LONGER_VERIFIED",
comment: "An indicator that a contact is no longer verified."))
} else if isBlocked {
cell.setAccessoryMessage(MessageStrings.conversationIsBlocked)
}
if isLocalUser {
// Use a custom avatar to avoid using the "note to self" icon.
let customAvatar = OWSProfileManager.shared().localProfileAvatarImage() ?? OWSContactAvatarBuilder(forLocalUserWithDiameter: kSmallAvatarSize).buildDefaultImage()
cell.setCustomAvatar(customAvatar)
cell.setCustomName(NSLocalizedString("GROUP_MEMBER_LOCAL_USER",
comment: "Label indicating the local user."))
cell.selectionStyle = .none
} else {
cell.selectionStyle = .default
}
cell.configure(withRecipientAddress: memberAddress)
if isGroupAdmin {
cell.setAttributedSubtitle(nil)
} else if isVerified {
cell.setAttributedSubtitle(cell.verifiedSubtitle())
} else {
cell.setAttributedSubtitle(nil)
}
let cellName = "user.\(memberAddress.stringForDisplay)"
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: cellName)
return cell
},
customRowHeight: UITableView.automaticDimension) { [weak self] in
guard !isLocalUser else {
return
}
self?.didSelectGroupMember(memberAddress)
})
}
if hasMoreMembers {
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cell = OWSTableItem.newCell()
cell.preservesSuperviewLayoutMargins = true
cell.contentView.preservesSuperviewLayoutMargins = true
let iconView = OWSTableItem.buildIconInCircleView(icon: .settingsShowAllMembers,
iconSize: kSmallAvatarSize,
innerIconSize: 19,
iconTintColor: Theme.secondaryTextAndIconColor)
let rowLabel = UILabel()
rowLabel.text = NSLocalizedString("CONVERSATION_SETTINGS_VIEW_ALL_MEMBERS",
comment: "Label for 'view all members' button in conversation settings view.")
rowLabel.textColor = Theme.secondaryTextAndIconColor
rowLabel.font = OWSTableItem.primaryLabelFont
rowLabel.lineBreakMode = .byTruncatingTail
let contentRow = UIStackView(arrangedSubviews: [ iconView, rowLabel ])
contentRow.spacing = self.iconSpacingSmall
cell.contentView.addSubview(contentRow)
contentRow.autoPinEdgesToSuperviewMargins()
return cell
},
customRowHeight: UITableView.automaticDimension) { [weak self] in
self?.showAllGroupMembers()
})
}
return section
}
private func buildPendingMembersSection(groupModel: TSGroupModel,
localAddress: SignalServiceAddress) -> OWSTableSection {
let section = OWSTableSection()
section.customHeaderHeight = 14
section.customFooterHeight = 14
let pendingMembers = groupModel.groupMembership.pendingMembers
let accessoryText: String
let hasPendingMembers = !pendingMembers.isEmpty
if hasPendingMembers {
accessoryText = OWSFormat.formatInt(pendingMembers.count)
} else {
accessoryText = NSLocalizedString("CONVERSATION_SETTINGS_PENDING_MEMBER_INVITES_NONE",
comment: "Indicates that there are no pending member invites in the group.")
}
let isLocalUserFullMember = groupModel.groupMembership.isNonPendingMember(localAddress)
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cellName = NSLocalizedString("PENDING_GROUP_MEMBERS_VIEW_TITLE",
comment: "The title for the 'pending group members' view.")
let cell = OWSTableItem.buildCellWithAccessoryLabel(icon: .settingsViewPendingInvites,
itemName: cellName,
accessoryText: accessoryText)
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "pending.members")
cell.accessoryType = isLocalUserFullMember ? .disclosureIndicator : .none
cell.selectionStyle = isLocalUserFullMember ? .default : .none
return cell
},
customRowHeight: UITableView.automaticDimension) { [weak self] in
if isLocalUserFullMember {
self?.showPendingMembersView()
}
})
return section
}
}