TM-SGNL-iOS/Signal/Calls/UserInterface/CallLinkApprovalViewModel.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

120 lines
3.6 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Combine
import SwiftUI
import LibSignalClient
import SignalServiceKit
// MARK: ApprovalRequest
struct CallLinkApprovalRequest: Hashable, Identifiable {
/// View ID. Shouldn't be tied to the content so that the
/// content can change without being seen as a new view.
var id: UUID
var aci: Aci
var address: SignalServiceAddress
var name: String
init(aci: Aci, name: String) {
self.id = aci.rawUUID
self.aci = aci
self.address = .init(aci)
self.name = name
}
}
// MARK: - CallLinkApprovalViewModel
@MainActor
class CallLinkApprovalViewModel: ObservableObject {
typealias ApprovalRequest = CallLinkApprovalRequest
private struct Deps {
let contactsManager: any ContactManager
let db: any DB
}
private let deps = Deps(
contactsManager: SSKEnvironment.shared.contactManagerRef,
db: DependenciesBridge.shared.db
)
@Published var requests: [ApprovalRequest] = []
/// When bulk approving or denying requests, we want to hide all requests
/// at once, even if it takes a moment for the requests to process,
/// so explicitly ignore them.
private var requestsToIgnore = Set<Aci>()
enum RequestAction {
case approve, deny, viewDetails
}
let performRequestAction = PassthroughSubject<(RequestAction, ApprovalRequest), Never>()
func loadRequestsWithSneakyTransaction(for uuids: [UUID]) {
let oldRequests = self.requests
let allAcis = uuids.map(Aci.init(fromUUID:))
// Once an ignored request is no longer in the list,
// stop ignoring it in case they request again.
requestsToIgnore.formIntersection(allAcis)
let acis = allAcis.filter { !requestsToIgnore.contains($0) }
guard oldRequests.map(\.aci) != acis else { return }
if uuids.isEmpty {
// Avoid opening database transaction
withAnimation(.quickSpring()) {
self.requests.removeAll()
}
return
}
let nameConfig: DisplayName.Config = .current()
let names = self.deps.db.read { tx in
self.deps.contactsManager.displayNames(
for: acis.map(SignalServiceAddress.init),
tx: SDSDB.shimOnlyBridge(tx)
)
}.map { displayName in
displayName.resolvedValue(config: nameConfig, useShortNameIfAvailable: false)
}
var requests = zip(acis, names).map { (aci, name) in
ApprovalRequest(aci: aci, name: name)
}
let shouldUpdateFrontCard = requests.count > 2 || (requests.count == 2 && oldRequests.count >= 2)
if shouldUpdateFrontCard, let oldFirstID = oldRequests.first?.id {
requests[0].id = oldFirstID
}
withAnimation(.quickSpring()) {
self.requests = requests
}
}
func bulkApprove(requests: [ApprovalRequest]) {
self.removeAndIgnore(requests: requests)
requests.forEach { self.performRequestAction.send((.approve, $0)) }
}
func bulkDeny(requests: [ApprovalRequest]) {
self.removeAndIgnore(requests: requests)
requests.forEach { self.performRequestAction.send((.deny, $0)) }
}
private func removeAndIgnore(requests: [ApprovalRequest]) {
let acis = requests.map(\.aci)
self.requestsToIgnore.formUnion(acis)
let requestsSet = Set(requests)
withAnimation(.quickSpring()) {
self.requests.removeAll(where: requestsSet.contains(_:))
}
}
}