// // Copyright (c) 2021 Open Whisper Systems. All rights reserved. // import Foundation import PromiseKit @objc public class AddToGroupViewController: OWSTableViewController2 { private let address: SignalServiceAddress private let collation = UILocalizedIndexedCollation.current() private let maxRecentGroups = 7 private lazy var threadViewHelper: ThreadViewHelper = { let threadViewHelper = ThreadViewHelper() threadViewHelper.delegate = self return threadViewHelper }() init(address: SignalServiceAddress) { self.address = address super.init() } public class func presentForUser(_ address: SignalServiceAddress, from fromViewController: UIViewController) { AssertIsOnMainThread() let view = AddToGroupViewController(address: address) let modal = OWSNavigationController(rootViewController: view) fromViewController.presentFormSheet(modal, animated: true) } // MARK: - public override func viewDidLoad() { super.viewDidLoad() navigationItem.title = NSLocalizedString("ADD_TO_GROUP_TITLE", comment: "Title of the 'add to group' view.") navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(didPressCloseButton)) defaultSeparatorInsetLeading = Self.cellHInnerMargin + CGFloat(kSmallAvatarSize) + ContactCellView.avatarTextHSpacing updateGroupThreadsAsync() } private var groupThreads = [TSGroupThread]() { didSet { AssertIsOnMainThread() updateTableContents() } } private func updateGroupThreadsAsync() { // make sure threadViewHelper is prepared on the main thread _ = threadViewHelper DispatchQueue.sharedUserInitiated.async { self.updateGroupThreads() } } private func updateGroupThreads() { owsAssertDebug(!Thread.isMainThread) let groupThreads = databaseStorage.read { transaction in return self.threadViewHelper.threads.filter { thread -> Bool in guard let groupThread = thread as? TSGroupThread else { return false } let threadViewModel = ThreadViewModel(thread: groupThread, forConversationList: false, transaction: transaction) let groupViewHelper = GroupViewHelper(threadViewModel: threadViewModel) return groupViewHelper.canEditConversationMembership } as? [TSGroupThread] ?? [] } DispatchQueue.main.async { self.groupThreads = groupThreads } } private func updateTableContents() { AssertIsOnMainThread() let contents = OWSTableContents() let recentGroups = groupThreads.count > maxRecentGroups ? Array(groupThreads[0.. maxRecentGroups ? Array(groupThreads[maxRecentGroups.. TSGroupModel? { do { return try databaseStorage.read { transaction in var builder = oldGroupModel.asBuilder let oldGroupMembership = oldGroupModel.groupMembership var groupMembershipBuilder = oldGroupMembership.asBuilder guard !oldGroupMembership.isMemberOfAnyKind(self.address) else { owsFailDebug("Recipient is already in group.") return nil } // GroupManager will separate out members as pending if necessary. groupMembershipBuilder.addFullMember(self.address, role: .normal) builder.groupMembership = groupMembershipBuilder.build() return try builder.build(transaction: transaction) } } catch { owsFailDebug("Error: \(error)") return nil } } func updateGroupThreadPromise(oldGroupModel: TSGroupModel, newGroupModel: TSGroupModel) -> Promise { guard let localAddress = TSAccountManager.localAddress else { return Promise(error: OWSAssertionError("Missing localAddress.")) } return firstly { () -> Promise in return GroupManager.messageProcessingPromise(for: oldGroupModel, description: self.logTag) }.then(on: .global()) { _ in // dmConfiguration: nil means don't change disappearing messages configuration. GroupManager.localUpdateExistingGroup(oldGroupModel: oldGroupModel, newGroupModel: newGroupModel, dmConfiguration: nil, groupUpdateSourceAddress: localAddress) }.asVoid() } // MARK: - private func item(forGroupThread groupThread: TSGroupThread) -> OWSTableItem { let alreadyAMemberText = NSLocalizedString( "ADD_TO_GROUP_ALREADY_A_MEMBER", comment: "Text indicating your contact is already a member of the group on the 'add to group' view." ) let isAlreadyAMember = groupThread.groupMembership.isFullMember(address) return OWSTableItem( customCellBlock: { let cell = GroupTableViewCell() cell.configure( thread: groupThread, customSubtitle: isAlreadyAMember ? alreadyAMemberText : nil, customTextColor: isAlreadyAMember ? Theme.ternaryTextColor : nil ) cell.isUserInteractionEnabled = !isAlreadyAMember return cell }, actionBlock: { [weak self] in self?.didSelectGroup(groupThread) } ) } } // MARK: - extension AddToGroupViewController: ThreadViewHelperDelegate { public func threadListDidChange() { updateGroupThreadsAsync() } }