mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Video chat improvements
This commit is contained in:
parent
243d0be940
commit
3d509c7bda
@ -212,6 +212,7 @@ public struct PresentationGroupCallState: Equatable {
|
|||||||
public var subscribedToScheduled: Bool
|
public var subscribedToScheduled: Bool
|
||||||
public var isVideoEnabled: Bool
|
public var isVideoEnabled: Bool
|
||||||
public var isVideoWatchersLimitReached: Bool
|
public var isVideoWatchersLimitReached: Bool
|
||||||
|
public var hasVideo: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
myPeerId: EnginePeer.Id,
|
myPeerId: EnginePeer.Id,
|
||||||
@ -226,7 +227,8 @@ public struct PresentationGroupCallState: Equatable {
|
|||||||
scheduleTimestamp: Int32?,
|
scheduleTimestamp: Int32?,
|
||||||
subscribedToScheduled: Bool,
|
subscribedToScheduled: Bool,
|
||||||
isVideoEnabled: Bool,
|
isVideoEnabled: Bool,
|
||||||
isVideoWatchersLimitReached: Bool
|
isVideoWatchersLimitReached: Bool,
|
||||||
|
hasVideo: Bool
|
||||||
) {
|
) {
|
||||||
self.myPeerId = myPeerId
|
self.myPeerId = myPeerId
|
||||||
self.networkState = networkState
|
self.networkState = networkState
|
||||||
@ -241,6 +243,7 @@ public struct PresentationGroupCallState: Equatable {
|
|||||||
self.subscribedToScheduled = subscribedToScheduled
|
self.subscribedToScheduled = subscribedToScheduled
|
||||||
self.isVideoEnabled = isVideoEnabled
|
self.isVideoEnabled = isVideoEnabled
|
||||||
self.isVideoWatchersLimitReached = isVideoWatchersLimitReached
|
self.isVideoWatchersLimitReached = isVideoWatchersLimitReached
|
||||||
|
self.hasVideo = hasVideo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +115,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
|
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
|
||||||
"//submodules/TelegramUI/Components/BackButtonComponent",
|
"//submodules/TelegramUI/Components/BackButtonComponent",
|
||||||
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
"//submodules/DirectMediaImageCache",
|
"//submodules/DirectMediaImageCache",
|
||||||
"//submodules/FastBlur",
|
"//submodules/FastBlur",
|
||||||
],
|
],
|
||||||
|
@ -268,7 +268,8 @@ private extension PresentationGroupCallState {
|
|||||||
scheduleTimestamp: scheduleTimestamp,
|
scheduleTimestamp: scheduleTimestamp,
|
||||||
subscribedToScheduled: subscribedToScheduled,
|
subscribedToScheduled: subscribedToScheduled,
|
||||||
isVideoEnabled: false,
|
isVideoEnabled: false,
|
||||||
isVideoWatchersLimitReached: false
|
isVideoWatchersLimitReached: false,
|
||||||
|
hasVideo: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2971,11 +2972,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
|
|
||||||
self.updateLocalVideoState()
|
self.updateLocalVideoState()
|
||||||
}
|
}
|
||||||
|
self.stateValue.hasVideo = self.hasVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
public func disableVideo() {
|
public func disableVideo() {
|
||||||
self.hasVideo = false
|
self.hasVideo = false
|
||||||
self.useFrontCamera = true;
|
self.useFrontCamera = true
|
||||||
if let _ = self.videoCapturer {
|
if let _ = self.videoCapturer {
|
||||||
self.videoCapturer = nil
|
self.videoCapturer = nil
|
||||||
self.isVideoMutedDisposable.set(nil)
|
self.isVideoMutedDisposable.set(nil)
|
||||||
@ -2984,6 +2986,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
|
|
||||||
self.updateLocalVideoState()
|
self.updateLocalVideoState()
|
||||||
}
|
}
|
||||||
|
self.stateValue.hasVideo = self.hasVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateLocalVideoState() {
|
private func updateLocalVideoState() {
|
||||||
|
@ -13,6 +13,10 @@ import BalancedTextComponent
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import Markdown
|
import Markdown
|
||||||
|
import HierarchyTrackingLayer
|
||||||
|
|
||||||
|
private let purple = UIColor(rgb: 0x3252ef)
|
||||||
|
private let pink = UIColor(rgb: 0xef436c)
|
||||||
|
|
||||||
private final class ScheduleVideoChatSheetContentComponent: Component {
|
private final class ScheduleVideoChatSheetContentComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
@ -33,7 +37,11 @@ private final class ScheduleVideoChatSheetContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView {
|
final class View: UIView {
|
||||||
|
private let hierarchyTrackingLayer: HierarchyTrackingLayer
|
||||||
|
|
||||||
private let button = ComponentView<Empty>()
|
private let button = ComponentView<Empty>()
|
||||||
|
private let buttonBackgroundLayer: SimpleGradientLayer
|
||||||
|
|
||||||
private let cancelButton = ComponentView<Empty>()
|
private let cancelButton = ComponentView<Empty>()
|
||||||
|
|
||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
@ -52,7 +60,29 @@ private final class ScheduleVideoChatSheetContentComponent: Component {
|
|||||||
self.dateFormatter.dateStyle = .short
|
self.dateFormatter.dateStyle = .short
|
||||||
self.dateFormatter.timeZone = TimeZone.current
|
self.dateFormatter.timeZone = TimeZone.current
|
||||||
|
|
||||||
|
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||||
|
|
||||||
|
self.buttonBackgroundLayer = SimpleGradientLayer()
|
||||||
|
self.buttonBackgroundLayer.type = .radial
|
||||||
|
self.buttonBackgroundLayer.colors = [pink.cgColor, purple.cgColor, purple.cgColor]
|
||||||
|
self.buttonBackgroundLayer.locations = [0.0, 0.85, 1.0]
|
||||||
|
self.buttonBackgroundLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
|
||||||
|
let radius = CGSize(width: 1.0, height: 2.0)
|
||||||
|
let endEndPoint = CGPoint(x: (self.buttonBackgroundLayer.startPoint.x + radius.width) * 1.0, y: (self.buttonBackgroundLayer.startPoint.y + radius.height) * 1.0)
|
||||||
|
self.buttonBackgroundLayer.endPoint = endEndPoint
|
||||||
|
self.buttonBackgroundLayer.cornerRadius = 10.0
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.hierarchyTrackingLayer)
|
||||||
|
self.hierarchyTrackingLayer.isInHierarchyUpdated = { [weak self] value in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if value {
|
||||||
|
self.updateAnimations()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -96,6 +126,46 @@ private final class ScheduleVideoChatSheetContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateAnimations() {
|
||||||
|
if let _ = self.buttonBackgroundLayer.animation(forKey: "movement") {
|
||||||
|
} else {
|
||||||
|
let previousValue = self.buttonBackgroundLayer.startPoint
|
||||||
|
let previousEndValue = self.buttonBackgroundLayer.endPoint
|
||||||
|
let newValue = CGPoint(x: CGFloat.random(in: 0.65 ..< 0.85), y: CGFloat.random(in: 0.1 ..< 0.45))
|
||||||
|
self.buttonBackgroundLayer.startPoint = newValue
|
||||||
|
|
||||||
|
let radius = CGSize(width: 1.0, height: 2.0)
|
||||||
|
let newEndValue = CGPoint(x: (self.buttonBackgroundLayer.startPoint.x + radius.width) * 1.0, y: (self.buttonBackgroundLayer.startPoint.y + radius.height) * 1.0)
|
||||||
|
|
||||||
|
CATransaction.begin()
|
||||||
|
|
||||||
|
let animation = CABasicAnimation(keyPath: "startPoint")
|
||||||
|
animation.duration = Double.random(in: 0.8 ..< 1.4)
|
||||||
|
animation.fromValue = previousValue
|
||||||
|
animation.toValue = newValue
|
||||||
|
|
||||||
|
CATransaction.setCompletionBlock { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.hierarchyTrackingLayer.isInHierarchy {
|
||||||
|
self.updateAnimations()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buttonBackgroundLayer.add(animation, forKey: "movement")
|
||||||
|
|
||||||
|
let endAnimation = CABasicAnimation(keyPath: "endPoint")
|
||||||
|
endAnimation.duration = animation.duration
|
||||||
|
endAnimation.fromValue = previousEndValue
|
||||||
|
endAnimation.toValue = newEndValue
|
||||||
|
|
||||||
|
self.buttonBackgroundLayer.add(animation, forKey: "movementEnd")
|
||||||
|
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: ScheduleVideoChatSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
func update(component: ScheduleVideoChatSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
let previousComponent = self.component
|
let previousComponent = self.component
|
||||||
let _ = previousComponent
|
let _ = previousComponent
|
||||||
@ -233,9 +303,9 @@ private final class ScheduleVideoChatSheetContentComponent: Component {
|
|||||||
transition: buttonTransition,
|
transition: buttonTransition,
|
||||||
component: AnyComponent(ButtonComponent(
|
component: AnyComponent(ButtonComponent(
|
||||||
background: ButtonComponent.Background(
|
background: ButtonComponent.Background(
|
||||||
color: UIColor(rgb: 0x3252EF),
|
color: .clear,
|
||||||
foreground: .white,
|
foreground: .white,
|
||||||
pressedColor: UIColor(rgb: 0x3252EF).withMultipliedAlpha(0.8)
|
pressedColor: UIColor(white: 1.0, alpha: 0.1)
|
||||||
),
|
),
|
||||||
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
|
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
|
||||||
HStack(buttonContents, spacing: 5.0)
|
HStack(buttonContents, spacing: 5.0)
|
||||||
@ -256,8 +326,10 @@ private final class ScheduleVideoChatSheetContentComponent: Component {
|
|||||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize)
|
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize)
|
||||||
if let buttonView = self.button.view {
|
if let buttonView = self.button.view {
|
||||||
if buttonView.superview == nil {
|
if buttonView.superview == nil {
|
||||||
|
self.layer.addSublayer(self.buttonBackgroundLayer)
|
||||||
self.addSubview(buttonView)
|
self.addSubview(buttonView)
|
||||||
}
|
}
|
||||||
|
transition.setFrame(layer: self.buttonBackgroundLayer, frame: buttonFrame)
|
||||||
transition.setFrame(view: buttonView, frame: buttonFrame)
|
transition.setFrame(view: buttonView, frame: buttonFrame)
|
||||||
}
|
}
|
||||||
contentHeight += buttonSize.height
|
contentHeight += buttonSize.height
|
||||||
@ -302,6 +374,8 @@ private final class ScheduleVideoChatSheetContentComponent: Component {
|
|||||||
contentHeight += environment.safeInsets.bottom + 14.0
|
contentHeight += environment.safeInsets.bottom + 14.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.updateAnimations()
|
||||||
|
|
||||||
return CGSize(width: availableSize.width, height: contentHeight)
|
return CGSize(width: availableSize.width, height: contentHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,13 @@ final class VideoChatActionButtonComponent: Component {
|
|||||||
case audio(audio: Audio)
|
case audio(audio: Audio)
|
||||||
case video
|
case video
|
||||||
case leave
|
case leave
|
||||||
|
case switchVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
case audio(audio: Audio)
|
case audio(audio: Audio)
|
||||||
case video(isActive: Bool)
|
case video(isActive: Bool)
|
||||||
case leave
|
case leave
|
||||||
|
case switchVideo
|
||||||
|
|
||||||
fileprivate var iconType: IconType {
|
fileprivate var iconType: IconType {
|
||||||
switch self {
|
switch self {
|
||||||
@ -57,6 +59,8 @@ final class VideoChatActionButtonComponent: Component {
|
|||||||
return .video
|
return .video
|
||||||
case .leave:
|
case .leave:
|
||||||
return .leave
|
return .leave
|
||||||
|
case .switchVideo:
|
||||||
|
return .switchVideo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,6 +178,19 @@ final class VideoChatActionButtonComponent: Component {
|
|||||||
backgroundColor = UIColor(rgb: 0x3252EF)
|
backgroundColor = UIColor(rgb: 0x3252EF)
|
||||||
}
|
}
|
||||||
iconDiameter = 60.0
|
iconDiameter = 60.0
|
||||||
|
case .switchVideo:
|
||||||
|
titleText = ""
|
||||||
|
switch component.microphoneState {
|
||||||
|
case .connecting:
|
||||||
|
backgroundColor = UIColor(white: 0.1, alpha: 1.0)
|
||||||
|
case .muted:
|
||||||
|
backgroundColor = UIColor(rgb: 0x027FFF)
|
||||||
|
case .unmuted:
|
||||||
|
backgroundColor = UIColor(rgb: 0x34C659)
|
||||||
|
case .raiseHand, .scheduled:
|
||||||
|
backgroundColor = UIColor(rgb: 0x3252EF)
|
||||||
|
}
|
||||||
|
iconDiameter = 54.0
|
||||||
case .leave:
|
case .leave:
|
||||||
titleText = "leave"
|
titleText = "leave"
|
||||||
backgroundColor = UIColor(rgb: 0x47191E)
|
backgroundColor = UIColor(rgb: 0x47191E)
|
||||||
@ -204,6 +221,8 @@ final class VideoChatActionButtonComponent: Component {
|
|||||||
self.contentImage = UIImage(bundleImageName: iconName)?.precomposed().withRenderingMode(.alwaysTemplate)
|
self.contentImage = UIImage(bundleImageName: iconName)?.precomposed().withRenderingMode(.alwaysTemplate)
|
||||||
case .video:
|
case .video:
|
||||||
self.contentImage = UIImage(bundleImageName: "Call/CallCameraButton")?.precomposed().withRenderingMode(.alwaysTemplate)
|
self.contentImage = UIImage(bundleImageName: "Call/CallCameraButton")?.precomposed().withRenderingMode(.alwaysTemplate)
|
||||||
|
case .switchVideo:
|
||||||
|
self.contentImage = UIImage(bundleImageName: "Call/CallSwitchCameraButton")?.precomposed().withRenderingMode(.alwaysTemplate)
|
||||||
case .leave:
|
case .leave:
|
||||||
self.contentImage = generateImage(CGSize(width: 28.0, height: 28.0), opaque: false, rotatedContext: { size, context in
|
self.contentImage = generateImage(CGSize(width: 28.0, height: 28.0), opaque: false, rotatedContext: { size, context in
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
@ -275,7 +294,9 @@ final class VideoChatActionButtonComponent: Component {
|
|||||||
if iconView.superview == nil {
|
if iconView.superview == nil {
|
||||||
self.addSubview(iconView)
|
self.addSubview(iconView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: iconView, frame: iconFrame)
|
transition.setPosition(view: iconView, position: iconFrame.center)
|
||||||
|
transition.setBounds(view: iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||||
|
transition.setScale(view: iconView, scale: availableSize.width / 56.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
|
@ -62,6 +62,7 @@ final class VideoChatMuteIconComponent: Component {
|
|||||||
self.isUpdating = false
|
self.isUpdating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousComponent = self.component
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
if case let .mute(isFilled, isMuted) = component.content {
|
if case let .mute(isFilled, isMuted) = component.content {
|
||||||
@ -77,7 +78,10 @@ final class VideoChatMuteIconComponent: Component {
|
|||||||
let animationSize = availableSize
|
let animationSize = availableSize
|
||||||
let animationFrame = animationSize.centered(in: CGRect(origin: CGPoint(), size: availableSize))
|
let animationFrame = animationSize.centered(in: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
transition.setFrame(view: icon.view, frame: animationFrame)
|
transition.setFrame(view: icon.view, frame: animationFrame)
|
||||||
|
if let previousComponent, previousComponent.content == component.content, previousComponent.color == component.color {
|
||||||
|
} else {
|
||||||
icon.update(state: VoiceChatMicrophoneNode.State(muted: isMuted, filled: isFilled, color: component.color), animated: !transition.animation.isImmediate)
|
icon.update(state: VoiceChatMicrophoneNode.State(muted: isMuted, filled: isFilled, color: component.color), animated: !transition.animation.isImmediate)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if let icon = self.icon {
|
if let icon = self.icon {
|
||||||
self.icon = nil
|
self.icon = nil
|
||||||
|
@ -136,6 +136,7 @@ final class VideoChatParticipantAvatarComponent: Component {
|
|||||||
let peer: EnginePeer
|
let peer: EnginePeer
|
||||||
let myPeerId: EnginePeer.Id
|
let myPeerId: EnginePeer.Id
|
||||||
let isSpeaking: Bool
|
let isSpeaking: Bool
|
||||||
|
let isMutedForMe: Bool
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -143,12 +144,14 @@ final class VideoChatParticipantAvatarComponent: Component {
|
|||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
myPeerId: EnginePeer.Id,
|
myPeerId: EnginePeer.Id,
|
||||||
isSpeaking: Bool,
|
isSpeaking: Bool,
|
||||||
|
isMutedForMe: Bool,
|
||||||
theme: PresentationTheme
|
theme: PresentationTheme
|
||||||
) {
|
) {
|
||||||
self.call = call
|
self.call = call
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.myPeerId = myPeerId
|
self.myPeerId = myPeerId
|
||||||
self.isSpeaking = isSpeaking
|
self.isSpeaking = isSpeaking
|
||||||
|
self.isMutedForMe = isMutedForMe
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,10 +162,13 @@ final class VideoChatParticipantAvatarComponent: Component {
|
|||||||
if lhs.peer != rhs.peer {
|
if lhs.peer != rhs.peer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.myPeerId != rhs.myPeerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.isSpeaking != rhs.isSpeaking {
|
if lhs.isSpeaking != rhs.isSpeaking {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.myPeerId != rhs.myPeerId {
|
if lhs.isMutedForMe != rhs.isMutedForMe {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.theme !== rhs.theme {
|
if lhs.theme !== rhs.theme {
|
||||||
@ -259,7 +265,15 @@ final class VideoChatParticipantAvatarComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
tintTransition = .immediate
|
tintTransition = .immediate
|
||||||
}
|
}
|
||||||
tintTransition.setTintColor(layer: blobView.blobsLayer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : component.theme.list.itemAccentColor)
|
let tintColor: UIColor
|
||||||
|
if component.isMutedForMe {
|
||||||
|
tintColor = UIColor(rgb: 0xff3b30)
|
||||||
|
} else if component.isSpeaking {
|
||||||
|
tintColor = UIColor(rgb: 0x33C758)
|
||||||
|
} else {
|
||||||
|
tintColor = component.theme.list.itemAccentColor
|
||||||
|
}
|
||||||
|
tintTransition.setTintColor(layer: blobView.blobsLayer, color: tintColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if component.peer.smallProfileImage != nil {
|
if component.peer.smallProfileImage != nil {
|
||||||
@ -362,7 +376,15 @@ final class VideoChatParticipantAvatarComponent: Component {
|
|||||||
avatarNode.layer.transform = CATransform3DMakeScale(1.0 + additionalScale, 1.0 + additionalScale, 1.0)
|
avatarNode.layer.transform = CATransform3DMakeScale(1.0 + additionalScale, 1.0 + additionalScale, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
ComponentTransition.immediate.setTintColor(layer: blobView.blobsLayer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : component.theme.list.itemAccentColor)
|
let tintColor: UIColor
|
||||||
|
if component.isMutedForMe {
|
||||||
|
tintColor = UIColor(rgb: 0xff3b30)
|
||||||
|
} else if component.isSpeaking {
|
||||||
|
tintColor = UIColor(rgb: 0x33C758)
|
||||||
|
} else {
|
||||||
|
tintColor = component.theme.list.itemAccentColor
|
||||||
|
}
|
||||||
|
ComponentTransition.immediate.setTintColor(layer: blobView.blobsLayer, color: tintColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if blobView.alpha == 0.0 {
|
if blobView.alpha == 0.0 {
|
||||||
|
@ -121,7 +121,9 @@ final class VideoChatParticipantStatusComponent: Component {
|
|||||||
}
|
}
|
||||||
if let iconView = muteStatusView.iconView {
|
if let iconView = muteStatusView.iconView {
|
||||||
let iconTintColor: UIColor
|
let iconTintColor: UIColor
|
||||||
if component.isSpeaking {
|
if let muteState = component.muteState, muteState.mutedByYou {
|
||||||
|
iconTintColor = UIColor(rgb: 0xff3b30)
|
||||||
|
} else if component.isSpeaking {
|
||||||
iconTintColor = UIColor(rgb: 0x33C758)
|
iconTintColor = UIColor(rgb: 0x33C758)
|
||||||
} else {
|
} else {
|
||||||
if let muteState = component.muteState {
|
if let muteState = component.muteState {
|
||||||
|
@ -352,7 +352,10 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha)
|
alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
let videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription
|
var videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription
|
||||||
|
if component.isPresentation && component.isMyPeer {
|
||||||
|
videoDescription = nil
|
||||||
|
}
|
||||||
|
|
||||||
var isEffectivelyPaused = false
|
var isEffectivelyPaused = false
|
||||||
if let videoDescription, videoDescription.isPaused {
|
if let videoDescription, videoDescription.isPaused {
|
||||||
|
@ -1154,9 +1154,16 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
|
|
||||||
let itemFrame = itemLayout.listItemFrame(at: i)
|
let itemFrame = itemLayout.listItemFrame(at: i)
|
||||||
|
|
||||||
|
var isMutedForMe = false
|
||||||
|
if let muteState = participant.muteState, muteState.mutedByYou {
|
||||||
|
isMutedForMe = true
|
||||||
|
}
|
||||||
|
|
||||||
let subtitle: PeerListItemComponent.Subtitle
|
let subtitle: PeerListItemComponent.Subtitle
|
||||||
if participant.peer.id == component.call.accountContext.account.peerId {
|
if participant.peer.id == component.call.accountContext.account.peerId {
|
||||||
subtitle = PeerListItemComponent.Subtitle(text: "this is you", color: .accent)
|
subtitle = PeerListItemComponent.Subtitle(text: "this is you", color: .accent)
|
||||||
|
} else if let muteState = participant.muteState, muteState.mutedByYou {
|
||||||
|
subtitle = PeerListItemComponent.Subtitle(text: "muted for you", color: .destructive)
|
||||||
} else if component.speakingParticipants.contains(participant.peer.id) {
|
} else if component.speakingParticipants.contains(participant.peer.id) {
|
||||||
if let volume = participant.volume, volume != 10000 {
|
if let volume = participant.volume, volume != 10000 {
|
||||||
subtitle = PeerListItemComponent.Subtitle(text: "\(volume / 100)% speaking", color: .constructive)
|
subtitle = PeerListItemComponent.Subtitle(text: "\(volume / 100)% speaking", color: .constructive)
|
||||||
@ -1190,6 +1197,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
peer: EnginePeer(participant.peer),
|
peer: EnginePeer(participant.peer),
|
||||||
myPeerId: component.participants?.myPeerId ?? component.call.accountContext.account.peerId,
|
myPeerId: component.participants?.myPeerId ?? component.call.accountContext.account.peerId,
|
||||||
isSpeaking: component.speakingParticipants.contains(participant.peer.id),
|
isSpeaking: component.speakingParticipants.contains(participant.peer.id),
|
||||||
|
isMutedForMe: isMutedForMe,
|
||||||
theme: component.theme
|
theme: component.theme
|
||||||
)),
|
)),
|
||||||
peer: EnginePeer(participant.peer),
|
peer: EnginePeer(participant.peer),
|
||||||
|
@ -6,6 +6,7 @@ import MultilineTextComponent
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import HierarchyTrackingLayer
|
import HierarchyTrackingLayer
|
||||||
|
import AnimatedTextComponent
|
||||||
|
|
||||||
private let purple = UIColor(rgb: 0x3252ef)
|
private let purple = UIColor(rgb: 0x3252ef)
|
||||||
private let pink = UIColor(rgb: 0xef436c)
|
private let pink = UIColor(rgb: 0xef436c)
|
||||||
@ -13,6 +14,33 @@ private let pink = UIColor(rgb: 0xef436c)
|
|||||||
private let latePurple = UIColor(rgb: 0x974aa9)
|
private let latePurple = UIColor(rgb: 0x974aa9)
|
||||||
private let latePink = UIColor(rgb: 0xf0436c)
|
private let latePink = UIColor(rgb: 0xf0436c)
|
||||||
|
|
||||||
|
private func textItemsForTimeout(value: Int32) -> [AnimatedTextComponent.Item] {
|
||||||
|
if value < 3600 {
|
||||||
|
let minutes = value / 60
|
||||||
|
let seconds = value % 60
|
||||||
|
|
||||||
|
var items: [AnimatedTextComponent.Item] = []
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(11), content: .number(Int(minutes), minDigits: 1)))
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(12), content: .text(":")))
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(13), content: .number(Int(seconds), minDigits: 2)))
|
||||||
|
|
||||||
|
return items
|
||||||
|
} else {
|
||||||
|
let hours = value / 3600
|
||||||
|
let minutes = (value % 3600) / 60
|
||||||
|
let seconds = value % 60
|
||||||
|
|
||||||
|
var items: [AnimatedTextComponent.Item] = []
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(9), content: .number(Int(hours), minDigits: 1)))
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(10), content: .text(":")))
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(11), content: .number(Int(minutes), minDigits: 2)))
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(12), content: .text(":")))
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(13), content: .number(Int(seconds), minDigits: 2)))
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class VideoChatScheduledInfoComponent: Component {
|
final class VideoChatScheduledInfoComponent: Component {
|
||||||
let timestamp: Int32
|
let timestamp: Int32
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
@ -46,8 +74,11 @@ final class VideoChatScheduledInfoComponent: Component {
|
|||||||
private let hierarchyTrackingLayer: HierarchyTrackingLayer
|
private let hierarchyTrackingLayer: HierarchyTrackingLayer
|
||||||
|
|
||||||
private var component: VideoChatScheduledInfoComponent?
|
private var component: VideoChatScheduledInfoComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
|
private var countdownTimer: Foundation.Timer?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.countdownContainerView = UIView()
|
self.countdownContainerView = UIView()
|
||||||
self.countdownMaskView = UIView()
|
self.countdownMaskView = UIView()
|
||||||
@ -76,6 +107,9 @@ final class VideoChatScheduledInfoComponent: Component {
|
|||||||
}
|
}
|
||||||
if value {
|
if value {
|
||||||
self.updateAnimations()
|
self.updateAnimations()
|
||||||
|
} else {
|
||||||
|
self.countdownTimer?.invalidate()
|
||||||
|
self.countdownTimer = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,6 +118,10 @@ final class VideoChatScheduledInfoComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.countdownTimer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
private func updateAnimations() {
|
private func updateAnimations() {
|
||||||
if let _ = self.countdownGradientLayer.animation(forKey: "movement") {
|
if let _ = self.countdownGradientLayer.animation(forKey: "movement") {
|
||||||
} else {
|
} else {
|
||||||
@ -110,6 +148,15 @@ final class VideoChatScheduledInfoComponent: Component {
|
|||||||
self.countdownGradientLayer.add(animation, forKey: "movement")
|
self.countdownGradientLayer.add(animation, forKey: "movement")
|
||||||
CATransaction.commit()
|
CATransaction.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.countdownTimer == nil {
|
||||||
|
self.countdownTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(component: VideoChatScheduledInfoComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: VideoChatScheduledInfoComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
@ -119,6 +166,7 @@ final class VideoChatScheduledInfoComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
@ -130,21 +178,20 @@ final class VideoChatScheduledInfoComponent: Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let remainingSeconds: Int32 = max(0, component.timestamp - Int32(Date().timeIntervalSince1970))
|
let remainingSeconds: Int32 = max(0, component.timestamp - Int32(Date().timeIntervalSince1970))
|
||||||
let countdownText: String
|
var items: [AnimatedTextComponent.Item] = []
|
||||||
if remainingSeconds >= 86400 {
|
if remainingSeconds >= 86400 {
|
||||||
countdownText = scheduledTimeIntervalString(strings: component.strings, value: remainingSeconds)
|
let countdownText = scheduledTimeIntervalString(strings: component.strings, value: remainingSeconds)
|
||||||
|
items.append(AnimatedTextComponent.Item(id: AnyHashable(0), content: .text(countdownText)))
|
||||||
} else {
|
} else {
|
||||||
countdownText = textForTimeout(value: abs(remainingSeconds))
|
items = textItemsForTimeout(value: remainingSeconds)
|
||||||
/*if remainingSeconds < 0 && !self.isLate {
|
|
||||||
self.isLate = true
|
|
||||||
self.foregroundGradientLayer.colors = [latePink.cgColor, latePurple.cgColor, latePurple.cgColor]
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let countdownTextSize = self.countdownText.update(
|
let countdownTextSize = self.countdownText.update(
|
||||||
transition: .immediate,
|
transition: transition,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(AnimatedTextComponent(
|
||||||
text: .plain(NSAttributedString(string: countdownText, font: Font.with(size: 68.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white))
|
font: Font.with(size: 68.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]),
|
||||||
|
color: .white,
|
||||||
|
items: items
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 400.0)
|
containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 400.0)
|
||||||
|
@ -76,6 +76,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
var navigationSidebarButton: ComponentView<Empty>?
|
var navigationSidebarButton: ComponentView<Empty>?
|
||||||
|
|
||||||
let videoButton = ComponentView<Empty>()
|
let videoButton = ComponentView<Empty>()
|
||||||
|
var switchVideoButton: ComponentView<Empty>?
|
||||||
let leaveButton = ComponentView<Empty>()
|
let leaveButton = ComponentView<Empty>()
|
||||||
let microphoneButton = ComponentView<Empty>()
|
let microphoneButton = ComponentView<Empty>()
|
||||||
|
|
||||||
@ -1316,10 +1317,17 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let actionButtonPlacementArea: (x: CGFloat, width: CGFloat)
|
||||||
|
if isTwoColumnLayout {
|
||||||
|
actionButtonPlacementArea = (availableSize.width - sideInset - mainColumnWidth, mainColumnWidth)
|
||||||
|
} else {
|
||||||
|
actionButtonPlacementArea = (0.0, availableSize.width)
|
||||||
|
}
|
||||||
|
|
||||||
let buttonsSideInset: CGFloat = 26.0
|
let buttonsSideInset: CGFloat = 26.0
|
||||||
|
|
||||||
let buttonsWidth: CGFloat = actionButtonDiameter * 2.0 + microphoneButtonDiameter
|
let buttonsWidth: CGFloat = actionButtonDiameter * 2.0 + microphoneButtonDiameter
|
||||||
let remainingButtonsSpace: CGFloat = availableSize.width - buttonsSideInset * 2.0 - buttonsWidth
|
let remainingButtonsSpace: CGFloat = actionButtonPlacementArea.width - buttonsSideInset * 2.0 - buttonsWidth
|
||||||
|
|
||||||
let effectiveMaxActionMicrophoneButtonSpacing: CGFloat
|
let effectiveMaxActionMicrophoneButtonSpacing: CGFloat
|
||||||
if areButtonsCollapsed {
|
if areButtonsCollapsed {
|
||||||
@ -1355,7 +1363,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let microphoneButtonFrame: CGRect
|
var microphoneButtonFrame: CGRect
|
||||||
if areButtonsCollapsed {
|
if areButtonsCollapsed {
|
||||||
microphoneButtonFrame = expandedMicrophoneButtonFrame
|
microphoneButtonFrame = expandedMicrophoneButtonFrame
|
||||||
} else {
|
} else {
|
||||||
@ -1376,8 +1384,41 @@ final class VideoChatScreenComponent: Component {
|
|||||||
expandedParticipantsClippingY = expandedMicrophoneButtonFrame.minY - 24.0
|
expandedParticipantsClippingY = expandedMicrophoneButtonFrame.minY - 24.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let leftActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
var leftActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||||
let rightActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
var rightActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||||
|
|
||||||
|
var additionalLeftActionButtonFrame: CGRect?
|
||||||
|
if let callState = self.callState, callState.hasVideo {
|
||||||
|
let additionalButtonDiameter: CGFloat
|
||||||
|
if areButtonsCollapsed {
|
||||||
|
additionalButtonDiameter = actionButtonDiameter
|
||||||
|
} else {
|
||||||
|
additionalButtonDiameter = floor(actionButtonDiameter * 0.64)
|
||||||
|
}
|
||||||
|
|
||||||
|
if areButtonsCollapsed {
|
||||||
|
let buttonCount: CGFloat = 4.0
|
||||||
|
|
||||||
|
let buttonsWidth: CGFloat = actionButtonDiameter * buttonCount
|
||||||
|
let remainingButtonsSpace: CGFloat = actionButtonPlacementArea.width - buttonsSideInset * 2.0 - buttonsWidth
|
||||||
|
let maxSpacing: CGFloat = 80.0
|
||||||
|
let effectiveSpacing = min(maxSpacing, floor(remainingButtonsSpace / (buttonCount - 1.0)))
|
||||||
|
|
||||||
|
let totalButtonsWidth: CGFloat = buttonsWidth + (buttonCount - 1.0) * effectiveSpacing
|
||||||
|
let totalButtonsX: CGFloat = actionButtonPlacementArea.x + floor((actionButtonPlacementArea.width - totalButtonsWidth) * 0.5)
|
||||||
|
additionalLeftActionButtonFrame = CGRect(origin: CGPoint(x: totalButtonsX + CGFloat(0.0) * (actionButtonDiameter + effectiveSpacing), y: leftActionButtonFrame.minY), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||||
|
leftActionButtonFrame = CGRect(origin: CGPoint(x: totalButtonsX + CGFloat(1.0) * (actionButtonDiameter + effectiveSpacing), y: leftActionButtonFrame.minY), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||||
|
microphoneButtonFrame = CGRect(origin: CGPoint(x: totalButtonsX + CGFloat(2.0) * (actionButtonDiameter + effectiveSpacing), y: leftActionButtonFrame.minY), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||||
|
rightActionButtonFrame = CGRect(origin: CGPoint(x: totalButtonsX + CGFloat(3.0) * (actionButtonDiameter + effectiveSpacing), y: leftActionButtonFrame.minY), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||||
|
} else {
|
||||||
|
let additionalButtonSpacing = 12.0
|
||||||
|
let totalLeftButtonHeight: CGFloat = leftActionButtonFrame.height + additionalButtonSpacing + additionalButtonDiameter
|
||||||
|
let totalLeftButtonOriginY: CGFloat = leftActionButtonFrame.minY + floor((leftActionButtonFrame.height - totalLeftButtonHeight) * 0.5)
|
||||||
|
leftActionButtonFrame.origin.y = totalLeftButtonOriginY + additionalButtonDiameter + additionalButtonSpacing
|
||||||
|
|
||||||
|
additionalLeftActionButtonFrame = CGRect(origin: CGPoint(x: leftActionButtonFrame.minX + floor((leftActionButtonFrame.width - additionalButtonDiameter) * 0.5), y: leftActionButtonFrame.minY - additionalButtonSpacing - additionalButtonDiameter), size: CGSize(width: additionalButtonDiameter, height: additionalButtonDiameter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let participantsSize = availableSize
|
let participantsSize = availableSize
|
||||||
|
|
||||||
@ -1728,7 +1769,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
videoButtonContent = .audio(audio: buttonAudio)
|
videoButtonContent = .audio(audio: buttonAudio)
|
||||||
} else {
|
} else {
|
||||||
//TODO:release
|
//TODO:release
|
||||||
videoButtonContent = .video(isActive: false)
|
videoButtonContent = .video(isActive: self.callState?.hasVideo ?? false)
|
||||||
}
|
}
|
||||||
let _ = self.videoButton.update(
|
let _ = self.videoButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@ -1763,6 +1804,62 @@ final class VideoChatScreenComponent: Component {
|
|||||||
transition.setBounds(view: videoButtonView, bounds: CGRect(origin: CGPoint(), size: leftActionButtonFrame.size))
|
transition.setBounds(view: videoButtonView, bounds: CGRect(origin: CGPoint(), size: leftActionButtonFrame.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let additionalLeftActionButtonFrame {
|
||||||
|
let switchVideoButton: ComponentView<Empty>
|
||||||
|
var switchVideoButtonTransition = transition
|
||||||
|
if let current = self.switchVideoButton {
|
||||||
|
switchVideoButton = current
|
||||||
|
} else {
|
||||||
|
switchVideoButtonTransition = switchVideoButtonTransition.withAnimation(.none)
|
||||||
|
switchVideoButton = ComponentView()
|
||||||
|
self.switchVideoButton = switchVideoButton
|
||||||
|
}
|
||||||
|
|
||||||
|
let switchVideoButtonContent: VideoChatActionButtonComponent.Content = .switchVideo
|
||||||
|
|
||||||
|
let _ = switchVideoButton.update(
|
||||||
|
transition: switchVideoButtonTransition,
|
||||||
|
component: AnyComponent(PlainButtonComponent(
|
||||||
|
content: AnyComponent(VideoChatActionButtonComponent(
|
||||||
|
strings: environment.strings,
|
||||||
|
content: switchVideoButtonContent,
|
||||||
|
microphoneState: actionButtonMicrophoneState,
|
||||||
|
isCollapsed: areButtonsCollapsed
|
||||||
|
)),
|
||||||
|
effectAlignment: .center,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.call.switchVideoCamera()
|
||||||
|
},
|
||||||
|
animateAlpha: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: additionalLeftActionButtonFrame.size
|
||||||
|
)
|
||||||
|
if let switchVideoButtonView = switchVideoButton.view {
|
||||||
|
var animateIn = false
|
||||||
|
if switchVideoButtonView.superview == nil {
|
||||||
|
self.containerView.addSubview(switchVideoButtonView)
|
||||||
|
animateIn = true
|
||||||
|
}
|
||||||
|
switchVideoButtonTransition.setFrame(view: switchVideoButtonView, frame: additionalLeftActionButtonFrame)
|
||||||
|
if animateIn {
|
||||||
|
alphaTransition.animateAlpha(view: switchVideoButtonView, from: 0.0, to: 1.0)
|
||||||
|
transition.animateScale(view: switchVideoButtonView, from: 0.001, to: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let switchVideoButton = self.switchVideoButton {
|
||||||
|
self.switchVideoButton = nil
|
||||||
|
if let switchVideoButtonView = switchVideoButton.view {
|
||||||
|
alphaTransition.setAlpha(view: switchVideoButtonView, alpha: 0.0, completion: { [weak switchVideoButtonView] _ in
|
||||||
|
switchVideoButtonView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
transition.setScale(view: switchVideoButtonView, scale: 0.001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _ = self.leaveButton.update(
|
let _ = self.leaveButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(PlainButtonComponent(
|
component: AnyComponent(PlainButtonComponent(
|
||||||
|
@ -169,6 +169,7 @@ public final class PeerListItemComponent: Component {
|
|||||||
case neutral
|
case neutral
|
||||||
case accent
|
case accent
|
||||||
case constructive
|
case constructive
|
||||||
|
case destructive
|
||||||
}
|
}
|
||||||
|
|
||||||
public var text: String
|
public var text: String
|
||||||
@ -937,8 +938,9 @@ public final class PeerListItemComponent: Component {
|
|||||||
case .accent:
|
case .accent:
|
||||||
labelColor = component.theme.list.itemAccentColor
|
labelColor = component.theme.list.itemAccentColor
|
||||||
case .constructive:
|
case .constructive:
|
||||||
//TODO:release
|
|
||||||
labelColor = UIColor(rgb: 0x33C758)
|
labelColor = UIColor(rgb: 0x33C758)
|
||||||
|
case .destructive:
|
||||||
|
labelColor = UIColor(rgb: 0xff3b30)
|
||||||
}
|
}
|
||||||
|
|
||||||
var animateLabelDirection: Bool?
|
var animateLabelDirection: Bool?
|
||||||
|
Loading…
x
Reference in New Issue
Block a user