2023-11-13 21:51:22 +04:00

299 lines
11 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import UIKit
import Display
import MetalEngine
import ComponentFlow
public final class PrivateCallScreen: UIView {
public struct State: Equatable {
public struct SignalInfo: Equatable {
public var quality: Double
public init(quality: Double) {
self.quality = quality
}
}
public struct ActiveState: Equatable {
public var startTime: Double
public var signalInfo: SignalInfo
public var emojiKey: [String]
public init(startTime: Double, signalInfo: SignalInfo, emojiKey: [String]) {
self.startTime = startTime
self.signalInfo = signalInfo
self.emojiKey = emojiKey
}
}
public enum LifecycleState: Equatable {
case connecting
case ringing
case exchangingKeys
case active(ActiveState)
}
public var lifecycleState: LifecycleState
public var name: String
public var avatarImage: UIImage?
public init(
lifecycleState: LifecycleState,
name: String,
avatarImage: UIImage?
) {
self.lifecycleState = lifecycleState
self.name = name
self.avatarImage = avatarImage
}
}
private struct Params: Equatable {
var size: CGSize
var insets: UIEdgeInsets
var screenCornerRadius: CGFloat
var state: State
init(size: CGSize, insets: UIEdgeInsets, screenCornerRadius: CGFloat, state: State) {
self.size = size
self.insets = insets
self.screenCornerRadius = screenCornerRadius
self.state = state
}
}
private let backgroundLayer: CallBackgroundLayer
private let contentOverlayLayer: ContentOverlayLayer
private let contentOverlayContainer: ContentOverlayContainer
private let blurContentsLayer: SimpleLayer
private let blurBackgroundLayer: CallBackgroundLayer
private let contentView: ContentView
private let buttonGroupView: ButtonGroupView
private var params: Params?
private var remoteVideo: VideoSource?
private var isSpeakerOn: Bool = false
private var isMicrophoneMuted: Bool = false
private var isVideoOn: Bool = false
public override init(frame: CGRect) {
self.blurContentsLayer = SimpleLayer()
self.backgroundLayer = CallBackgroundLayer(isBlur: false)
self.contentOverlayLayer = ContentOverlayLayer()
self.contentOverlayContainer = ContentOverlayContainer(overlayLayer: self.contentOverlayLayer)
self.blurBackgroundLayer = CallBackgroundLayer(isBlur: true)
self.contentView = ContentView(frame: CGRect())
self.buttonGroupView = ButtonGroupView()
super.init(frame: frame)
self.contentOverlayLayer.contentsLayer = self.blurContentsLayer
self.layer.addSublayer(self.backgroundLayer)
self.blurContentsLayer.addSublayer(self.blurBackgroundLayer)
self.addSubview(self.contentView)
self.blurContentsLayer.addSublayer(self.contentView.blurContentsLayer)
self.layer.addSublayer(self.contentOverlayLayer)
self.addSubview(self.contentOverlayContainer)
self.contentOverlayContainer.addSubview(self.buttonGroupView)
/*self.buttonGroupView.audioPressed = { [weak self] in
guard let self, var params = self.params else {
return
}
self.isSpeakerOn = !self.isSpeakerOn
switch params.state.lifecycleState {
case .connecting:
params.state.lifecycleState = .ringing
case .ringing:
params.state.lifecycleState = .exchangingKeys
case .exchangingKeys:
params.state.lifecycleState = .active(State.ActiveState(
startTime: Date().timeIntervalSince1970,
signalInfo: State.SignalInfo(quality: 1.0),
emojiKey: ["🐱", "🚂", "", "🎨"]
))
case var .active(activeState):
if activeState.signalInfo.quality == 1.0 {
activeState.signalInfo.quality = 0.1
} else {
activeState.signalInfo.quality = 1.0
}
params.state.lifecycleState = .active(activeState)
}
self.params = params
self.update(transition: .spring(duration: 0.3))
}
self.buttonGroupView.toggleVideo = { [weak self] in
guard let self else {
return
}
if self.remoteVideo == nil {
if let url = Bundle.main.url(forResource: "test2", withExtension: "mp4") {
self.remoteVideo = FileVideoSource(device: MetalEngine.shared.device, url: url)
}
} else {
self.remoteVideo = nil
}
self.isVideoOn = !self.isVideoOn
self.update(transition: .spring(duration: 0.3))
}*/
}
public required init?(coder: NSCoder) {
fatalError()
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let result = super.hitTest(point, with: event) else {
return nil
}
return result
}
public func update(size: CGSize, insets: UIEdgeInsets, screenCornerRadius: CGFloat, state: State, transition: Transition) {
let params = Params(size: size, insets: insets, screenCornerRadius: screenCornerRadius, state: state)
if self.params == params {
return
}
self.params = params
self.updateInternal(params: params, transition: transition)
}
private func update(transition: Transition) {
guard let params = self.params else {
return
}
self.updateInternal(params: params, transition: transition)
}
private func updateInternal(params: Params, transition: Transition) {
let backgroundFrame = CGRect(origin: CGPoint(), size: params.size)
let aspect: CGFloat = params.size.width / params.size.height
let sizeNorm: CGFloat = 64.0
let renderingSize = CGSize(width: floor(sizeNorm * aspect), height: sizeNorm)
let edgeSize: Int = 2
let visualBackgroundFrame = backgroundFrame.insetBy(dx: -CGFloat(edgeSize) / renderingSize.width * backgroundFrame.width, dy: -CGFloat(edgeSize) / renderingSize.height * backgroundFrame.height)
self.backgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2))
transition.setFrame(layer: self.backgroundLayer, frame: visualBackgroundFrame)
let backgroundStateIndex: Int
switch params.state.lifecycleState {
case .connecting:
backgroundStateIndex = 0
case .ringing:
backgroundStateIndex = 0
case .exchangingKeys:
backgroundStateIndex = 0
case let .active(activeState):
if activeState.signalInfo.quality <= 0.2 {
backgroundStateIndex = 2
} else {
backgroundStateIndex = 1
}
}
self.backgroundLayer.update(stateIndex: backgroundStateIndex, transition: transition)
self.contentOverlayLayer.frame = CGRect(origin: CGPoint(), size: params.size)
self.contentOverlayLayer.update(size: params.size, contentInsets: UIEdgeInsets())
self.contentOverlayContainer.frame = CGRect(origin: CGPoint(), size: params.size)
self.blurBackgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2))
self.blurBackgroundLayer.update(stateIndex: backgroundStateIndex, transition: transition)
transition.setFrame(layer: self.blurBackgroundLayer, frame: visualBackgroundFrame)
self.buttonGroupView.frame = CGRect(origin: CGPoint(), size: params.size)
let buttons: [ButtonGroupView.Button] = [
ButtonGroupView.Button(content: .speaker(isActive: self.isSpeakerOn), action: { [weak self] in
guard let self, var params = self.params else {
return
}
self.isSpeakerOn = !self.isSpeakerOn
switch params.state.lifecycleState {
case .connecting:
params.state.lifecycleState = .ringing
case .ringing:
params.state.lifecycleState = .exchangingKeys
case .exchangingKeys:
params.state.lifecycleState = .active(State.ActiveState(
startTime: Date().timeIntervalSince1970,
signalInfo: State.SignalInfo(quality: 1.0),
emojiKey: ["🐱", "🚂", "❄️", "🎨"]
))
case var .active(activeState):
if activeState.signalInfo.quality == 1.0 {
activeState.signalInfo.quality = 0.1
} else {
activeState.signalInfo.quality = 1.0
}
params.state.lifecycleState = .active(activeState)
}
self.params = params
self.update(transition: .spring(duration: 0.3))
}),
ButtonGroupView.Button(content: .video(isActive: self.isVideoOn), action: { [weak self] in
guard let self else {
return
}
if self.remoteVideo == nil {
if let url = Bundle.main.url(forResource: "test2", withExtension: "mp4") {
self.remoteVideo = FileVideoSource(device: MetalEngine.shared.device, url: url)
}
} else {
self.remoteVideo = nil
}
self.isVideoOn = !self.isVideoOn
self.update(transition: .spring(duration: 0.3))
}),
ButtonGroupView.Button(content: .microphone(isMuted: self.isMicrophoneMuted), action: {
}),
ButtonGroupView.Button(content: .end, action: {
})
]
self.buttonGroupView.update(size: params.size, buttons: buttons, transition: transition)
self.contentView.frame = CGRect(origin: CGPoint(), size: params.size)
self.contentView.update(
size: params.size,
insets: params.insets,
screenCornerRadius: params.screenCornerRadius,
state: params.state,
remoteVideo: remoteVideo,
transition: transition
)
}
}