Skip to content

Commit

Permalink
[WIP] Call UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Isaac committed Dec 4, 2023
1 parent e08c340 commit a189174
Show file tree
Hide file tree
Showing 18 changed files with 626 additions and 73 deletions.
10 changes: 9 additions & 1 deletion Tests/CallUITest/Sources/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public final class ViewController: UIViewController {
audioOutput: .internalSpeaker,
isMicrophoneMuted: false,
localVideo: nil,
remoteVideo: nil
remoteVideo: nil,
isRemoteBatteryLow: false
)

private var currentLayout: (size: CGSize, insets: UIEdgeInsets)?
Expand Down Expand Up @@ -143,6 +144,13 @@ public final class ViewController: UIViewController {
self.callState.localVideo = nil
self.update(transition: .spring(duration: 0.4))
}
callScreenView.backAction = { [weak self] in
guard let self else {
return
}
self.callState.isMicrophoneMuted = !self.callState.isMicrophoneMuted
self.update(transition: .spring(duration: 0.4))
}
}

private func update(transition: Transition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ private final class VariableBlurView: UIVisualEffectView {
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage")
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
variableBlur.setValue(UIScreenScale, forKey: "scale")

let backdropLayer = self.subviews.first?.layer
backdropLayer?.filters = [variableBlur]
Expand Down
106 changes: 87 additions & 19 deletions submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
}
self.endCall?()
}
self.callScreen.backAction = { [weak self] in
guard let self else {
return
}
self.back?()
}

self.callScreenState = PrivateCallScreen.State(
lifecycleState: .connecting,
Expand All @@ -126,7 +132,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
audioOutput: .internalSpeaker,
isMicrophoneMuted: false,
localVideo: nil,
remoteVideo: nil
remoteVideo: nil,
isRemoteBatteryLow: false
)
if let peer = call.peer {
self.updatePeer(peer: peer)
Expand Down Expand Up @@ -326,33 +333,46 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
mappedLifecycleState = .terminated(PrivateCallScreen.State.TerminatedState(duration: duration))
}

