Signal-iOS/Signal/Calls/UserInterface/CallLinkApprovalRequestDetailsSheet.swift
Igor Solomennikov e1377c9aa9
OWSTableSheetViewController refactoring.
1. Instead of overriding updateTableContents(shouldReload:) in every subclass let the base class to do the reloading and subclasses will just provide contents for the OWSTableViewController2.
2. Remove unnecessary rebuilding of the OWSTableViewContents in didLayoutSubviews(). Do not reload if content doesn't change (it does not in any of the subclasses).
3. Instead of keeping a pre-configured UIStackView in case there's footer content to show - keep a UIView which is more lightweight. Subclasses just add their own footer content to that.
2025-10-17 13:10:59 -07:00

196 lines
6.5 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import UIKit
import SignalUI
import LibSignalClient
import SignalServiceKit
// MARK: - CallLinkApprovalRequestDetailsSheet
class CallLinkApprovalRequestDetailsSheet: OWSTableSheetViewController {
private struct Deps {
let contactsManager: any ContactManager
let db: any DB
}
private let deps = Deps(
contactsManager: SSKEnvironment.shared.contactManagerRef,
db: DependenciesBridge.shared.db
)
let approvalRequest: CallLinkApprovalRequest
let approvalViewModel: CallLinkApprovalViewModel
override var handleBackgroundColor: UIColor {
UIColor.Signal.transparentSeparator
}
init(
approvalRequest: CallLinkApprovalRequest,
approvalViewModel: CallLinkApprovalViewModel
) {
self.approvalRequest = approvalRequest
self.approvalViewModel = approvalViewModel
super.init()
self.overrideUserInterfaceStyle = .dark
self.tableViewController.forceDarkMode = true
}
private weak var fromViewController: UIViewController?
func present(
from viewController: UIViewController,
dismissalDelegate: (any SheetDismissalDelegate)? = nil,
animated: Bool = true
) {
self.fromViewController = viewController
self.dismissalDelegate = dismissalDelegate
viewController.present(self, animated: animated)
}
// MARK: Table contents
override func tableContents() -> OWSTableContents {
let contents = OWSTableContents()
contents.add(.init(
items: [
// TODO: It would be nice to eventually make OWSTableItem's default values not dependent on Theme and instead use dynamic colors
.item(
icon: .checkCircle,
tintColor: UIColor.Signal.label,
name: OWSLocalizedString(
"CALL_LINK_JOIN_REQUEST_APPROVE_BUTTON",
comment: "Button on an action sheet to approve a request to join a call link."
),
textColor: UIColor.Signal.label
) { [weak self] in
guard let self else { return }
self.dismiss(animated: true)
self.approvalViewModel.performRequestAction.send((.approve, self.approvalRequest))
},
.item(
icon: .xCircle,
tintColor: UIColor.Signal.label,
name: OWSLocalizedString(
"CALL_LINK_JOIN_REQUEST_DENY_BUTTON",
comment: "Button on an action sheet to deny a request to join a call link."
),
textColor: UIColor.Signal.label
) { [weak self] in
guard let self else { return }
self.dismiss(animated: true)
self.approvalViewModel.performRequestAction.send((.deny, self.approvalRequest))
},
],
headerView: self.buildHeader()
))
return contents
}
// MARK: Header
private func buildHeader() -> UIView {
let vStack = UIStackView()
vStack.axis = .vertical
vStack.spacing = 8
vStack.alignment = .center
vStack.isLayoutMarginsRelativeArrangement = true
vStack.layoutMargins = .init(
top: 20,
left: 0,
bottom: 36,
right: 0
)
// [CallLink] TODO: This should expand to a full-screen preview when tapped
let avatarView = ConversationAvatarView(
sizeClass: .eightyEight,
localUserDisplayMode: .asLocalUser,
badged: true
)
let (contactTitle, mutualThreads): (NSAttributedString, [TSGroupThread]) = self.deps.db.read { tx in
avatarView.update(SDSDB.shimOnlyBridge(tx)) { config in
config.dataSource = .address(self.approvalRequest.address)
}
let isSystemContact = self.deps.contactsManager.fetchSignalAccount(
for: self.approvalRequest.address,
transaction: SDSDB.shimOnlyBridge(tx)
) != nil
let mutualThreads = TSGroupThread.groupThreads(
with: self.approvalRequest.address,
transaction: SDSDB.shimOnlyBridge(tx)
)
.filter(\.isLocalUserFullMember)
.filter(\.shouldThreadBeVisible)
let contactTitle = ConversationHeaderBuilder.threadAttributedString(
threadName: self.approvalRequest.name,
isNoteToSelf: false,
isSystemContact: isSystemContact,
canTap: true,
tx: SDSDB.shimOnlyBridge(tx)
)
return (contactTitle, mutualThreads)
}
vStack.addArrangedSubview(avatarView)
let nameButton = OWSButton { [weak self] in
self?.didTapName()
}
nameButton.dimsWhenHighlighted = true
nameButton.titleLabel?.numberOfLines = 0
nameButton.titleLabel?.textAlignment = .center
nameButton.setAttributedTitle(contactTitle, for: .normal)
vStack.addArrangedSubview(nameButton)
let mutualGroupsLabel = UILabel()
mutualGroupsLabel.text = ProfileDetailLabel.mutualGroupsString(isInGroupContext: false, mutualGroups: mutualThreads)
mutualGroupsLabel.font = .dynamicTypeSubheadline
mutualGroupsLabel.textColor = UIColor.Signal.secondaryLabel
vStack.addArrangedSubview(mutualGroupsLabel)
return vStack
}
private func didTapName() {
guard let fromViewController else {
owsFailDebug("Parent view controller missing")
return
}
let thread = TSContactThread.getOrCreateThread(contactAddress: approvalRequest.address)
let sheet = ContactAboutSheet(thread: thread, spoilerState: .init())
sheet.overrideUserInterfaceStyle = .dark
self.dismiss(animated: true) {
sheet.present(from: fromViewController, dismissalDelegate: self.dismissalDelegate)
}
}
}
// MARK: - Previews
#if DEBUG
@available(iOS 17.0, *)
#Preview {
SheetPreviewViewController { viewController, animated in
CallLinkApprovalRequestDetailsSheet(
approvalRequest: .init(aci: .init(fromUUID: UUID()), name: "Candice"),
approvalViewModel: CallLinkApprovalViewModel()
)
.present(from: viewController, animated: animated)
}
}
#endif