mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Video Chat Improvements
This commit is contained in:
parent
4a465a5893
commit
dbc3f8882f
@ -552,7 +552,12 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
|
||||||
let offset = self.contentContainerNode.frame.height
|
let offset = self.contentContainerNode.frame.height
|
||||||
self.wrappingScrollNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
let position = self.wrappingScrollNode.position
|
||||||
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
|
self.wrappingScrollNode.position = CGPoint(x: position.x, y: position.y + offset)
|
||||||
|
transition.animateView({
|
||||||
|
self.wrappingScrollNode.position = position
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: (() -> Void)? = nil) {
|
func animateOut(completion: (() -> Void)? = nil) {
|
||||||
|
@ -549,11 +549,17 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
|||||||
func animateIn() {
|
func animateIn() {
|
||||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
|
||||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
let offset: CGFloat = 510.0
|
||||||
|
|
||||||
let dimPosition = self.dimNode.layer.position
|
let dimPosition = self.dimNode.layer.position
|
||||||
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
|
||||||
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
|
let targetBounds = self.bounds
|
||||||
|
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
||||||
|
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
|
||||||
|
transition.animateView({
|
||||||
|
self.bounds = targetBounds
|
||||||
|
self.dimNode.position = dimPosition
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: (() -> Void)? = nil) {
|
func animateOut(completion: (() -> Void)? = nil) {
|
||||||
|
@ -287,15 +287,15 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
|
|||||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
|
||||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||||
|
|
||||||
let dimPosition = self.dimNode.layer.position
|
let dimPosition = self.dimNode.layer.position
|
||||||
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
|
||||||
|
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
let targetBounds = self.bounds
|
let targetBounds = self.bounds
|
||||||
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
||||||
|
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
|
||||||
transition.animateView({
|
transition.animateView({
|
||||||
self.bounds = targetBounds
|
self.bounds = targetBounds
|
||||||
|
self.dimNode.position = dimPosition
|
||||||
})
|
})
|
||||||
|
|
||||||
self.applicationStateDisposable = (self.context.sharedContext.applicationBindings.applicationIsActive
|
self.applicationStateDisposable = (self.context.sharedContext.applicationBindings.applicationIsActive
|
||||||
|
@ -225,7 +225,7 @@ final class GroupVideoNode: ASDisplayNode {
|
|||||||
rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width)
|
rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width)
|
||||||
rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height)
|
rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height)
|
||||||
|
|
||||||
var videoSize = rotatedVideoFrame.size
|
var videoSize = rotatedVideoFrame.size.aspectFilled(CGSize(width: 1080.0, height: 1080.0))
|
||||||
transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center)
|
transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center)
|
||||||
transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: videoSize))
|
transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: videoSize))
|
||||||
|
|
||||||
@ -241,7 +241,7 @@ final class GroupVideoNode: ASDisplayNode {
|
|||||||
|
|
||||||
// TODO: properly fix the issue
|
// TODO: properly fix the issue
|
||||||
// On iOS 13 and later metal layer transformation is broken if the layer does not require compositing
|
// On iOS 13 and later metal layer transformation is broken if the layer does not require compositing
|
||||||
self.videoView.view.alpha = 0.995
|
self.videoView.view.alpha = 0.99
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,8 +254,8 @@ private final class MainVideoContainerNode: ASDisplayNode {
|
|||||||
private var currentVideoNode: GroupVideoNode?
|
private var currentVideoNode: GroupVideoNode?
|
||||||
private var candidateVideoNode: GroupVideoNode?
|
private var candidateVideoNode: GroupVideoNode?
|
||||||
|
|
||||||
fileprivate let otherVideoButtonNode: HighlightTrackingButtonNode
|
private let otherVideoButtonNode: HighlightTrackingButtonNode
|
||||||
private let otherVideoWrapperNode: ASDisplayNode
|
fileprivate let otherVideoWrapperNode: ASDisplayNode
|
||||||
private let otherVideoShadowNode: ASImageNode
|
private let otherVideoShadowNode: ASImageNode
|
||||||
private var otherVideoNode: GroupVideoNode?
|
private var otherVideoNode: GroupVideoNode?
|
||||||
|
|
||||||
@ -270,6 +270,9 @@ private final class MainVideoContainerNode: ASDisplayNode {
|
|||||||
var tapped: (() -> Void)?
|
var tapped: (() -> Void)?
|
||||||
var otherVideoTapped: (() -> Void)?
|
var otherVideoTapped: (() -> Void)?
|
||||||
|
|
||||||
|
private let videoReadyDisposable = MetaDisposable()
|
||||||
|
private let otherVideoReadyDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(context: AccountContext, call: PresentationGroupCall) {
|
init(context: AccountContext, call: PresentationGroupCall) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.call = call
|
self.call = call
|
||||||
@ -337,6 +340,11 @@ private final class MainVideoContainerNode: ASDisplayNode {
|
|||||||
self.otherVideoButtonNode.addTarget(self, action: #selector(self.otherVideoPressed), forControlEvents: .touchUpInside)
|
self.otherVideoButtonNode.addTarget(self, action: #selector(self.otherVideoPressed), forControlEvents: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.videoReadyDisposable.dispose()
|
||||||
|
self.otherVideoReadyDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
@ -361,10 +369,6 @@ private final class MainVideoContainerNode: ASDisplayNode {
|
|||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||||
if let otherEndpointId = otherEndpointId {
|
if let otherEndpointId = otherEndpointId {
|
||||||
if otherEndpointId != previousPeer?.2 {
|
if otherEndpointId != previousPeer?.2 {
|
||||||
if self.otherVideoWrapperNode.alpha.isZero {
|
|
||||||
transition.updateAlpha(node: self.otherVideoWrapperNode, alpha: 1.0)
|
|
||||||
transition.updateTransformScale(node: self.otherVideoWrapperNode, scale: 1.0)
|
|
||||||
}
|
|
||||||
self.call.makeIncomingVideoView(endpointId: otherEndpointId) { [weak self] videoView in
|
self.call.makeIncomingVideoView(endpointId: otherEndpointId) { [weak self] videoView in
|
||||||
guard let strongSelf = self, let videoView = videoView else {
|
guard let strongSelf = self, let videoView = videoView else {
|
||||||
return
|
return
|
||||||
@ -380,11 +384,24 @@ private final class MainVideoContainerNode: ASDisplayNode {
|
|||||||
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
|
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
|
||||||
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
|
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strongSelf.otherVideoWrapperNode.alpha.isZero {
|
||||||
|
strongSelf.otherVideoReadyDisposable.set((videoNode.ready
|
||||||
|
|> filter { $0 }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
if let strongSelf = self {
|
||||||
|
transition.updateAlpha(node: strongSelf.otherVideoWrapperNode, alpha: 1.0)
|
||||||
|
transition.updateTransformScale(node: strongSelf.otherVideoWrapperNode, scale: 1.0)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let otherVideoNode = self.otherVideoNode {
|
if let otherVideoNode = self.otherVideoNode {
|
||||||
self.otherVideoNode = nil
|
self.otherVideoNode = nil
|
||||||
|
self.otherVideoReadyDisposable.set(nil)
|
||||||
let completion = {
|
let completion = {
|
||||||
otherVideoNode.removeFromSupernode()
|
otherVideoNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
@ -405,45 +422,37 @@ private final class MainVideoContainerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if waitForFullSize {
|
|
||||||
let candidateVideoNode = GroupVideoNode(videoView: videoView)
|
|
||||||
strongSelf.candidateVideoNode = candidateVideoNode
|
|
||||||
|
|
||||||
Queue.mainQueue().after(0.3, { [weak candidateVideoNode] in
|
|
||||||
guard let strongSelf = self, let videoNode = candidateVideoNode, videoNode === strongSelf.candidateVideoNode else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let currentVideoNode = strongSelf.currentVideoNode {
|
|
||||||
currentVideoNode.removeFromSupernode()
|
|
||||||
strongSelf.currentVideoNode = nil
|
|
||||||
}
|
|
||||||
strongSelf.currentVideoNode = videoNode
|
|
||||||
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
|
|
||||||
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
|
|
||||||
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
|
|
||||||
}
|
|
||||||
completion?()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
strongSelf.candidateVideoNode = nil
|
|
||||||
|
|
||||||
let videoNode = GroupVideoNode(videoView: videoView)
|
let videoNode = GroupVideoNode(videoView: videoView)
|
||||||
if let currentVideoNode = strongSelf.currentVideoNode {
|
if let currentVideoNode = strongSelf.currentVideoNode {
|
||||||
currentVideoNode.removeFromSupernode()
|
|
||||||
strongSelf.currentVideoNode = nil
|
strongSelf.currentVideoNode = nil
|
||||||
|
|
||||||
|
currentVideoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak currentVideoNode] _ in
|
||||||
|
currentVideoNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
strongSelf.currentVideoNode = videoNode
|
strongSelf.currentVideoNode = videoNode
|
||||||
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
|
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
|
||||||
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
|
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
|
||||||
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
|
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if waitForFullSize {
|
||||||
|
strongSelf.videoReadyDisposable.set((videoNode.ready
|
||||||
|
|> filter { $0 }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
|
completion?()
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
strongSelf.videoReadyDisposable.set(nil)
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
self.videoReadyDisposable.set(nil)
|
||||||
|
self.otherVideoReadyDisposable.set(nil)
|
||||||
if let currentVideoNode = self.currentVideoNode {
|
if let currentVideoNode = self.currentVideoNode {
|
||||||
currentVideoNode.removeFromSupernode()
|
currentVideoNode.removeFromSupernode()
|
||||||
self.currentVideoNode = nil
|
self.currentVideoNode = nil
|
||||||
@ -456,7 +465,7 @@ private final class MainVideoContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let currentVideoNode = self.currentVideoNode {
|
if let currentVideoNode = self.currentVideoNode {
|
||||||
transition.updateFrame(node: currentVideoNode, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(node: currentVideoNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
currentVideoNode.updateLayout(size: size, isLandscape: isLandscape, transition: transition)
|
currentVideoNode.updateLayout(size: size, isLandscape: true, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
let smallVideoSize = CGSize(width: 40.0, height: 40.0)
|
let smallVideoSize = CGSize(width: 40.0, height: 40.0)
|
||||||
@ -502,6 +511,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
private final class Interaction {
|
private final class Interaction {
|
||||||
let updateIsMuted: (PeerId, Bool) -> Void
|
let updateIsMuted: (PeerId, Bool) -> Void
|
||||||
let pinPeer: (PeerId) -> Void
|
let pinPeer: (PeerId) -> Void
|
||||||
|
let togglePeerVideo: (PeerId) -> Void
|
||||||
let openInvite: () -> Void
|
let openInvite: () -> Void
|
||||||
let peerContextAction: (PeerEntry, ASDisplayNode, ContextGesture?) -> Void
|
let peerContextAction: (PeerEntry, ASDisplayNode, ContextGesture?) -> Void
|
||||||
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||||
@ -515,6 +525,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
init(
|
init(
|
||||||
updateIsMuted: @escaping (PeerId, Bool) -> Void,
|
updateIsMuted: @escaping (PeerId, Bool) -> Void,
|
||||||
pinPeer: @escaping (PeerId) -> Void,
|
pinPeer: @escaping (PeerId) -> Void,
|
||||||
|
togglePeerVideo: @escaping (PeerId) -> Void,
|
||||||
openInvite: @escaping () -> Void,
|
openInvite: @escaping () -> Void,
|
||||||
peerContextAction: @escaping (PeerEntry, ASDisplayNode, ContextGesture?) -> Void,
|
peerContextAction: @escaping (PeerEntry, ASDisplayNode, ContextGesture?) -> Void,
|
||||||
setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void,
|
setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void,
|
||||||
@ -522,6 +533,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
) {
|
) {
|
||||||
self.updateIsMuted = updateIsMuted
|
self.updateIsMuted = updateIsMuted
|
||||||
self.pinPeer = pinPeer
|
self.pinPeer = pinPeer
|
||||||
|
self.togglePeerVideo = togglePeerVideo
|
||||||
self.openInvite = openInvite
|
self.openInvite = openInvite
|
||||||
self.peerContextAction = peerContextAction
|
self.peerContextAction = peerContextAction
|
||||||
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
||||||
@ -870,8 +882,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
if case .list = peerEntry.style {
|
if case .list = peerEntry.style {
|
||||||
interaction.peerContextAction(peerEntry, node, nil)
|
interaction.peerContextAction(peerEntry, node, nil)
|
||||||
} else if peerEntry.effectiveVideoEndpointId != nil {
|
} else if peerEntry.effectiveVideoEndpointId != nil {
|
||||||
|
if peerEntry.pinned && peerEntry.videoEndpointId != nil && peerEntry.screencastEndpointId != nil {
|
||||||
|
interaction.togglePeerVideo(peer.id)
|
||||||
|
} else {
|
||||||
interaction.pinPeer(peer.id)
|
interaction.pinPeer(peer.id)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, contextAction: peerEntry.style == .list ? { node, gesture in
|
}, contextAction: peerEntry.style == .list ? { node, gesture in
|
||||||
interaction.peerContextAction(peerEntry, node, gesture)
|
interaction.peerContextAction(peerEntry, node, gesture)
|
||||||
} : nil, getIsExpanded: {
|
} : nil, getIsExpanded: {
|
||||||
@ -1237,6 +1253,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
strongSelf.updateMainParticipant(waitForFullSize: false)
|
strongSelf.updateMainParticipant(waitForFullSize: false)
|
||||||
}
|
}
|
||||||
|
}, togglePeerVideo: { [weak self] peerId in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.mainVideoContainerNode?.otherVideoTapped?()
|
||||||
}, openInvite: { [weak self] in
|
}, openInvite: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -2043,7 +2064,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
if strongSelf.currentDominantSpeakerWithVideo != peerId {
|
if strongSelf.currentDominantSpeakerWithVideo != peerId {
|
||||||
strongSelf.currentDominantSpeakerWithVideo = peerId
|
strongSelf.currentDominantSpeakerWithVideo = peerId
|
||||||
if !strongSelf.requestedVideoSources.isEmpty {
|
if !strongSelf.requestedVideoSources.isEmpty {
|
||||||
strongSelf.updateMainParticipant(waitForFullSize: true)
|
strongSelf.updateMainParticipant(waitForFullSize: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2144,8 +2165,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
let videoNode = GroupVideoNode(videoView: videoView)
|
let videoNode = GroupVideoNode(videoView: videoView)
|
||||||
strongSelf.videoNodes.append((endpointId, videoNode))
|
strongSelf.videoNodes.append((endpointId, videoNode))
|
||||||
|
|
||||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
if let _ = strongSelf.validLayout {
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
|
// strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||||
|
|
||||||
loop: for i in 0 ..< strongSelf.currentEntries.count {
|
loop: for i in 0 ..< strongSelf.currentEntries.count {
|
||||||
let entry = strongSelf.currentEntries[i]
|
let entry = strongSelf.currentEntries[i]
|
||||||
@ -2178,7 +2199,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
strongSelf.requestedVideoSources.remove(source)
|
strongSelf.requestedVideoSources.remove(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
var updated = false
|
|
||||||
for i in (0 ..< strongSelf.videoNodes.count).reversed() {
|
for i in (0 ..< strongSelf.videoNodes.count).reversed() {
|
||||||
if !validSources.contains(strongSelf.videoNodes[i].0) {
|
if !validSources.contains(strongSelf.videoNodes[i].0) {
|
||||||
let endpointId = strongSelf.videoNodes[i].0
|
let endpointId = strongSelf.videoNodes[i].0
|
||||||
@ -2191,15 +2211,14 @@ public final class VoiceChatController: ViewController {
|
|||||||
case let .peer(peerEntry):
|
case let .peer(peerEntry):
|
||||||
if peerEntry.effectiveVideoEndpointId == endpointId {
|
if peerEntry.effectiveVideoEndpointId == endpointId {
|
||||||
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
|
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
|
||||||
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
|
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
|
||||||
strongSelf.tileListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: tileEntry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
|
strongSelf.tileListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: tileEntry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updated = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2213,14 +2232,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
strongSelf.updateMainParticipant(waitForFullSize: false)
|
strongSelf.updateMainParticipant(waitForFullSize: false)
|
||||||
}
|
}
|
||||||
} else if strongSelf.currentDominantSpeakerWithVideo != nil && !strongSelf.requestedVideoSources.isEmpty {
|
} else if strongSelf.currentDominantSpeakerWithVideo != nil && !strongSelf.requestedVideoSources.isEmpty && !strongSelf.didSetMainParticipant {
|
||||||
|
strongSelf.didSetMainParticipant = true
|
||||||
|
Queue.mainQueue().after(0.1) {
|
||||||
strongSelf.updateMainParticipant(waitForFullSize: true)
|
strongSelf.updateMainParticipant(waitForFullSize: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if updated {
|
|
||||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -2247,7 +2263,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.mainVideoContainerNode?.tapped = { [weak self] in
|
self.mainVideoContainerNode?.tapped = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self, !strongSelf.animatingExpansion {
|
||||||
var effectiveDisplayMode = strongSelf.displayMode
|
var effectiveDisplayMode = strongSelf.displayMode
|
||||||
var isLandscape = false
|
var isLandscape = false
|
||||||
if let (layout, _) = strongSelf.validLayout, layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
|
if let (layout, _) = strongSelf.validLayout, layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
|
||||||
@ -3402,10 +3418,16 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var animatingButtons = false
|
||||||
@objc private func cameraPressed() {
|
@objc private func cameraPressed() {
|
||||||
if self.call.hasVideo || self.call.hasScreencast {
|
if self.call.hasVideo || self.call.hasScreencast {
|
||||||
self.call.disableVideo()
|
self.call.disableVideo()
|
||||||
self.call.disableScreencast()
|
self.call.disableScreencast()
|
||||||
|
|
||||||
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
|
self.animatingButtons = true
|
||||||
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .linear))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.call.makeOutgoingVideoView { [weak self] view in
|
self.call.makeOutgoingVideoView { [weak self] view in
|
||||||
guard let strongSelf = self, let view = view else {
|
guard let strongSelf = self, let view = view else {
|
||||||
@ -3415,6 +3437,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] videoNode in
|
let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] videoNode in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.call.requestVideo()
|
strongSelf.call.requestVideo()
|
||||||
|
|
||||||
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
|
strongSelf.animatingButtons = true
|
||||||
|
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .linear))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, switchCamera: { [weak self] in
|
}, switchCamera: { [weak self] in
|
||||||
self?.call.switchVideoCamera()
|
self?.call.switchVideoCamera()
|
||||||
@ -3445,6 +3472,10 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var hasMainVideo: Bool {
|
||||||
|
return self.mainVideoContainerNode != nil && self.effectiveSpeakerWithVideo != nil
|
||||||
|
}
|
||||||
|
|
||||||
private var bringVideoToBackOnCompletion = false
|
private var bringVideoToBackOnCompletion = false
|
||||||
private func updateDecorationsLayout(transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
|
private func updateDecorationsLayout(transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
|
||||||
guard let (layout, _) = self.validLayout else {
|
guard let (layout, _) = self.validLayout else {
|
||||||
@ -3481,7 +3512,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - bottomPanelHeight)
|
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - bottomPanelHeight)
|
||||||
let topInset: CGFloat
|
let topInset: CGFloat
|
||||||
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
|
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
|
||||||
if self.isExpanded {
|
if self.isExpanded && !self.hasMainVideo {
|
||||||
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
|
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
|
||||||
} else {
|
} else {
|
||||||
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
|
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
|
||||||
@ -3543,7 +3574,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
videoY = layout.statusBarHeight ?? 20.0
|
videoY = layout.statusBarHeight ?? 20.0
|
||||||
isFullscreen = true
|
isFullscreen = true
|
||||||
}
|
}
|
||||||
videoClippingFrame = CGRect(origin: CGPoint(x: videoInset, y: videoY), size: CGSize(width: layout.size.width - videoInset * 2.0, height: self.isFullscreen ? videoHeight : 0.0))
|
videoClippingFrame = CGRect(origin: CGPoint(x: videoInset, y: videoY), size: CGSize(width: layout.size.width - videoInset * 2.0, height: self.hasMainVideo ? videoHeight : 0.0))
|
||||||
videoContainerFrame = CGRect(origin: CGPoint(x: -videoInset, y: 0.0), size: CGSize(width: layout.size.width, height: videoHeight))
|
videoContainerFrame = CGRect(origin: CGPoint(x: -videoInset, y: 0.0), size: CGSize(width: layout.size.width, height: videoHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3648,7 +3679,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
let previousBottomCornersFrame = self.bottomCornersNode.frame
|
let previousBottomCornersFrame = self.bottomCornersNode.frame
|
||||||
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
|
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
|
||||||
self.bottomCornersNode.frame = bottomCornersFrame
|
self.bottomCornersNode.frame = bottomCornersFrame
|
||||||
self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset, width: size.width, height: 2000.0)
|
self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset + bottomDelta, width: size.width, height: 2000.0)
|
||||||
|
|
||||||
let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY)
|
let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY)
|
||||||
transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta)
|
transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta)
|
||||||
@ -3873,10 +3904,13 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
let hasVideo = self.call.hasVideo || self.call.hasScreencast
|
let hasVideo = self.call.hasVideo || self.call.hasScreencast
|
||||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
|
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
|
||||||
self.cameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: hasVideo ? .cameraOn : .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
|
self.cameraButton.update(size: hasVideo ? sideButtonSize : videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: hasVideo ? activeButtonAppearance : normalButtonAppearance, image: hasVideo ? .cameraOn : .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
|
||||||
|
|
||||||
transition.updateAlpha(node: self.switchCameraButton, alpha: self.call.hasVideo ? 1.0 : 0.0)
|
transition.updateAlpha(node: self.switchCameraButton, alpha: hasVideo ? 1.0 : 0.0)
|
||||||
transition.updateAlpha(node: self.audioButton, alpha: self.call.hasVideo ? 0.0 : 1.0)
|
transition.updateTransformScale(node: self.switchCameraButton, scale: hasVideo ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
transition.updateAlpha(node: self.audioButton, alpha: hasVideo ? 0.0 : 1.0)
|
||||||
|
transition.updateTransformScale(node: self.audioButton, scale: hasVideo ? 0.0 : 1.0)
|
||||||
|
|
||||||
self.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .flipCamera), text: "", transition: transition)
|
self.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .flipCamera), text: "", transition: transition)
|
||||||
|
|
||||||
@ -3885,7 +3919,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.leaveButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
|
self.leaveButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
|
||||||
|
|
||||||
transition.updateAlpha(node: self.cameraButton.textNode, alpha: 0.0)
|
transition.updateAlpha(node: self.cameraButton.textNode, alpha: hasVideo ? 1.0 : 0.0)
|
||||||
transition.updateAlpha(node: self.switchCameraButton.textNode, alpha: buttonsTitleAlpha)
|
transition.updateAlpha(node: self.switchCameraButton.textNode, alpha: buttonsTitleAlpha)
|
||||||
transition.updateAlpha(node: self.audioButton.textNode, alpha: buttonsTitleAlpha)
|
transition.updateAlpha(node: self.audioButton.textNode, alpha: buttonsTitleAlpha)
|
||||||
transition.updateAlpha(node: self.leaveButton.textNode, alpha: buttonsTitleAlpha)
|
transition.updateAlpha(node: self.leaveButton.textNode, alpha: buttonsTitleAlpha)
|
||||||
@ -3920,7 +3954,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
if !self.isFullscreen {
|
if !self.isFullscreen {
|
||||||
self.isExpanded = true
|
self.isExpanded = true
|
||||||
self.updateIsFullscreen(true)
|
self.updateIsFullscreen(true)
|
||||||
// self.tileListNode.isHidden = false
|
}
|
||||||
|
if self.hasMainVideo {
|
||||||
|
self.tileListNode.isHidden = false
|
||||||
}
|
}
|
||||||
if case .fullscreen = effectiveDisplayMode {
|
if case .fullscreen = effectiveDisplayMode {
|
||||||
} else {
|
} else {
|
||||||
@ -3986,8 +4022,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
var topCornersY = topPanelHeight
|
var topCornersY = topPanelHeight
|
||||||
if isLandscape {
|
if isLandscape {
|
||||||
listTopInset = topPanelHeight
|
listTopInset = topPanelHeight
|
||||||
// topCornersY = -50.0
|
} else if self.mainVideoContainerNode != nil && self.isExpanded {
|
||||||
} else if self.mainVideoContainerNode != nil && self.isFullscreen {
|
|
||||||
let videoContainerHeight = min(mainVideoHeight, layout.size.width)
|
let videoContainerHeight = min(mainVideoHeight, layout.size.width)
|
||||||
listTopInset += videoContainerHeight
|
listTopInset += videoContainerHeight
|
||||||
topCornersY += videoContainerHeight
|
topCornersY += videoContainerHeight
|
||||||
@ -3996,7 +4031,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - (isLandscape ? layout.intrinsicInsets.bottom : bottomPanelHeight))
|
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - (isLandscape ? layout.intrinsicInsets.bottom : bottomPanelHeight))
|
||||||
let topInset: CGFloat
|
let topInset: CGFloat
|
||||||
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
|
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
|
||||||
if self.isExpanded {
|
if self.isExpanded && !self.hasMainVideo {
|
||||||
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
|
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
|
||||||
} else {
|
} else {
|
||||||
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
|
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
|
||||||
@ -4236,10 +4271,21 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.updateButtons(animated: !isFirstTime)
|
self.updateButtons(animated: !isFirstTime)
|
||||||
|
|
||||||
if self.audioButton.supernode === self.bottomPanelNode {
|
if self.audioButton.supernode === self.bottomPanelNode {
|
||||||
transition.updateFrame(node: self.cameraButton, frame: firstButtonFrame)
|
transition.updateFrameAsPositionAndBounds(node: self.switchCameraButton, frame: firstButtonFrame)
|
||||||
transition.updateFrame(node: self.switchCameraButton, frame: firstButtonFrame)
|
|
||||||
transition.updateFrame(node: self.audioButton, frame: secondButtonFrame)
|
if !self.animatingButtons || transition.isAnimated {
|
||||||
transition.updateFrame(node: self.leaveButton, frame: forthButtonFrame)
|
if self.call.hasVideo {
|
||||||
|
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: secondButtonFrame, completion: { [weak self] _ in
|
||||||
|
self?.animatingButtons = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: firstButtonFrame, completion: { [weak self] _ in
|
||||||
|
self?.animatingButtons = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transition.updateFrameAsPositionAndBounds(node: self.audioButton, frame: secondButtonFrame)
|
||||||
|
transition.updateFrameAsPositionAndBounds(node: self.leaveButton, frame: forthButtonFrame)
|
||||||
}
|
}
|
||||||
if isFirstTime {
|
if isFirstTime {
|
||||||
while !self.enqueuedTransitions.isEmpty {
|
while !self.enqueuedTransitions.isEmpty {
|
||||||
@ -4255,6 +4301,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
guard let (layout, navigationHeight) = self.validLayout else {
|
guard let (layout, navigationHeight) = self.validLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.hasMainVideo && !self.isFullscreen {
|
||||||
|
self.updateIsFullscreen(true)
|
||||||
|
}
|
||||||
|
|
||||||
self.updateDecorationsLayout(transition: .immediate)
|
self.updateDecorationsLayout(transition: .immediate)
|
||||||
|
|
||||||
self.animatingAppearance = true
|
self.animatingAppearance = true
|
||||||
@ -4645,6 +4696,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.endpointToPeerId = endpointIdToPeerId
|
self.endpointToPeerId = endpointIdToPeerId
|
||||||
self.peerIdToEndpoint = peerIdToEndpointId
|
self.peerIdToEndpoint = peerIdToEndpointId
|
||||||
|
|
||||||
|
let previousPinnedEntry = self.pinnedEntry
|
||||||
self.pinnedEntry = pinnedEntry
|
self.pinnedEntry = pinnedEntry
|
||||||
|
|
||||||
let previousEntries = self.currentEntries
|
let previousEntries = self.currentEntries
|
||||||
@ -4652,6 +4704,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.currentEntries = entries
|
self.currentEntries = entries
|
||||||
self.currentTileEntries = tileEntries
|
self.currentTileEntries = tileEntries
|
||||||
|
|
||||||
|
if let previousPinnedEntry = previousPinnedEntry, case let .peer(previousPeerEntry) = previousPinnedEntry, let pinnedEntry = pinnedEntry, case let .peer(peerEntry) = pinnedEntry {
|
||||||
|
if previousPeerEntry.videoEndpointId != peerEntry.videoEndpointId || previousPeerEntry.screencastEndpointId != peerEntry.screencastEndpointId {
|
||||||
|
self.updateMainParticipant(waitForFullSize: false, updateMembers: false, force: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if previousEntries.count == entries.count {
|
if previousEntries.count == entries.count {
|
||||||
var allEqual = true
|
var allEqual = true
|
||||||
for i in 0 ..< previousEntries.count {
|
for i in 0 ..< previousEntries.count {
|
||||||
@ -4685,7 +4743,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.enqueueTileTransition(tileTransition)
|
self.enqueueTileTransition(tileTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMainParticipant(waitForFullSize: Bool, force: Bool = false) {
|
private var didSetMainParticipant = false
|
||||||
|
private func updateMainParticipant(waitForFullSize: Bool, updateMembers: Bool = true, force: Bool = false) {
|
||||||
let effectiveMainParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo
|
let effectiveMainParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo
|
||||||
guard effectiveMainParticipant != self.effectiveSpeakerWithVideo?.0 || force else {
|
guard effectiveMainParticipant != self.effectiveSpeakerWithVideo?.0 || force else {
|
||||||
return
|
return
|
||||||
@ -4720,8 +4779,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let completion = {
|
let completion = {
|
||||||
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
|
||||||
|
|
||||||
var updateLayout = false
|
var updateLayout = false
|
||||||
if self.effectiveSpeakerWithVideo != nil && !self.isExpanded {
|
if self.effectiveSpeakerWithVideo != nil && !self.isExpanded {
|
||||||
self.isExpanded = true
|
self.isExpanded = true
|
||||||
@ -4734,10 +4791,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
if updateLayout {
|
if updateLayout {
|
||||||
self.updateIsFullscreen(self.isExpanded)
|
self.updateIsFullscreen(self.isExpanded)
|
||||||
self.animatingExpansion = true
|
self.animatingExpansion = true
|
||||||
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
|
||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
|
||||||
}
|
}
|
||||||
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
|
self.updateDecorationsLayout(transition: transition, completion: {
|
||||||
self.animatingExpansion = false
|
self.animatingExpansion = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -4746,13 +4804,22 @@ public final class VoiceChatController: ViewController {
|
|||||||
var waitForFullSize = waitForFullSize
|
var waitForFullSize = waitForFullSize
|
||||||
if !self.isExpanded {
|
if !self.isExpanded {
|
||||||
waitForFullSize = true
|
waitForFullSize = true
|
||||||
|
self.mainVideoClippingNode.alpha = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
self.effectiveSpeakerWithVideo = effectivePeer.flatMap { ($0.0, $0.1) }
|
self.effectiveSpeakerWithVideo = effectivePeer.flatMap { ($0.0, $0.1) }
|
||||||
|
if updateMembers {
|
||||||
|
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
||||||
|
}
|
||||||
self.call.setFullSizeVideo(endpointId: effectivePeer?.1)
|
self.call.setFullSizeVideo(endpointId: effectivePeer?.1)
|
||||||
self.mainVideoContainerNode?.updatePeer(peer: effectivePeer, waitForFullSize: waitForFullSize, completion: {
|
self.mainVideoContainerNode?.updatePeer(peer: effectivePeer, waitForFullSize: waitForFullSize, completion: { [weak self] in
|
||||||
if waitForFullSize {
|
if waitForFullSize {
|
||||||
completion()
|
completion()
|
||||||
|
|
||||||
|
if let strongSelf = self, strongSelf.mainVideoClippingNode.alpha.isZero {
|
||||||
|
strongSelf.mainVideoClippingNode.alpha = 1.0
|
||||||
|
strongSelf.mainVideoClippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if !waitForFullSize {
|
if !waitForFullSize {
|
||||||
@ -4814,6 +4881,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
if isScheduling && translation < 0.0 {
|
if isScheduling && translation < 0.0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var topInset: CGFloat = 0.0
|
var topInset: CGFloat = 0.0
|
||||||
if let (currentTopInset, currentPanOffset) = self.panGestureArguments {
|
if let (currentTopInset, currentPanOffset) = self.panGestureArguments {
|
||||||
topInset = currentTopInset
|
topInset = currentTopInset
|
||||||
@ -4836,7 +4904,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.updateIsFullscreen(false)
|
self.updateIsFullscreen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.isExpanded {
|
if self.isExpanded && !self.hasMainVideo {
|
||||||
} else {
|
} else {
|
||||||
if currentOffset > 0.0 {
|
if currentOffset > 0.0 {
|
||||||
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
|
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
|
||||||
@ -4848,7 +4916,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.updateDecorationsLayout(transition: .immediate)
|
self.updateDecorationsLayout(transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.isExpanded {
|
if !self.isExpanded || self.hasMainVideo {
|
||||||
var bounds = self.contentContainer.bounds
|
var bounds = self.contentContainer.bounds
|
||||||
bounds.origin.y = -translation
|
bounds.origin.y = -translation
|
||||||
bounds.origin.y = min(0.0, bounds.origin.y)
|
bounds.origin.y = min(0.0, bounds.origin.y)
|
||||||
@ -4882,7 +4950,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
topInset = self.listNode.frame.height
|
topInset = self.listNode.frame.height
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.isExpanded {
|
if self.isExpanded && !self.hasMainVideo {
|
||||||
self.panGestureArguments = nil
|
self.panGestureArguments = nil
|
||||||
if velocity.y > 300.0 || offset > topInset / 2.0 {
|
if velocity.y > 300.0 || offset > topInset / 2.0 {
|
||||||
self.isExpanded = false
|
self.isExpanded = false
|
||||||
@ -4934,7 +5002,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
|
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
|
||||||
self.animatingExpansion = false
|
self.animatingExpansion = false
|
||||||
})
|
})
|
||||||
} else if !isScheduling {
|
} else if !isScheduling && !self.hasMainVideo {
|
||||||
self.updateIsFullscreen(false)
|
self.updateIsFullscreen(false)
|
||||||
self.animatingExpansion = true
|
self.animatingExpansion = true
|
||||||
self.listNode.scroller.setContentOffset(CGPoint(), animated: false)
|
self.listNode.scroller.setContentOffset(CGPoint(), animated: false)
|
||||||
@ -4946,7 +5014,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.animatingExpansion = false
|
self.animatingExpansion = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if !dismissing {
|
if !dismissing && self.hasMainVideo {
|
||||||
var bounds = self.contentContainer.bounds
|
var bounds = self.contentContainer.bounds
|
||||||
let previousBounds = bounds
|
let previousBounds = bounds
|
||||||
bounds.origin.y = 0.0
|
bounds.origin.y = 0.0
|
||||||
@ -5304,7 +5372,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
switch self.displayMode {
|
switch self.displayMode {
|
||||||
case .default:
|
case .default:
|
||||||
let location = videoContainerNode.view.convert(videoContainerNode.otherVideoButtonNode.frame, to: nil)
|
let location = videoContainerNode.view.convert(videoContainerNode.otherVideoWrapperNode.frame, to: nil)
|
||||||
self.controller?.present(TooltipScreen(text: screencast ? self.presentationData.strings.VoiceChat_TapToViewCameraVideo : self.presentationData.strings.VoiceChat_TapToViewScreenVideo, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
|
self.controller?.present(TooltipScreen(text: screencast ? self.presentationData.strings.VoiceChat_TapToViewCameraVideo : self.presentationData.strings.VoiceChat_TapToViewScreenVideo, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
|
||||||
return .dismiss(consume: false)
|
return .dismiss(consume: false)
|
||||||
}), in: .window(.root))
|
}), in: .window(.root))
|
||||||
|
@ -814,6 +814,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if item.pinned {
|
if item.pinned {
|
||||||
self.avatarNode.alpha = 1.0
|
self.avatarNode.alpha = 1.0
|
||||||
videoNode.alpha = 0.0
|
videoNode.alpha = 0.0
|
||||||
|
startContainerPosition = startContainerPosition.offsetBy(dx: 0.0, dy: 9.0)
|
||||||
} else {
|
} else {
|
||||||
self.avatarNode.alpha = 0.0
|
self.avatarNode.alpha = 0.0
|
||||||
}
|
}
|
||||||
@ -971,6 +972,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let currentItem = self.layoutParams?.0
|
let currentItem = self.layoutParams?.0
|
||||||
let currentTitle = self.currentTitle
|
let currentTitle = self.currentTitle
|
||||||
|
let hasVideo = self.videoNode != nil
|
||||||
|
|
||||||
return { item, params, first, last in
|
return { item, params, first, last in
|
||||||
var updatedTheme: PresentationTheme?
|
var updatedTheme: PresentationTheme?
|
||||||
@ -988,14 +990,16 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if case .list = item.style, item.transparent{
|
if case .list = item.style, item.transparent{
|
||||||
titleFont = Font.semibold(17.0)
|
titleFont = Font.semibold(17.0)
|
||||||
titleColor = UIColor(rgb: 0xffffff, alpha: 0.65)
|
titleColor = UIColor(rgb: 0xffffff, alpha: 0.65)
|
||||||
} else if case .tile = item.style {
|
} else if case .tile = item.style, !hasVideo {
|
||||||
switch item.text {
|
switch item.text {
|
||||||
case let .text(_, _, textColor):
|
case let .text(_, _, textColor):
|
||||||
switch textColor {
|
switch textColor {
|
||||||
case .generic:
|
case .generic:
|
||||||
titleColor = item.presentationData.theme.list.itemPrimaryTextColor
|
titleColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||||
case .accent:
|
case .accent:
|
||||||
|
if item.peer.id != item.context.account.peerId {
|
||||||
titleColor = item.presentationData.theme.list.itemAccentColor
|
titleColor = item.presentationData.theme.list.itemAccentColor
|
||||||
|
}
|
||||||
case .constructive:
|
case .constructive:
|
||||||
titleColor = constructiveColor
|
titleColor = constructiveColor
|
||||||
case .destructive:
|
case .destructive:
|
||||||
@ -1550,6 +1554,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
strongSelf.borderImageNode.isHidden = !item.pinned || item.style == .list
|
strongSelf.borderImageNode.isHidden = !item.pinned || item.style == .list
|
||||||
|
|
||||||
|
let canUpdateAvatarVisibility = !strongSelf.isExtracted && !strongSelf.animatingExtraction
|
||||||
|
|
||||||
if let videoNode = videoNode {
|
if let videoNode = videoNode {
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||||
if !strongSelf.isExtracted && !strongSelf.animatingExtraction {
|
if !strongSelf.isExtracted && !strongSelf.animatingExtraction {
|
||||||
@ -1575,7 +1581,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else {
|
} else {
|
||||||
if item.pinned {
|
if item.pinned {
|
||||||
videoNode.alpha = 0.0
|
videoNode.alpha = 0.0
|
||||||
|
if canUpdateAvatarVisibility {
|
||||||
strongSelf.avatarNode.alpha = 1.0
|
strongSelf.avatarNode.alpha = 1.0
|
||||||
|
}
|
||||||
} else if strongSelf.videoReady {
|
} else if strongSelf.videoReady {
|
||||||
videoNode.alpha = 1.0
|
videoNode.alpha = 1.0
|
||||||
strongSelf.avatarNode.alpha = 0.0
|
strongSelf.avatarNode.alpha = 0.0
|
||||||
@ -1584,7 +1592,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else {
|
} else {
|
||||||
if item.pinned {
|
if item.pinned {
|
||||||
videoNode.alpha = 0.0
|
videoNode.alpha = 0.0
|
||||||
|
if canUpdateAvatarVisibility {
|
||||||
strongSelf.avatarNode.alpha = 1.0
|
strongSelf.avatarNode.alpha = 1.0
|
||||||
|
}
|
||||||
} else if strongSelf.videoReady {
|
} else if strongSelf.videoReady {
|
||||||
videoNode.alpha = 1.0
|
videoNode.alpha = 1.0
|
||||||
strongSelf.avatarNode.alpha = 0.0
|
strongSelf.avatarNode.alpha = 0.0
|
||||||
@ -1612,13 +1622,18 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.videoReadyDelayed = true
|
strongSelf.videoReadyDelayed = true
|
||||||
}
|
}
|
||||||
strongSelf.videoReady = ready
|
strongSelf.videoReady = ready
|
||||||
if let videoNode = strongSelf.videoNode, ready && (strongSelf.item?.transparent != true) {
|
if let videoNode = strongSelf.videoNode, ready && !item.transparent {
|
||||||
if strongSelf.videoReadyDelayed {
|
if strongSelf.videoReadyDelayed {
|
||||||
Queue.mainQueue().after(0.15) {
|
Queue.mainQueue().after(0.15) {
|
||||||
switch item.style {
|
guard let currentItem = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch currentItem.style {
|
||||||
case .list:
|
case .list:
|
||||||
if item.pinned {
|
if currentItem.pinned {
|
||||||
|
if canUpdateAvatarVisibility {
|
||||||
strongSelf.avatarNode.alpha = 1.0
|
strongSelf.avatarNode.alpha = 1.0
|
||||||
|
}
|
||||||
videoNode.alpha = 0.0
|
videoNode.alpha = 0.0
|
||||||
} else {
|
} else {
|
||||||
strongSelf.avatarNode.alpha = 0.0
|
strongSelf.avatarNode.alpha = 0.0
|
||||||
@ -1627,8 +1642,10 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
videoNode.alpha = 1.0
|
videoNode.alpha = 1.0
|
||||||
}
|
}
|
||||||
case .tile:
|
case .tile:
|
||||||
if item.pinned {
|
if currentItem.pinned {
|
||||||
|
if canUpdateAvatarVisibility {
|
||||||
strongSelf.avatarNode.alpha = 1.0
|
strongSelf.avatarNode.alpha = 1.0
|
||||||
|
}
|
||||||
videoNode.alpha = 0.0
|
videoNode.alpha = 0.0
|
||||||
} else {
|
} else {
|
||||||
strongSelf.avatarNode.alpha = 0.0
|
strongSelf.avatarNode.alpha = 0.0
|
||||||
@ -1640,7 +1657,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if item.pinned {
|
if item.pinned {
|
||||||
|
if canUpdateAvatarVisibility {
|
||||||
strongSelf.avatarNode.alpha = 1.0
|
strongSelf.avatarNode.alpha = 1.0
|
||||||
|
}
|
||||||
videoNode.alpha = 0.0
|
videoNode.alpha = 0.0
|
||||||
} else {
|
} else {
|
||||||
strongSelf.avatarNode.alpha = 0.0
|
strongSelf.avatarNode.alpha = 0.0
|
||||||
|
@ -1025,6 +1025,33 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode, sourceRect)
|
let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode, sourceRect)
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
|
||||||
|
var isScheduledMessages = false
|
||||||
|
if case .scheduledMessages = interfaceState.subject {
|
||||||
|
isScheduledMessages = true
|
||||||
|
}
|
||||||
|
if !isScheduledMessages {
|
||||||
|
if case let .peer(peerId) = interfaceState.chatLocation {
|
||||||
|
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if isSaved || isGifSaved {
|
if isSaved || isGifSaved {
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
@ -1099,12 +1126,33 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
|
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var menuItems: [ContextMenuItem] = []
|
var menuItems: [ContextMenuItem] = []
|
||||||
menuItems = [
|
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
|
||||||
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
var isScheduledMessages = false
|
||||||
|
if case .scheduledMessages = interfaceState.subject {
|
||||||
|
isScheduledMessages = true
|
||||||
|
}
|
||||||
|
if !isScheduledMessages {
|
||||||
|
if case let .peer(peerId) = interfaceState.chatLocation {
|
||||||
|
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
|
})))
|
||||||
})),
|
}
|
||||||
|
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.append(
|
||||||
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
@ -1115,8 +1163,9 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
|
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})),
|
}))
|
||||||
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in
|
)
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
@ -1143,8 +1192,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
})))
|
||||||
]
|
|
||||||
|
|
||||||
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))
|
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))
|
||||||
} else {
|
} else {
|
||||||
@ -1185,12 +1234,33 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
|
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var menuItems: [ContextMenuItem] = []
|
var menuItems: [ContextMenuItem] = []
|
||||||
menuItems = [
|
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
|
||||||
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
var isScheduledMessages = false
|
||||||
|
if case .scheduledMessages = interfaceState.subject {
|
||||||
|
isScheduledMessages = true
|
||||||
|
}
|
||||||
|
if !isScheduledMessages {
|
||||||
|
if case let .peer(peerId) = interfaceState.chatLocation {
|
||||||
|
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
|
})))
|
||||||
})),
|
}
|
||||||
|
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.append(
|
||||||
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
@ -1201,7 +1271,9 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
|
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})),
|
}))
|
||||||
|
)
|
||||||
|
menuItems.append(
|
||||||
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in
|
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
@ -1230,7 +1302,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
]
|
)
|
||||||
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
|
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -314,10 +314,16 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
|||||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
|
||||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||||
|
|
||||||
let dimPosition = self.dimNode.layer.position
|
let dimPosition = self.dimNode.layer.position
|
||||||
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
|
||||||
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
|
let targetBounds = self.bounds
|
||||||
|
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
||||||
|
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
|
||||||
|
transition.animateView({
|
||||||
|
self.bounds = targetBounds
|
||||||
|
self.dimNode.position = dimPosition
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: (() -> Void)? = nil) {
|
func animateOut(completion: (() -> Void)? = nil) {
|
||||||
|
@ -401,10 +401,16 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi
|
|||||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
|
||||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||||
|
|
||||||
let dimPosition = self.dimNode.layer.position
|
let dimPosition = self.dimNode.layer.position
|
||||||
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
|
||||||
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
|
let targetBounds = self.bounds
|
||||||
|
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
||||||
|
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
|
||||||
|
transition.animateView({
|
||||||
|
self.bounds = targetBounds
|
||||||
|
self.dimNode.position = dimPosition
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: (() -> Void)? = nil) {
|
func animateOut(completion: (() -> Void)? = nil) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user