switch callState.remoteVideoState {
case .active, .paused:
if self.remoteVideo == nil, let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: true) {
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
case .inactive:
switch callState.state {
case .terminating, .terminated:
self.localVideo = nil
self.remoteVideo = nil
}

switch callState.videoState {
case .active(let isScreencast), .paused(let isScreencast):
if isScreencast {
default:
switch callState.videoState {
case .active(let isScreencast), .paused(let isScreencast):
if isScreencast {
self.localVideo = nil
} else {
if self.localVideo == nil, let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: false) {
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
}
case .inactive, .notAvailable:
self.localVideo = nil
} else {
if self.localVideo == nil, let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: false) {
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}

switch callState.remoteVideoState {
case .active, .paused:
if self.remoteVideo == nil, let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: true) {
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
case .inactive:
self.remoteVideo = nil
}
case .inactive, .notAvailable:
self.localVideo = nil
}

if var callScreenState = self.callScreenState {
callScreenState.lifecycleState = mappedLifecycleState
callScreenState.remoteVideo = self.remoteVideo
callScreenState.localVideo = self.localVideo

switch callState.remoteBatteryLevel {
case .low:
callScreenState.isRemoteBatteryLow = true
case .normal:
callScreenState.isRemoteBatteryLow = false
}

if self.callScreenState != callScreenState {
self.callScreenState = callScreenState
self.update(transition: .animated(duration: 0.35, curve: .spring))
Expand Down Expand Up @@ -509,7 +529,7 @@ private final class AdaptedCallVideoSource: VideoSource {
}

let rotationAngle: Float
switch videoFrameData.orientation {
switch videoFrameData.deviceRelativeOrientation ?? videoFrameData.orientation {
case .rotation0:
rotationAngle = 0.0
case .rotation90:
Expand All @@ -520,6 +540,47 @@ private final class AdaptedCallVideoSource: VideoSource {
rotationAngle = Float.pi * 3.0 / 2.0
}

var mirrorDirection: Output.MirrorDirection = []

var sourceId: Int = 0
if videoFrameData.mirrorHorizontally || videoFrameData.mirrorVertically {
sourceId = 1
}

if let deviceRelativeOrientation = videoFrameData.deviceRelativeOrientation, deviceRelativeOrientation != videoFrameData.orientation {
let shouldMirror = videoFrameData.mirrorHorizontally || videoFrameData.mirrorVertically

var mirrorHorizontally = false
var mirrorVertically = false

if shouldMirror {
switch deviceRelativeOrientation {
case .rotation0:
mirrorHorizontally = true
case .rotation90:
mirrorVertically = true
case .rotation180:
mirrorHorizontally = true
case .rotation270:
mirrorVertically = true
}
}

if mirrorHorizontally {
mirrorDirection.insert(.horizontal)
}
if mirrorVertically {
mirrorDirection.insert(.vertical)
}
} else {
if videoFrameData.mirrorHorizontally {
mirrorDirection.insert(.horizontal)
}
if videoFrameData.mirrorVertically {
mirrorDirection.insert(.vertical)
}
}

AdaptedCallVideoSource.queue.async { [weak self] in
let output: Output
switch videoFrameData.buffer {
Expand All @@ -538,7 +599,14 @@ private final class AdaptedCallVideoSource: VideoSource {
return
}

output = Output(resolution: CGSize(width: CGFloat(yTexture.width), height: CGFloat(yTexture.height)), y: yTexture, uv: uvTexture, rotationAngle: rotationAngle, sourceId: videoFrameData.mirrorHorizontally || videoFrameData.mirrorVertically ? 1 : 0)
output = Output(
resolution: CGSize(width: CGFloat(yTexture.width), height: CGFloat(yTexture.height)),
y: yTexture,
uv: uvTexture,
rotationAngle: rotationAngle,
mirrorDirection: mirrorDirection,
sourceId: sourceId
)
default:
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ kernel void videoYUVToRGBA(

vertex QuadVertexOut mainVideoVertex(
const device Rectangle &rect [[ buffer(0) ]],
const device uint2 &mirror [[ buffer(1) ]],
unsigned int vid [[ vertex_id ]]
) {
float2 quadVertex = quadVertices[vid];
Expand All @@ -262,6 +263,12 @@ vertex QuadVertexOut mainVideoVertex(
out.position.y = -1.0 + out.position.y * 2.0;

out.uv = float2(quadVertex.x, 1.0 - quadVertex.y);
if (mirror.x == 1) {
out.uv.x = 1.0 - out.uv.x;
}
if (mirror.y == 1) {
out.uv.y = 1.0 - out.uv.y;
}

return out;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Foundation
import UIKit
import Display

final class BackButtonView: HighlightableButton {
private let iconView: UIImageView
private let textView: TextView

let size: CGSize

var pressAction: (() -> Void)?

init(text: String) {
self.iconView = UIImageView(image: NavigationBar.backArrowImage(color: .white))
self.iconView.isUserInteractionEnabled = false

self.textView = TextView()
self.textView.isUserInteractionEnabled = false

let spacing: CGFloat = 8.0

var iconSize: CGSize = self.iconView.image?.size ?? CGSize(width: 2.0, height: 2.0)
let iconScaleFactor: CGFloat = 0.9
iconSize.width = floor(iconSize.width * iconScaleFactor)
iconSize.height = floor(iconSize.height * iconScaleFactor)

let textSize = self.textView.update(string: text, fontSize: 17.0, fontWeight: UIFont.Weight.regular.rawValue, color: .white, constrainedWidth: 100.0, transition: .immediate)
self.size = CGSize(width: iconSize.width + spacing + textSize.width, height: textSize.height)

self.iconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((self.size.height - iconSize.height) * 0.5)), size: iconSize)
self.textView.frame = CGRect(origin: CGPoint(x: iconSize.width + spacing, y: floorToScreenPixels((self.size.height - textSize.height) * 0.5)), size: textSize)

super.init(frame: CGRect())

self.addSubview(self.iconView)
self.addSubview(self.textView)

self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc private func pressed() {
self.pressAction?()
}

override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.bounds.insetBy(dx: -8.0, dy: -4.0).contains(point) {
return super.hitTest(self.bounds.center, with: event)
} else {
return nil
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,21 @@ final class ButtonGroupView: OverlayMaskContainerView {
}
}

final class Notice {
let id: AnyHashable
let text: String

init(id: AnyHashable, text: String) {
self.id = id
self.text = text
}
}

private var buttons: [Button]?
private var buttonViews: [Button.Content.Key: ContentOverlayButton] = [:]

private var noticeViews: [AnyHashable: NoticeView] = [:]

override init(frame: CGRect) {
super.init(frame: frame)
}
Expand All @@ -67,20 +79,87 @@ final class ButtonGroupView: OverlayMaskContainerView {
return result
}

func update(size: CGSize, insets: UIEdgeInsets, controlsHidden: Bool, buttons: [Button], transition: Transition) -> CGFloat {
func update(size: CGSize, insets: UIEdgeInsets, controlsHidden: Bool, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat {
self.buttons = buttons

let buttonSize: CGFloat = 56.0
let buttonSpacing: CGFloat = 36.0

let buttonNoticeSpacing: CGFloat = 16.0
let controlsHiddenNoticeSpacing: CGFloat = 0.0
var nextNoticeY: CGFloat
if controlsHidden {
nextNoticeY = size.height - insets.bottom - 4.0
} else {
nextNoticeY = size.height - insets.bottom - 52.0 - buttonSize - buttonNoticeSpacing
}
let noticeSpacing: CGFloat = 8.0

var validNoticeIds: [AnyHashable] = []
var noticesHeight: CGFloat = 0.0
for notice in notices {
validNoticeIds.append(notice.id)

let noticeView: NoticeView
var noticeTransition = transition
var animateIn = false
if let current = self.noticeViews[notice.id] {
noticeView = current
} else {
noticeTransition = noticeTransition.withAnimation(.none)
animateIn = true
noticeView = NoticeView()
self.noticeViews[notice.id] = noticeView
self.addSubview(noticeView)
}

if noticesHeight != 0.0 {
noticesHeight += noticeSpacing
} else {
if controlsHidden {
noticesHeight += controlsHiddenNoticeSpacing
} else {
noticesHeight += buttonNoticeSpacing
}
}
let noticeSize = noticeView.update(text: notice.text, constrainedWidth: size.width - insets.left * 2.0 - 16.0 * 2.0, transition: noticeTransition)
let noticeFrame = CGRect(origin: CGPoint(x: floor((size.width - noticeSize.width) * 0.5), y: nextNoticeY - noticeSize.height), size: noticeSize)
noticesHeight += noticeSize.height
nextNoticeY -= noticeSize.height + noticeSpacing

noticeTransition.setFrame(view: noticeView, frame: noticeFrame)
if animateIn, !transition.animation.isImmediate {
noticeView.animateIn()
}
}
if noticesHeight != 0.0 {
noticesHeight += 5.0
}
var removedNoticeIds: [AnyHashable] = []
for (id, noticeView) in self.noticeViews {
if !validNoticeIds.contains(id) {
removedNoticeIds.append(id)
if !transition.animation.isImmediate {
noticeView.animateOut(completion: { [weak noticeView] in
noticeView?.removeFromSuperview()
})
} else {
noticeView.removeFromSuperview()
}
}
}
for id in removedNoticeIds {
self.noticeViews.removeValue(forKey: id)
}

let buttonY: CGFloat
let resultHeight: CGFloat
if controlsHidden {
buttonY = size.height + 12.0
resultHeight = insets.bottom + 4.0
resultHeight = insets.bottom + 4.0 + noticesHeight
} else {
buttonY = size.height - insets.bottom - 52.0 - buttonSize
resultHeight = size.height - buttonY
resultHeight = size.height - buttonY + noticesHeight
}
var buttonX: CGFloat = floor((size.width - buttonSize * CGFloat(buttons.count) - buttonSpacing * CGFloat(buttons.count - 1)) * 0.5)

Expand Down
Loading

0 comments on commit a189174

Please sign in to comment.