Signal-iOS/Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController+Contents.swift
2021-03-27 12:13:20 -07:00

771 lines
38 KiB
Swift

//
// Copyright (c) 2021 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 contactsManagerImpl.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()
let isNoteToSelf = thread.isNoteToSelf
// Main section.
let mainSection = OWSTableSection()
let header = buildMainHeader()
lastContentWidth = view.width
mainSection.customHeaderView = header
addBasicItems(to: mainSection)
// TODO: We can remove this item once message requests are mandatory.
addProfileSharingItems(to: mainSection)
if DebugFlags.shouldShowColorPicker {
addColorPickerItems(to: mainSection)
}
contents.addSection(mainSection)
buildDisappearingMessagesSection(to: mainSection)
if !isNoteToSelf {
contents.addSection(buildNotificationsSection())
}
if let groupModel = currentGroupModel,
!groupModel.isPlaceholder {
if let groupModelV2 = groupModel as? TSGroupModelV2 {
if canEditConversationAccess {
buildGroupAccessSections(groupModelV2: groupModelV2, contents: contents)
}
buildRequestsInvitesAndLinkSection(groupModelV2: groupModelV2, contents: contents)
}
contents.addSection(buildGroupMembershipSection(groupModel: groupModel))
}
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,
contactsManagerImpl.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()
}))
if !groupViewHelper.isBlockedByMigration {
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()
}))
}
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cell = OWSTableItem.buildCellWithAccessoryLabel(
icon: .settingsWallpaper,
itemName: NSLocalizedString("SETTINGS_ITEM_WALLPAPER",
comment: "Label for settings view that allows user to change the wallpaper."),
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "wallpaper")
)
return cell
},
actionBlock: { [weak self] in
self?.showWallpaperSettingsView()
}))
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.contactsManagerImpl.supportsContactEditing {
self.presentContactViewController()
}
}))
}
}
private func addProfileSharingItems(to section: OWSTableSection) {
guard !thread.isGroupV2Thread else {
return
}
let isLocalUserFullMember = self.isLocalUserFullMember
// 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 = isLocalUserFullMember
return cell
},
actionBlock: { [weak self] in
self?.showShareProfileAlert()
}))
}
}
private func buildDisappearingMessagesSection(to section: OWSTableSection) {
let canEditConversationAttributes = self.canEditConversationAttributes
let disappearingMessagesConfiguration = self.disappearingMessagesConfiguration
section.add(.init(
customCellBlock: {
let cell = OWSTableItem.buildIconNameCell(
icon: disappearingMessagesConfiguration.isEnabled
? .settingsTimer
: .settingsTimerDisabled,
itemName: NSLocalizedString(
"DISAPPEARING_MESSAGES",
comment: "table cell label in conversation settings"
),
accessoryText: disappearingMessagesConfiguration.isEnabled
? NSString.formatDurationSeconds(disappearingMessagesConfiguration.durationSeconds, useShortFormat: true)
: CommonStrings.switchOff,
accessoryType: .disclosureIndicator,
accessoryImage: nil,
customColor: canEditConversationAttributes ? nil : Theme.secondaryTextAndIconColor,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "disappearing_messages")
)
cell.isUserInteractionEnabled = canEditConversationAttributes
return cell
}, actionBlock: { [weak self] in
let vc = DisappearingMessagesTimerSettingsViewController(configuration: disappearingMessagesConfiguration) { configuration in
self?.disappearingMessagesConfiguration = configuration
self?.updateTableContents()
}
self?.presentFormSheet(OWSNavigationController(rootViewController: vc), animated: true)
}
))
}
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.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(forSound: sound))
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "notifications")
return cell
},
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 self.thread.mutedUntilTimestamp == TSThread.alwaysMutedTimestamp {
muteStatus = NSLocalizedString("CONVERSATION_SETTINGS_MUTED_ALWAYS",
comment: "Indicates that this thread is muted forever.")
} else 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
},
actionBlock: { [weak self] in
self?.showMuteUnmuteActionSheet()
}))
if Mention.threadAllowsMentionSend(thread) {
section.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
owsFailDebug("Missing self")
return OWSTableItem.newCell()
}
let cell = OWSTableItem.buildCellWithAccessoryLabel(icon: .settingsMention,
itemName: NSLocalizedString("CONVERSATION_SETTINGS_MENTIONS_LABEL",
comment: "label for 'mentions' cell in conversation settings"),
accessoryText: self.nameForMentionMode(self.thread.mentionNotificationMode))
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "mentions")
return cell
},
actionBlock: { [weak self] in
self?.showMentionNotificationModeActionSheet()
}))
}
return section
}
func nameForMentionMode(_ mode: TSThreadMentionNotificationMode) -> String {
switch mode {
case .default, .always:
return NSLocalizedString("CONVERSATION_SETTINGS_MENTION_MODE_AlWAYS",
comment: "label for 'always' option for mention notifications in conversation settings")
case .never:
return NSLocalizedString("CONVERSATION_SETTINGS_MENTION_MODE_NEVER",
comment: "label for 'never' option for mention notifications in conversation settings")
}
}
private func buildBlockAndLeaveSection() -> OWSTableSection {
let section = OWSTableSection()
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, isLocalUserFullOrInvitedMember {
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) {
let section = OWSTableSection()
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()
}))
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)
}
private func accessoryLabel(forAccess access: GroupV2Access) -> String {
switch access {
case .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.")
case .unknown, .unsatisfiable:
owsFailDebug("Invalid access")
return NSLocalizedString("CONVERSATION_SETTINGS_ATTRIBUTES_ACCESS_NONE",
comment: "Label indicating that no member can update the group's attributes: name, avatar, etc.")
}
}
private func buildGroupMembershipSection(groupModel: TSGroupModel) -> OWSTableSection {
let section = OWSTableSection()
section.separatorInsetLeading = NSNumber(value: Float(Self.cellHInnerMargin + CGFloat(kSmallAvatarSize) + kContactCellAvatarTextMargin))
guard let localAddress = tsAccountManager.localAddress else {
owsFailDebug("Missing localAddress.")
return section
}
let helper = contactsViewHelper
// "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: 24,
iconTintColor: Theme.primaryTextColor)
let rowLabel = UILabel()
rowLabel.text = NSLocalizedString("CONVERSATION_SETTINGS_ADD_MEMBERS",
comment: "Label for 'add members' button in conversation settings view.")
rowLabel.textColor = Theme.primaryTextColor
rowLabel.font = OWSTableItem.primaryLabelFont
rowLabel.lineBreakMode = .byTruncatingTail
let contentRow = UIStackView(arrangedSubviews: [ iconView, rowLabel ])
contentRow.spacing = self.iconSpacingSmall
cell.contentView.addSubview(contentRow)
contentRow.autoPinWidthToSuperviewMargins()
contentRow.autoPinHeightToSuperview(withMargin: 7)
return cell
}) { [weak self] in
self?.showAddMembersView()
})
}
let groupMembership = groupModel.groupMembership
let allMembers = groupMembership.fullMembers
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.contactsManagerImpl.sortSignalServiceAddresses(Array(allMembers),
transaction: transaction)
}
var membersToRender = [SignalServiceAddress]()
if groupMembership.isFullMember(localAddress) {
// Make sure local user is first.
membersToRender.insert(localAddress, at: 0)
}
// Admin users are second.
let adminMembers = allMembersSorted.filter { $0 != localAddress && groupMembership.isFullMemberAndAdministrator($0) }
membersToRender += adminMembers
// Non-admin users are third.
let nonAdminMembers = allMembersSorted.filter { $0 != localAddress && !groupMembership.isFullMemberAndAdministrator($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()
let isGroupAdmin = groupMembership.isFullMemberAndAdministrator(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 = Self.profileManagerImpl.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.configureWithSneakyTransaction(recipientAddress: memberAddress)
if isVerified {
cell.setAttributedSubtitle(cell.verifiedSubtitle())
} else if !memberAddress.isLocalAddress,
let bioForDisplay = (Self.databaseStorage.read { transaction in
Self.profileManagerImpl.profileBioForDisplay(for: memberAddress, transaction: transaction)
}) {
cell.setAttributedSubtitle(NSAttributedString(string: bioForDisplay))
} else {
cell.setAttributedSubtitle(nil)
}
let cellName = "user.\(memberAddress.stringForDisplay)"
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: cellName)
return cell
}) { [weak self] in
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: 24,
iconTintColor: Theme.primaryTextColor)
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.primaryTextColor
rowLabel.font = OWSTableItem.primaryLabelFont
rowLabel.lineBreakMode = .byTruncatingTail
let contentRow = UIStackView(arrangedSubviews: [ iconView, rowLabel ])
contentRow.spacing = self.iconSpacingSmall
cell.contentView.addSubview(contentRow)
contentRow.autoPinWidthToSuperviewMargins()
contentRow.autoPinHeightToSuperview(withMargin: 7)
return cell
}) { [weak self] in
self?.showAllGroupMembers()
})
}
return section
}
private func buildRequestsInvitesAndLinkSection(groupModelV2: TSGroupModelV2,
contents: OWSTableContents) {
let section = OWSTableSection()
let itemTitle = (RemoteConfig.groupsV2InviteLinks
? NSLocalizedString("CONVERSATION_SETTINGS_MEMBER_REQUESTS_AND_INVITES",
comment: "Label for 'member requests & invites' action in conversation settings view.")
: NSLocalizedString("CONVERSATION_SETTINGS_MEMBER_INVITES",
comment: "Label for 'member invites' action in conversation settings view."))
section.add(OWSTableItem.disclosureItem(icon: .settingsViewRequestAndInvites,
name: itemTitle,
accessibilityIdentifier: "conversation_settings_requests_and_invites",
actionBlock: { [weak self] in
self?.showMemberRequestsAndInvitesView()
}))
if RemoteConfig.groupsV2InviteLinks {
let groupLinkStatus = (groupModelV2.isGroupInviteLinkEnabled
? CommonStrings.switchOn
: CommonStrings.switchOff)
section.add(OWSTableItem.disclosureItem(icon: .settingsLink,
name: NSLocalizedString("CONVERSATION_SETTINGS_GROUP_LINK",
comment: "Label for 'group link' action in conversation settings view."),
accessoryText: groupLinkStatus,
accessibilityIdentifier: "conversation_settings_group_link",
actionBlock: { [weak self] in
self?.showGroupLinkView()
}))
}
contents.addSection(section)
}
}