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
981367c33c
commit
877d7459bf
@ -107,8 +107,8 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
|
|||||||
guard !isAnimating else { return }
|
guard !isAnimating else { return }
|
||||||
isAnimating = true
|
isAnimating = true
|
||||||
|
|
||||||
mediumBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false)
|
mediumBlob.layer.animateScale(from: 0.75, to: 1, duration: 0.35, removeOnCompletion: false)
|
||||||
bigBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false)
|
bigBlob.layer.animateScale(from: 0.75, to: 1, duration: 0.35, removeOnCompletion: false)
|
||||||
|
|
||||||
updateBlobsState()
|
updateBlobsState()
|
||||||
|
|
||||||
@ -123,8 +123,8 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
|
|||||||
guard isAnimating else { return }
|
guard isAnimating else { return }
|
||||||
isAnimating = false
|
isAnimating = false
|
||||||
|
|
||||||
mediumBlob.layer.animateScale(from: 1.0, to: 0.5, duration: duration, removeOnCompletion: false)
|
mediumBlob.layer.animateScale(from: 1.0, to: 0.75, duration: duration, removeOnCompletion: false)
|
||||||
bigBlob.layer.animateScale(from: 1.0, to: 0.5, duration: duration, removeOnCompletion: false)
|
bigBlob.layer.animateScale(from: 1.0, to: 0.75, duration: duration, removeOnCompletion: false)
|
||||||
|
|
||||||
updateBlobsState()
|
updateBlobsState()
|
||||||
|
|
||||||
|
@ -1390,6 +1390,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
} else {
|
} else {
|
||||||
overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset)
|
overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset)
|
||||||
contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -overflowOffset - contentParentNode.contentRect.minY)
|
contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -overflowOffset - contentParentNode.contentRect.minY)
|
||||||
|
|
||||||
|
if contentContainerFrame.maxX > layout.size.width {
|
||||||
|
contentContainerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentContainerFrame.width) / 2.0), y: contentContainerFrame.minY), size: contentContainerFrame.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .extracted(source) = self.source, source.centerVertically {
|
if case let .extracted(source) = self.source, source.centerVertically {
|
||||||
|
@ -185,7 +185,10 @@
|
|||||||
|
|
||||||
- (void)cleanup
|
- (void)cleanup
|
||||||
{
|
{
|
||||||
[_diskCache cleanup];
|
TGModernCache *diskCache = _diskCache;
|
||||||
|
TGDispatchAfter(10.0, dispatch_get_main_queue(), ^{
|
||||||
|
[diskCache cleanup];
|
||||||
|
});
|
||||||
|
|
||||||
[[NSFileManager defaultManager] removeItemAtPath:_fullSizeResultsUrl.path error:nil];
|
[[NSFileManager defaultManager] removeItemAtPath:_fullSizeResultsUrl.path error:nil];
|
||||||
[[NSFileManager defaultManager] removeItemAtPath:_paintingImagesUrl.path error:nil];
|
[[NSFileManager defaultManager] removeItemAtPath:_paintingImagesUrl.path error:nil];
|
||||||
@ -991,7 +994,7 @@
|
|||||||
|
|
||||||
+ (NSUInteger)diskMemoryLimit
|
+ (NSUInteger)diskMemoryLimit
|
||||||
{
|
{
|
||||||
return 64 * 1024 * 1024;
|
return 512 * 1024 * 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSUInteger)imageSoftMemoryLimit
|
+ (NSUInteger)imageSoftMemoryLimit
|
||||||
|
@ -146,7 +146,6 @@
|
|||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true];
|
_actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true];
|
||||||
_standaloneEditingContext = [[TGMediaEditingContext alloc] init];
|
|
||||||
|
|
||||||
self.automaticallyManageScrollViewInsets = false;
|
self.automaticallyManageScrollViewInsets = false;
|
||||||
self.autoManageStatusBarBackground = false;
|
self.autoManageStatusBarBackground = false;
|
||||||
@ -2182,10 +2181,14 @@
|
|||||||
|
|
||||||
- (TGMediaEditingContext *)editingContext
|
- (TGMediaEditingContext *)editingContext
|
||||||
{
|
{
|
||||||
if (_editingContext)
|
if (_editingContext) {
|
||||||
return _editingContext;
|
return _editingContext;
|
||||||
else
|
} else {
|
||||||
|
if (_standaloneEditingContext == nil) {
|
||||||
|
_standaloneEditingContext = [[TGMediaEditingContext alloc] init];
|
||||||
|
}
|
||||||
return _standaloneEditingContext;
|
return _standaloneEditingContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)doneButtonLongPressed:(UIButton *)sender
|
- (void)doneButtonLongPressed:(UIButton *)sender
|
||||||
|
@ -40,12 +40,15 @@ final class GroupVideoNode: ASDisplayNode {
|
|||||||
self.backdropVideoViewContainer.addSubview(backdropVideoView.view)
|
self.backdropVideoViewContainer.addSubview(backdropVideoView.view)
|
||||||
self.view.addSubview(self.backdropVideoViewContainer)
|
self.view.addSubview(self.backdropVideoViewContainer)
|
||||||
|
|
||||||
|
let effect: UIVisualEffect
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
let backdropEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThinMaterialDark))
|
effect = UIBlurEffect(style: .systemThinMaterialDark)
|
||||||
self.view.addSubview(backdropEffectView)
|
|
||||||
self.backdropEffectView = backdropEffectView
|
|
||||||
} else {
|
} else {
|
||||||
|
effect = UIBlurEffect(style: .dark)
|
||||||
}
|
}
|
||||||
|
let backdropEffectView = UIVisualEffectView(effect: effect)
|
||||||
|
self.view.addSubview(backdropEffectView)
|
||||||
|
self.backdropEffectView = backdropEffectView
|
||||||
}
|
}
|
||||||
|
|
||||||
self.videoViewContainer.addSubview(self.videoView.view)
|
self.videoViewContainer.addSubview(self.videoView.view)
|
||||||
@ -131,10 +134,15 @@ final class GroupVideoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var aspectRatio: CGFloat {
|
||||||
|
return self.videoView.getAspect()
|
||||||
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, isLandscape: Bool, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, isLandscape: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
self.validLayout = (size, isLandscape)
|
self.validLayout = (size, isLandscape)
|
||||||
transition.updateFrameAsPositionAndBounds(layer: self.videoViewContainer.layer, frame: CGRect(origin: CGPoint(), size: size))
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
transition.updateFrameAsPositionAndBounds(layer: self.backdropVideoViewContainer.layer, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrameAsPositionAndBounds(layer: self.videoViewContainer.layer, frame: bounds)
|
||||||
|
transition.updateFrameAsPositionAndBounds(layer: self.backdropVideoViewContainer.layer, frame: bounds)
|
||||||
|
|
||||||
let orientation = self.videoView.getOrientation()
|
let orientation = self.videoView.getOrientation()
|
||||||
var aspect = self.videoView.getAspect()
|
var aspect = self.videoView.getAspect()
|
||||||
@ -194,9 +202,6 @@ final class GroupVideoNode: ASDisplayNode {
|
|||||||
let transformScale: CGFloat = rotatedVideoFrame.width / videoSize.width
|
let transformScale: CGFloat = rotatedVideoFrame.width / videoSize.width
|
||||||
transition.updateTransformScale(layer: self.videoViewContainer.layer, scale: transformScale)
|
transition.updateTransformScale(layer: self.videoViewContainer.layer, scale: transformScale)
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .immediate
|
|
||||||
transition.updateTransformRotation(view: self.videoView.view, angle: angle)
|
|
||||||
|
|
||||||
if let backdropVideoView = self.backdropVideoView {
|
if let backdropVideoView = self.backdropVideoView {
|
||||||
rotatedVideoSize = filledSize
|
rotatedVideoSize = filledSize
|
||||||
var rotatedVideoFrame = CGRect(origin: CGPoint(x: floor((size.width - rotatedVideoSize.width) / 2.0), y: floor((size.height - rotatedVideoSize.height) / 2.0)), size: rotatedVideoSize)
|
var rotatedVideoFrame = CGRect(origin: CGPoint(x: floor((size.width - rotatedVideoSize.width) / 2.0), y: floor((size.height - rotatedVideoSize.height) / 2.0)), size: rotatedVideoSize)
|
||||||
@ -217,11 +222,16 @@ final class GroupVideoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let backdropEffectView = self.backdropEffectView {
|
if let backdropEffectView = self.backdropEffectView {
|
||||||
transition.updateFrame(view: backdropEffectView, frame: self.bounds)
|
let maxSide = max(bounds.width, bounds.height)
|
||||||
|
let squareBounds = CGRect(x: (bounds.width - maxSide) / 2.0, y: (bounds.width - maxSide) / 2.0, width: maxSide, height: maxSide)
|
||||||
|
transition.updateFrame(view: backdropEffectView, frame: squareBounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition = .immediate
|
||||||
|
transition.updateTransformRotation(view: self.videoView.view, angle: angle)
|
||||||
|
|
||||||
if let effectView = self.effectView {
|
if let effectView = self.effectView {
|
||||||
transition.updateFrame(view: effectView, frame: self.bounds)
|
transition.updateFrame(view: effectView, frame: bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: properly fix the issue
|
// TODO: properly fix the issue
|
||||||
|
@ -657,7 +657,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
|
|
||||||
self.temporaryJoinTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
self.temporaryJoinTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||||
|
|
||||||
self.isVideoEnabled = accountContext.sharedContext.immediateExperimentalUISettings.demoVideoChats
|
self.isVideoEnabled = true
|
||||||
self.hasVideo = false
|
self.hasVideo = false
|
||||||
self.hasScreencast = false
|
self.hasScreencast = false
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
private let containerNode: ASDisplayNode
|
private let containerNode: ASDisplayNode
|
||||||
private let backgroundNode: VoiceChatActionButtonBackgroundNode
|
private let backgroundNode: VoiceChatActionButtonBackgroundNode
|
||||||
private let iconNode: VoiceChatActionButtonIconNode
|
private let iconNode: VoiceChatActionButtonIconNode
|
||||||
|
private let labelContainerNode: ASDisplayNode
|
||||||
let titleLabel: ImmediateTextNode
|
let titleLabel: ImmediateTextNode
|
||||||
private let subtitleLabel: ImmediateTextNode
|
private let subtitleLabel: ImmediateTextNode
|
||||||
private let buttonTitleLabel: ImmediateTextNode
|
private let buttonTitleLabel: ImmediateTextNode
|
||||||
@ -138,6 +139,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
self.backgroundNode = VoiceChatActionButtonBackgroundNode()
|
self.backgroundNode = VoiceChatActionButtonBackgroundNode()
|
||||||
self.iconNode = VoiceChatActionButtonIconNode(isColored: false)
|
self.iconNode = VoiceChatActionButtonIconNode(isColored: false)
|
||||||
|
|
||||||
|
self.labelContainerNode = ASDisplayNode()
|
||||||
self.titleLabel = ImmediateTextNode()
|
self.titleLabel = ImmediateTextNode()
|
||||||
self.subtitleLabel = ImmediateTextNode()
|
self.subtitleLabel = ImmediateTextNode()
|
||||||
self.buttonTitleLabel = ImmediateTextNode()
|
self.buttonTitleLabel = ImmediateTextNode()
|
||||||
@ -147,9 +149,10 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.bottomNode)
|
self.addSubnode(self.bottomNode)
|
||||||
self.addSubnode(self.titleLabel)
|
self.labelContainerNode.addSubnode(self.titleLabel)
|
||||||
self.addSubnode(self.subtitleLabel)
|
self.labelContainerNode.addSubnode(self.subtitleLabel)
|
||||||
|
self.addSubnode(self.labelContainerNode)
|
||||||
|
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.containerNode.addSubnode(self.backgroundNode)
|
self.containerNode.addSubnode(self.backgroundNode)
|
||||||
self.containerNode.addSubnode(self.iconNode)
|
self.containerNode.addSubnode(self.iconNode)
|
||||||
@ -242,6 +245,8 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||||
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
||||||
|
|
||||||
|
self.labelContainerNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - totalHeight) / 2.0) + 84.0), size: titleSize)
|
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - totalHeight) / 2.0) + 84.0), size: titleSize)
|
||||||
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
||||||
|
|
||||||
@ -272,17 +277,19 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0)
|
transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0)
|
||||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 0.0)
|
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 0.0)
|
||||||
} else {
|
} else {
|
||||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
|
||||||
if small {
|
if small {
|
||||||
transition.updateTransformScale(node: self.backgroundNode, scale: self.pressing ? smallScale * 0.9 : smallScale, delay: 0.05)
|
transition.updateTransformScale(node: self.backgroundNode, scale: self.pressing ? smallScale * 0.9 : smallScale, delay: 0.0)
|
||||||
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? smallIconScale * 0.9 : smallIconScale, delay: 0.05)
|
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? smallIconScale * 0.9 : smallIconScale, delay: 0.0)
|
||||||
transition.updateAlpha(node: self.titleLabel, alpha: 0.0)
|
transition.updateAlpha(node: self.titleLabel, alpha: 0.0)
|
||||||
transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0)
|
transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0)
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.labelContainerNode.layer, offset: CGPoint(x: 0.0, y: -40.0))
|
||||||
} else {
|
} else {
|
||||||
transition.updateTransformScale(node: self.backgroundNode, scale: 1.0, delay: 0.05)
|
transition.updateTransformScale(node: self.backgroundNode, scale: 1.0, delay: 0.0)
|
||||||
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? 0.9 : 1.0, delay: 0.05)
|
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? 0.9 : 1.0, delay: 0.0)
|
||||||
transition.updateAlpha(node: self.titleLabel, alpha: 1.0, delay: 0.05)
|
transition.updateAlpha(node: self.titleLabel, alpha: 1.0, delay: 0.05)
|
||||||
transition.updateAlpha(node: self.subtitleLabel, alpha: 1.0, delay: 0.05)
|
transition.updateAlpha(node: self.subtitleLabel, alpha: 1.0, delay: 0.05)
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.labelContainerNode.layer, offset: CGPoint())
|
||||||
}
|
}
|
||||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 1.0)
|
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 1.0)
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,12 @@ final class VoiceChatCameraPreviewController: ViewController {
|
|||||||
private var animatedIn = false
|
private var animatedIn = false
|
||||||
|
|
||||||
private let cameraNode: GroupVideoNode
|
private let cameraNode: GroupVideoNode
|
||||||
private let shareCamera: (ASDisplayNode) -> Void
|
private let shareCamera: (ASDisplayNode, Bool) -> Void
|
||||||
private let switchCamera: () -> Void
|
private let switchCamera: () -> Void
|
||||||
|
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
init(context: AccountContext, cameraNode: GroupVideoNode, shareCamera: @escaping (ASDisplayNode) -> Void, switchCamera: @escaping () -> Void) {
|
init(context: AccountContext, cameraNode: GroupVideoNode, shareCamera: @escaping (ASDisplayNode, Bool) -> Void, switchCamera: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.cameraNode = cameraNode
|
self.cameraNode = cameraNode
|
||||||
self.shareCamera = shareCamera
|
self.shareCamera = shareCamera
|
||||||
@ -60,9 +60,9 @@ final class VoiceChatCameraPreviewController: ViewController {
|
|||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = VoiceChatCameraPreviewControllerNode(controller: self, context: self.context, cameraNode: self.cameraNode)
|
self.displayNode = VoiceChatCameraPreviewControllerNode(controller: self, context: self.context, cameraNode: self.cameraNode)
|
||||||
self.controllerNode.shareCamera = { [weak self] in
|
self.controllerNode.shareCamera = { [weak self] unmuted in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.shareCamera(strongSelf.cameraNode)
|
strongSelf.shareCamera(strongSelf.cameraNode, unmuted)
|
||||||
strongSelf.dismiss()
|
strongSelf.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,6 +121,10 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
|
|||||||
private var broadcastPickerView: UIView?
|
private var broadcastPickerView: UIView?
|
||||||
private let cancelButton: SolidRoundedButtonNode
|
private let cancelButton: SolidRoundedButtonNode
|
||||||
|
|
||||||
|
private let microphoneButton: HighlightTrackingButtonNode
|
||||||
|
private let microphoneEffectView: UIVisualEffectView
|
||||||
|
private let microphoneIconNode: VoiceChatMicrophoneNode
|
||||||
|
|
||||||
private let switchCameraButton: HighlightTrackingButtonNode
|
private let switchCameraButton: HighlightTrackingButtonNode
|
||||||
private let switchCameraEffectView: UIVisualEffectView
|
private let switchCameraEffectView: UIVisualEffectView
|
||||||
private let switchCameraIconNode: ASImageNode
|
private let switchCameraIconNode: ASImageNode
|
||||||
@ -129,7 +133,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
|
|||||||
|
|
||||||
private var applicationStateDisposable: Disposable?
|
private var applicationStateDisposable: Disposable?
|
||||||
|
|
||||||
var shareCamera: (() -> Void)?
|
var shareCamera: ((Bool) -> Void)?
|
||||||
var switchCamera: (() -> Void)?
|
var switchCamera: (() -> Void)?
|
||||||
var dismiss: (() -> Void)?
|
var dismiss: (() -> Void)?
|
||||||
var cancel: (() -> Void)?
|
var cancel: (() -> Void)?
|
||||||
@ -196,6 +200,16 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
|
|||||||
self.previewContainerNode.cornerRadius = 11.0
|
self.previewContainerNode.cornerRadius = 11.0
|
||||||
self.previewContainerNode.backgroundColor = .black
|
self.previewContainerNode.backgroundColor = .black
|
||||||
|
|
||||||
|
self.microphoneButton = HighlightTrackingButtonNode()
|
||||||
|
self.microphoneButton.isSelected = true
|
||||||
|
self.microphoneEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
|
self.microphoneEffectView.clipsToBounds = true
|
||||||
|
self.microphoneEffectView.layer.cornerRadius = 24.0
|
||||||
|
self.microphoneEffectView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.microphoneIconNode = VoiceChatMicrophoneNode()
|
||||||
|
self.microphoneIconNode.update(state: .init(muted: false, filled: true, color: .white), animated: false)
|
||||||
|
|
||||||
self.switchCameraButton = HighlightTrackingButtonNode()
|
self.switchCameraButton = HighlightTrackingButtonNode()
|
||||||
self.switchCameraEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
self.switchCameraEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
self.switchCameraEffectView.clipsToBounds = true
|
self.switchCameraEffectView.clipsToBounds = true
|
||||||
@ -234,13 +248,16 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
|
|||||||
self.contentContainerNode.addSubnode(self.previewContainerNode)
|
self.contentContainerNode.addSubnode(self.previewContainerNode)
|
||||||
|
|
||||||
self.previewContainerNode.addSubnode(self.cameraNode)
|
self.previewContainerNode.addSubnode(self.cameraNode)
|
||||||
|
self.previewContainerNode.addSubnode(self.microphoneButton)
|
||||||
|
self.microphoneButton.view.addSubview(self.microphoneEffectView)
|
||||||
|
self.microphoneButton.addSubnode(self.microphoneIconNode)
|
||||||
self.previewContainerNode.addSubnode(self.switchCameraButton)
|
self.previewContainerNode.addSubnode(self.switchCameraButton)
|
||||||
self.switchCameraButton.view.addSubview(self.switchCameraEffectView)
|
self.switchCameraButton.view.addSubview(self.switchCameraEffectView)
|
||||||
self.switchCameraButton.addSubnode(self.switchCameraIconNode)
|
self.switchCameraButton.addSubnode(self.switchCameraIconNode)
|
||||||
|
|
||||||
self.cameraButton.pressed = { [weak self] in
|
self.cameraButton.pressed = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.shareCamera?()
|
strongSelf.shareCamera?(strongSelf.microphoneButton.isSelected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.cancelButton.pressed = { [weak self] in
|
self.cancelButton.pressed = { [weak self] in
|
||||||
@ -249,6 +266,19 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.microphoneButton.addTarget(self, action: #selector(self.microphonePressed), forControlEvents: .touchUpInside)
|
||||||
|
self.microphoneButton.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .spring)
|
||||||
|
transition.updateSublayerTransformScale(node: strongSelf.microphoneButton, scale: 0.9)
|
||||||
|
} else {
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
|
||||||
|
transition.updateSublayerTransformScale(node: strongSelf.microphoneButton, scale: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.switchCameraButton.addTarget(self, action: #selector(self.switchCameraPressed), forControlEvents: .touchUpInside)
|
self.switchCameraButton.addTarget(self, action: #selector(self.switchCameraPressed), forControlEvents: .touchUpInside)
|
||||||
self.switchCameraButton.highligthedChanged = { [weak self] highlighted in
|
self.switchCameraButton.highligthedChanged = { [weak self] highlighted in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -263,6 +293,11 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func microphonePressed() {
|
||||||
|
self.microphoneButton.isSelected = !self.microphoneButton.isSelected
|
||||||
|
self.microphoneIconNode.update(state: .init(muted: !self.microphoneButton.isSelected, filled: true, color: .white), animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func switchCameraPressed() {
|
@objc private func switchCameraPressed() {
|
||||||
self.switchCamera?()
|
self.switchCamera?()
|
||||||
|
|
||||||
@ -403,6 +438,12 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
|
|||||||
self.cameraNode.frame = CGRect(origin: CGPoint(), size: previewSize)
|
self.cameraNode.frame = CGRect(origin: CGPoint(), size: previewSize)
|
||||||
self.cameraNode.updateLayout(size: previewSize, isLandscape: false, transition: .immediate)
|
self.cameraNode.updateLayout(size: previewSize, isLandscape: false, transition: .immediate)
|
||||||
|
|
||||||
|
let microphoneFrame = CGRect(x: 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0)
|
||||||
|
transition.updateFrame(node: self.microphoneButton, frame: microphoneFrame)
|
||||||
|
transition.updateFrame(view: self.microphoneEffectView, frame: CGRect(origin: CGPoint(), size: microphoneFrame.size))
|
||||||
|
transition.updateFrameAsPositionAndBounds(node: self.microphoneIconNode, frame: CGRect(origin: CGPoint(x: 1.0, y: 0.0), size: microphoneFrame.size).insetBy(dx: 6.0, dy: 6.0))
|
||||||
|
self.microphoneIconNode.transform = CATransform3DMakeScale(1.2, 1.2, 1.0)
|
||||||
|
|
||||||
let switchCameraFrame = CGRect(x: previewSize.width - 48.0 - 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0)
|
let switchCameraFrame = CGRect(x: previewSize.width - 48.0 - 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0)
|
||||||
transition.updateFrame(node: self.switchCameraButton, frame: switchCameraFrame)
|
transition.updateFrame(node: self.switchCameraButton, frame: switchCameraFrame)
|
||||||
transition.updateFrame(view: self.switchCameraEffectView, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
|
transition.updateFrame(view: self.switchCameraEffectView, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -72,6 +72,7 @@ final class VoiceChatFullscreenParticipantItem: ListViewItem {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
let icon: Icon
|
let icon: Icon
|
||||||
|
let text: VoiceChatParticipantItem.ParticipantText
|
||||||
let color: Color
|
let color: Color
|
||||||
let isLandscape: Bool
|
let isLandscape: Bool
|
||||||
let active: Bool
|
let active: Bool
|
||||||
@ -83,12 +84,13 @@ final class VoiceChatFullscreenParticipantItem: ListViewItem {
|
|||||||
|
|
||||||
public let selectable: Bool = true
|
public let selectable: Bool = true
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, icon: Icon, color: Color, isLandscape: Bool, active: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, action: ((ASDisplayNode?) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
public init(presentationData: ItemListPresentationData, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, color: Color, isLandscape: Bool, active: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, action: ((ASDisplayNode?) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
self.text = text
|
||||||
self.color = color
|
self.color = color
|
||||||
self.isLandscape = isLandscape
|
self.isLandscape = isLandscape
|
||||||
self.active = active
|
self.active = active
|
||||||
@ -157,6 +159,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let avatarNode: AvatarNode
|
let avatarNode: AvatarNode
|
||||||
let contentWrapperNode: ASDisplayNode
|
let contentWrapperNode: ASDisplayNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
|
private let statusNode: VoiceChatParticipantStatusNode
|
||||||
private var credibilityIconNode: ASImageNode?
|
private var credibilityIconNode: ASImageNode?
|
||||||
|
|
||||||
private let actionContainerNode: ASDisplayNode
|
private let actionContainerNode: ASDisplayNode
|
||||||
@ -174,6 +177,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private var layoutParams: (VoiceChatFullscreenParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
private var layoutParams: (VoiceChatFullscreenParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
||||||
private var isExtracted = false
|
private var isExtracted = false
|
||||||
private var animatingExtraction = false
|
private var animatingExtraction = false
|
||||||
|
private var animatingSelection = false
|
||||||
private var wavesColor: UIColor?
|
private var wavesColor: UIColor?
|
||||||
|
|
||||||
let videoContainerNode: ASDisplayNode
|
let videoContainerNode: ASDisplayNode
|
||||||
@ -183,14 +187,14 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private var videoReadyDelayed = false
|
private var videoReadyDelayed = false
|
||||||
private var videoReady = false
|
private var videoReady = false
|
||||||
|
|
||||||
|
private var profileNode: VoiceChatPeerProfileNode?
|
||||||
|
|
||||||
private var raiseHandTimer: SwiftSignalKit.Timer?
|
private var raiseHandTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
var item: VoiceChatFullscreenParticipantItem? {
|
var item: VoiceChatFullscreenParticipantItem? {
|
||||||
return self.layoutParams?.0
|
return self.layoutParams?.0
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentTitle: String?
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
@ -231,6 +235,8 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
self.titleNode.contentMode = .left
|
self.titleNode.contentMode = .left
|
||||||
self.titleNode.contentsScale = UIScreen.main.scale
|
self.titleNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
|
self.statusNode = VoiceChatParticipantStatusNode()
|
||||||
|
|
||||||
self.actionContainerNode = ASDisplayNode()
|
self.actionContainerNode = ASDisplayNode()
|
||||||
self.actionButtonNode = HighlightableButtonNode()
|
self.actionButtonNode = HighlightableButtonNode()
|
||||||
@ -256,7 +262,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
|
|
||||||
self.containerNode.shouldBegin = { [weak self] location in
|
self.containerNode.shouldBegin = { [weak self] location in
|
||||||
guard let strongSelf = self else {
|
guard let _ = self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -268,6 +274,12 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
contextAction(strongSelf.contextSourceNode, gesture)
|
contextAction(strongSelf.contextSourceNode, gesture)
|
||||||
}
|
}
|
||||||
|
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
|
||||||
|
guard let strongSelf = self, let _ = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.updateIsExtracted(isExtracted, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
// self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
|
// self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
|
||||||
// guard let strongSelf = self, let item = strongSelf.layoutParams?.0 else {
|
// guard let strongSelf = self, let item = strongSelf.layoutParams?.0 else {
|
||||||
@ -575,10 +587,16 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.layoutParams?.0.action?(self.contextSourceNode)
|
self.layoutParams?.0.action?(self.contextSourceNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateTransitionIn(from sourceNode: ASDisplayNode, containerNode: ASDisplayNode, animate: Bool = true) {
|
func animateTransitionIn(from sourceNode: ASDisplayNode, containerNode: ASDisplayNode, transition: ContainedViewLayoutTransition, animate: Bool = true) {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var duration: Double = 0.2
|
||||||
|
var timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue
|
||||||
|
if case let .animated(transitionDuration, curve) = transition {
|
||||||
|
duration = transitionDuration
|
||||||
|
timingFunction = curve.timingFunction
|
||||||
|
}
|
||||||
|
|
||||||
let initialAnimate = animate
|
let initialAnimate = animate
|
||||||
if let sourceNode = sourceNode as? VoiceChatTileItemNode {
|
if let sourceNode = sourceNode as? VoiceChatTileItemNode {
|
||||||
@ -602,12 +620,11 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.videoContainerNode.insertSubnode(videoNode, at: 0)
|
self.videoContainerNode.insertSubnode(videoNode, at: 0)
|
||||||
|
|
||||||
if animate {
|
if animate {
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
|
||||||
videoNode.updateLayout(size: videoSize, isLandscape: true, transition: transition)
|
videoNode.updateLayout(size: videoSize, isLandscape: true, transition: transition)
|
||||||
|
|
||||||
let scale = sourceNode.bounds.width / videoSize.width
|
let scale = sourceNode.bounds.width / videoSize.width
|
||||||
self.videoContainerNode.layer.animateScale(from: sourceNode.bounds.width / videoSize.width, to: tileSize.width / videoSize.width, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
self.videoContainerNode.layer.animateScale(from: sourceNode.bounds.width / videoSize.width, to: tileSize.width / videoSize.width, duration: duration, timingFunction: timingFunction)
|
||||||
self.videoContainerNode.layer.animate(from: backgroundCornerRadius * (1.0 / scale) as NSNumber, to: videoCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
self.videoContainerNode.layer.animate(from: backgroundCornerRadius * (1.0 / scale) as NSNumber, to: videoCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: timingFunction, duration: duration, removeOnCompletion: false, completion: { _ in
|
||||||
})
|
})
|
||||||
|
|
||||||
self.videoFadeNode.alpha = 1.0
|
self.videoFadeNode.alpha = 1.0
|
||||||
@ -625,7 +642,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.contextSourceNode.position = targetContainerPosition
|
self.contextSourceNode.position = targetContainerPosition
|
||||||
containerNode.addSubnode(self.contextSourceNode)
|
containerNode.addSubnode(self.contextSourceNode)
|
||||||
|
|
||||||
self.contextSourceNode.layer.animatePosition(from: startContainerPosition, to: targetContainerPosition, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak self] _ in
|
self.contextSourceNode.layer.animatePosition(from: startContainerPosition, to: targetContainerPosition, duration: duration, timingFunction: timingFunction, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.contextSourceNode.position = initialPosition
|
strongSelf.contextSourceNode.position = initialPosition
|
||||||
strongSelf.containerNode.addSubnode(strongSelf.contextSourceNode)
|
strongSelf.containerNode.addSubnode(strongSelf.contextSourceNode)
|
||||||
@ -634,16 +651,16 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
if item.active {
|
if item.active {
|
||||||
self.borderImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.borderImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
self.borderImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
self.borderImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, timingFunction: timingFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
self.backgroundImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
} else if !initialAnimate {
|
} else if !initialAnimate {
|
||||||
self.contextSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.contextSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
self.contextSourceNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
|
self.contextSourceNode.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
}
|
}
|
||||||
} else if let sourceNode = sourceNode as? VoiceChatParticipantItemNode, let _ = sourceNode.item {
|
} else if let sourceNode = sourceNode as? VoiceChatParticipantItemNode, let _ = sourceNode.item {
|
||||||
var startContainerPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
var startContainerPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
||||||
@ -662,8 +679,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.contextSourceNode.position = targetContainerPosition
|
self.contextSourceNode.position = targetContainerPosition
|
||||||
containerNode.addSubnode(self.contextSourceNode)
|
containerNode.addSubnode(self.contextSourceNode)
|
||||||
|
|
||||||
let timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue
|
self.contextSourceNode.layer.animatePosition(from: startContainerPosition, to: targetContainerPosition, duration: duration, timingFunction: timingFunction, completion: { [weak self, weak sourceNode] _ in
|
||||||
self.contextSourceNode.layer.animatePosition(from: startContainerPosition, to: targetContainerPosition, duration: 0.2, timingFunction: timingFunction, completion: { [weak self, weak sourceNode] _ in
|
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
sourceNode?.avatarNode.alpha = 1.0
|
sourceNode?.avatarNode.alpha = 1.0
|
||||||
strongSelf.contextSourceNode.position = initialPosition
|
strongSelf.contextSourceNode.position = initialPosition
|
||||||
@ -676,18 +692,49 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.borderImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, timingFunction: timingFunction)
|
self.borderImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, timingFunction: timingFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.avatarNode.layer.animateScale(from: 0.8, to: 1.0, duration: 0.2)
|
self.avatarNode.layer.animateScale(from: 0.8, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
|
|
||||||
self.backgroundImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, timingFunction: timingFunction)
|
self.backgroundImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, timingFunction: timingFunction)
|
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, timingFunction: timingFunction)
|
self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: timingFunction)
|
self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private func updateIsExtracted(_ isExtracted: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
|
guard self.isExtracted != isExtracted, let extractedRect = self.extractedRect, let nonExtractedRect = self.nonExtractedRect, let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.isExtracted = isExtracted
|
||||||
|
|
||||||
|
if isExtracted {
|
||||||
|
let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, peer: item.peer, text: item.text, customNode: self.videoContainerNode, additionalEntry: .single(nil), requestDismiss: { [weak self] in
|
||||||
|
self?.contextSourceNode.requestDismiss?()
|
||||||
|
})
|
||||||
|
self.profileNode = profileNode
|
||||||
|
self.contextSourceNode.contentNode.addSubnode(profileNode)
|
||||||
|
|
||||||
|
profileNode.animateIn(from: self, targetRect: extractedRect, transition: transition)
|
||||||
|
|
||||||
|
self.contextSourceNode.contentNode.customHitTest = { [weak self] point in
|
||||||
|
if let strongSelf = self, let profileNode = strongSelf.profileNode {
|
||||||
|
if profileNode.avatarListWrapperNode.frame.contains(point) {
|
||||||
|
return profileNode.avatarListNode.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if let profileNode = self.profileNode {
|
||||||
|
self.profileNode = nil
|
||||||
|
profileNode.animateOut(to: self, targetRect: nonExtractedRect, transition: transition)
|
||||||
|
|
||||||
|
self.contextSourceNode.contentNode.customHitTest = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: VoiceChatFullscreenParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
|
func asyncLayout() -> (_ item: VoiceChatFullscreenParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
let makeStatusLayout = self.statusNode.asyncLayout()
|
||||||
|
|
||||||
let currentItem = self.layoutParams?.0
|
let currentItem = self.layoutParams?.0
|
||||||
let hasVideo = self.videoNode != nil
|
let hasVideo = self.videoNode != nil
|
||||||
@ -760,6 +807,9 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let constrainedWidth = params.width - 24.0 - 10.0
|
let constrainedWidth = params.width - 24.0 - 10.0
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
let availableWidth = params.availableHeight
|
||||||
|
let (statusLayout, _) = makeStatusLayout(CGSize(width: availableWidth - 30.0, height: CGFloat.greatestFiniteMagnitude), item.text, true)
|
||||||
|
|
||||||
let contentSize = tileSize
|
let contentSize = tileSize
|
||||||
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: !last ? 6.0 : 0.0, right: 0.0)
|
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: !last ? 6.0 : 0.0, right: 0.0)
|
||||||
|
|
||||||
@ -769,7 +819,6 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let hadItem = strongSelf.layoutParams?.0 != nil
|
let hadItem = strongSelf.layoutParams?.0 != nil
|
||||||
strongSelf.layoutParams = (item, params, first, last)
|
strongSelf.layoutParams = (item, params, first, last)
|
||||||
strongSelf.currentTitle = titleAttributedString?.string
|
|
||||||
strongSelf.wavesColor = wavesColor
|
strongSelf.wavesColor = wavesColor
|
||||||
|
|
||||||
let videoNode = item.getVideo()
|
let videoNode = item.getVideo()
|
||||||
@ -877,7 +926,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
transition.updateFrameAsPositionAndBounds(node: strongSelf.avatarNode, frame: avatarFrame)
|
transition.updateFrameAsPositionAndBounds(node: strongSelf.avatarNode, frame: avatarFrame)
|
||||||
|
|
||||||
let blobFrame = avatarFrame.insetBy(dx: -14.0, dy: -14.0)
|
let blobFrame = avatarFrame.insetBy(dx: -18.0, dy: -18.0)
|
||||||
if let getAudioLevel = item.getAudioLevel {
|
if let getAudioLevel = item.getAudioLevel {
|
||||||
if !strongSelf.didSetupAudioLevel || currentItem?.peer.id != item.peer.id {
|
if !strongSelf.didSetupAudioLevel || currentItem?.peer.id != item.peer.id {
|
||||||
strongSelf.audioLevelView?.frame = blobFrame
|
strongSelf.audioLevelView?.frame = blobFrame
|
||||||
@ -902,7 +951,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
playbackMaskLayer.frame = maskRect
|
playbackMaskLayer.frame = maskRect
|
||||||
playbackMaskLayer.fillRule = .evenOdd
|
playbackMaskLayer.fillRule = .evenOdd
|
||||||
let maskPath = UIBezierPath()
|
let maskPath = UIBezierPath()
|
||||||
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 14, dy: 14), cornerRadius: 22))
|
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 18, dy: 18), cornerRadius: 22))
|
||||||
maskPath.append(UIBezierPath(rect: maskRect))
|
maskPath.append(UIBezierPath(rect: maskRect))
|
||||||
playbackMaskLayer.path = maskPath.cgPath
|
playbackMaskLayer.path = maskPath.cgPath
|
||||||
audioLevelView.layer.mask = playbackMaskLayer
|
audioLevelView.layer.mask = playbackMaskLayer
|
||||||
@ -912,6 +961,10 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
strongSelf.audioLevelView = audioLevelView
|
strongSelf.audioLevelView = audioLevelView
|
||||||
strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0)
|
strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0)
|
||||||
|
|
||||||
|
if let item = strongSelf.item, strongSelf.videoNode != nil || item.active {
|
||||||
|
audioLevelView.alpha = 0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let level = min(1.0, max(0.0, CGFloat(value)))
|
let level = min(1.0, max(0.0, CGFloat(value)))
|
||||||
@ -926,12 +979,13 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
audioLevelView.setColor(wavesColor, animated: true)
|
audioLevelView.setColor(wavesColor, animated: true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
audioLevelView.stopAnimating(duration: 0.5)
|
|
||||||
avatarScale = 1.0
|
avatarScale = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
|
if !strongSelf.animatingSelection {
|
||||||
transition.updateTransformScale(node: strongSelf.avatarNode, scale: strongSelf.isExtracted ? 1.0 : avatarScale, beginWithCurrentState: true)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
|
||||||
|
transition.updateTransformScale(node: strongSelf.avatarNode, scale: strongSelf.isExtracted ? 1.0 : avatarScale, beginWithCurrentState: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1073,19 +1127,27 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.videoContainerNode.layer.animateScale(from: videoContainerScale, to: 0.001, duration: 0.2)
|
strongSelf.videoContainerNode.layer.animateScale(from: videoContainerScale, to: 0.001, duration: 0.2)
|
||||||
strongSelf.avatarNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
|
strongSelf.avatarNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
strongSelf.videoContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -9.0), duration: 0.2, additive: true)
|
strongSelf.videoContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -9.0), duration: 0.2, additive: true)
|
||||||
|
strongSelf.audioLevelView?.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
transition.updateAlpha(node: videoNode, alpha: 0.0)
|
transition.updateAlpha(node: videoNode, alpha: 0.0)
|
||||||
transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 0.0)
|
transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 0.0)
|
||||||
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 1.0)
|
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 1.0)
|
||||||
|
if let audioLevelView = strongSelf.audioLevelView {
|
||||||
|
transition.updateAlpha(layer: audioLevelView.layer, alpha: 1.0)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if !strongSelf.avatarNode.alpha.isZero {
|
if !strongSelf.avatarNode.alpha.isZero {
|
||||||
strongSelf.videoContainerNode.layer.animateScale(from: 0.001, to: videoContainerScale, duration: 0.2)
|
strongSelf.videoContainerNode.layer.animateScale(from: 0.001, to: videoContainerScale, duration: 0.2)
|
||||||
strongSelf.avatarNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
|
strongSelf.avatarNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
|
||||||
|
strongSelf.audioLevelView?.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
|
||||||
strongSelf.videoContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -9.0), to: CGPoint(), duration: 0.2, additive: true)
|
strongSelf.videoContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -9.0), to: CGPoint(), duration: 0.2, additive: true)
|
||||||
}
|
}
|
||||||
transition.updateAlpha(node: videoNode, alpha: 1.0)
|
transition.updateAlpha(node: videoNode, alpha: 1.0)
|
||||||
transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 1.0)
|
transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 1.0)
|
||||||
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 0.0)
|
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 0.0)
|
||||||
|
if let audioLevelView = strongSelf.audioLevelView {
|
||||||
|
transition.updateAlpha(layer: audioLevelView.layer, alpha: 0.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if item.active {
|
if item.active {
|
||||||
|
@ -169,13 +169,13 @@ public final class VoiceChatOverlayController: ViewController {
|
|||||||
|
|
||||||
if reclaim {
|
if reclaim {
|
||||||
self.dismissed = true
|
self.dismissed = true
|
||||||
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - 205.0 / 2.0 - 2.0)
|
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - bottomAreaHeight / 2.0 - 3.0)
|
||||||
if self.isSlidOffscreen {
|
if self.isSlidOffscreen {
|
||||||
self.isSlidOffscreen = false
|
self.isSlidOffscreen = false
|
||||||
self.isButtonHidden = true
|
self.isButtonHidden = true
|
||||||
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
||||||
actionButton.update(snap: false, animated: false)
|
actionButton.update(snap: false, animated: false)
|
||||||
actionButton.position = CGPoint(x: targetPosition.x, y: 205.0 / 2.0)
|
actionButton.position = CGPoint(x: targetPosition.x, y: bottomAreaHeight / 2.0)
|
||||||
|
|
||||||
leftButton.isHidden = false
|
leftButton.isHidden = false
|
||||||
rightButton.isHidden = false
|
rightButton.isHidden = false
|
||||||
@ -191,7 +191,7 @@ public final class VoiceChatOverlayController: ViewController {
|
|||||||
actionButton.layer.removeAllAnimations()
|
actionButton.layer.removeAllAnimations()
|
||||||
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
||||||
actionButton.update(snap: false, animated: false)
|
actionButton.update(snap: false, animated: false)
|
||||||
actionButton.position = CGPoint(x: targetPosition.x, y: 205.0 / 2.0)
|
actionButton.position = CGPoint(x: targetPosition.x, y: bottomAreaHeight / 2.0)
|
||||||
|
|
||||||
leftButton.isHidden = false
|
leftButton.isHidden = false
|
||||||
rightButton.isHidden = false
|
rightButton.isHidden = false
|
||||||
|
@ -138,7 +138,7 @@ private let accentColor: UIColor = UIColor(rgb: 0x007aff)
|
|||||||
private let constructiveColor: UIColor = UIColor(rgb: 0x34c759)
|
private let constructiveColor: UIColor = UIColor(rgb: 0x34c759)
|
||||||
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
||||||
|
|
||||||
private class VoiceChatParticipantStatusNode: ASDisplayNode {
|
class VoiceChatParticipantStatusNode: ASDisplayNode {
|
||||||
private var iconNodes: [ASImageNode]
|
private var iconNodes: [ASImageNode]
|
||||||
private let textNode: TextNode
|
private let textNode: TextNode
|
||||||
|
|
||||||
@ -156,10 +156,10 @@ private class VoiceChatParticipantStatusNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ size: CGSize, _ text: VoiceChatParticipantItem.ParticipantText, _ transparent: Bool) -> (CGSize, () -> Void) {
|
func asyncLayout() -> (_ size: CGSize, _ text: VoiceChatParticipantItem.ParticipantText, _ expanded: Bool) -> (CGSize, () -> Void) {
|
||||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
|
|
||||||
return { size, text, transparent in
|
return { size, text, expanded in
|
||||||
let statusFont = Font.regular(14.0)
|
let statusFont = Font.regular(14.0)
|
||||||
|
|
||||||
var attributedString: NSAttributedString?
|
var attributedString: NSAttributedString?
|
||||||
@ -184,9 +184,6 @@ private class VoiceChatParticipantStatusNode: ASDisplayNode {
|
|||||||
case .destructive:
|
case .destructive:
|
||||||
textColorValue = destructiveColor
|
textColorValue = destructiveColor
|
||||||
}
|
}
|
||||||
if transparent {
|
|
||||||
textColorValue = UIColor(rgb: 0xffffff, alpha: 0.65)
|
|
||||||
}
|
|
||||||
color = textColorValue
|
color = textColorValue
|
||||||
attributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue)
|
attributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue)
|
||||||
default:
|
default:
|
||||||
@ -207,7 +204,7 @@ private class VoiceChatParticipantStatusNode: ASDisplayNode {
|
|||||||
icons.append(image)
|
icons.append(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: size.width - (iconSize.width + spacing) * CGFloat(icons.count), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: expanded ? 4 : 1, truncationType: .end, constrainedSize: CGSize(width: size.width - (iconSize.width + spacing) * CGFloat(icons.count), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
var contentSize = textLayout.size
|
var contentSize = textLayout.size
|
||||||
contentSize.width += (iconSize.width + spacing) * CGFloat(icons.count)
|
contentSize.width += (iconSize.width + spacing) * CGFloat(icons.count)
|
||||||
|
|
||||||
@ -388,7 +385,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
strongSelf.isExtracted = isExtracted
|
strongSelf.isExtracted = isExtracted
|
||||||
|
|
||||||
let inset: CGFloat = 12.0
|
let inset: CGFloat = 0.0
|
||||||
if isExtracted {
|
if isExtracted {
|
||||||
strongSelf.contextSourceNode.contentNode.customHitTest = { [weak self] point in
|
strongSelf.contextSourceNode.contentNode.customHitTest = { [weak self] point in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -492,8 +489,6 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.avatarNode.isHidden = true
|
strongSelf.avatarNode.isHidden = true
|
||||||
avatarListWrapperNode.contentNode.addSubnode(transitionNode)
|
avatarListWrapperNode.contentNode.addSubnode(transitionNode)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
strongSelf.avatarTransitionNode = transitionNode
|
strongSelf.avatarTransitionNode = transitionNode
|
||||||
|
|
||||||
let avatarListContainerNode = ASDisplayNode()
|
let avatarListContainerNode = ASDisplayNode()
|
||||||
@ -503,8 +498,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
avatarListContainerNode.cornerRadius = targetRect.width / 2.0
|
avatarListContainerNode.cornerRadius = targetRect.width / 2.0
|
||||||
|
|
||||||
avatarListWrapperNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
avatarListWrapperNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||||
avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: avatarInitialRect.center), to: NSValue(cgPoint: avatarListWrapperNode.position), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, completion: { [weak self] _ in
|
avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: avatarInitialRect.center), to: NSValue(cgPoint: avatarListWrapperNode.position), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||||
})
|
|
||||||
|
|
||||||
radiusTransition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: 0.0)
|
radiusTransition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: 0.0)
|
||||||
|
|
||||||
@ -576,10 +570,10 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
alphaTransition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0, delay: isExtracted ? 0.0 : 0.1)
|
alphaTransition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0, delay: isExtracted ? 0.0 : 0.1)
|
||||||
|
|
||||||
let offsetInitialSublayerTransform = strongSelf.offsetContainerNode.layer.sublayerTransform
|
let offsetInitialSublayerTransform = strongSelf.offsetContainerNode.layer.sublayerTransform
|
||||||
strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -33 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0)
|
strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -43 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0)
|
||||||
|
|
||||||
let actionInitialSublayerTransform = strongSelf.actionContainerNode.layer.sublayerTransform
|
let actionInitialSublayerTransform = strongSelf.actionContainerNode.layer.sublayerTransform
|
||||||
strongSelf.actionContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? 21.0 : 0.0, 0.0, 0.0)
|
strongSelf.actionContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? 43.0 : 0.0, 0.0, 0.0)
|
||||||
|
|
||||||
let initialBackgroundPosition = strongSelf.backgroundImageNode.position
|
let initialBackgroundPosition = strongSelf.backgroundImageNode.position
|
||||||
strongSelf.backgroundImageNode.layer.position = rect.center
|
strongSelf.backgroundImageNode.layer.position = rect.center
|
||||||
@ -636,7 +630,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
transition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0)
|
transition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||||
|
|
||||||
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? inset : 0.0, y: isExtracted ? extractedVerticalOffset : 0.0))
|
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? inset : 0.0, y: isExtracted ? extractedVerticalOffset : 0.0))
|
||||||
transition.updateSublayerTransformOffset(layer: strongSelf.actionContainerNode.layer, offset: CGPoint(x: isExtracted ? -24.0 : 0.0, y: 0.0))
|
transition.updateSublayerTransformOffset(layer: strongSelf.actionContainerNode.layer, offset: CGPoint(x: isExtracted ? -inset * 2.0 : 0.0, y: 0.0))
|
||||||
|
|
||||||
transition.updateAlpha(node: strongSelf.backgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
transition.updateAlpha(node: strongSelf.backgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
||||||
if !isExtracted {
|
if !isExtracted {
|
||||||
@ -659,11 +653,17 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.layoutParams?.0.action?(self.contextSourceNode)
|
self.layoutParams?.0.action?(self.contextSourceNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateTransitionIn(from sourceNode: ASDisplayNode, containerNode: ASDisplayNode) {
|
func animateTransitionIn(from sourceNode: ASDisplayNode, containerNode: ASDisplayNode, transition: ContainedViewLayoutTransition) {
|
||||||
guard let _ = self.item, let sourceNode = sourceNode as? VoiceChatFullscreenParticipantItemNode, let _ = sourceNode.item else {
|
guard let _ = self.item, let sourceNode = sourceNode as? VoiceChatFullscreenParticipantItemNode, let _ = sourceNode.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var duration: Double = 0.2
|
||||||
|
var timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue
|
||||||
|
if case let .animated(transitionDuration, curve) = transition {
|
||||||
|
duration = transitionDuration
|
||||||
|
timingFunction = curve.timingFunction
|
||||||
|
}
|
||||||
|
|
||||||
let startContainerAvatarPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
let startContainerAvatarPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
||||||
var animate = true
|
var animate = true
|
||||||
if startContainerAvatarPosition.x < -tileSize.width || startContainerAvatarPosition.x > containerNode.frame.width + tileSize.width {
|
if startContainerAvatarPosition.x < -tileSize.width || startContainerAvatarPosition.x > containerNode.frame.width + tileSize.width {
|
||||||
@ -673,13 +673,13 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
sourceNode.avatarNode.alpha = 0.0
|
sourceNode.avatarNode.alpha = 0.0
|
||||||
|
|
||||||
let initialAvatarPosition = self.avatarNode.position
|
let initialAvatarPosition = self.avatarNode.position
|
||||||
let targetContainerAvatarPosition = self.avatarNode.view.convert(self.avatarNode.bounds, to: containerNode.view).center
|
let initialBackgroundPosition = sourceNode.backgroundImageNode.position
|
||||||
|
let initialContentPosition = sourceNode.contentWrapperNode.position
|
||||||
|
|
||||||
let startContainerBackgroundPosition = sourceNode.backgroundImageNode.view.convert(sourceNode.backgroundImageNode.bounds, to: containerNode.view).center
|
let startContainerBackgroundPosition = sourceNode.backgroundImageNode.view.convert(sourceNode.backgroundImageNode.bounds, to: containerNode.view).center
|
||||||
let startContainerContentPosition = sourceNode.contentWrapperNode.view.convert(sourceNode.contentWrapperNode.bounds, to: containerNode.view).center
|
let startContainerContentPosition = sourceNode.contentWrapperNode.view.convert(sourceNode.contentWrapperNode.bounds, to: containerNode.view).center
|
||||||
|
|
||||||
let initialBackgroundPosition = sourceNode.backgroundImageNode.position
|
let targetContainerAvatarPosition = self.avatarNode.view.convert(self.avatarNode.bounds, to: containerNode.view).center
|
||||||
let initialContentPosition = sourceNode.contentWrapperNode.position
|
|
||||||
|
|
||||||
sourceNode.backgroundImageNode.position = targetContainerAvatarPosition
|
sourceNode.backgroundImageNode.position = targetContainerAvatarPosition
|
||||||
sourceNode.contentWrapperNode.position = targetContainerAvatarPosition
|
sourceNode.contentWrapperNode.position = targetContainerAvatarPosition
|
||||||
@ -687,9 +687,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
containerNode.addSubnode(sourceNode.contentWrapperNode)
|
containerNode.addSubnode(sourceNode.contentWrapperNode)
|
||||||
|
|
||||||
sourceNode.borderImageNode.alpha = 0.0
|
sourceNode.borderImageNode.alpha = 0.0
|
||||||
|
|
||||||
let timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue
|
sourceNode.backgroundImageNode.layer.animatePosition(from: startContainerBackgroundPosition, to: targetContainerAvatarPosition, duration: duration, timingFunction: timingFunction, completion: { [weak sourceNode] _ in
|
||||||
sourceNode.backgroundImageNode.layer.animatePosition(from: startContainerBackgroundPosition, to: targetContainerAvatarPosition, duration: 0.2, timingFunction: timingFunction, completion: { [weak sourceNode] _ in
|
|
||||||
if let sourceNode = sourceNode {
|
if let sourceNode = sourceNode {
|
||||||
sourceNode.backgroundImageNode.alpha = 1.0
|
sourceNode.backgroundImageNode.alpha = 1.0
|
||||||
sourceNode.borderImageNode.alpha = 1.0
|
sourceNode.borderImageNode.alpha = 1.0
|
||||||
@ -698,7 +697,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sourceNode.contentWrapperNode.layer.animatePosition(from: startContainerContentPosition, to: targetContainerAvatarPosition, duration: 0.2, timingFunction: timingFunction, completion: { [weak sourceNode] _ in
|
sourceNode.contentWrapperNode.layer.animatePosition(from: startContainerContentPosition, to: targetContainerAvatarPosition, duration: duration, timingFunction: timingFunction, completion: { [weak sourceNode] _ in
|
||||||
if let sourceNode = sourceNode {
|
if let sourceNode = sourceNode {
|
||||||
sourceNode.avatarNode.alpha = 1.0
|
sourceNode.avatarNode.alpha = 1.0
|
||||||
sourceNode.contentWrapperNode.position = initialContentPosition
|
sourceNode.contentWrapperNode.position = initialContentPosition
|
||||||
@ -709,19 +708,18 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.avatarNode.position = targetContainerAvatarPosition
|
self.avatarNode.position = targetContainerAvatarPosition
|
||||||
containerNode.addSubnode(self.avatarNode)
|
containerNode.addSubnode(self.avatarNode)
|
||||||
|
|
||||||
self.avatarNode.layer.animateScale(from: 1.25, to: 1.0, duration: 0.2, timingFunction: timingFunction)
|
self.avatarNode.layer.animateScale(from: 1.25, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
|
self.avatarNode.layer.animatePosition(from: startContainerAvatarPosition, to: targetContainerAvatarPosition, duration: duration, timingFunction: timingFunction, completion: { [weak self] _ in
|
||||||
self.avatarNode.layer.animatePosition(from: startContainerAvatarPosition, to: targetContainerAvatarPosition, duration: 0.2, timingFunction: timingFunction, completion: { [weak self] _ in
|
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.avatarNode.position = initialAvatarPosition
|
strongSelf.avatarNode.position = initialAvatarPosition
|
||||||
strongSelf.offsetContainerNode.addSubnode(strongSelf.avatarNode)
|
strongSelf.offsetContainerNode.addSubnode(strongSelf.avatarNode)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sourceNode.backgroundImageNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.25, timingFunction: timingFunction)
|
sourceNode.backgroundImageNode.layer.animateScale(from: 1.0, to: 0.001, duration: duration, timingFunction: timingFunction)
|
||||||
sourceNode.backgroundImageNode.layer.animateAlpha(from: sourceNode.backgroundImageNode.alpha, to: 0.0, duration: 0.35, timingFunction: timingFunction)
|
sourceNode.backgroundImageNode.layer.animateAlpha(from: sourceNode.backgroundImageNode.alpha, to: 0.0, duration: duration, timingFunction: timingFunction)
|
||||||
sourceNode.contentWrapperNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.25, timingFunction: timingFunction)
|
sourceNode.contentWrapperNode.layer.animateScale(from: 1.0, to: 0.001, duration: duration, timingFunction: timingFunction)
|
||||||
sourceNode.contentWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, timingFunction: timingFunction)
|
sourceNode.contentWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: timingFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -819,7 +817,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let (statusLayout, statusApply) = makeStatusLayout(CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), item.text, false)
|
let (statusLayout, statusApply) = makeStatusLayout(CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), item.text, false)
|
||||||
let (expandedStatusLayout, expandedStatusApply) = makeExpandedStatusLayout(CGSize(width: params.width - leftInset - 8.0 - rightInset - expandedRightInset, height: CGFloat.greatestFiniteMagnitude), item.expandedText ?? item.text, false)
|
let (expandedStatusLayout, expandedStatusApply) = makeExpandedStatusLayout(CGSize(width: params.width - leftInset - 8.0 - rightInset - expandedRightInset, height: CGFloat.greatestFiniteMagnitude), item.expandedText ?? item.text, true)
|
||||||
|
|
||||||
let titleSpacing: CGFloat = statusLayout.height == 0.0 ? 0.0 : 1.0
|
let titleSpacing: CGFloat = statusLayout.height == 0.0 ? 0.0 : 1.0
|
||||||
|
|
||||||
@ -861,7 +859,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
animationFrame = CGRect(x: params.width - animationSize.width - 6.0 - params.rightInset, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height)
|
animationFrame = CGRect(x: params.width - animationSize.width - 6.0 - params.rightInset, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height)
|
||||||
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + verticalOffset), size: titleLayout.size)
|
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + verticalOffset), size: titleLayout.size)
|
||||||
|
|
||||||
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
|
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: params.leftInset, dy: 0.0)
|
||||||
var extractedHeight = extractedRect.height + expandedStatusLayout.height - statusLayout.height
|
var extractedHeight = extractedRect.height + expandedStatusLayout.height - statusLayout.height
|
||||||
var extractedVerticalOffset: CGFloat = 0.0
|
var extractedVerticalOffset: CGFloat = 0.0
|
||||||
if item.peer.smallProfileImage != nil {
|
if item.peer.smallProfileImage != nil {
|
||||||
@ -996,7 +994,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if false, strongSelf.audioLevelView == nil, value > 0.0 {
|
if strongSelf.audioLevelView == nil, value > 0.0 {
|
||||||
let audioLevelView = VoiceBlobView(
|
let audioLevelView = VoiceBlobView(
|
||||||
frame: blobFrame,
|
frame: blobFrame,
|
||||||
maxLevel: 1.5,
|
maxLevel: 1.5,
|
||||||
@ -1034,7 +1032,6 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
audioLevelView.setColor(wavesColor, animated: true)
|
audioLevelView.setColor(wavesColor, animated: true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
audioLevelView.stopAnimating(duration: 0.5)
|
|
||||||
avatarScale = 1.0
|
avatarScale = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,341 @@
|
|||||||
//
|
|
||||||
// VoiceChatPeerProfileNode.swift
|
|
||||||
// _idx_TelegramCallsUI_5BDA0798_ios_min9.0
|
|
||||||
//
|
|
||||||
// Created by Ilya Laktyushin on 11.05.2021.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import SyncCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import TelegramUIPreferences
|
||||||
|
import PresentationDataUtils
|
||||||
|
import AvatarNode
|
||||||
|
import TelegramStringFormatting
|
||||||
|
import ContextUI
|
||||||
|
import AccountContext
|
||||||
|
import LegacyComponents
|
||||||
|
import PeerInfoAvatarListNode
|
||||||
|
|
||||||
|
private let backgroundCornerRadius: CGFloat = 14.0
|
||||||
|
|
||||||
|
final class VoiceChatPeerProfileNode: ASDisplayNode {
|
||||||
|
private let context: AccountContext
|
||||||
|
private let size: CGSize
|
||||||
|
private var peer: Peer
|
||||||
|
private var text: VoiceChatParticipantItem.ParticipantText
|
||||||
|
private let customNode: ASDisplayNode?
|
||||||
|
private let additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>
|
||||||
|
|
||||||
|
private let backgroundImageNode: ASImageNode
|
||||||
|
private let avatarListContainerNode: ASDisplayNode
|
||||||
|
let avatarListWrapperNode: PinchSourceContainerNode
|
||||||
|
let avatarListNode: PeerInfoAvatarListContainerNode
|
||||||
|
private var videoFadeNode: ASImageNode
|
||||||
|
private let infoNode: ASDisplayNode
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
private let statusNode: VoiceChatParticipantStatusNode
|
||||||
|
|
||||||
|
private var videoNode: GroupVideoNode?
|
||||||
|
|
||||||
|
private var appeared = false
|
||||||
|
|
||||||
|
init(context: AccountContext, size: CGSize, peer: Peer, text: VoiceChatParticipantItem.ParticipantText, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>, requestDismiss: (() -> Void)?) {
|
||||||
|
self.context = context
|
||||||
|
self.size = size
|
||||||
|
self.peer = peer
|
||||||
|
self.text = text
|
||||||
|
self.customNode = customNode
|
||||||
|
self.additionalEntry = additionalEntry
|
||||||
|
|
||||||
|
self.backgroundImageNode = ASImageNode()
|
||||||
|
self.backgroundImageNode.clipsToBounds = true
|
||||||
|
self.backgroundImageNode.displaysAsynchronously = false
|
||||||
|
self.backgroundImageNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
|
self.videoFadeNode = ASImageNode()
|
||||||
|
self.videoFadeNode.displaysAsynchronously = false
|
||||||
|
self.videoFadeNode.contentMode = .scaleToFill
|
||||||
|
|
||||||
|
self.avatarListContainerNode = ASDisplayNode()
|
||||||
|
self.avatarListContainerNode.clipsToBounds = true
|
||||||
|
|
||||||
|
self.avatarListWrapperNode = PinchSourceContainerNode()
|
||||||
|
self.avatarListWrapperNode.clipsToBounds = true
|
||||||
|
self.avatarListWrapperNode.cornerRadius = backgroundCornerRadius
|
||||||
|
|
||||||
|
self.avatarListNode = PeerInfoAvatarListContainerNode(context: context)
|
||||||
|
self.avatarListNode.backgroundColor = .clear
|
||||||
|
self.avatarListNode.peer = peer
|
||||||
|
self.avatarListNode.firstFullSizeOnly = true
|
||||||
|
self.avatarListNode.offsetLocation = true
|
||||||
|
self.avatarListNode.customCenterTapAction = {
|
||||||
|
requestDismiss?()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.infoNode = ASDisplayNode()
|
||||||
|
|
||||||
|
self.titleNode = ImmediateTextNode()
|
||||||
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
self.titleNode.contentMode = .left
|
||||||
|
self.titleNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
|
self.statusNode = VoiceChatParticipantStatusNode()
|
||||||
|
self.statusNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundImageNode)
|
||||||
|
self.addSubnode(self.infoNode)
|
||||||
|
self.addSubnode(self.videoFadeNode)
|
||||||
|
self.addSubnode(self.avatarListWrapperNode)
|
||||||
|
self.infoNode.addSubnode(self.titleNode)
|
||||||
|
self.infoNode.addSubnode(self.statusNode)
|
||||||
|
|
||||||
|
self.avatarListContainerNode.addSubnode(self.avatarListNode)
|
||||||
|
self.avatarListContainerNode.addSubnode(self.avatarListNode.controlsClippingOffsetNode)
|
||||||
|
self.avatarListWrapperNode.contentNode.addSubnode(self.avatarListContainerNode)
|
||||||
|
|
||||||
|
self.avatarListWrapperNode.activate = { [weak self] sourceNode in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.avatarListNode.controlsContainerNode.alpha = 0.0
|
||||||
|
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
|
||||||
|
return UIScreen.main.bounds
|
||||||
|
})
|
||||||
|
context.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController)
|
||||||
|
}
|
||||||
|
self.avatarListWrapperNode.deactivated = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.avatarListWrapperNode.contentNode.layer.animate(from: 0.0 as NSNumber, to: backgroundCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3, completion: { _ in
|
||||||
|
})
|
||||||
|
}
|
||||||
|
self.avatarListWrapperNode.animatedOut = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.avatarListNode.controlsContainerNode.alpha = 1.0
|
||||||
|
strongSelf.avatarListNode.controlsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateInfo(size: size, animate: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateInfo(size: CGSize, animate: Bool) {
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let titleFont = Font.regular(17.0)
|
||||||
|
let titleColor = UIColor.white
|
||||||
|
var titleAttributedString: NSAttributedString?
|
||||||
|
if let user = self.peer as? TelegramUser {
|
||||||
|
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
|
||||||
|
let string = NSMutableAttributedString()
|
||||||
|
switch presentationData.nameDisplayOrder {
|
||||||
|
case .firstLast:
|
||||||
|
string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
|
||||||
|
string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
|
||||||
|
string.append(NSAttributedString(string: lastName, font: titleFont, textColor: titleColor))
|
||||||
|
case .lastFirst:
|
||||||
|
string.append(NSAttributedString(string: lastName, font: titleFont, textColor: titleColor))
|
||||||
|
string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
|
||||||
|
string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
|
||||||
|
}
|
||||||
|
titleAttributedString = string
|
||||||
|
} else if let firstName = user.firstName, !firstName.isEmpty {
|
||||||
|
titleAttributedString = NSAttributedString(string: firstName, font: titleFont, textColor: titleColor)
|
||||||
|
} else if let lastName = user.lastName, !lastName.isEmpty {
|
||||||
|
titleAttributedString = NSAttributedString(string: lastName, font: titleFont, textColor: titleColor)
|
||||||
|
} else {
|
||||||
|
titleAttributedString = NSAttributedString(string: presentationData.strings.User_DeletedAccount, font: titleFont, textColor: titleColor)
|
||||||
|
}
|
||||||
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
titleAttributedString = NSAttributedString(string: group.title, font: titleFont, textColor: titleColor)
|
||||||
|
} else if let channel = peer as? TelegramChannel {
|
||||||
|
titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor)
|
||||||
|
}
|
||||||
|
self.titleNode.attributedText = titleAttributedString
|
||||||
|
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: self.size.width - 24.0, height: size.height))
|
||||||
|
|
||||||
|
let makeStatusLayout = self.statusNode.asyncLayout()
|
||||||
|
let (statusLayout, statusApply) = makeStatusLayout(CGSize(width: self.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude), self.text, true)
|
||||||
|
let _ = statusApply()
|
||||||
|
|
||||||
|
self.titleNode.frame = CGRect(origin: CGPoint(x: 14.0, y: 0.0), size: titleSize)
|
||||||
|
self.statusNode.frame = CGRect(origin: CGPoint(x: 14.0, y: titleSize.height + 3.0), size: statusLayout)
|
||||||
|
|
||||||
|
let totalHeight = titleSize.height + statusLayout.height + 3.0 + 8.0
|
||||||
|
let infoFrame = CGRect(x: 0.0, y: size.height - totalHeight, width: self.size.width, height: totalHeight)
|
||||||
|
|
||||||
|
if animate {
|
||||||
|
let springDuration: Double = !self.appeared ? 0.42 : 0.3
|
||||||
|
let springDamping: CGFloat = !self.appeared ? 104.0 : 1000.0
|
||||||
|
|
||||||
|
let initialInfoPosition = self.infoNode.position
|
||||||
|
self.infoNode.layer.position = infoFrame.center
|
||||||
|
let initialInfoBounds = self.infoNode.bounds
|
||||||
|
self.infoNode.layer.bounds = CGRect(origin: CGPoint(), size: infoFrame.size)
|
||||||
|
|
||||||
|
self.infoNode.layer.animateSpring(from: NSValue(cgPoint: initialInfoPosition), to: NSValue(cgPoint: self.infoNode.position), keyPath: "position", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping)
|
||||||
|
self.infoNode.layer.animateSpring(from: NSValue(cgRect: initialInfoBounds), to: NSValue(cgRect: self.infoNode.bounds), keyPath: "bounds", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||||
|
} else {
|
||||||
|
self.infoNode.frame = infoFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn(from sourceNode: ASDisplayNode, targetRect: CGRect, transition: ContainedViewLayoutTransition) {
|
||||||
|
let radiusTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||||
|
let springDuration: Double = 0.42
|
||||||
|
let springDamping: CGFloat = 104.0
|
||||||
|
|
||||||
|
if let sourceNode = sourceNode as? VoiceChatTileItemNode {
|
||||||
|
let sourceRect = sourceNode.bounds
|
||||||
|
self.backgroundImageNode.frame = sourceNode.bounds
|
||||||
|
self.updateInfo(size: sourceNode.bounds.size, animate: false)
|
||||||
|
self.updateInfo(size: targetRect.size, animate: true)
|
||||||
|
|
||||||
|
self.backgroundImageNode.image = generateImage(CGSize(width: backgroundCornerRadius * 2.0, height: backgroundCornerRadius * 2.0), rotatedContext: { (size, context) in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
|
||||||
|
context.setFillColor(UIColor(rgb: 0x1c1c1e).cgColor)
|
||||||
|
context.fillEllipse(in: bounds)
|
||||||
|
context.fill(CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height / 2.0))
|
||||||
|
})?.stretchableImage(withLeftCapWidth: Int(backgroundCornerRadius), topCapHeight: Int(backgroundCornerRadius))
|
||||||
|
self.backgroundImageNode.cornerRadius = backgroundCornerRadius
|
||||||
|
|
||||||
|
transition.updateCornerRadius(node: self.backgroundImageNode, cornerRadius: 0.0)
|
||||||
|
|
||||||
|
let initialRect = sourceNode.frame
|
||||||
|
let initialScale: CGFloat = sourceRect.width / targetRect.width
|
||||||
|
|
||||||
|
let targetSize = CGSize(width: targetRect.size.width, height: targetRect.size.width)
|
||||||
|
self.avatarListWrapperNode.update(size: targetSize, transition: .immediate)
|
||||||
|
self.avatarListWrapperNode.frame = CGRect(x: targetRect.minX, y: targetRect.minY, width: targetRect.width, height: targetRect.width + backgroundCornerRadius)
|
||||||
|
|
||||||
|
self.avatarListContainerNode.frame = CGRect(origin: CGPoint(), size: targetSize)
|
||||||
|
self.avatarListContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
self.avatarListContainerNode.cornerRadius = targetRect.width / 2.0
|
||||||
|
|
||||||
|
if let videoNode = sourceNode.videoNode {
|
||||||
|
videoNode.updateLayout(size: targetSize, isLandscape: true, transition: transition)
|
||||||
|
transition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: targetSize))
|
||||||
|
transition.updateFrame(node: sourceNode.videoContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: targetSize.width, height: targetSize.height + backgroundCornerRadius)))
|
||||||
|
sourceNode.videoContainerNode.cornerRadius = backgroundCornerRadius
|
||||||
|
}
|
||||||
|
self.insertSubnode(sourceNode.videoContainerNode, belowSubnode: self.avatarListWrapperNode)
|
||||||
|
|
||||||
|
if let snapshotView = sourceNode.infoNode.view.snapshotView(afterScreenUpdates: false) {
|
||||||
|
self.videoFadeNode.image = sourceNode.fadeNode.image
|
||||||
|
self.videoFadeNode.frame = CGRect(x: 0.0, y: sourceRect.height - sourceNode.fadeNode.frame.height, width: sourceRect.width, height: sourceNode.fadeNode.frame.height)
|
||||||
|
|
||||||
|
self.insertSubnode(self.videoFadeNode, aboveSubnode: sourceNode.videoContainerNode)
|
||||||
|
self.view.insertSubview(snapshotView, aboveSubview: sourceNode.videoContainerNode.view)
|
||||||
|
snapshotView.frame = sourceRect
|
||||||
|
transition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - snapshotView.frame.size.height), size: snapshotView.frame.size))
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
snapshotView.removeFromSuperview()
|
||||||
|
})
|
||||||
|
transition.updateFrame(node: self.videoFadeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - self.videoFadeNode.frame.size.height), size: CGSize(width: targetSize.width, height: self.videoFadeNode.frame.height)))
|
||||||
|
self.videoFadeNode.alpha = 0.0
|
||||||
|
self.videoFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.avatarListWrapperNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||||
|
self.avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: initialRect.center), to: NSValue(cgPoint: self.avatarListWrapperNode.position), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, completion: { [weak self] _ in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.avatarListNode.currentItemNode?.addSubnode(sourceNode.videoContainerNode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
radiusTransition.updateCornerRadius(node: self.avatarListContainerNode, cornerRadius: 0.0)
|
||||||
|
|
||||||
|
self.avatarListWrapperNode.contentNode.clipsToBounds = true
|
||||||
|
|
||||||
|
self.avatarListNode.frame = CGRect(x: targetRect.width / 2.0, y: targetRect.width / 2.0, width: targetRect.width, height: targetRect.width)
|
||||||
|
self.avatarListNode.controlsClippingNode.frame = CGRect(x: -targetRect.width / 2.0, y: -targetRect.width / 2.0, width: targetRect.width, height: targetRect.width)
|
||||||
|
self.avatarListNode.controlsClippingOffsetNode.frame = CGRect(origin: CGPoint(x: targetRect.width / 2.0, y: targetRect.width / 2.0), size: CGSize())
|
||||||
|
self.avatarListNode.stripContainerNode.frame = CGRect(x: 0.0, y: 13.0, width: targetRect.width, height: 2.0)
|
||||||
|
|
||||||
|
self.avatarListNode.update(size: targetSize, peer: self.peer, customNode: self.customNode, additionalEntry: self.additionalEntry, isExpanded: true, transition: .immediate)
|
||||||
|
|
||||||
|
let backgroundTargetRect = CGRect(x: 0.0, y: targetSize.height - backgroundCornerRadius * 2.0, width: targetRect.width, height: targetRect.height - targetSize.height + backgroundCornerRadius * 2.0)
|
||||||
|
let initialBackgroundPosition = self.backgroundImageNode.position
|
||||||
|
self.backgroundImageNode.layer.position = backgroundTargetRect.center
|
||||||
|
let initialBackgroundBounds = self.backgroundImageNode.bounds
|
||||||
|
self.backgroundImageNode.layer.bounds = CGRect(origin: CGPoint(), size: backgroundTargetRect.size)
|
||||||
|
|
||||||
|
self.backgroundImageNode.layer.animateSpring(from: NSValue(cgPoint: initialBackgroundPosition), to: NSValue(cgPoint: self.backgroundImageNode.position), keyPath: "position", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping)
|
||||||
|
self.backgroundImageNode.layer.animateSpring(from: NSValue(cgRect: initialBackgroundBounds), to: NSValue(cgRect: self.backgroundImageNode.bounds), keyPath: "bounds", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||||
|
} else if let sourceNode = sourceNode as? VoiceChatFullscreenParticipantItemNode {
|
||||||
|
|
||||||
|
}
|
||||||
|
self.appeared = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(to targetNode: ASDisplayNode, targetRect: CGRect, transition: ContainedViewLayoutTransition) {
|
||||||
|
let radiusTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||||
|
let springDuration: Double = 0.3
|
||||||
|
let springDamping: CGFloat = 1000.0
|
||||||
|
if let targetNode = targetNode as? VoiceChatTileItemNode {
|
||||||
|
let initialSize = self.bounds
|
||||||
|
self.updateInfo(size: targetRect.size, animate: true)
|
||||||
|
|
||||||
|
transition.updateCornerRadius(node: self.backgroundImageNode, cornerRadius: backgroundCornerRadius)
|
||||||
|
|
||||||
|
let targetScale = targetRect.width / avatarListContainerNode.frame.width
|
||||||
|
|
||||||
|
self.insertSubnode(targetNode.videoContainerNode, belowSubnode: self.avatarListWrapperNode)
|
||||||
|
self.insertSubnode(self.videoFadeNode, aboveSubnode: targetNode.videoContainerNode)
|
||||||
|
self.avatarListWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
|
||||||
|
self.avatarListWrapperNode.layer.animate(from: 1.0 as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
|
||||||
|
self.avatarListWrapperNode.layer.animate(from: NSValue(cgPoint: self.avatarListWrapperNode.position), to: NSValue(cgPoint: targetRect.center), keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak self, weak targetNode] _ in
|
||||||
|
if let targetNode = targetNode {
|
||||||
|
targetNode.contentNode.insertSubnode(targetNode.videoContainerNode, aboveSubnode: targetNode.backgroundNode)
|
||||||
|
}
|
||||||
|
self?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
|
||||||
|
radiusTransition.updateCornerRadius(node: self.avatarListContainerNode, cornerRadius: backgroundCornerRadius)
|
||||||
|
|
||||||
|
if let snapshotView = targetNode.infoNode.view.snapshotView(afterScreenUpdates: false) {
|
||||||
|
self.view.insertSubview(snapshotView, aboveSubview: targetNode.videoContainerNode.view)
|
||||||
|
let snapshotFrame = snapshotView.frame
|
||||||
|
snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: initialSize.width - snapshotView.frame.size.height), size: snapshotView.frame.size)
|
||||||
|
transition.updateFrame(view: snapshotView, frame: snapshotFrame)
|
||||||
|
snapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
transition.updateFrame(node: self.videoFadeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetRect.height - self.videoFadeNode.frame.size.height), size: CGSize(width: targetRect.width, height: self.videoFadeNode.frame.height)))
|
||||||
|
self.videoFadeNode.alpha = 1.0
|
||||||
|
self.videoFadeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let videoNode = targetNode.videoNode {
|
||||||
|
videoNode.updateLayout(size: targetRect.size, isLandscape: true, transition: transition)
|
||||||
|
transition.updateFrame(node: videoNode, frame: targetRect)
|
||||||
|
transition.updateFrame(node: targetNode.videoContainerNode, frame: targetRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundTargetRect = targetRect
|
||||||
|
let initialBackgroundPosition = self.backgroundImageNode.position
|
||||||
|
self.backgroundImageNode.layer.position = backgroundTargetRect.center
|
||||||
|
let initialBackgroundBounds = self.backgroundImageNode.bounds
|
||||||
|
self.backgroundImageNode.layer.bounds = CGRect(origin: CGPoint(), size: backgroundTargetRect.size)
|
||||||
|
|
||||||
|
self.backgroundImageNode.layer.animateSpring(from: NSValue(cgPoint: initialBackgroundPosition), to: NSValue(cgPoint: self.backgroundImageNode.position), keyPath: "position", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping)
|
||||||
|
self.backgroundImageNode.layer.animateSpring(from: NSValue(cgRect: initialBackgroundBounds), to: NSValue(cgRect: self.backgroundImageNode.bounds), keyPath: "bounds", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||||
|
|
||||||
|
self.avatarListNode.stripContainerNode.alpha = 0.0
|
||||||
|
self.avatarListNode.stripContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||||
|
|
||||||
|
self.infoNode.alpha = 0.0
|
||||||
|
self.infoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||||
|
} else if let targetNode = targetNode as? VoiceChatFullscreenParticipantItemNode {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -48,12 +48,12 @@ final class VoiceChatTileGridNode: ASDisplayNode {
|
|||||||
var wasAdded = false
|
var wasAdded = false
|
||||||
if let current = self.itemNodes[item.id] {
|
if let current = self.itemNodes[item.id] {
|
||||||
itemNode = current
|
itemNode = current
|
||||||
current.update(size: itemSize, item: item, transition: transition)
|
current.update(size: itemSize, availableWidth: size.width, item: item, transition: transition)
|
||||||
} else {
|
} else {
|
||||||
wasAdded = true
|
wasAdded = true
|
||||||
let addedItemNode = VoiceChatTileItemNode(context: self.context)
|
let addedItemNode = VoiceChatTileItemNode(context: self.context)
|
||||||
itemNode = addedItemNode
|
itemNode = addedItemNode
|
||||||
addedItemNode.update(size: itemSize, item: item, transition: .immediate)
|
addedItemNode.update(size: itemSize, availableWidth: size.width, item: item, transition: .immediate)
|
||||||
self.itemNodes[self.items[i].id] = addedItemNode
|
self.itemNodes[self.items[i].id] = addedItemNode
|
||||||
self.addSubnode(addedItemNode)
|
self.addSubnode(addedItemNode)
|
||||||
}
|
}
|
||||||
@ -197,15 +197,12 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode {
|
|||||||
strongSelf.tileGridNode = tileGridNode
|
strongSelf.tileGridNode = tileGridNode
|
||||||
}
|
}
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = currentItem == nil ? .immediate : .animated(duration: 0.3, curve: .spring)
|
let transition: ContainedViewLayoutTransition = currentItem == nil ? .immediate : .animated(duration: 0.4, curve: .spring)
|
||||||
let tileGridSize = tileGridNode.update(size: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), items: item.tiles, transition: transition)
|
let tileGridSize = tileGridNode.update(size: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), items: item.tiles, transition: transition)
|
||||||
if currentItem == nil {
|
if currentItem == nil {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||||
tileGridNode.frame = CGRect(x: params.leftInset, y: 0.0, width: tileGridSize.width, height: 0.0)
|
tileGridNode.frame = CGRect(x: params.leftInset, y: 0.0, width: tileGridSize.width, height: 0.0)
|
||||||
// transition.updateFrame(node: tileGridNode, frame: CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: tileGridSize))
|
|
||||||
|
|
||||||
strongSelf.backgroundNode.frame = tileGridNode.frame
|
strongSelf.backgroundNode.frame = tileGridNode.frame
|
||||||
// transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: tileGridSize))
|
|
||||||
} else {
|
} else {
|
||||||
transition.updateFrame(node: tileGridNode, frame: CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: tileGridSize))
|
transition.updateFrame(node: tileGridNode, frame: CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: tileGridSize))
|
||||||
transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: tileGridSize))
|
transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: tileGridSize))
|
||||||
|
@ -35,11 +35,13 @@ final class VoiceChatTileItem: Equatable {
|
|||||||
|
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
let videoEndpointId: String
|
let videoEndpointId: String
|
||||||
let icon: Icon
|
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let nameDisplayOrder: PresentationPersonNameOrder
|
let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
|
let icon: Icon
|
||||||
|
let text: VoiceChatParticipantItem.ParticipantText
|
||||||
let speaking: Bool
|
let speaking: Bool
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
let getVideo: () -> GroupVideoNode?
|
let getVideo: () -> GroupVideoNode?
|
||||||
let getAudioLevel: (() -> Signal<Float, NoError>)?
|
let getAudioLevel: (() -> Signal<Float, NoError>)?
|
||||||
|
|
||||||
@ -47,14 +49,16 @@ final class VoiceChatTileItem: Equatable {
|
|||||||
return self.videoEndpointId
|
return self.videoEndpointId
|
||||||
}
|
}
|
||||||
|
|
||||||
init(peer: Peer, videoEndpointId: String, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, icon: Icon, action: @escaping () -> Void, getVideo: @escaping () -> GroupVideoNode?, getAudioLevel: (() -> Signal<Float, NoError>)?) {
|
init(peer: Peer, videoEndpointId: String, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping () -> GroupVideoNode?, getAudioLevel: (() -> Signal<Float, NoError>)?) {
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.videoEndpointId = videoEndpointId
|
self.videoEndpointId = videoEndpointId
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
self.text = text
|
||||||
self.speaking = speaking
|
self.speaking = speaking
|
||||||
self.action = action
|
self.action = action
|
||||||
|
self.contextAction = contextAction
|
||||||
self.getVideo = getVideo
|
self.getVideo = getVideo
|
||||||
self.getAudioLevel = getAudioLevel
|
self.getAudioLevel = getAudioLevel
|
||||||
}
|
}
|
||||||
@ -93,16 +97,26 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
let contextSourceNode: ContextExtractedContentContainingNode
|
let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
private let containerNode: ContextControllerSourceNode
|
private let containerNode: ContextControllerSourceNode
|
||||||
private let backgroundNode: ASDisplayNode
|
let contentNode: ASDisplayNode
|
||||||
|
let backgroundNode: ASDisplayNode
|
||||||
|
var videoContainerNode: ASDisplayNode
|
||||||
var videoNode: GroupVideoNode?
|
var videoNode: GroupVideoNode?
|
||||||
private let fadeNode: ASImageNode
|
let infoNode: ASDisplayNode
|
||||||
|
let fadeNode: ASImageNode
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private var animationNode: VoiceChatMicrophoneNode?
|
private var animationNode: VoiceChatMicrophoneNode?
|
||||||
private var highlightNode: ASImageNode
|
private var highlightNode: ASImageNode
|
||||||
|
private let statusNode: VoiceChatParticipantStatusNode
|
||||||
|
|
||||||
private var validLayout: CGSize?
|
private var profileNode: VoiceChatPeerProfileNode?
|
||||||
|
private var extractedRect: CGRect?
|
||||||
|
private var nonExtractedRect: CGRect?
|
||||||
|
private var extractedVerticalOffset: CGFloat?
|
||||||
|
|
||||||
|
private var validLayout: (CGSize, CGFloat)?
|
||||||
var item: VoiceChatTileItem?
|
var item: VoiceChatTileItem?
|
||||||
|
private var isExtracted = false
|
||||||
|
|
||||||
private let audioLevelDisposable = MetaDisposable()
|
private let audioLevelDisposable = MetaDisposable()
|
||||||
|
|
||||||
@ -111,10 +125,19 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
|
|
||||||
|
self.contentNode = ASDisplayNode()
|
||||||
|
self.contentNode.clipsToBounds = true
|
||||||
|
self.contentNode.cornerRadius = 11.0
|
||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.backgroundColor = panelBackgroundColor
|
self.backgroundNode.backgroundColor = panelBackgroundColor
|
||||||
|
|
||||||
|
self.videoContainerNode = ASDisplayNode()
|
||||||
|
self.videoContainerNode.clipsToBounds = true
|
||||||
|
|
||||||
|
self.infoNode = ASDisplayNode()
|
||||||
|
|
||||||
self.fadeNode = ASImageNode()
|
self.fadeNode = ASImageNode()
|
||||||
self.fadeNode.displaysAsynchronously = false
|
self.fadeNode.displaysAsynchronously = false
|
||||||
self.fadeNode.displayWithoutProcessing = true
|
self.fadeNode.displayWithoutProcessing = true
|
||||||
@ -122,6 +145,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
self.fadeNode.image = fadeImage
|
self.fadeNode.image = fadeImage
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
|
self.statusNode = VoiceChatParticipantStatusNode()
|
||||||
|
|
||||||
self.iconNode = ASImageNode()
|
self.iconNode = ASImageNode()
|
||||||
self.iconNode.displaysAsynchronously = false
|
self.iconNode.displaysAsynchronously = false
|
||||||
@ -136,18 +160,38 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.clipsToBounds = true
|
self.clipsToBounds = true
|
||||||
|
|
||||||
self.contextSourceNode.contentNode.clipsToBounds = true
|
|
||||||
self.contextSourceNode.contentNode.cornerRadius = 11.0
|
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.contextSourceNode)
|
self.containerNode.addSubnode(self.contextSourceNode)
|
||||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
|
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.backgroundNode)
|
self.contextSourceNode.contentNode.addSubnode(self.contentNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.fadeNode)
|
self.contentNode.addSubnode(self.backgroundNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.titleNode)
|
self.contentNode.addSubnode(self.videoContainerNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.iconNode)
|
self.contentNode.addSubnode(self.fadeNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.highlightNode)
|
self.contentNode.addSubnode(self.infoNode)
|
||||||
|
self.infoNode.addSubnode(self.titleNode)
|
||||||
|
self.infoNode.addSubnode(self.iconNode)
|
||||||
|
self.contentNode.addSubnode(self.highlightNode)
|
||||||
|
|
||||||
|
self.containerNode.shouldBegin = { [weak self] location in
|
||||||
|
guard let _ = self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
self.containerNode.activated = { [weak self] gesture, _ in
|
||||||
|
guard let strongSelf = self, let item = strongSelf.item, let contextAction = item.contextAction else {
|
||||||
|
gesture.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contextAction(strongSelf.contextSourceNode, gesture)
|
||||||
|
}
|
||||||
|
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
|
||||||
|
guard let strongSelf = self, let _ = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.updateIsExtracted(isExtracted, transition: transition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -164,10 +208,44 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
self.item?.action()
|
self.item?.action()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, item: VoiceChatTileItem, transition: ContainedViewLayoutTransition) {
|
private func updateIsExtracted(_ isExtracted: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
guard self.validLayout != size || self.item != item else {
|
guard self.isExtracted != isExtracted, let extractedRect = self.extractedRect, let nonExtractedRect = self.nonExtractedRect, let item = self.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.isExtracted = isExtracted
|
||||||
|
|
||||||
|
if isExtracted {
|
||||||
|
let profileNode = VoiceChatPeerProfileNode(context: self.context, size: extractedRect.size, peer: item.peer, text: item.text, customNode: self.videoContainerNode, additionalEntry: .single(nil), requestDismiss: { [weak self] in
|
||||||
|
self?.contextSourceNode.requestDismiss?()
|
||||||
|
})
|
||||||
|
profileNode.frame = CGRect(origin: CGPoint(), size: extractedRect.size)
|
||||||
|
self.profileNode = profileNode
|
||||||
|
self.contextSourceNode.contentNode.addSubnode(profileNode)
|
||||||
|
|
||||||
|
profileNode.animateIn(from: self, targetRect: extractedRect, transition: transition)
|
||||||
|
|
||||||
|
self.contextSourceNode.contentNode.customHitTest = { [weak self] point in
|
||||||
|
if let strongSelf = self, let profileNode = strongSelf.profileNode {
|
||||||
|
if profileNode.avatarListWrapperNode.frame.contains(point) {
|
||||||
|
return profileNode.avatarListNode.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if let profileNode = self.profileNode {
|
||||||
|
self.profileNode = nil
|
||||||
|
profileNode.animateOut(to: self, targetRect: nonExtractedRect, transition: transition)
|
||||||
|
|
||||||
|
self.contextSourceNode.contentNode.customHitTest = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, availableWidth: CGFloat, item: VoiceChatTileItem, transition: ContainedViewLayoutTransition) {
|
||||||
|
guard self.validLayout?.0 != size || self.validLayout?.1 != availableWidth || self.item != item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.validLayout = (size, availableWidth)
|
||||||
|
|
||||||
var itemTransition = transition
|
var itemTransition = transition
|
||||||
if self.item != item {
|
if self.item != item {
|
||||||
@ -206,7 +284,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
if let videoNode = item.getVideo() {
|
if let videoNode = item.getVideo() {
|
||||||
itemTransition = .immediate
|
itemTransition = .immediate
|
||||||
self.videoNode = videoNode
|
self.videoNode = videoNode
|
||||||
self.contextSourceNode.contentNode.insertSubnode(videoNode, at: 1)
|
self.videoContainerNode.addSubnode(videoNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,7 +326,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
animationNode = VoiceChatMicrophoneNode()
|
animationNode = VoiceChatMicrophoneNode()
|
||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
self.contextSourceNode.contentNode.addSubnode(animationNode)
|
self.infoNode.addSubnode(animationNode)
|
||||||
}
|
}
|
||||||
animationNode.alpha = 1.0
|
animationNode.alpha = 1.0
|
||||||
animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: UIColor.white), animated: true)
|
animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: UIColor.white), animated: true)
|
||||||
@ -259,34 +337,56 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.contentNode.frame = bounds
|
||||||
self.containerNode.frame = bounds
|
self.containerNode.frame = bounds
|
||||||
self.contextSourceNode.frame = bounds
|
self.contextSourceNode.frame = bounds
|
||||||
self.contextSourceNode.contentNode.frame = bounds
|
self.contextSourceNode.contentNode.frame = bounds
|
||||||
|
|
||||||
if let videoNode = self.videoNode {
|
let extractedWidth = availableWidth
|
||||||
transition.updateFrame(node: videoNode, frame: bounds)
|
let makeStatusLayout = self.statusNode.asyncLayout()
|
||||||
videoNode.updateLayout(size: size, isLandscape: true, transition: itemTransition)
|
let (statusLayout, _) = makeStatusLayout(CGSize(width: availableWidth - 30.0, height: CGFloat.greatestFiniteMagnitude), item.text, true)
|
||||||
|
|
||||||
|
let extractedRect = CGRect(x: 0.0, y: 0.0, width: extractedWidth, height: extractedWidth + statusLayout.height + 39.0)
|
||||||
|
let nonExtractedRect = bounds
|
||||||
|
self.extractedRect = extractedRect
|
||||||
|
self.nonExtractedRect = nonExtractedRect
|
||||||
|
|
||||||
|
self.contextSourceNode.contentRect = extractedRect
|
||||||
|
|
||||||
|
if self.videoContainerNode.supernode === self.contentNode {
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
transition.updateFrame(node: videoNode, frame: bounds)
|
||||||
|
videoNode.updateLayout(size: size, isLandscape: true, transition: itemTransition)
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.videoContainerNode, frame: bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: bounds)
|
transition.updateFrame(node: self.backgroundNode, frame: bounds)
|
||||||
transition.updateFrame(node: self.highlightNode, frame: bounds)
|
transition.updateFrame(node: self.highlightNode, frame: bounds)
|
||||||
|
transition.updateFrame(node: self.infoNode, frame: bounds)
|
||||||
transition.updateFrame(node: self.fadeNode, frame: CGRect(x: 0.0, y: size.height - fadeHeight, width: size.width, height: fadeHeight))
|
transition.updateFrame(node: self.fadeNode, frame: CGRect(x: 0.0, y: size.height - fadeHeight, width: size.width, height: fadeHeight))
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 50.0, height: size.height))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 50.0, height: size.height))
|
||||||
self.titleNode.frame = CGRect(origin: CGPoint(x: 11.0, y: size.height - titleSize.height - 8.0), size: titleSize)
|
self.titleNode.frame = CGRect(origin: CGPoint(x: 30.0, y: size.height - titleSize.height - 8.0), size: titleSize)
|
||||||
|
|
||||||
if let animationNode = self.animationNode {
|
if let animationNode = self.animationNode {
|
||||||
let animationSize = CGSize(width: 36.0, height: 36.0)
|
let animationSize = CGSize(width: 36.0, height: 36.0)
|
||||||
animationNode.bounds = CGRect(origin: CGPoint(), size: animationSize)
|
animationNode.bounds = CGRect(origin: CGPoint(), size: animationSize)
|
||||||
animationNode.transform = CATransform3DMakeScale(0.66667, 0.66667, 1.0)
|
animationNode.transform = CATransform3DMakeScale(0.66667, 0.66667, 1.0)
|
||||||
transition.updatePosition(node: animationNode, position: CGPoint(x: size.width - 19.0, y: size.height - 15.0))
|
transition.updatePosition(node: animationNode, position: CGPoint(x: 16.0, y: size.height - 15.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateTransitionIn(from sourceNode: ASDisplayNode, containerNode: ASDisplayNode, animate: Bool = true) {
|
func animateTransitionIn(from sourceNode: ASDisplayNode, containerNode: ASDisplayNode, transition: ContainedViewLayoutTransition, animate: Bool = true) {
|
||||||
guard let _ = self.item else {
|
guard let _ = self.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var duration: Double = 0.2
|
||||||
|
var timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue
|
||||||
|
if case let .animated(transitionDuration, curve) = transition {
|
||||||
|
duration = transitionDuration
|
||||||
|
timingFunction = curve.timingFunction
|
||||||
|
}
|
||||||
|
|
||||||
if let sourceNode = sourceNode as? VoiceChatFullscreenParticipantItemNode, let _ = sourceNode.item {
|
if let sourceNode = sourceNode as? VoiceChatFullscreenParticipantItemNode, let _ = sourceNode.item {
|
||||||
let initialAnimate = animate
|
let initialAnimate = animate
|
||||||
@ -301,7 +401,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
sourceNode.videoNode = nil
|
sourceNode.videoNode = nil
|
||||||
videoNode.alpha = 1.0
|
videoNode.alpha = 1.0
|
||||||
self.videoNode = videoNode
|
self.videoNode = videoNode
|
||||||
self.contextSourceNode.contentNode.insertSubnode(videoNode, at: 1)
|
self.videoContainerNode.addSubnode(videoNode)
|
||||||
|
|
||||||
if animate {
|
if animate {
|
||||||
// self.videoContainerNode.layer.animateScale(from: sourceNode.bounds.width / videoSize.width, to: tileSize.width / videoSize.width, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
// self.videoContainerNode.layer.animateScale(from: sourceNode.bounds.width / videoSize.width, to: tileSize.width / videoSize.width, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||||
@ -322,15 +422,14 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
self.contextSourceNode.position = targetContainerPosition
|
self.contextSourceNode.position = targetContainerPosition
|
||||||
containerNode.addSubnode(self.contextSourceNode)
|
containerNode.addSubnode(self.contextSourceNode)
|
||||||
|
|
||||||
self.contextSourceNode.layer.animateScale(from: 0.467, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
self.contextSourceNode.layer.animateScale(from: 0.467, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
self.contextSourceNode.layer.animatePosition(from: startContainerPosition, to: targetContainerPosition, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak self] _ in
|
self.contextSourceNode.layer.animatePosition(from: startContainerPosition, to: targetContainerPosition, duration: duration, timingFunction: timingFunction, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.contextSourceNode.position = initialPosition
|
strongSelf.contextSourceNode.position = initialPosition
|
||||||
strongSelf.containerNode.addSubnode(strongSelf.contextSourceNode)
|
strongSelf.containerNode.addSubnode(strongSelf.contextSourceNode)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
|
||||||
self.videoNode?.updateLayout(size: self.bounds.size, isLandscape: true, transition: transition)
|
self.videoNode?.updateLayout(size: self.bounds.size, isLandscape: true, transition: transition)
|
||||||
self.videoNode?.frame = self.bounds
|
self.videoNode?.frame = self.bounds
|
||||||
} else if !initialAnimate {
|
} else if !initialAnimate {
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Call/Pin.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/Pin.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "pin.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Call/Pin.imageset/pin.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/Pin.imageset/pin.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Call/Unpin.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/Unpin.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "unpin.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Call/Unpin.imageset/unpin.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/Unpin.imageset/unpin.pdf
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user