Signal-iOS/Signal/Calls/UserInterface/CallControlsOverflowView.swift
2024-05-14 15:54:06 -04:00

171 lines
4.9 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalRingRTC
import SignalServiceKit
class CallControlsOverflowView: UIView {
private lazy var reactionPicker: MessageReactionPicker = {
let picker = MessageReactionPicker(
selectedEmoji: nil,
delegate: self,
style: .contextMenu,
forceDarkTheme: true
)
picker.isHidden = true
return picker
}()
private var reactionSender: ReactionSender?
private var reactionsSink: ReactionsSink?
private weak var emojiPickerSheetPresenter: EmojiPickerSheetPresenter?
private weak var callControlsOverflowPresenter: CallControlsOverflowPresenter?
private override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(reactionPicker)
reactionPicker.autoPinEdgesToSuperviewEdges()
// TODO: Add Raise Hand button
}
convenience init(
reactionSender: ReactionSender,
reactionsSink: ReactionsSink,
emojiPickerSheetPresenter: EmojiPickerSheetPresenter,
callControlsOverflowPresenter: CallControlsOverflowPresenter
) {
self.init(frame: .zero)
self.reactionSender = reactionSender
self.reactionsSink = reactionsSink
self.emojiPickerSheetPresenter = emojiPickerSheetPresenter
self.callControlsOverflowPresenter = callControlsOverflowPresenter
}
// MARK: - Animations
private var isAnimating = false
func animateIn() {
self.isHidden = false
guard !isAnimating else {
return
}
isAnimating = true
self.callControlsOverflowPresenter?.callControlsOverflowWillAppear()
self.reactionPicker.isHidden = false
// `playPresentationAnimation` is built to be called on new, rather
// than reused, reaction pickers. Since we're reusing here, we need
// to reset the alpha.
self.reactionPicker.alpha = 1
self.reactionPicker.playPresentationAnimation(duration: 0.2) { [weak self] in
self?.isAnimating = false
}
// TODO: Animate Raise Hand menu item
}
func animateOut() {
guard !isAnimating else {
return
}
isAnimating = true
self.reactionPicker.playDismissalAnimation(
duration: 0.2,
completion: { [weak self] in
self?.isAnimating = false
self?.isHidden = true
self?.callControlsOverflowPresenter?.callControlsOverflowDidDisappear()
}
)
// TODO: Animate Raise Hand menu item
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - MessageReactionPickerDelegate
extension CallControlsOverflowView: MessageReactionPickerDelegate {
func didSelectReaction(reaction: String, isRemoving: Bool, inPosition position: Int) {
self.react(with: reaction)
}
func didSelectAnyEmoji() {
let sheet = EmojiPickerSheet(
message: nil,
allowReactionConfiguration: false,
forceDarkTheme: true
) { [weak self] selectedEmoji in
guard let selectedEmoji else { return }
self?.react(with: selectedEmoji.rawValue)
}
emojiPickerSheetPresenter?.present(
sheet: sheet,
animated: true
)
}
private func react(with reaction: String) {
self.callControlsOverflowPresenter?.willSendReaction()
self.reactionSender?.react(value: reaction)
let localAci = databaseStorage.read { tx in
DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx.asV2Read)?.aci
}
guard let localAci else {
owsFailDebug("Local user is in call but doesn't have ACI!")
return
}
// Locally-sent reactions do not come in via the API, so we add them here.
self.reactionsSink?.addReactions(
reactions: [
Reaction(
emoji: reaction,
name: CommonStrings.you,
aci: localAci,
timestamp: Date.timeIntervalSinceReferenceDate
)
]
)
}
}
// MARK: ReactionSender
protocol ReactionSender {
func react(value: String)
}
extension GroupCall: ReactionSender {}
// MARK: - EmojiPickerSheetPresenter
protocol EmojiPickerSheetPresenter: AnyObject {
func present(sheet: EmojiPickerSheet, animated: Bool)
}
extension GroupCallViewController: EmojiPickerSheetPresenter {
func present(sheet: EmojiPickerSheet, animated: Bool) {
self.present(sheet, animated: animated)
}
}
// MARK: - CallControlsOverflowPresenter
protocol CallControlsOverflowPresenter: AnyObject {
func callControlsOverflowWillAppear()
func callControlsOverflowDidDisappear()
func willSendReaction()
}