mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
[WIP] Call UI
This commit is contained in:
parent
61efee0d51
commit
5da3442266
@ -142,6 +142,8 @@ public final class ViewController: UIViewController {
|
|||||||
self.callState.lifecycleState = .terminated(PrivateCallScreen.State.TerminatedState(duration: 82.0))
|
self.callState.lifecycleState = .terminated(PrivateCallScreen.State.TerminatedState(duration: 82.0))
|
||||||
self.callState.remoteVideo = nil
|
self.callState.remoteVideo = nil
|
||||||
self.callState.localVideo = nil
|
self.callState.localVideo = nil
|
||||||
|
self.callState.isMicrophoneMuted = false
|
||||||
|
self.callState.isRemoteBatteryLow = false
|
||||||
self.update(transition: .spring(duration: 0.4))
|
self.update(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
callScreenView.backAction = { [weak self] in
|
callScreenView.backAction = { [weak self] in
|
||||||
@ -151,6 +153,12 @@ public final class ViewController: UIViewController {
|
|||||||
self.callState.isMicrophoneMuted = !self.callState.isMicrophoneMuted
|
self.callState.isMicrophoneMuted = !self.callState.isMicrophoneMuted
|
||||||
self.update(transition: .spring(duration: 0.4))
|
self.update(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
|
callScreenView.closeAction = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.callScreenView?.speakerAction?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func update(transition: Transition) {
|
private func update(transition: Transition) {
|
||||||
|
|||||||
@ -136,7 +136,7 @@ public final class CallController: ViewController {
|
|||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
if self.sharedContext.immediateExperimentalUISettings.callUIV2 {
|
if self.sharedContext.immediateExperimentalUISettings.callUIV2 {
|
||||||
self.displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
self.displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), easyDebugAccess: self.easyDebugAccess, call: self.call)
|
||||||
} else {
|
} else {
|
||||||
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,8 +29,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
private let callScreen: PrivateCallScreen
|
private let callScreen: PrivateCallScreen
|
||||||
private var callScreenState: PrivateCallScreen.State?
|
private var callScreenState: PrivateCallScreen.State?
|
||||||
|
|
||||||
private var shouldStayHiddenUntilConnection: Bool = false
|
|
||||||
|
|
||||||
private var callStartTimestamp: Double?
|
private var callStartTimestamp: Double?
|
||||||
|
|
||||||
private var callState: PresentationCallState?
|
private var callState: PresentationCallState?
|
||||||
@ -67,7 +65,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
presentationData: PresentationData,
|
presentationData: PresentationData,
|
||||||
statusBar: StatusBar,
|
statusBar: StatusBar,
|
||||||
debugInfo: Signal<(String, String), NoError>,
|
debugInfo: Signal<(String, String), NoError>,
|
||||||
shouldStayHiddenUntilConnection: Bool = false,
|
|
||||||
easyDebugAccess: Bool,
|
easyDebugAccess: Bool,
|
||||||
call: PresentationCall
|
call: PresentationCall
|
||||||
) {
|
) {
|
||||||
@ -80,8 +77,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
self.containerView = UIView()
|
self.containerView = UIView()
|
||||||
self.callScreen = PrivateCallScreen()
|
self.callScreen = PrivateCallScreen()
|
||||||
|
|
||||||
self.shouldStayHiddenUntilConnection = shouldStayHiddenUntilConnection
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.view.addSubview(self.containerView)
|
self.view.addSubview(self.containerView)
|
||||||
@ -123,6 +118,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
}
|
}
|
||||||
self.back?()
|
self.back?()
|
||||||
}
|
}
|
||||||
|
self.callScreen.closeAction = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.dismissedInteractively?()
|
||||||
|
}
|
||||||
|
|
||||||
self.callScreenState = PrivateCallScreen.State(
|
self.callScreenState = PrivateCallScreen.State(
|
||||||
lifecycleState: .connecting,
|
lifecycleState: .connecting,
|
||||||
@ -130,7 +131,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
shortName: " ",
|
shortName: " ",
|
||||||
avatarImage: nil,
|
avatarImage: nil,
|
||||||
audioOutput: .internalSpeaker,
|
audioOutput: .internalSpeaker,
|
||||||
isMicrophoneMuted: false,
|
isLocalAudioMuted: false,
|
||||||
|
isRemoteAudioMuted: false,
|
||||||
localVideo: nil,
|
localVideo: nil,
|
||||||
remoteVideo: nil,
|
remoteVideo: nil,
|
||||||
isRemoteBatteryLow: false
|
isRemoteBatteryLow: false
|
||||||
@ -145,8 +147,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.isMuted = isMuted
|
self.isMuted = isMuted
|
||||||
if callScreenState.isMicrophoneMuted != isMuted {
|
if callScreenState.isLocalAudioMuted != isMuted {
|
||||||
callScreenState.isMicrophoneMuted = isMuted
|
callScreenState.isLocalAudioMuted = isMuted
|
||||||
self.callScreenState = callScreenState
|
self.callScreenState = callScreenState
|
||||||
self.update(transition: .animated(duration: 0.3, curve: .spring))
|
self.update(transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
@ -373,6 +375,13 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
callScreenState.isRemoteBatteryLow = false
|
callScreenState.isRemoteBatteryLow = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch callState.remoteAudioState {
|
||||||
|
case .muted:
|
||||||
|
callScreenState.isRemoteAudioMuted = true
|
||||||
|
case .active:
|
||||||
|
callScreenState.isRemoteAudioMuted = false
|
||||||
|
}
|
||||||
|
|
||||||
if self.callScreenState != callScreenState {
|
if self.callScreenState != callScreenState {
|
||||||
self.callScreenState = callScreenState
|
self.callScreenState = callScreenState
|
||||||
self.update(transition: .animated(duration: 0.35, curve: .spring))
|
self.update(transition: .animated(duration: 0.35, curve: .spring))
|
||||||
@ -393,6 +402,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
callScreenState.name = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
|
callScreenState.name = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
|
||||||
|
callScreenState.shortName = peer.compactDisplayTitle
|
||||||
|
|
||||||
if self.currentPeer?.smallProfileImage != peer.smallProfileImage {
|
if self.currentPeer?.smallProfileImage != peer.smallProfileImage {
|
||||||
self.peerAvatarDisposable?.dispose()
|
self.peerAvatarDisposable?.dispose()
|
||||||
@ -460,16 +470,14 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
self.containerView.layer.removeAnimation(forKey: "scale")
|
self.containerView.layer.removeAnimation(forKey: "scale")
|
||||||
self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
|
||||||
if !self.shouldStayHiddenUntilConnection {
|
|
||||||
self.containerView.layer.animateScale(from: 1.04, to: 1.0, duration: 0.3)
|
self.containerView.layer.animateScale(from: 1.04, to: 1.0, duration: 0.3)
|
||||||
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
self.statusBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
self.statusBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
if !self.shouldStayHiddenUntilConnection || self.containerView.alpha > 0.0 {
|
if self.containerView.alpha > 0.0 {
|
||||||
self.containerView.layer.allowsGroupOpacity = true
|
self.containerView.layer.allowsGroupOpacity = true
|
||||||
self.containerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
|
self.containerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
self?.containerView.layer.allowsGroupOpacity = false
|
self?.containerView.layer.allowsGroupOpacity = false
|
||||||
@ -499,7 +507,14 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
transition.updateFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
transition.updateFrame(view: self.callScreen, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(view: self.callScreen, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
|
||||||
if let callScreenState = self.callScreenState {
|
if var callScreenState = self.callScreenState {
|
||||||
|
if case .terminated = callScreenState.lifecycleState {
|
||||||
|
callScreenState.isLocalAudioMuted = false
|
||||||
|
callScreenState.isRemoteAudioMuted = false
|
||||||
|
callScreenState.isRemoteBatteryLow = false
|
||||||
|
callScreenState.localVideo = nil
|
||||||
|
callScreenState.remoteVideo = nil
|
||||||
|
}
|
||||||
self.callScreen.update(
|
self.callScreen.update(
|
||||||
size: layout.size,
|
size: layout.size,
|
||||||
insets: layout.insets(options: [.statusBar]),
|
insets: layout.insets(options: [.statusBar]),
|
||||||
|
|||||||
@ -67,6 +67,7 @@ swift_library(
|
|||||||
"//submodules/SSignalKit/SwiftSignalKit",
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
"//submodules/AppBundle",
|
"//submodules/AppBundle",
|
||||||
|
"//submodules/UIKitRuntimeUtils",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -58,8 +58,10 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
|
|
||||||
private var buttons: [Button]?
|
private var buttons: [Button]?
|
||||||
private var buttonViews: [Button.Content.Key: ContentOverlayButton] = [:]
|
private var buttonViews: [Button.Content.Key: ContentOverlayButton] = [:]
|
||||||
|
|
||||||
private var noticeViews: [AnyHashable: NoticeView] = [:]
|
private var noticeViews: [AnyHashable: NoticeView] = [:]
|
||||||
|
private var closeButtonView: CloseButtonView?
|
||||||
|
|
||||||
|
var closePressed: (() -> Void)?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -79,7 +81,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, insets: UIEdgeInsets, controlsHidden: Bool, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat {
|
func update(size: CGSize, insets: UIEdgeInsets, minWidth: CGFloat, controlsHidden: Bool, displayClose: Bool, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat {
|
||||||
self.buttons = buttons
|
self.buttons = buttons
|
||||||
|
|
||||||
let buttonSize: CGFloat = 56.0
|
let buttonSize: CGFloat = 56.0
|
||||||
@ -163,6 +165,46 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
}
|
}
|
||||||
var buttonX: CGFloat = floor((size.width - buttonSize * CGFloat(buttons.count) - buttonSpacing * CGFloat(buttons.count - 1)) * 0.5)
|
var buttonX: CGFloat = floor((size.width - buttonSize * CGFloat(buttons.count) - buttonSpacing * CGFloat(buttons.count - 1)) * 0.5)
|
||||||
|
|
||||||
|
if displayClose {
|
||||||
|
let closeButtonView: CloseButtonView
|
||||||
|
var closeButtonTransition = transition
|
||||||
|
var animateIn = false
|
||||||
|
if let current = self.closeButtonView {
|
||||||
|
closeButtonView = current
|
||||||
|
} else {
|
||||||
|
closeButtonTransition = closeButtonTransition.withAnimation(.none)
|
||||||
|
animateIn = true
|
||||||
|
|
||||||
|
closeButtonView = CloseButtonView()
|
||||||
|
self.closeButtonView = closeButtonView
|
||||||
|
self.addSubview(closeButtonView)
|
||||||
|
closeButtonView.pressAction = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.closePressed?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let closeButtonSize = CGSize(width: minWidth, height: buttonSize)
|
||||||
|
closeButtonView.update(text: "Close", size: closeButtonSize, transition: closeButtonTransition)
|
||||||
|
closeButtonTransition.setFrame(view: closeButtonView, frame: CGRect(origin: CGPoint(x: floor((size.width - closeButtonSize.width) * 0.5), y: buttonY), size: closeButtonSize))
|
||||||
|
|
||||||
|
if animateIn && !transition.animation.isImmediate {
|
||||||
|
closeButtonView.animateIn()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let closeButtonView = self.closeButtonView {
|
||||||
|
self.closeButtonView = nil
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
closeButtonView.animateOut(completion: { [weak closeButtonView] in
|
||||||
|
closeButtonView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
closeButtonView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for button in buttons {
|
for button in buttons {
|
||||||
let title: String
|
let title: String
|
||||||
let image: UIImage?
|
let image: UIImage?
|
||||||
@ -213,9 +255,10 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
Transition.immediate.setScale(view: buttonView, scale: 0.001)
|
Transition.immediate.setScale(view: buttonView, scale: 0.001)
|
||||||
buttonView.alpha = 0.0
|
buttonView.alpha = 0.0
|
||||||
transition.setScale(view: buttonView, scale: 1.0)
|
transition.setScale(view: buttonView, scale: 1.0)
|
||||||
transition.setAlpha(view: buttonView, alpha: 1.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transition.setAlpha(view: buttonView, alpha: displayClose ? 0.0 : 1.0)
|
||||||
|
|
||||||
buttonTransition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: buttonX, y: buttonY), size: CGSize(width: buttonSize, height: buttonSize)))
|
buttonTransition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: buttonX, y: buttonY), size: CGSize(width: buttonSize, height: buttonSize)))
|
||||||
buttonView.update(size: CGSize(width: buttonSize, height: buttonSize), image: image, isSelected: isActive, isDestructive: isDestructive, title: title, transition: buttonTransition)
|
buttonView.update(size: CGSize(width: buttonSize, height: buttonSize), image: image, isSelected: isActive, isDestructive: isDestructive, title: title, transition: buttonTransition)
|
||||||
buttonX += buttonSize + buttonSpacing
|
buttonX += buttonSize + buttonSpacing
|
||||||
|
|||||||
@ -0,0 +1,185 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import UIKitRuntimeUtils
|
||||||
|
|
||||||
|
final class CloseButtonView: HighlightTrackingButton, OverlayMaskContainerViewProtocol {
|
||||||
|
private struct Params: Equatable {
|
||||||
|
var text: String
|
||||||
|
var size: CGSize
|
||||||
|
|
||||||
|
init(text: String, size: CGSize) {
|
||||||
|
self.text = text
|
||||||
|
self.size = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let backdropBackgroundView: RoundedCornersView
|
||||||
|
private let backgroundView: RoundedCornersView
|
||||||
|
private let backgroundMaskView: UIView
|
||||||
|
private let backgroundClippingView: UIView
|
||||||
|
|
||||||
|
private let duration: Double = 5.0
|
||||||
|
private var fillTime: Double = 0.0
|
||||||
|
|
||||||
|
private let backgroundTextView: TextView
|
||||||
|
private let backgroundTextClippingView: UIView
|
||||||
|
|
||||||
|
private let textView: TextView
|
||||||
|
|
||||||
|
var pressAction: (() -> Void)?
|
||||||
|
|
||||||
|
private var params: Params?
|
||||||
|
private var updateDisplayLink: SharedDisplayLinkDriver.Link?
|
||||||
|
|
||||||
|
let maskContents: UIView
|
||||||
|
override static var layerClass: AnyClass {
|
||||||
|
return MirroringLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.backdropBackgroundView = RoundedCornersView(color: .white, smoothCorners: true)
|
||||||
|
self.backdropBackgroundView.update(cornerRadius: 12.0, transition: .immediate)
|
||||||
|
|
||||||
|
self.backgroundView = RoundedCornersView(color: .white, smoothCorners: true)
|
||||||
|
self.backgroundView.update(cornerRadius: 12.0, transition: .immediate)
|
||||||
|
self.backgroundView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.backgroundMaskView = UIView()
|
||||||
|
self.backgroundMaskView.backgroundColor = .white
|
||||||
|
self.backgroundView.mask = self.backgroundMaskView
|
||||||
|
if let filter = makeLuminanceToAlphaFilter() {
|
||||||
|
self.backgroundMaskView.layer.filters = [filter]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backgroundClippingView = UIView()
|
||||||
|
self.backgroundClippingView.clipsToBounds = true
|
||||||
|
self.backgroundClippingView.layer.cornerRadius = 12.0
|
||||||
|
|
||||||
|
self.backgroundTextClippingView = UIView()
|
||||||
|
self.backgroundTextClippingView.clipsToBounds = true
|
||||||
|
|
||||||
|
self.backgroundTextView = TextView()
|
||||||
|
self.textView = TextView()
|
||||||
|
|
||||||
|
self.maskContents = UIView()
|
||||||
|
|
||||||
|
self.maskContents.addSubview(self.backdropBackgroundView)
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
(self.layer as? MirroringLayer)?.targetLayer = self.maskContents.layer
|
||||||
|
|
||||||
|
self.backgroundTextClippingView.addSubview(self.backgroundTextView)
|
||||||
|
self.backgroundTextClippingView.isUserInteractionEnabled = false
|
||||||
|
self.addSubview(self.backgroundTextClippingView)
|
||||||
|
|
||||||
|
self.backgroundClippingView.addSubview(self.backgroundView)
|
||||||
|
self.backgroundClippingView.isUserInteractionEnabled = false
|
||||||
|
self.addSubview(self.backgroundClippingView)
|
||||||
|
|
||||||
|
self.backgroundMaskView.addSubview(self.textView)
|
||||||
|
|
||||||
|
self.internalHighligthedChanged = { [weak self] highlighted in
|
||||||
|
if let self, self.bounds.width > 0.0 {
|
||||||
|
let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width
|
||||||
|
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
|
||||||
|
|
||||||
|
if highlighted {
|
||||||
|
self.layer.removeAnimation(forKey: "sublayerTransform")
|
||||||
|
let transition = Transition(animation: .curve(duration: 0.15, curve: .easeInOut))
|
||||||
|
transition.setScale(layer: self.layer, scale: topScale)
|
||||||
|
} else {
|
||||||
|
let t = self.layer.presentation()?.transform ?? layer.transform
|
||||||
|
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||||
|
|
||||||
|
let transition = Transition(animation: .none)
|
||||||
|
transition.setScale(layer: self.layer, scale: 1.0)
|
||||||
|
|
||||||
|
self.layer.animateScale(from: currentScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] completed in
|
||||||
|
guard let self, completed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
|
|
||||||
|
(self.layer as? MirroringLayer)?.didEnterHierarchy = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.fillTime < self.duration && self.updateDisplayLink == nil {
|
||||||
|
self.updateDisplayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.fillTime = min(self.duration, self.fillTime + deltaTime)
|
||||||
|
if let params = self.params {
|
||||||
|
self.update(params: params, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.fillTime >= self.duration {
|
||||||
|
self.updateDisplayLink = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func pressed() {
|
||||||
|
self.pressAction?()
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(text: String, size: CGSize, transition: Transition) {
|
||||||
|
let params = Params(text: text, size: size)
|
||||||
|
if self.params == params {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.params = params
|
||||||
|
self.update(params: params, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func update(params: Params, transition: Transition) {
|
||||||
|
let fillFraction: CGFloat = CGFloat(self.fillTime / self.duration)
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 12.0
|
||||||
|
let textSize = self.textView.update(string: params.text, fontSize: 17.0, fontWeight: UIFont.Weight.semibold.rawValue, color: .black, constrainedWidth: params.size.width - sideInset * 2.0, transition: .immediate)
|
||||||
|
let _ = self.backgroundTextView.update(string: params.text, fontSize: 17.0, fontWeight: UIFont.Weight.semibold.rawValue, color: .white, constrainedWidth: params.size.width - sideInset * 2.0, transition: .immediate)
|
||||||
|
|
||||||
|
transition.setFrame(view: self.backdropBackgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||||
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||||
|
transition.setFrame(view: self.backgroundMaskView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||||
|
|
||||||
|
let progressWidth: CGFloat = max(0.0, min(params.size.width, floorToScreenPixels(fillFraction * params.size.width)))
|
||||||
|
let backgroundClippingFrame = CGRect(origin: CGPoint(x: progressWidth, y: 0.0), size: CGSize(width: params.size.width - progressWidth, height: params.size.height))
|
||||||
|
transition.setPosition(view: self.backgroundClippingView, position: backgroundClippingFrame.center)
|
||||||
|
transition.setBounds(view: self.backgroundClippingView, bounds: CGRect(origin: CGPoint(x: backgroundClippingFrame.minX, y: 0.0), size: backgroundClippingFrame.size))
|
||||||
|
|
||||||
|
let backgroundTextClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: progressWidth, height: params.size.height))
|
||||||
|
transition.setPosition(view: self.backgroundTextClippingView, position: backgroundTextClippingFrame.center)
|
||||||
|
transition.setBounds(view: self.backgroundTextClippingView, bounds: CGRect(origin: CGPoint(), size: backgroundTextClippingFrame.size))
|
||||||
|
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: floor((params.size.width - textSize.width) * 0.5), y: floor((params.size.height - textSize.height) * 0.5)), size: textSize)
|
||||||
|
transition.setFrame(view: self.textView, frame: textFrame)
|
||||||
|
transition.setFrame(view: self.backgroundTextView, frame: textFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -63,8 +63,7 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
|
|||||||
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
|
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
|
||||||
|
|
||||||
if highlighted {
|
if highlighted {
|
||||||
self.layer.removeAnimation(forKey: "opacity")
|
self.layer.removeAnimation(forKey: "sublayerTransform")
|
||||||
self.layer.removeAnimation(forKey: "transform")
|
|
||||||
let transition = Transition(animation: .curve(duration: 0.15, curve: .easeInOut))
|
let transition = Transition(animation: .curve(duration: 0.15, curve: .easeInOut))
|
||||||
transition.setScale(layer: self.layer, scale: topScale)
|
transition.setScale(layer: self.layer, scale: topScale)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -5,10 +5,10 @@ import ComponentFlow
|
|||||||
|
|
||||||
final class EmojiExpandedInfoView: OverlayMaskContainerView {
|
final class EmojiExpandedInfoView: OverlayMaskContainerView {
|
||||||
private struct Params: Equatable {
|
private struct Params: Equatable {
|
||||||
var constrainedWidth: CGFloat
|
var width: CGFloat
|
||||||
|
|
||||||
init(constrainedWidth: CGFloat) {
|
init(width: CGFloat) {
|
||||||
self.constrainedWidth = constrainedWidth
|
self.width = width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,8 +129,8 @@ final class EmojiExpandedInfoView: OverlayMaskContainerView {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(constrainedWidth: CGFloat, transition: Transition) -> CGSize {
|
func update(width: CGFloat, transition: Transition) -> CGSize {
|
||||||
let params = Params(constrainedWidth: constrainedWidth)
|
let params = Params(width: width)
|
||||||
if let currentLayout = self.currentLayout, currentLayout.params == params {
|
if let currentLayout = self.currentLayout, currentLayout.params == params {
|
||||||
return currentLayout.size
|
return currentLayout.size
|
||||||
}
|
}
|
||||||
@ -142,16 +142,12 @@ final class EmojiExpandedInfoView: OverlayMaskContainerView {
|
|||||||
private func update(params: Params, transition: Transition) -> CGSize {
|
private func update(params: Params, transition: Transition) -> CGSize {
|
||||||
let buttonHeight: CGFloat = 56.0
|
let buttonHeight: CGFloat = 56.0
|
||||||
|
|
||||||
var constrainedWidth = params.constrainedWidth
|
let titleSize = self.titleView.update(string: self.title, fontSize: 16.0, fontWeight: 0.3, alignment: .center, color: .white, constrainedWidth: params.width - 16.0 * 2.0, transition: transition)
|
||||||
constrainedWidth = min(constrainedWidth, 300.0)
|
let textSize = self.textView.update(string: self.text, fontSize: 16.0, fontWeight: 0.0, alignment: .center, color: .white, constrainedWidth: params.width - 16.0 * 2.0, transition: transition)
|
||||||
|
|
||||||
let titleSize = self.titleView.update(string: self.title, fontSize: 16.0, fontWeight: 0.3, alignment: .center, color: .white, constrainedWidth: constrainedWidth - 16.0 * 2.0, transition: transition)
|
|
||||||
let textSize = self.textView.update(string: self.text, fontSize: 16.0, fontWeight: 0.0, alignment: .center, color: .white, constrainedWidth: constrainedWidth - 16.0 * 2.0, transition: transition)
|
|
||||||
|
|
||||||
let contentWidth: CGFloat = max(titleSize.width, textSize.width) + 26.0 * 2.0
|
|
||||||
let contentHeight = 78.0 + titleSize.height + 10.0 + textSize.height + 22.0 + buttonHeight
|
let contentHeight = 78.0 + titleSize.height + 10.0 + textSize.height + 22.0 + buttonHeight
|
||||||
|
|
||||||
let size = CGSize(width: contentWidth, height: contentHeight)
|
let size = CGSize(width: params.width, height: contentHeight)
|
||||||
|
|
||||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
|||||||
@ -198,8 +198,8 @@ final class PrivateCallVideoLayer: MetalEngineSubjectLayer, MetalEngineSubject {
|
|||||||
|
|
||||||
encoder.setFragmentTexture(blurredTexture, index: 0)
|
encoder.setFragmentTexture(blurredTexture, index: 0)
|
||||||
|
|
||||||
var brightness: Float = 1.0
|
var brightness: Float = 0.7
|
||||||
var saturation: Float = 1.2
|
var saturation: Float = 1.3
|
||||||
var overlay: SIMD4<Float> = SIMD4<Float>(1.0, 1.0, 1.0, 0.2)
|
var overlay: SIMD4<Float> = SIMD4<Float>(1.0, 1.0, 1.0, 0.2)
|
||||||
encoder.setFragmentBytes(&brightness, length: 4, index: 0)
|
encoder.setFragmentBytes(&brightness, length: 4, index: 0)
|
||||||
encoder.setFragmentBytes(&saturation, length: 4, index: 1)
|
encoder.setFragmentBytes(&saturation, length: 4, index: 1)
|
||||||
|
|||||||
@ -0,0 +1,73 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
|
||||||
|
final class RatingView: OverlayMaskContainerView {
|
||||||
|
private let backgroundView: RoundedCornersView
|
||||||
|
private let textContainer: UIView
|
||||||
|
private let textView: TextView
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.backgroundView = RoundedCornersView(color: .white)
|
||||||
|
self.textContainer = UIView()
|
||||||
|
self.textContainer.clipsToBounds = true
|
||||||
|
self.textView = TextView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
self.maskContents.addSubview(self.backgroundView)
|
||||||
|
|
||||||
|
self.textContainer.addSubview(self.textView)
|
||||||
|
self.addSubview(self.textContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
let delay: Double = 0.2
|
||||||
|
|
||||||
|
self.layer.animateScale(from: 0.001, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
self.textView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
|
||||||
|
|
||||||
|
self.backgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
self.backgroundView.layer.animateFrame(from: CGRect(origin: CGPoint(x: (self.bounds.width - self.bounds.height) * 0.5, y: 0.0), size: CGSize(width: self.bounds.height, height: self.bounds.height)), to: self.backgroundView.frame, duration: 0.5, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
|
self.textContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
|
||||||
|
self.textContainer.layer.cornerRadius = self.bounds.height * 0.5
|
||||||
|
self.textContainer.layer.animateFrame(from: CGRect(origin: CGPoint(x: (self.bounds.width - self.bounds.height) * 0.5, y: 0.0), size: CGSize(width: self.bounds.height, height: self.bounds.height)), to: self.textContainer.frame, duration: 0.5, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] completed in
|
||||||
|
guard let self, completed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.textContainer.layer.cornerRadius = 0.0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
self.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(text: String, constrainedWidth: CGFloat, transition: Transition) -> CGSize {
|
||||||
|
let sideInset: CGFloat = 12.0
|
||||||
|
let verticalInset: CGFloat = 6.0
|
||||||
|
|
||||||
|
let textSize = self.textView.update(string: text, fontSize: 15.0, fontWeight: 0.0, color: .white, constrainedWidth: constrainedWidth - sideInset * 2.0, transition: .immediate)
|
||||||
|
let size = CGSize(width: textSize.width + sideInset * 2.0, height: textSize.height + verticalInset * 2.0)
|
||||||
|
|
||||||
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.backgroundView.update(cornerRadius: floor(size.height * 0.5), transition: transition)
|
||||||
|
|
||||||
|
transition.setFrame(view: self.textContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
transition.setFrame(view: self.textView, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: textSize))
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,17 +5,16 @@ import ComponentFlow
|
|||||||
|
|
||||||
final class RoundedCornersView: UIImageView {
|
final class RoundedCornersView: UIImageView {
|
||||||
private let color: UIColor
|
private let color: UIColor
|
||||||
|
private let smoothCorners: Bool
|
||||||
|
|
||||||
private var currentCornerRadius: CGFloat?
|
private var currentCornerRadius: CGFloat?
|
||||||
private var cornerImage: UIImage?
|
private var cornerImage: UIImage?
|
||||||
|
|
||||||
init(color: UIColor) {
|
init(color: UIColor, smoothCorners: Bool = false) {
|
||||||
self.color = color
|
self.color = color
|
||||||
|
self.smoothCorners = smoothCorners
|
||||||
|
|
||||||
super.init(image: nil)
|
super.init(image: nil)
|
||||||
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.layer.cornerCurve = .circular
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -26,11 +25,24 @@ final class RoundedCornersView: UIImageView {
|
|||||||
guard let cornerRadius = self.currentCornerRadius else {
|
guard let cornerRadius = self.currentCornerRadius else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let cornerImage = self.cornerImage, cornerImage.size.height == cornerRadius * 2.0 {
|
if self.smoothCorners {
|
||||||
|
let size = CGSize(width: cornerRadius * 2.0 + 10.0, height: cornerRadius * 2.0 + 10.0)
|
||||||
|
if let cornerImage = self.cornerImage, cornerImage.size == size {
|
||||||
|
} else {
|
||||||
|
self.cornerImage = generateImage(size, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: cornerRadius).cgPath)
|
||||||
|
context.setFillColor(self.color.cgColor)
|
||||||
|
context.fillPath()
|
||||||
|
})?.stretchableImage(withLeftCapWidth: Int(cornerRadius) + 5, topCapHeight: Int(cornerRadius) + 5)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let size = CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0)
|
let size = CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0)
|
||||||
|
if let cornerImage = self.cornerImage, cornerImage.size == size {
|
||||||
|
} else {
|
||||||
self.cornerImage = generateStretchableFilledCircleImage(diameter: size.width, color: self.color)
|
self.cornerImage = generateStretchableFilledCircleImage(diameter: size.width, color: self.color)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.image = self.cornerImage
|
self.image = self.cornerImage
|
||||||
self.clipsToBounds = false
|
self.clipsToBounds = false
|
||||||
self.backgroundColor = nil
|
self.backgroundColor = nil
|
||||||
@ -52,6 +64,14 @@ final class RoundedCornersView: UIImageView {
|
|||||||
if let previousCornerRadius, self.layer.animation(forKey: "cornerRadius") == nil {
|
if let previousCornerRadius, self.layer.animation(forKey: "cornerRadius") == nil {
|
||||||
self.layer.cornerRadius = previousCornerRadius
|
self.layer.cornerRadius = previousCornerRadius
|
||||||
}
|
}
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
if self.smoothCorners {
|
||||||
|
self.layer.cornerCurve = .continuous
|
||||||
|
} else {
|
||||||
|
self.layer.cornerCurve = .circular
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius, completion: { [weak self] completed in
|
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius, completion: { [weak self] completed in
|
||||||
guard let self, completed else {
|
guard let self, completed else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -100,7 +100,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
public var shortName: String
|
public var shortName: String
|
||||||
public var avatarImage: UIImage?
|
public var avatarImage: UIImage?
|
||||||
public var audioOutput: AudioOutput
|
public var audioOutput: AudioOutput
|
||||||
public var isMicrophoneMuted: Bool
|
public var isLocalAudioMuted: Bool
|
||||||
|
public var isRemoteAudioMuted: Bool
|
||||||
public var localVideo: VideoSource?
|
public var localVideo: VideoSource?
|
||||||
public var remoteVideo: VideoSource?
|
public var remoteVideo: VideoSource?
|
||||||
public var isRemoteBatteryLow: Bool
|
public var isRemoteBatteryLow: Bool
|
||||||
@ -111,7 +112,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
shortName: String,
|
shortName: String,
|
||||||
avatarImage: UIImage?,
|
avatarImage: UIImage?,
|
||||||
audioOutput: AudioOutput,
|
audioOutput: AudioOutput,
|
||||||
isMicrophoneMuted: Bool,
|
isLocalAudioMuted: Bool,
|
||||||
|
isRemoteAudioMuted: Bool,
|
||||||
localVideo: VideoSource?,
|
localVideo: VideoSource?,
|
||||||
remoteVideo: VideoSource?,
|
remoteVideo: VideoSource?,
|
||||||
isRemoteBatteryLow: Bool
|
isRemoteBatteryLow: Bool
|
||||||
@ -121,7 +123,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
self.shortName = shortName
|
self.shortName = shortName
|
||||||
self.avatarImage = avatarImage
|
self.avatarImage = avatarImage
|
||||||
self.audioOutput = audioOutput
|
self.audioOutput = audioOutput
|
||||||
self.isMicrophoneMuted = isMicrophoneMuted
|
self.isLocalAudioMuted = isLocalAudioMuted
|
||||||
|
self.isRemoteAudioMuted = isRemoteAudioMuted
|
||||||
self.localVideo = localVideo
|
self.localVideo = localVideo
|
||||||
self.remoteVideo = remoteVideo
|
self.remoteVideo = remoteVideo
|
||||||
self.isRemoteBatteryLow = isRemoteBatteryLow
|
self.isRemoteBatteryLow = isRemoteBatteryLow
|
||||||
@ -143,7 +146,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
if lhs.audioOutput != rhs.audioOutput {
|
if lhs.audioOutput != rhs.audioOutput {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.isMicrophoneMuted != rhs.isMicrophoneMuted {
|
if lhs.isLocalAudioMuted != rhs.isLocalAudioMuted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isRemoteAudioMuted != rhs.isRemoteAudioMuted {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.localVideo !== rhs.localVideo {
|
if lhs.localVideo !== rhs.localVideo {
|
||||||
@ -224,6 +230,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
public var microhoneMuteAction: (() -> Void)?
|
public var microhoneMuteAction: (() -> Void)?
|
||||||
public var endCallAction: (() -> Void)?
|
public var endCallAction: (() -> Void)?
|
||||||
public var backAction: (() -> Void)?
|
public var backAction: (() -> Void)?
|
||||||
|
public var closeAction: (() -> Void)?
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
self.overlayContentsView = UIView()
|
self.overlayContentsView = UIView()
|
||||||
@ -264,10 +271,6 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
self.avatarTransformLayer.addSublayer(self.avatarLayer)
|
self.avatarTransformLayer.addSublayer(self.avatarLayer)
|
||||||
self.layer.addSublayer(self.avatarTransformLayer)
|
self.layer.addSublayer(self.avatarTransformLayer)
|
||||||
|
|
||||||
/*let edgeTestLayer = EdgeTestLayer()
|
|
||||||
edgeTestLayer.frame = CGRect(origin: CGPoint(x: 20.0, y: 100.0), size: CGSize(width: 100.0, height: 100.0))
|
|
||||||
self.layer.addSublayer(edgeTestLayer)*/
|
|
||||||
|
|
||||||
self.addSubview(self.videoContainerBackgroundView)
|
self.addSubview(self.videoContainerBackgroundView)
|
||||||
|
|
||||||
self.overlayContentsView.mask = self.maskContents
|
self.overlayContentsView.mask = self.maskContents
|
||||||
@ -310,6 +313,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
}
|
}
|
||||||
self.backAction?()
|
self.backAction?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.buttonGroupView.closePressed = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.closeAction?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public required init?(coder: NSCoder) {
|
public required init?(coder: NSCoder) {
|
||||||
@ -497,6 +507,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(), size: params.size)
|
let backgroundFrame = CGRect(origin: CGPoint(), size: params.size)
|
||||||
|
|
||||||
|
let wideContentWidth: CGFloat
|
||||||
|
if params.size.width < 500.0 {
|
||||||
|
wideContentWidth = params.size.width - 44.0 * 2.0
|
||||||
|
} else {
|
||||||
|
wideContentWidth = 400.0
|
||||||
|
}
|
||||||
|
|
||||||
var activeVideoSources: [(VideoContainerView.Key, VideoSource)] = []
|
var activeVideoSources: [(VideoContainerView.Key, VideoSource)] = []
|
||||||
if self.swapLocalAndRemoteVideo {
|
if self.swapLocalAndRemoteVideo {
|
||||||
if let activeLocalVideoSource = self.activeLocalVideoSource {
|
if let activeLocalVideoSource = self.activeLocalVideoSource {
|
||||||
@ -554,7 +571,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
}
|
}
|
||||||
self.videoAction?()
|
self.videoAction?()
|
||||||
}),
|
}),
|
||||||
ButtonGroupView.Button(content: .microphone(isMuted: params.state.isMicrophoneMuted), action: { [weak self] in
|
ButtonGroupView.Button(content: .microphone(isMuted: params.state.isLocalAudioMuted), action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -584,9 +601,12 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var notices: [ButtonGroupView.Notice] = []
|
var notices: [ButtonGroupView.Notice] = []
|
||||||
if params.state.isMicrophoneMuted {
|
if params.state.isLocalAudioMuted {
|
||||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), text: "Your microphone is turned off"))
|
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), text: "Your microphone is turned off"))
|
||||||
}
|
}
|
||||||
|
if params.state.isRemoteAudioMuted {
|
||||||
|
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), text: "\(params.state.shortName)'s microphone is turned off"))
|
||||||
|
}
|
||||||
if params.state.remoteVideo != nil && params.state.localVideo == nil {
|
if params.state.remoteVideo != nil && params.state.localVideo == nil {
|
||||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), text: "Your camera is turned off"))
|
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), text: "Your camera is turned off"))
|
||||||
}
|
}
|
||||||
@ -594,7 +614,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), text: "\(params.state.shortName)'s battery is low"))
|
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), text: "\(params.state.shortName)'s battery is low"))
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, controlsHidden: currentAreControlsHidden, buttons: buttons, notices: notices, transition: transition)
|
var displayClose = false
|
||||||
|
if case .terminated = params.state.lifecycleState {
|
||||||
|
displayClose = true
|
||||||
|
}
|
||||||
|
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, buttons: buttons, notices: notices, transition: transition)
|
||||||
|
|
||||||
var expandedEmojiKeyRect: CGRect?
|
var expandedEmojiKeyRect: CGRect?
|
||||||
if self.isEmojiKeyExpanded {
|
if self.isEmojiKeyExpanded {
|
||||||
@ -632,7 +656,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let emojiExpandedInfoSize = emojiExpandedInfoView.update(constrainedWidth: params.size.width - (params.insets.left + 16.0) * 2.0, transition: emojiExpandedInfoTransition)
|
let emojiExpandedInfoSize = emojiExpandedInfoView.update(width: wideContentWidth, transition: emojiExpandedInfoTransition)
|
||||||
let emojiExpandedInfoFrame = CGRect(origin: CGPoint(x: floor((params.size.width - emojiExpandedInfoSize.width) * 0.5), y: params.insets.top + 73.0), size: emojiExpandedInfoSize)
|
let emojiExpandedInfoFrame = CGRect(origin: CGPoint(x: floor((params.size.width - emojiExpandedInfoSize.width) * 0.5), y: params.insets.top + 73.0), size: emojiExpandedInfoSize)
|
||||||
emojiExpandedInfoTransition.setPosition(view: emojiExpandedInfoView, position: CGPoint(x: emojiExpandedInfoFrame.minX + emojiExpandedInfoView.layer.anchorPoint.x * emojiExpandedInfoFrame.width, y: emojiExpandedInfoFrame.minY + emojiExpandedInfoView.layer.anchorPoint.y * emojiExpandedInfoFrame.height))
|
emojiExpandedInfoTransition.setPosition(view: emojiExpandedInfoView, position: CGPoint(x: emojiExpandedInfoFrame.minX + emojiExpandedInfoView.layer.anchorPoint.x * emojiExpandedInfoFrame.width, y: emojiExpandedInfoFrame.minY + emojiExpandedInfoView.layer.anchorPoint.y * emojiExpandedInfoFrame.height))
|
||||||
emojiExpandedInfoTransition.setBounds(view: emojiExpandedInfoView, bounds: CGRect(origin: CGPoint(), size: emojiExpandedInfoFrame.size))
|
emojiExpandedInfoTransition.setBounds(view: emojiExpandedInfoView, bounds: CGRect(origin: CGPoint(), size: emojiExpandedInfoFrame.size))
|
||||||
|
|||||||
@ -132,8 +132,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
private var groupCallDisposable: Disposable?
|
private var groupCallDisposable: Disposable?
|
||||||
|
|
||||||
private var callController: CallController?
|
private var callController: CallController?
|
||||||
|
private var call: PresentationCall?
|
||||||
public let hasOngoingCall = ValuePromise<Bool>(false)
|
public let hasOngoingCall = ValuePromise<Bool>(false)
|
||||||
private let callState = Promise<PresentationCallState?>(nil)
|
private let callState = Promise<PresentationCallState?>(nil)
|
||||||
|
private var awaitingCallConnectionDisposable: Disposable?
|
||||||
|
|
||||||
private var groupCallController: VoiceChatController?
|
private var groupCallController: VoiceChatController?
|
||||||
public var currentGroupCallController: ViewController? {
|
public var currentGroupCallController: ViewController? {
|
||||||
@ -741,26 +743,49 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
|
|
||||||
self.callDisposable = (callManager.currentCallSignal
|
self.callDisposable = (callManager.currentCallSignal
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] call in
|
|> deliverOnMainQueue).start(next: { [weak self] call in
|
||||||
if let strongSelf = self {
|
guard let self else {
|
||||||
if call !== strongSelf.callController?.call {
|
return
|
||||||
strongSelf.callController?.dismiss()
|
|
||||||
strongSelf.callController = nil
|
|
||||||
strongSelf.hasOngoingCall.set(false)
|
|
||||||
|
|
||||||
if let call = call {
|
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
|
||||||
let callController = CallController(sharedContext: strongSelf, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
|
|
||||||
strongSelf.callController = callController
|
|
||||||
strongSelf.mainWindow?.present(callController, on: .calls)
|
|
||||||
strongSelf.callState.set(call.state
|
|
||||||
|> map(Optional.init))
|
|
||||||
strongSelf.hasOngoingCall.set(true)
|
|
||||||
setNotificationCall(call)
|
|
||||||
} else {
|
|
||||||
strongSelf.callState.set(.single(nil))
|
|
||||||
strongSelf.hasOngoingCall.set(false)
|
|
||||||
setNotificationCall(nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if call !== self.call {
|
||||||
|
self.call = call
|
||||||
|
|
||||||
|
self.callController?.dismiss()
|
||||||
|
self.callController = nil
|
||||||
|
self.hasOngoingCall.set(false)
|
||||||
|
|
||||||
|
if let call {
|
||||||
|
self.callState.set(call.state
|
||||||
|
|> map(Optional.init))
|
||||||
|
self.hasOngoingCall.set(true)
|
||||||
|
setNotificationCall(call)
|
||||||
|
|
||||||
|
if !call.isOutgoing && call.isIntegratedWithCallKit {
|
||||||
|
self.awaitingCallConnectionDisposable = (call.state
|
||||||
|
|> filter { state in
|
||||||
|
switch state.state {
|
||||||
|
case .ringing:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentControllerWithCurrentCall()
|
||||||
|
})
|
||||||
|
} else{
|
||||||
|
self.presentControllerWithCurrentCall()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.callState.set(.single(nil))
|
||||||
|
self.hasOngoingCall.set(false)
|
||||||
|
self.awaitingCallConnectionDisposable?.dispose()
|
||||||
|
self.awaitingCallConnectionDisposable = nil
|
||||||
|
setNotificationCall(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -951,6 +976,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
self.callDisposable?.dispose()
|
self.callDisposable?.dispose()
|
||||||
self.groupCallDisposable?.dispose()
|
self.groupCallDisposable?.dispose()
|
||||||
self.callStateDisposable?.dispose()
|
self.callStateDisposable?.dispose()
|
||||||
|
self.awaitingCallConnectionDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var didPerformAccountSettingsImport = false
|
private var didPerformAccountSettingsImport = false
|
||||||
@ -1010,6 +1036,27 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentControllerWithCurrentCall() {
|
||||||
|
guard let call = self.call else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentCallController = self.callController {
|
||||||
|
if currentCallController.call === call {
|
||||||
|
self.navigateToCurrentCall()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
self.callController = nil
|
||||||
|
currentCallController.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mainWindow?.hostView.containerView.endEditing(true)
|
||||||
|
let callController = CallController(sharedContext: self, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
|
||||||
|
self.callController = callController
|
||||||
|
self.mainWindow?.present(callController, on: .calls)
|
||||||
|
}
|
||||||
|
|
||||||
public func updateNotificationTokensRegistration() {
|
public func updateNotificationTokensRegistration() {
|
||||||
let sandbox: Bool
|
let sandbox: Bool
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user