Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/src/ViewControllers/ThreadSettings/GroupViewHelper.swift
1 views
//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalServiceKit
import SignalUI

protocol GroupViewHelperDelegate: AnyObject {
    func groupViewHelperDidUpdateGroup()

    var currentGroupModel: TSGroupModel? { get }

    var fromViewController: UIViewController? { get }
}

// MARK: -

class GroupViewHelper {

    weak var delegate: GroupViewHelperDelegate?

    let threadViewModel: ThreadViewModel

    let memberLabelCoordinator: MemberLabelCoordinator?

    var thread: TSThread {
        return threadViewModel.threadRecord
    }

    var fromViewController: UIViewController? {
        return delegate?.fromViewController
    }

    init(threadViewModel: ThreadViewModel, memberLabelCoordinator: MemberLabelCoordinator?) {
        self.threadViewModel = threadViewModel
        self.memberLabelCoordinator = memberLabelCoordinator
    }

    // MARK: - Accessors

    // Don't use this method directly;
    // Use canEditConversationAttributes or canEditConversationMembership instead.
    private func canLocalUserEditConversation(v2AccessTypeBlock: (GroupAccess) -> GroupV2Access) -> Bool {
        if threadViewModel.hasPendingMessageRequest {
            return false
        }
        guard isLocalUserFullMember else {
            return false
        }
        guard let groupThread = thread as? TSGroupThread else {
            // Both users can edit contact threads.
            return true
        }
        guard !isGroupV1Thread else {
            return false
        }
        guard !threadViewModel.isBlocked else {
            return false
        }
        guard let groupModelV2 = groupThread.groupModel as? TSGroupModelV2 else {
            // All users can edit v1 groups.
            return true
        }
        guard let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction?.aciAddress else {
            owsFailDebug("Missing localAddress.")
            return false
        }
        // Use the block to pick the access type: attributes or members?
        let access = v2AccessTypeBlock(groupModelV2.access)
        switch access {
        case .unknown:
            owsFailDebug("Unknown access.")
            return false
        case .unsatisfiable:
            owsFailDebug("Invalid access.")
            return false
        case .any:
            return true
        case .member:
            return groupModelV2.groupMembership.isFullMember(localAddress)
        case .administrator:
            return groupModelV2.groupMembership.isFullMemberAndAdministrator(localAddress)
        }
    }

    var isGroupV1Thread: Bool {
        thread.isGroupV1Thread
    }

    // Can local user edit conversation attributes:
    //
    // * DM state
    // * Group title (if group)
    // * Group avatar (if group)
    // * Pinned Messages (if group)
    var canEditConversationAttributes: Bool {
        return canLocalUserEditConversation { groupAccess in
            return groupAccess.attributes
        }
    }

    var canEditMemberLabels: Bool {
        guard BuildFlags.MemberLabel.send else {
            return false
        }

        return canLocalUserEditConversation { groupAccess in
            return groupAccess.memberLabels
        }
    }

    // Can local user edit group membership.
    var canEditConversationMembership: Bool {
        return canLocalUserEditConversation { groupAccess in
            return groupAccess.members
        }
    }

    // Can local user edit group access and message send permission.
    var canEditPermissions: Bool {
        guard let groupThread = thread as? TSGroupThread else {
            return false
        }
        return
            !threadViewModel.hasPendingMessageRequest
                && groupThread.isGroupV2Thread
                && groupThread.groupModel.groupMembership.isLocalUserFullMemberAndAdministrator

    }

    var canRevokePendingInvites: Bool {
        guard let groupThread = thread as? TSGroupThread else {
            return false
        }
        return
            !threadViewModel.hasPendingMessageRequest
                && groupThread.isGroupV2Thread
                && groupThread.groupModel.groupMembership.isLocalUserFullMemberAndAdministrator

    }

    var isTerminatedGroup: Bool {
        guard
            let groupThread = thread as? TSGroupThread,
            let groupModelV2 = groupThread.groupModel as? TSGroupModelV2
        else {
            return false
        }
        return groupModelV2.isTerminated
    }

    var canResendInvites: Bool {
        return !threadViewModel.hasPendingMessageRequest && isLocalUserFullMember
    }

    var canApproveMemberRequests: Bool {
        guard let groupThread = thread as? TSGroupThread else {
            return false
        }
        return
            !threadViewModel.hasPendingMessageRequest
                && groupThread.isGroupV2Thread
                && groupThread.groupModel.groupMembership.isLocalUserFullMemberAndAdministrator

    }

    var isLocalUserFullMember: Bool {
        guard let groupThread = thread as? TSGroupThread else {
            return true
        }
        return groupThread.groupModel.groupMembership.isLocalUserFullMember
    }

    var isLocalUserFullOrInvitedMember: Bool {
        guard let groupThread = thread as? TSGroupThread else {
            return true
        }
        return groupThread.groupModel.groupMembership.isLocalUserFullOrInvitedMember
    }

    func isFullOrInvitedMember(_ address: SignalServiceAddress) -> Bool {
        guard let groupThread = thread as? TSGroupThread else {
            return false
        }
        let groupMembership = groupThread.groupModel.groupMembership
        return groupMembership.isFullMember(address) || groupMembership.isInvitedMember(address)
    }
}