mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-17 03:40:18 +00:00
Video Chat Improvements
This commit is contained in:
parent
e0f9f4614d
commit
a434c891a2
@ -147,7 +147,23 @@ final class GroupVideoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var aspectRatio: CGFloat {
|
var aspectRatio: CGFloat {
|
||||||
return self.videoView.getAspect()
|
let orientation = self.videoView.getOrientation()
|
||||||
|
var aspect = self.videoView.getAspect()
|
||||||
|
if aspect <= 0.01 {
|
||||||
|
aspect = 3.0 / 4.0
|
||||||
|
}
|
||||||
|
let rotatedAspect: CGFloat
|
||||||
|
switch orientation {
|
||||||
|
case .rotation0:
|
||||||
|
rotatedAspect = 1.0 / aspect
|
||||||
|
case .rotation90:
|
||||||
|
rotatedAspect = aspect
|
||||||
|
case .rotation180:
|
||||||
|
rotatedAspect = 1.0 / aspect
|
||||||
|
case .rotation270:
|
||||||
|
rotatedAspect = aspect
|
||||||
|
}
|
||||||
|
return rotatedAspect
|
||||||
}
|
}
|
||||||
|
|
||||||
var keepBackdropSize = false
|
var keepBackdropSize = false
|
||||||
@ -247,7 +263,7 @@ final class GroupVideoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let backdropEffectView = self.backdropEffectView {
|
if let backdropEffectView = self.backdropEffectView {
|
||||||
let maxSide = max(bounds.width, bounds.height)
|
let maxSide = max(bounds.width, bounds.height) * 2.0
|
||||||
let squareBounds = CGRect(x: (bounds.width - maxSide) / 2.0, y: (bounds.width - maxSide) / 2.0, width: maxSide, height: maxSide)
|
let squareBounds = CGRect(x: (bounds.width - maxSide) / 2.0, y: (bounds.width - maxSide) / 2.0, width: maxSide, height: maxSide)
|
||||||
transition.animateView {
|
transition.animateView {
|
||||||
backdropEffectView.frame = squareBounds
|
backdropEffectView.frame = squareBounds
|
||||||
|
|||||||
@ -701,8 +701,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
fileprivate let actionButton: VoiceChatActionButton
|
fileprivate let actionButton: VoiceChatActionButton
|
||||||
private let leftBorderNode: ASDisplayNode
|
private let leftBorderNode: ASDisplayNode
|
||||||
private let rightBorderNode: ASDisplayNode
|
private let rightBorderNode: ASDisplayNode
|
||||||
private let mainStageNode: VoiceChatMainStageNode
|
|
||||||
private let mainStageContainerNode: ASDisplayNode
|
private let mainStageContainerNode: ASDisplayNode
|
||||||
|
private let mainStageBackgroundNode: ASDisplayNode
|
||||||
|
private let mainStageNode: VoiceChatMainStageNode
|
||||||
private let transitionContainerNode: ASDisplayNode
|
private let transitionContainerNode: ASDisplayNode
|
||||||
|
|
||||||
private var isScheduling = false
|
private var isScheduling = false
|
||||||
@ -807,7 +808,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
private var endpointToPeerId: [String: PeerId] = [:]
|
private var endpointToPeerId: [String: PeerId] = [:]
|
||||||
private var peerIdToEndpoint: [PeerId: String] = [:]
|
private var peerIdToEndpoint: [PeerId: String] = [:]
|
||||||
|
|
||||||
private var currentDominantSpeaker: PeerId?
|
private var currentDominantSpeaker: (PeerId, Double)?
|
||||||
private var currentForcedSpeaker: PeerId?
|
private var currentForcedSpeaker: PeerId?
|
||||||
private var effectiveSpeaker: (PeerId, String?)?
|
private var effectiveSpeaker: (PeerId, String?)?
|
||||||
|
|
||||||
@ -969,13 +970,18 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.rightBorderNode.isUserInteractionEnabled = false
|
self.rightBorderNode.isUserInteractionEnabled = false
|
||||||
self.rightBorderNode.clipsToBounds = false
|
self.rightBorderNode.clipsToBounds = false
|
||||||
|
|
||||||
self.mainStageNode = VoiceChatMainStageNode(context: self.context, call: self.call)
|
|
||||||
|
|
||||||
self.mainStageContainerNode = ASDisplayNode()
|
self.mainStageContainerNode = ASDisplayNode()
|
||||||
self.mainStageContainerNode.clipsToBounds = true
|
self.mainStageContainerNode.clipsToBounds = true
|
||||||
self.mainStageContainerNode.isUserInteractionEnabled = false
|
self.mainStageContainerNode.isUserInteractionEnabled = false
|
||||||
self.mainStageContainerNode.isHidden = true
|
self.mainStageContainerNode.isHidden = true
|
||||||
|
|
||||||
|
self.mainStageBackgroundNode = ASDisplayNode()
|
||||||
|
// self.mainStageBackgroundNode.backgroundColor = .black
|
||||||
|
self.mainStageBackgroundNode.alpha = 0.0
|
||||||
|
self.mainStageBackgroundNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.mainStageNode = VoiceChatMainStageNode(context: self.context, call: self.call)
|
||||||
|
|
||||||
self.transitionContainerNode = ASDisplayNode()
|
self.transitionContainerNode = ASDisplayNode()
|
||||||
self.transitionContainerNode.clipsToBounds = true
|
self.transitionContainerNode.clipsToBounds = true
|
||||||
self.transitionContainerNode.isUserInteractionEnabled = false
|
self.transitionContainerNode.isUserInteractionEnabled = false
|
||||||
@ -1032,13 +1038,13 @@ public final class VoiceChatController: ViewController {
|
|||||||
}, switchToPeer: { [weak self] peerId, videoEndpointId, expand in
|
}, switchToPeer: { [weak self] peerId, videoEndpointId, expand in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if expand, let videoEndpointId = videoEndpointId {
|
if expand, let videoEndpointId = videoEndpointId {
|
||||||
strongSelf.currentDominantSpeaker = peerId
|
strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime())
|
||||||
strongSelf.effectiveSpeaker = (peerId, videoEndpointId)
|
strongSelf.effectiveSpeaker = (peerId, videoEndpointId)
|
||||||
strongSelf.updateDisplayMode(.fullscreen(controlsHidden: false))
|
strongSelf.updateDisplayMode(.fullscreen(controlsHidden: false))
|
||||||
} else {
|
} else {
|
||||||
strongSelf.currentForcedSpeaker = nil
|
strongSelf.currentForcedSpeaker = nil
|
||||||
if peerId != strongSelf.currentDominantSpeaker {
|
if peerId != strongSelf.currentDominantSpeaker?.0 {
|
||||||
strongSelf.currentDominantSpeaker = peerId
|
strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime())
|
||||||
}
|
}
|
||||||
strongSelf.updateMainVideo(waitForFullSize: false, updateMembers: true, force: true)
|
strongSelf.updateMainVideo(waitForFullSize: false, updateMembers: true, force: true)
|
||||||
}
|
}
|
||||||
@ -1657,6 +1663,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.contentContainer.addSubnode(self.fullscreenListNode)
|
self.contentContainer.addSubnode(self.fullscreenListNode)
|
||||||
self.addSubnode(self.transitionContainerNode)
|
self.addSubnode(self.transitionContainerNode)
|
||||||
|
|
||||||
|
self.mainStageContainerNode.addSubnode(self.mainStageBackgroundNode)
|
||||||
self.mainStageContainerNode.addSubnode(self.mainStageNode)
|
self.mainStageContainerNode.addSubnode(self.mainStageNode)
|
||||||
|
|
||||||
self.updateDecorationsColors()
|
self.updateDecorationsColors()
|
||||||
@ -1801,7 +1808,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
if maxLevelWithVideo == nil {
|
if maxLevelWithVideo == nil {
|
||||||
if let peerId = strongSelf.currentDominantSpeaker {
|
if let peerId = strongSelf.currentDominantSpeaker {
|
||||||
maxLevelWithVideo = (peerId, 0.0)
|
maxLevelWithVideo = (peerId.0, 0.0)
|
||||||
} else if strongSelf.peerIdToEndpoint.count > 0 {
|
} else if strongSelf.peerIdToEndpoint.count > 0 {
|
||||||
for entry in strongSelf.currentEntries {
|
for entry in strongSelf.currentEntries {
|
||||||
if case let .peer(peerEntry) = entry {
|
if case let .peer(peerEntry) = entry {
|
||||||
@ -1816,7 +1823,10 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
if case .fullscreen = strongSelf.displayMode {
|
if case .fullscreen = strongSelf.displayMode {
|
||||||
if let (peerId, _) = maxLevelWithVideo {
|
if let (peerId, _) = maxLevelWithVideo {
|
||||||
strongSelf.currentDominantSpeaker = peerId
|
if let peer = strongSelf.currentDominantSpeaker, CACurrentMediaTime() - peer.1 < 5.0 {
|
||||||
|
} else {
|
||||||
|
strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime())
|
||||||
|
}
|
||||||
strongSelf.updateMainVideo(waitForFullSize: false)
|
strongSelf.updateMainVideo(waitForFullSize: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1962,7 +1972,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.mainStageNode.togglePin = { [weak self] in
|
self.mainStageNode.togglePin = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let peerId = strongSelf.currentForcedSpeaker {
|
if let peerId = strongSelf.currentForcedSpeaker {
|
||||||
strongSelf.currentDominantSpeaker = peerId
|
strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime())
|
||||||
strongSelf.currentForcedSpeaker = nil
|
strongSelf.currentForcedSpeaker = nil
|
||||||
} else {
|
} else {
|
||||||
strongSelf.currentForcedSpeaker = strongSelf.effectiveSpeaker?.0
|
strongSelf.currentForcedSpeaker = strongSelf.effectiveSpeaker?.0
|
||||||
@ -3137,6 +3147,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
let videoBottomEdgeY = self.isLandscape ? layout.size.height : layout.size.height - layout.intrinsicInsets.bottom - 84.0
|
let videoBottomEdgeY = self.isLandscape ? layout.size.height : layout.size.height - layout.intrinsicInsets.bottom - 84.0
|
||||||
let videoFrame = CGRect(x: 0.0, y: videoTopEdgeY, width: isLandscape ? layout.size.width - layout.safeInsets.right - 84.0 : layout.size.width, height: videoBottomEdgeY - videoTopEdgeY)
|
let videoFrame = CGRect(x: 0.0, y: videoTopEdgeY, width: isLandscape ? layout.size.width - layout.safeInsets.right - 84.0 : layout.size.width, height: videoBottomEdgeY - videoTopEdgeY)
|
||||||
transition.updateFrame(node: self.mainStageContainerNode, frame: videoFrame)
|
transition.updateFrame(node: self.mainStageContainerNode, frame: videoFrame)
|
||||||
|
transition.updateFrame(node: self.mainStageBackgroundNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size))
|
||||||
if !self.mainStageNode.animating {
|
if !self.mainStageNode.animating {
|
||||||
transition.updateFrame(node: self.mainStageNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size))
|
transition.updateFrame(node: self.mainStageNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size))
|
||||||
}
|
}
|
||||||
@ -4331,26 +4342,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
if !validSources.contains(videoEndpointId) {
|
if !validSources.contains(videoEndpointId) {
|
||||||
self.videoNodes[videoEndpointId] = nil
|
self.videoNodes[videoEndpointId] = nil
|
||||||
self.readyVideoDisposables.set(nil, forKey: videoEndpointId)
|
self.readyVideoDisposables.set(nil, forKey: videoEndpointId)
|
||||||
|
|
||||||
// loop: for j in 0 ..< self.currentFullscreenEntries.count {
|
|
||||||
// let fullscreenEntry = self.currentFullscreenEntries[j]
|
|
||||||
// switch fullscreenEntry {
|
|
||||||
// case let .peer(peerEntry):
|
|
||||||
// if peerEntry.effectiveVideoEndpointId == videoEndpointId {
|
|
||||||
// let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
|
|
||||||
// self.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: fullscreenEntry.item(context: self.context, presentationData: presentationData, interaction: self.itemInteraction!), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
|
|
||||||
// break loop
|
|
||||||
// }
|
|
||||||
// default:
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMainVideo(waitForFullSize: Bool, updateMembers: Bool = true, force: Bool = false, completion: (() -> Void)? = nil) {
|
private func updateMainVideo(waitForFullSize: Bool, updateMembers: Bool = true, force: Bool = false, completion: (() -> Void)? = nil) {
|
||||||
let effectiveMainSpeaker = self.currentForcedSpeaker ?? self.currentDominantSpeaker
|
let effectiveMainSpeaker = self.currentForcedSpeaker ?? self.currentDominantSpeaker?.0
|
||||||
guard effectiveMainSpeaker != self.effectiveSpeaker?.0 || force else {
|
guard effectiveMainSpeaker != self.effectiveSpeaker?.0 || force else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -4474,7 +4471,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .modal(isExpanded, previousIsFilled) = self.effectiveDisplayMode {
|
switch self.effectiveDisplayMode {
|
||||||
|
case let .modal(isExpanded, previousIsFilled):
|
||||||
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
|
||||||
@ -4509,6 +4507,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
|
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case let .fullscreen(controlsHidden):
|
||||||
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
@ -5051,6 +5052,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
let effectiveSpeakerPeerId = self.effectiveSpeaker?.0
|
let effectiveSpeakerPeerId = self.effectiveSpeaker?.0
|
||||||
if let effectiveSpeakerPeerId = effectiveSpeakerPeerId, let otherItemNode = verticalItemNodes[effectiveSpeakerPeerId] {
|
if let effectiveSpeakerPeerId = effectiveSpeakerPeerId, let otherItemNode = verticalItemNodes[effectiveSpeakerPeerId] {
|
||||||
self.mainStageNode.animateTransitionIn(from: otherItemNode, transition: transition)
|
self.mainStageNode.animateTransitionIn(from: otherItemNode, transition: transition)
|
||||||
|
|
||||||
|
self.mainStageBackgroundNode.alpha = 1.0
|
||||||
|
self.mainStageBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fullscreenListNode.forEachItemNode { itemNode in
|
self.fullscreenListNode.forEachItemNode { itemNode in
|
||||||
@ -5125,6 +5129,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
self?.mainStageContainerNode.isHidden = true
|
self?.mainStageContainerNode.isHidden = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.mainStageBackgroundNode.alpha = 0.0
|
||||||
|
self.mainStageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||||
|
|
||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
|
||||||
|
|||||||
@ -253,12 +253,15 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
transition.updateFrame(view: snapshotView, frame: infoFrame)
|
transition.updateFrame(view: snapshotView, frame: infoFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceNode.alpha = 0.0
|
||||||
|
|
||||||
self.animatingIn = true
|
self.animatingIn = true
|
||||||
let startLocalFrame = sourceNode.view.convert(sourceNode.bounds, to: self.supernode?.view)
|
let startLocalFrame = sourceNode.view.convert(sourceNode.bounds, to: self.supernode?.view)
|
||||||
self.update(size: startLocalFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: .immediate)
|
self.update(size: startLocalFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: .immediate)
|
||||||
self.frame = startLocalFrame
|
self.frame = startLocalFrame
|
||||||
self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition)
|
self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition)
|
||||||
transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in
|
transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in
|
||||||
|
sourceNode.alpha = 1.0
|
||||||
self?.animatingIn = false
|
self?.animatingIn = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -300,12 +303,15 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
transition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(), size: targetFrame.size))
|
transition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(), size: targetFrame.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetNode.alpha = 0.0
|
||||||
|
|
||||||
self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition)
|
self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition)
|
||||||
transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in
|
transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
completion()
|
completion()
|
||||||
|
|
||||||
infoView?.removeFromSuperview()
|
infoView?.removeFromSuperview()
|
||||||
|
targetNode.alpha = 1.0
|
||||||
strongSelf.animatingOut = false
|
strongSelf.animatingOut = false
|
||||||
strongSelf.frame = initialFrame
|
strongSelf.frame = initialFrame
|
||||||
strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
|
strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
|
||||||
|
|||||||
@ -0,0 +1,184 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AppBundle
|
||||||
|
import ContextUI
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
|
final class VoiceChatShareScreenContextItem: ContextMenuCustomItem {
|
||||||
|
fileprivate let text: String
|
||||||
|
fileprivate let icon: (PresentationTheme) -> UIImage?
|
||||||
|
fileprivate let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
|
||||||
|
|
||||||
|
init(text: String, icon: @escaping (PresentationTheme) -> UIImage?, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
|
||||||
|
self.text = text
|
||||||
|
self.icon = icon
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||||
|
return VoiceChatShareScreenContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let textFont = Font.regular(17.0)
|
||||||
|
|
||||||
|
private final class VoiceChatShareScreenContextItemNode: ASDisplayNode, ContextMenuCustomNode {
|
||||||
|
private let item: VoiceChatShareScreenContextItem
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
private let getController: () -> ContextControllerProtocol?
|
||||||
|
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||||
|
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
private let textNode: ImmediateTextNode
|
||||||
|
private let iconNode: VoiceChatRecordingIconNode
|
||||||
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
|
|
||||||
|
private var timer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
private var pointerInteraction: PointerInteraction?
|
||||||
|
|
||||||
|
init(presentationData: PresentationData, item: VoiceChatShareScreenContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||||
|
self.item = item
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.getController = getController
|
||||||
|
self.actionSelected = actionSelected
|
||||||
|
|
||||||
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
|
||||||
|
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isAccessibilityElement = false
|
||||||
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
|
self.highlightedBackgroundNode.isAccessibilityElement = false
|
||||||
|
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
|
||||||
|
self.textNode = ImmediateTextNode()
|
||||||
|
self.textNode.isAccessibilityElement = false
|
||||||
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
self.textNode.displaysAsynchronously = false
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: item.text, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
|
||||||
|
self.textNode.maximumNumberOfLines = 1
|
||||||
|
|
||||||
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
|
self.buttonNode.isAccessibilityElement = true
|
||||||
|
self.buttonNode.accessibilityLabel = presentationData.strings.VoiceChat_StopRecording
|
||||||
|
|
||||||
|
self.iconNode = VoiceChatRecordingIconNode(hasBackground: true)
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.addSubnode(self.highlightedBackgroundNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.iconNode)
|
||||||
|
self.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
|
self.buttonNode.highligthedChanged = { [weak self] highligted in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if highligted {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
} else {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.timer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.75
|
||||||
|
}
|
||||||
|
}, willExit: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private var validLayout: CGSize?
|
||||||
|
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
let iconSideInset: CGFloat = 12.0
|
||||||
|
let verticalInset: CGFloat = 12.0
|
||||||
|
|
||||||
|
let iconSide = 16.0 + (1.0 + UIScreenPixel) * 2.0
|
||||||
|
let iconSize: CGSize = CGSize(width: iconSide, height: iconSide)
|
||||||
|
|
||||||
|
let standardIconWidth: CGFloat = 32.0
|
||||||
|
var rightTextInset: CGFloat = sideInset
|
||||||
|
if !iconSize.width.isZero {
|
||||||
|
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset
|
||||||
|
}
|
||||||
|
|
||||||
|
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
|
||||||
|
let verticalSpacing: CGFloat = 2.0
|
||||||
|
let combinedTextHeight = textSize.height + verticalSpacing
|
||||||
|
return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in
|
||||||
|
let hadLayout = self.validLayout != nil
|
||||||
|
self.validLayout = size
|
||||||
|
|
||||||
|
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
|
||||||
|
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||||
|
|
||||||
|
if !iconSize.width.isZero {
|
||||||
|
transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTheme(presentationData: PresentationData) {
|
||||||
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
|
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
|
||||||
|
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed() {
|
||||||
|
self.performAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
func performAction() {
|
||||||
|
guard let controller = self.getController() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.item.action(controller, { [weak self] result in
|
||||||
|
self?.actionSelected(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIsHighlighted(_ value: Bool) {
|
||||||
|
if value {
|
||||||
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
} else {
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user