771 lines
38 KiB
Swift
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)
|
|
}
|
|
}
|