mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Voice Chat UI improvements
This commit is contained in:
parent
57e9aa8123
commit
a2a722d87d
@ -93,6 +93,10 @@ public let listViewAnimationCurveLinear: (CGFloat) -> CGFloat = { t in
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public let listViewAnimationCurveEaseInOut: (CGFloat) -> CGFloat = { t in
|
||||||
|
return bezierPoint(0.42, 0.0, 0.58, 1.0, t)
|
||||||
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIView.AnimationOptions) -> (CGFloat) -> CGFloat {
|
public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIView.AnimationOptions) -> (CGFloat) -> CGFloat {
|
||||||
if animationOptions.rawValue == UInt(7 << 16) {
|
if animationOptions.rawValue == UInt(7 << 16) {
|
||||||
|
@ -119,6 +119,10 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
|||||||
|
|
||||||
final let wantsScrollDynamics: Bool
|
final let wantsScrollDynamics: Bool
|
||||||
|
|
||||||
|
open var preferredAnimationCurve: (CGFloat) -> CGFloat {
|
||||||
|
return listViewAnimationCurveSystem
|
||||||
|
}
|
||||||
|
|
||||||
public final var wantsTrailingItemSpaceUpdates: Bool = false
|
public final var wantsTrailingItemSpaceUpdates: Bool = false
|
||||||
|
|
||||||
public final var scrollPositioningInsets: UIEdgeInsets = UIEdgeInsets()
|
public final var scrollPositioningInsets: UIEdgeInsets = UIEdgeInsets()
|
||||||
@ -427,7 +431,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func addHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
|
public func addHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
|
||||||
let animation = ListViewAnimation(from: self.bounds.height, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in
|
let animation = ListViewAnimation(from: self.bounds.height, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] progress, currentValue in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let frame = strongSelf.frame
|
let frame = strongSelf.frame
|
||||||
strongSelf.frame = CGRect(origin: frame.origin, size: CGSize(width: frame.width, height: currentValue))
|
strongSelf.frame = CGRect(origin: frame.origin, size: CGSize(width: frame.width, height: currentValue))
|
||||||
@ -461,7 +465,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
|
public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
|
||||||
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in
|
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] progress, currentValue in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.apparentHeight = currentValue
|
strongSelf.apparentHeight = currentValue
|
||||||
if let update = update {
|
if let update = update {
|
||||||
@ -494,7 +498,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func addTransitionOffsetAnimation(_ value: CGFloat, duration: Double, beginAt: Double) {
|
public func addTransitionOffsetAnimation(_ value: CGFloat, duration: Double, beginAt: Double) {
|
||||||
let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in
|
let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] _, currentValue in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.transitionOffset = currentValue
|
strongSelf.transitionOffset = currentValue
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import AnimatedCountLabelNode
|
|||||||
private let blue = UIColor(rgb: 0x0078ff)
|
private let blue = UIColor(rgb: 0x0078ff)
|
||||||
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
||||||
private let green = UIColor(rgb: 0x33c659)
|
private let green = UIColor(rgb: 0x33c659)
|
||||||
|
private let activeBlue = UIColor(rgb: 0x00a0b9)
|
||||||
|
|
||||||
private class CallStatusBarBackgroundNode: ASDisplayNode {
|
private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||||
private let foregroundView: UIView
|
private let foregroundView: UIView
|
||||||
@ -46,7 +47,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
let initialColors = self.foregroundGradientLayer.colors
|
let initialColors = self.foregroundGradientLayer.colors
|
||||||
let targetColors: [CGColor]
|
let targetColors: [CGColor]
|
||||||
if let speaking = self.speaking {
|
if let speaking = self.speaking {
|
||||||
targetColors = speaking ? [green.cgColor, blue.cgColor] : [blue.cgColor, lightBlue.cgColor]
|
targetColors = speaking ? [green.cgColor, activeBlue.cgColor] : [blue.cgColor, lightBlue.cgColor]
|
||||||
} else {
|
} else {
|
||||||
targetColors = [connectingColor.cgColor, connectingColor.cgColor]
|
targetColors = [connectingColor.cgColor, connectingColor.cgColor]
|
||||||
}
|
}
|
||||||
@ -159,6 +160,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
private let backgroundNode: CallStatusBarBackgroundNode
|
private let backgroundNode: CallStatusBarBackgroundNode
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let subtitleNode: ImmediateAnimatedCountLabelNode
|
private let subtitleNode: ImmediateAnimatedCountLabelNode
|
||||||
|
private let speakerNode: ImmediateTextNode
|
||||||
|
|
||||||
private let audioLevelDisposable = MetaDisposable()
|
private let audioLevelDisposable = MetaDisposable()
|
||||||
private let stateDisposable = MetaDisposable()
|
private let stateDisposable = MetaDisposable()
|
||||||
@ -175,6 +177,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
private var currentCallState: PresentationCallState?
|
private var currentCallState: PresentationCallState?
|
||||||
private var currentGroupCallState: PresentationGroupCallSummaryState?
|
private var currentGroupCallState: PresentationGroupCallSummaryState?
|
||||||
private var currentIsMuted = true
|
private var currentIsMuted = true
|
||||||
|
private var currentMembers: PresentationGroupCallMembers?
|
||||||
private var currentIsConnected = true
|
private var currentIsConnected = true
|
||||||
|
|
||||||
public override init() {
|
public override init() {
|
||||||
@ -182,12 +185,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
self.subtitleNode = ImmediateAnimatedCountLabelNode()
|
self.subtitleNode = ImmediateAnimatedCountLabelNode()
|
||||||
self.subtitleNode.reverseAnimationDirection = true
|
self.subtitleNode.reverseAnimationDirection = true
|
||||||
|
self.speakerNode = ImmediateTextNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.subtitleNode)
|
self.addSubnode(self.subtitleNode)
|
||||||
|
self.addSubnode(self.speakerNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -257,12 +262,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
(combineLatest(
|
(combineLatest(
|
||||||
account.postbox.peerView(id: call.peerId),
|
account.postbox.peerView(id: call.peerId),
|
||||||
call.summaryState,
|
call.summaryState,
|
||||||
call.isMuted
|
call.isMuted,
|
||||||
|
call.members
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] view, state, isMuted in
|
|> deliverOnMainQueue).start(next: { [weak self] view, state, isMuted, members in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.currentPeer = view.peers[view.peerId]
|
strongSelf.currentPeer = view.peers[view.peerId]
|
||||||
strongSelf.currentGroupCallState = state
|
strongSelf.currentGroupCallState = state
|
||||||
|
strongSelf.currentMembers = members
|
||||||
|
|
||||||
var isMuted = isMuted
|
var isMuted = isMuted
|
||||||
if let state = state, let muteState = state.callState.muteState {
|
if let state = state, let muteState = state.callState.muteState {
|
||||||
@ -289,17 +296,18 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var effectiveLevel: Float = 0.0
|
var effectiveLevel: Float = 0.0
|
||||||
|
var audioLevels = audioLevels
|
||||||
if !strongSelf.currentIsMuted {
|
if !strongSelf.currentIsMuted {
|
||||||
effectiveLevel = myAudioLevel
|
audioLevels.append((PeerId(0), myAudioLevel, true))
|
||||||
} else {
|
|
||||||
effectiveLevel = audioLevels.map { $0.1 }.max() ?? 0.0
|
|
||||||
}
|
}
|
||||||
|
effectiveLevel = audioLevels.map { $0.1 }.max() ?? 0.0
|
||||||
strongSelf.backgroundNode.audioLevel = effectiveLevel
|
strongSelf.backgroundNode.audioLevel = effectiveLevel
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var title: String = ""
|
var title: String = ""
|
||||||
|
var speakerSubtitle: String = ""
|
||||||
|
|
||||||
let textFont = Font.regular(13.0)
|
let textFont = Font.regular(13.0)
|
||||||
let textColor = UIColor.white
|
let textColor = UIColor.white
|
||||||
@ -316,6 +324,21 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
membersCount = 1
|
membersCount = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var speakingPeer: Peer?
|
||||||
|
if let members = currentMembers {
|
||||||
|
var speakingPeers: [Peer] = []
|
||||||
|
for member in members.participants {
|
||||||
|
if members.speakingParticipants.contains(member.peer.id) {
|
||||||
|
speakingPeers.append(member.peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
speakingPeer = speakingPeers.first
|
||||||
|
}
|
||||||
|
|
||||||
|
if let speakingPeer = speakingPeer {
|
||||||
|
speakerSubtitle = speakingPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
}
|
||||||
|
|
||||||
if let membersCount = membersCount {
|
if let membersCount = membersCount {
|
||||||
var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount)
|
var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount)
|
||||||
if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") {
|
if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") {
|
||||||
@ -366,15 +389,24 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
self.backgroundNode.connectingColor = color
|
self.backgroundNode.connectingColor = color
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.subtitleNode.segments != segments {
|
if self.subtitleNode.segments != segments && speakerSubtitle.isEmpty {
|
||||||
self.subtitleNode.segments = segments
|
self.subtitleNode.segments = segments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||||
|
alphaTransition.updateAlpha(node: self.subtitleNode, alpha: !speakerSubtitle.isEmpty ? 0.0 : 1.0)
|
||||||
|
alphaTransition.updateAlpha(node: self.speakerNode, alpha: !speakerSubtitle.isEmpty ? 1.0 : 0.0)
|
||||||
|
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white)
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white)
|
||||||
|
|
||||||
|
if !speakerSubtitle.isEmpty {
|
||||||
|
self.speakerNode.attributedText = NSAttributedString(string: speakerSubtitle, font: Font.regular(13.0), textColor: .white)
|
||||||
|
}
|
||||||
|
|
||||||
let spacing: CGFloat = 5.0
|
let spacing: CGFloat = 5.0
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: size.height))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: size.height))
|
||||||
let subtitleSize = self.subtitleNode.updateLayout(size: CGSize(width: 160.0, height: size.height), animated: true)
|
let subtitleSize = self.subtitleNode.updateLayout(size: CGSize(width: 160.0, height: size.height), animated: true)
|
||||||
|
let speakerSize = self.speakerNode.updateLayout(CGSize(width: 160.0, height: size.height))
|
||||||
|
|
||||||
let totalWidth = titleSize.width + spacing + subtitleSize.width
|
let totalWidth = titleSize.width + spacing + subtitleSize.width
|
||||||
let horizontalOrigin: CGFloat = floor((size.width - totalWidth) / 2.0)
|
let horizontalOrigin: CGFloat = floor((size.width - totalWidth) / 2.0)
|
||||||
@ -386,6 +418,10 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin + floor((contentHeight - titleSize.height) / 2.0)), size: titleSize))
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin + floor((contentHeight - titleSize.height) / 2.0)), size: titleSize))
|
||||||
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize))
|
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize))
|
||||||
|
|
||||||
|
if !speakerSubtitle.isEmpty {
|
||||||
|
self.speakerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - speakerSize.height) / 2.0)), size: speakerSize)
|
||||||
|
}
|
||||||
|
|
||||||
self.backgroundNode.speaking = self.currentIsConnected ? !self.currentIsMuted : nil
|
self.backgroundNode.speaking = self.currentIsConnected ? !self.currentIsMuted : nil
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 18.0))
|
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 18.0))
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ private let secondaryGreyColor = UIColor(rgb: 0x1c1c1e)
|
|||||||
private let blue = UIColor(rgb: 0x0078ff)
|
private let blue = UIColor(rgb: 0x0078ff)
|
||||||
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
||||||
private let green = UIColor(rgb: 0x33c659)
|
private let green = UIColor(rgb: 0x33c659)
|
||||||
|
private let activeBlue = UIColor(rgb: 0x00a0b9)
|
||||||
|
|
||||||
private let areaSize = CGSize(width: 440.0, height: 440.0)
|
private let areaSize = CGSize(width: 440.0, height: 440.0)
|
||||||
private let blobSize = CGSize(width: 244.0, height: 244.0)
|
private let blobSize = CGSize(width: 244.0, height: 244.0)
|
||||||
@ -626,7 +627,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
let targetScale: CGFloat
|
let targetScale: CGFloat
|
||||||
if let active = active {
|
if let active = active {
|
||||||
if active {
|
if active {
|
||||||
targetColors = [blue.cgColor, green.cgColor]
|
targetColors = [activeBlue.cgColor, green.cgColor]
|
||||||
targetScale = 0.89
|
targetScale = 0.89
|
||||||
outerColor = UIColor(rgb: 0x21674f)
|
outerColor = UIColor(rgb: 0x21674f)
|
||||||
} else {
|
} else {
|
||||||
|
@ -257,4 +257,8 @@ class VoiceChatActionItemNode: ListViewItemNode {
|
|||||||
override public func header() -> ListViewItemHeader? {
|
override public func header() -> ListViewItemHeader? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var preferredAnimationCurve: (CGFloat) -> CGFloat {
|
||||||
|
return listViewAnimationCurveEaseInOut
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
let isEmpty: Bool
|
let isEmpty: Bool
|
||||||
let crossFade: Bool
|
let crossFade: Bool
|
||||||
let count: Int
|
let count: Int
|
||||||
|
let isExpanded: Bool
|
||||||
let animated: Bool
|
let animated: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,14 +363,14 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func preparedTransition(from fromEntries: [ListEntry], to toEntries: [ListEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: PresentationData, interaction: Interaction) -> ListTransition {
|
private func preparedTransition(from fromEntries: [ListEntry], to toEntries: [ListEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: PresentationData, interaction: Interaction, isExpanded: Bool) -> ListTransition {
|
||||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
||||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
||||||
|
|
||||||
return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade, count: toEntries.count, animated: fromEntries.count != toEntries.count)
|
return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade, count: toEntries.count, isExpanded: isExpanded, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private weak var controller: VoiceChatController?
|
private weak var controller: VoiceChatController?
|
||||||
@ -413,6 +414,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
|
private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
|
||||||
private var currentInvitedPeers: [Peer]?
|
private var currentInvitedPeers: [Peer]?
|
||||||
private var currentSpeakingPeers: Set<PeerId>?
|
private var currentSpeakingPeers: Set<PeerId>?
|
||||||
|
private var currentIsExpanded: Bool = false
|
||||||
|
private var currentContentOffset: CGFloat?
|
||||||
|
private var ignoreScrolling = false
|
||||||
private var accountPeer: Peer?
|
private var accountPeer: Peer?
|
||||||
private var currentAudioButtonColor: UIColor?
|
private var currentAudioButtonColor: UIColor?
|
||||||
|
|
||||||
@ -467,12 +471,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor
|
self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor
|
||||||
|
self.backgroundNode.clipsToBounds = false
|
||||||
|
|
||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
|
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
|
||||||
self.listNode.clipsToBounds = true
|
self.listNode.clipsToBounds = true
|
||||||
// self.listNode.stackFromBottom = true
|
|
||||||
// self.listNode.keepMinimalScrollHeightWithTopInset = 0
|
|
||||||
|
|
||||||
self.topPanelNode = ASDisplayNode()
|
self.topPanelNode = ASDisplayNode()
|
||||||
self.topPanelNode.clipsToBounds = false
|
self.topPanelNode.clipsToBounds = false
|
||||||
@ -483,8 +486,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.topPanelEdgeNode = ASDisplayNode()
|
self.topPanelEdgeNode = ASDisplayNode()
|
||||||
self.topPanelEdgeNode.backgroundColor = panelBackgroundColor
|
self.topPanelEdgeNode.backgroundColor = panelBackgroundColor
|
||||||
self.topPanelEdgeNode.layer.cornerRadius = 12.0
|
self.topPanelEdgeNode.cornerRadius = 12.0
|
||||||
self.topPanelEdgeNode.isUserInteractionEnabled = false
|
self.topPanelEdgeNode.isUserInteractionEnabled = false
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
self.topPanelEdgeNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
|
}
|
||||||
|
|
||||||
self.optionsButton = VoiceChatHeaderButton()
|
self.optionsButton = VoiceChatHeaderButton()
|
||||||
self.optionsButton.setImage(optionsButtonImage(dark: false))
|
self.optionsButton.setImage(optionsButtonImage(dark: false))
|
||||||
@ -517,10 +523,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.leftBorderNode = ASDisplayNode()
|
self.leftBorderNode = ASDisplayNode()
|
||||||
self.leftBorderNode.backgroundColor = panelBackgroundColor
|
self.leftBorderNode.backgroundColor = panelBackgroundColor
|
||||||
self.leftBorderNode.isUserInteractionEnabled = false
|
self.leftBorderNode.isUserInteractionEnabled = false
|
||||||
|
self.leftBorderNode.clipsToBounds = false
|
||||||
|
|
||||||
self.rightBorderNode = ASDisplayNode()
|
self.rightBorderNode = ASDisplayNode()
|
||||||
self.rightBorderNode.backgroundColor = panelBackgroundColor
|
self.rightBorderNode.backgroundColor = panelBackgroundColor
|
||||||
self.rightBorderNode.isUserInteractionEnabled = false
|
self.rightBorderNode.isUserInteractionEnabled = false
|
||||||
|
self.rightBorderNode.clipsToBounds = false
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -878,7 +886,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: callMembers?.participants ?? [], invitedPeers: invitedPeers, speakingPeers: callMembers?.speakingParticipants ?? [])
|
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: callMembers?.participants ?? [], invitedPeers: invitedPeers, speakingPeers: callMembers?.speakingParticipants ?? [], isExpanded: strongSelf.currentIsExpanded)
|
||||||
|
|
||||||
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers?.totalCount ?? 0)))
|
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers?.totalCount ?? 0)))
|
||||||
strongSelf.currentSubtitle = subtitle
|
strongSelf.currentSubtitle = subtitle
|
||||||
@ -899,7 +907,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
if !strongSelf.didSetDataReady {
|
if !strongSelf.didSetDataReady {
|
||||||
strongSelf.accountPeer = accountPeer
|
strongSelf.accountPeer = accountPeer
|
||||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), isExpanded: strongSelf.currentIsExpanded)
|
||||||
|
|
||||||
if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
|
if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
|
||||||
let addressName = channel.addressName ?? ""
|
let addressName = channel.addressName ?? ""
|
||||||
@ -1091,6 +1099,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.currentContentOffset = offset
|
||||||
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
|
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1099,10 +1108,27 @@ public final class VoiceChatController: ViewController {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch strongSelf.listNode.visibleContentOffset() {
|
if strongSelf.ignoreScrolling {
|
||||||
|
Queue.mainQueue().after(0.5) {
|
||||||
|
strongSelf.ignoreScrolling = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listNode.visibleContentOffsetChanged = { [weak self] offset in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch offset {
|
||||||
case let .known(value):
|
case let .known(value):
|
||||||
if value <= -10.0 {
|
// strongSelf.updateFloatingHeaderOffset(offset: -value + strongSelf.listNode.insets.top, transition: strongSelf.listNode.isTracking ? .immediate : .animated(duration: 0.4, curve: .linear))
|
||||||
// strongSelf.controller?.dismiss()
|
|
||||||
|
if value > 5, !strongSelf.currentIsExpanded && strongSelf.listNode.isTracking && !strongSelf.ignoreScrolling {
|
||||||
|
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers:strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), isExpanded: true)
|
||||||
|
strongSelf.ignoreScrolling = true
|
||||||
|
} else if value < -5, strongSelf.currentIsExpanded && strongSelf.listNode.isTracking && !strongSelf.ignoreScrolling {
|
||||||
|
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? [], isExpanded: false)
|
||||||
|
strongSelf.ignoreScrolling = true
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -1197,7 +1223,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set(), isExpanded: self.currentIsExpanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
@objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||||
@ -1242,7 +1268,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set(), isExpanded: self.currentIsExpanded)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1316,82 +1342,82 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
guard let (validLayout, _) = self.validLayout else {
|
guard let (layout, _) = self.validLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.floatingHeaderOffset = offset
|
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||||
|
|
||||||
let layoutTopInset: CGFloat = max(validLayout.statusBarHeight ?? 0.0, validLayout.safeInsets.top)
|
|
||||||
|
|
||||||
let topPanelHeight: CGFloat = 63.0
|
let topPanelHeight: CGFloat = 63.0
|
||||||
let listTopInset = layoutTopInset + topPanelHeight
|
let listTopInset = layoutTopInset + topPanelHeight
|
||||||
|
let bottomAreaHeight: CGFloat = 268.0
|
||||||
|
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
||||||
|
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
||||||
|
let topInset = self.topInset ?? listSize.height
|
||||||
|
|
||||||
|
var offset = offset + topInset
|
||||||
|
self.floatingHeaderOffset = offset
|
||||||
|
|
||||||
let rawPanelOffset = offset + listTopInset - topPanelHeight
|
let rawPanelOffset = offset + listTopInset - topPanelHeight
|
||||||
let panelOffset = max(layoutTopInset, rawPanelOffset)
|
let panelOffset = max(layoutTopInset, rawPanelOffset)
|
||||||
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: validLayout.size.width, height: topPanelHeight))
|
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: layout.size.width, height: topPanelHeight))
|
||||||
|
|
||||||
|
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: layout.size.width, height: layout.size.height))
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
let leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: layout.size.height))
|
||||||
|
let rightBorderFrame = CGRect(origin: CGPoint(x: layout.size.width - sideInset, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: layout.size.height))
|
||||||
|
|
||||||
let previousTopPanelFrame = self.topPanelNode.frame
|
let previousTopPanelFrame = self.topPanelNode.frame
|
||||||
|
let previousBackgroundFrame = self.backgroundNode.frame
|
||||||
|
let previousLeftBorderFrame = self.leftBorderNode.frame
|
||||||
|
let previousRightBorderFrame = self.rightBorderNode.frame
|
||||||
|
|
||||||
if !topPanelFrame.equalTo(previousTopPanelFrame) {
|
if !topPanelFrame.equalTo(previousTopPanelFrame) {
|
||||||
self.topPanelNode.frame = topPanelFrame
|
self.topPanelNode.frame = topPanelFrame
|
||||||
|
let positionDelta = CGPoint(x: 0.0, y: topPanelFrame.minY - previousTopPanelFrame.minY)
|
||||||
let positionDelta = CGPoint(x: topPanelFrame.minX - previousTopPanelFrame.minX, y: topPanelFrame.minY - previousTopPanelFrame.minY)
|
|
||||||
transition.animateOffsetAdditive(node: self.topPanelNode, offset: positionDelta.y)
|
transition.animateOffsetAdditive(node: self.topPanelNode, offset: positionDelta.y)
|
||||||
|
|
||||||
|
self.backgroundNode.frame = backgroundFrame
|
||||||
|
let backgroundPositionDelta = CGPoint(x: 0.0, y: previousBackgroundFrame.minY - backgroundFrame.minY)
|
||||||
|
transition.animatePositionAdditive(node: self.backgroundNode, offset: backgroundPositionDelta)
|
||||||
|
|
||||||
|
self.leftBorderNode.frame = leftBorderFrame
|
||||||
|
let leftBorderPositionDelta = CGPoint(x: 0.0, y: previousLeftBorderFrame.minY - leftBorderFrame.minY)
|
||||||
|
transition.animatePositionAdditive(node: self.leftBorderNode, offset: leftBorderPositionDelta)
|
||||||
|
|
||||||
|
self.rightBorderNode.frame = rightBorderFrame
|
||||||
|
let rightBorderPositionDelta = CGPoint(x: 0.0, y: previousRightBorderFrame.minY - rightBorderFrame.minY)
|
||||||
|
transition.animatePositionAdditive(node: self.rightBorderNode, offset: rightBorderPositionDelta)
|
||||||
}
|
}
|
||||||
self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: validLayout.size.width, height: 24.0)
|
self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: layout.size.width, height: 24.0)
|
||||||
self.topPanelEdgeNode.frame = CGRect(x: 0.0, y: 0.0, width: validLayout.size.width, height: topPanelHeight)
|
|
||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: validLayout.size.width, height: validLayout.size.height))
|
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
|
||||||
let leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: validLayout.size.height))
|
|
||||||
let rightBorderFrame = CGRect(origin: CGPoint(x: validLayout.size.width - sideInset, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: validLayout.size.height))
|
|
||||||
|
|
||||||
var bottomEdge: CGFloat = 0.0
|
var bottomEdge: CGFloat = 0.0
|
||||||
self.listNode.forEachItemNode { itemNode in
|
self.listNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? ListViewItemNode {
|
if let itemNode = itemNode as? ListViewItemNode {
|
||||||
let convertedFrame = self.listNode.view.convert(itemNode.apparentFrame, to: self.view)
|
let convertedFrame = self.listNode.view.convert(itemNode.frame, to: self.view)
|
||||||
if convertedFrame.maxY > bottomEdge {
|
if convertedFrame.maxY > bottomEdge {
|
||||||
bottomEdge = convertedFrame.maxY
|
bottomEdge = convertedFrame.maxY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let listMaxY = listTopInset + listSize.height
|
||||||
|
if bottomEdge.isZero {
|
||||||
|
bottomEdge = listMaxY
|
||||||
|
}
|
||||||
var bottomOffset: CGFloat = 0.0
|
var bottomOffset: CGFloat = 0.0
|
||||||
if bottomEdge < self.listNode.frame.maxY {
|
if bottomEdge < listMaxY {
|
||||||
bottomOffset = bottomEdge - self.listNode.frame.maxY
|
bottomOffset = bottomEdge - listMaxY
|
||||||
}
|
}
|
||||||
|
|
||||||
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset), size: CGSize(width: validLayout.size.width - sideInset * 2.0, height: 50.0))
|
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset), size: CGSize(width: layout.size.width - sideInset * 2.0, height: 50.0))
|
||||||
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: validLayout.size.width, height: 2000.0)
|
self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset, width: layout.size.width, height: 2000.0)
|
||||||
|
|
||||||
let positionDelta = CGPoint(x: bottomCornersFrame.minX - previousBottomCornersFrame.minX, y: bottomCornersFrame.minY - previousBottomCornersFrame.minY)
|
let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY)
|
||||||
transition.animateOffsetAdditive(node: self.bottomCornersNode, offset: positionDelta.y)
|
transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta)
|
||||||
transition.animateOffsetAdditive(node: self.bottomPanelBackgroundNode, offset: positionDelta.y)
|
transition.animatePositionAdditive(node: self.bottomPanelBackgroundNode, offset: positionDelta)
|
||||||
}
|
|
||||||
|
|
||||||
let previousBackgroundFrame = self.backgroundNode.frame
|
|
||||||
let previousLeftBorderFrame = self.leftBorderNode.frame
|
|
||||||
let previousRightBorderFrame = self.rightBorderNode.frame
|
|
||||||
|
|
||||||
self.updateColors(fullscreen: abs(panelOffset - layoutTopInset) < 1.0)
|
|
||||||
|
|
||||||
if !backgroundFrame.equalTo(previousBackgroundFrame) {
|
|
||||||
self.backgroundNode.frame = backgroundFrame
|
|
||||||
self.leftBorderNode.frame = leftBorderFrame
|
|
||||||
self.rightBorderNode.frame = rightBorderFrame
|
|
||||||
|
|
||||||
let backgroundPositionDelta = CGPoint(x: backgroundFrame.minX - previousBackgroundFrame.minX, y: backgroundFrame.minY - previousBackgroundFrame.minY)
|
|
||||||
transition.animateOffsetAdditive(node: self.backgroundNode, offset: backgroundPositionDelta.y)
|
|
||||||
|
|
||||||
let leftBorderPositionDelta = CGPoint(x: leftBorderFrame.minX - previousLeftBorderFrame.minX, y: leftBorderFrame.minY - previousLeftBorderFrame.minY)
|
|
||||||
transition.animateOffsetAdditive(node: self.leftBorderNode, offset: leftBorderPositionDelta.y)
|
|
||||||
|
|
||||||
let rightBorderPositionDelta = CGPoint(x: rightBorderFrame.minX - previousRightBorderFrame.minX, y: rightBorderFrame.minY - previousRightBorderFrame.minY)
|
|
||||||
transition.animateOffsetAdditive(node: self.rightBorderNode, offset: rightBorderPositionDelta.y)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1406,8 +1432,22 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
|
||||||
|
|
||||||
// transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: fullscreen ? layout.deviceMetrics.screenCornerRadius : 12.0)
|
let topPanelHeight: CGFloat = 63.0
|
||||||
transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor)
|
let topEdgeFrame: CGRect
|
||||||
|
if self.isFullscreen {
|
||||||
|
let offset: CGFloat
|
||||||
|
if let statusBarHeight = layout.statusBarHeight {
|
||||||
|
offset = statusBarHeight
|
||||||
|
} else {
|
||||||
|
offset = 44.0
|
||||||
|
}
|
||||||
|
topEdgeFrame = CGRect(x: 0.0, y: -offset, width: layout.size.width, height: topPanelHeight + offset)
|
||||||
|
} else {
|
||||||
|
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight)
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
|
||||||
|
transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: fullscreen ? layout.deviceMetrics.screenCornerRadius - 0.5 : 12.0)
|
||||||
|
// transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor)
|
||||||
transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
||||||
transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
||||||
transition.updateBackgroundColor(node: self.backgroundNode, color: fullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor)
|
transition.updateBackgroundColor(node: self.backgroundNode, color: fullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor)
|
||||||
@ -1542,13 +1582,26 @@ public final class VoiceChatController: ViewController {
|
|||||||
insets.left = layout.safeInsets.left + sideInset
|
insets.left = layout.safeInsets.left + sideInset
|
||||||
insets.right = layout.safeInsets.right + sideInset
|
insets.right = layout.safeInsets.right + sideInset
|
||||||
|
|
||||||
|
let topPanelHeight: CGFloat = 63.0
|
||||||
|
let topEdgeFrame: CGRect
|
||||||
|
if self.isFullscreen {
|
||||||
|
let offset: CGFloat
|
||||||
|
if let statusBarHeight = layout.statusBarHeight {
|
||||||
|
offset = statusBarHeight
|
||||||
|
} else {
|
||||||
|
offset = 44.0
|
||||||
|
}
|
||||||
|
topEdgeFrame = CGRect(x: 0.0, y: -offset, width: layout.size.width, height: topPanelHeight + offset)
|
||||||
|
} else {
|
||||||
|
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight)
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
|
||||||
|
|
||||||
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
||||||
let listTopInset = layoutTopInset + 63.0
|
let listTopInset = layoutTopInset + topPanelHeight
|
||||||
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
||||||
|
|
||||||
insets.top = max(0.0, self.topInset ?? listSize.height)
|
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + (self.topInset ?? listSize.height)), size: listSize))
|
||||||
|
|
||||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listSize))
|
|
||||||
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
|
||||||
@ -1672,7 +1725,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
let topPanelFrame = self.topPanelNode.view.convert(self.topPanelNode.bounds, to: self.view)
|
let topPanelFrame = self.topPanelNode.view.convert(self.topPanelNode.bounds, to: self.view)
|
||||||
|
|
||||||
self.contentContainer.layer.animateBoundsOriginYAdditive(from: self.contentContainer.bounds.origin.y, to: -(layout.size.height - topPanelFrame.minY), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
self.contentContainer.layer.animateBoundsOriginYAdditive(from: self.contentContainer.bounds.origin.y, to: -(layout.size.height - topPanelFrame.minY) - 44.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||||
offsetCompleted = true
|
offsetCompleted = true
|
||||||
internalCompletion()
|
internalCompletion()
|
||||||
})
|
})
|
||||||
@ -1698,7 +1751,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.enqueuedTransitions.remove(at: 0)
|
self.enqueuedTransitions.remove(at: 0)
|
||||||
|
|
||||||
var options = ListViewDeleteAndInsertOptions()
|
var options = ListViewDeleteAndInsertOptions()
|
||||||
if !isFirstTime {
|
if self.isFirstTime {
|
||||||
|
self.isFirstTime = false
|
||||||
|
} else {
|
||||||
if transition.crossFade {
|
if transition.crossFade {
|
||||||
options.insert(.AnimateCrossfade)
|
options.insert(.AnimateCrossfade)
|
||||||
}
|
}
|
||||||
@ -1709,12 +1764,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
options.insert(.LowLatency)
|
options.insert(.LowLatency)
|
||||||
options.insert(.PreferSynchronousResourceLoading)
|
options.insert(.PreferSynchronousResourceLoading)
|
||||||
|
|
||||||
var scrollToItem: ListViewScrollToItem?
|
|
||||||
if self.isFirstTime {
|
|
||||||
self.isFirstTime = false
|
|
||||||
// scrollToItem = ListViewScrollToItem(index: 0, position: .bottom(0), animated: false, curve: .Default(duration: nil), directionHint: .Up)
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemsHeight: CGFloat = 46.0 + CGFloat(transition.count - 1) * 56.0
|
var itemsHeight: CGFloat = 46.0 + CGFloat(transition.count - 1) * 56.0
|
||||||
|
|
||||||
let bottomAreaHeight: CGFloat = 268.0
|
let bottomAreaHeight: CGFloat = 268.0
|
||||||
@ -1729,13 +1778,24 @@ public final class VoiceChatController: ViewController {
|
|||||||
let listTopInset = layoutTopInset + 63.0
|
let listTopInset = layoutTopInset + 63.0
|
||||||
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
||||||
|
|
||||||
insets.top = max(0.0, max(listSize.height - itemsHeight, listSize.height - 46.0 - floor(56.0 * 3.5)))
|
let previousIsExpanded = self.currentIsExpanded
|
||||||
self.topInset = insets.top
|
self.currentIsExpanded = transition.isExpanded
|
||||||
|
self.topInset = max(0.0, transition.isExpanded ? 0.0 : max(listSize.height - itemsHeight, listSize.height - 46.0 - floor(56.0 * 3.5)))
|
||||||
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: .animated(duration: 0.4, curve: .spring))
|
let frameTransition: ContainedViewLayoutTransition
|
||||||
|
if previousIsExpanded != self.currentIsExpanded {
|
||||||
|
frameTransition = .animated(duration: 0.4, curve: .spring)
|
||||||
|
} else {
|
||||||
|
frameTransition = .animated(duration: 0.4, curve: .easeInOut)
|
||||||
|
}
|
||||||
|
frameTransition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + (self.topInset ?? listSize.height)), size: listSize))
|
||||||
|
|
||||||
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: frameTransition)
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
|
||||||
|
|
||||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, updateOpaqueState: nil, completion: { [weak self] _ in
|
self.updateColors(fullscreen: transition.isExpanded)
|
||||||
|
|
||||||
|
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1744,9 +1804,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
strongSelf.controller?.contentsReady.set(true)
|
strongSelf.controller?.contentsReady.set(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if previousIsExpanded != self.currentIsExpanded {
|
||||||
|
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: frameTransition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>) {
|
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>, isExpanded: Bool) {
|
||||||
self.currentCallMembers = callMembers
|
self.currentCallMembers = callMembers
|
||||||
self.currentSpeakingPeers = speakingPeers
|
self.currentSpeakingPeers = speakingPeers
|
||||||
self.currentInvitedPeers = invitedPeers
|
self.currentInvitedPeers = invitedPeers
|
||||||
@ -1811,7 +1874,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.currentEntries = entries
|
self.currentEntries = entries
|
||||||
|
|
||||||
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
|
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
|
||||||
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!)
|
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!, isExpanded: isExpanded ?? self.currentIsExpanded)
|
||||||
self.enqueueTransition(transition)
|
self.enqueueTransition(transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ import AccountContext
|
|||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import AudioBlob
|
import AudioBlob
|
||||||
|
|
||||||
public final class VoiceChatParticipantItem: ListViewItem {
|
final class VoiceChatParticipantItem: ListViewItem {
|
||||||
public enum ParticipantText {
|
enum ParticipantText {
|
||||||
public enum TextColor {
|
public enum TextColor {
|
||||||
case generic
|
case generic
|
||||||
case accent
|
case accent
|
||||||
@ -31,25 +31,25 @@ public final class VoiceChatParticipantItem: ListViewItem {
|
|||||||
case none
|
case none
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Icon {
|
enum Icon {
|
||||||
case none
|
case none
|
||||||
case microphone(Bool, UIColor)
|
case microphone(Bool, UIColor)
|
||||||
case invite(Bool)
|
case invite(Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct RevealOption {
|
struct RevealOption {
|
||||||
public enum RevealOptionType {
|
enum RevealOptionType {
|
||||||
case neutral
|
case neutral
|
||||||
case warning
|
case warning
|
||||||
case destructive
|
case destructive
|
||||||
case accent
|
case accent
|
||||||
}
|
}
|
||||||
|
|
||||||
public var type: RevealOptionType
|
var type: RevealOptionType
|
||||||
public var title: String
|
var title: String
|
||||||
public var action: () -> Void
|
var action: () -> Void
|
||||||
|
|
||||||
public init(type: RevealOptionType, title: String, action: @escaping () -> Void) {
|
init(type: RevealOptionType, title: String, action: @escaping () -> Void) {
|
||||||
self.type = type
|
self.type = type
|
||||||
self.title = title
|
self.title = title
|
||||||
self.action = action
|
self.action = action
|
||||||
@ -138,7 +138,7 @@ public final class VoiceChatParticipantItem: ListViewItem {
|
|||||||
|
|
||||||
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
|
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
|
||||||
|
|
||||||
public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
@ -170,7 +170,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||||
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
||||||
|
|
||||||
public init() {
|
init() {
|
||||||
self.topStripeNode = ASDisplayNode()
|
self.topStripeNode = ASDisplayNode()
|
||||||
self.topStripeNode.isLayerBacked = true
|
self.topStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
@ -281,7 +281,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.audioLevelDisposable.dispose()
|
self.audioLevelDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func asyncLayout() -> (_ item: VoiceChatParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
|
func asyncLayout() -> (_ item: VoiceChatParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||||
var currentDisabledOverlayNode = self.disabledOverlayNode
|
var currentDisabledOverlayNode = self.disabledOverlayNode
|
||||||
@ -706,7 +706,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||||
|
|
||||||
self.isHighlighted = highlighted
|
self.isHighlighted = highlighted
|
||||||
@ -714,20 +714,19 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.updateIsHighlighted(transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
|
self.updateIsHighlighted(transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func header() -> ListViewItemHeader? {
|
||||||
override public func header() -> ListViewItemHeader? {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
var rect = rect
|
var rect = rect
|
||||||
rect.origin.y += self.insets.top
|
rect.origin.y += self.insets.top
|
||||||
self.absoluteLocation = (rect, containerSize)
|
self.absoluteLocation = (rect, containerSize)
|
||||||
@ -739,7 +738,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
super.updateRevealOffset(offset: offset, transition: transition)
|
super.updateRevealOffset(offset: offset, transition: transition)
|
||||||
|
|
||||||
if let _ = self.layoutParams?.0, let params = self.layoutParams?.1 {
|
if let _ = self.layoutParams?.0, let params = self.layoutParams?.1 {
|
||||||
@ -761,19 +760,19 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func revealOptionsInteractivelyOpened() {
|
override func revealOptionsInteractivelyOpened() {
|
||||||
if let item = self.layoutParams?.0 {
|
if let item = self.layoutParams?.0 {
|
||||||
item.setPeerIdWithRevealedOptions(item.peer.id, nil)
|
item.setPeerIdWithRevealedOptions(item.peer.id, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func revealOptionsInteractivelyClosed() {
|
override func revealOptionsInteractivelyClosed() {
|
||||||
if let item = self.layoutParams?.0 {
|
if let item = self.layoutParams?.0 {
|
||||||
item.setPeerIdWithRevealedOptions(nil, item.peer.id)
|
item.setPeerIdWithRevealedOptions(nil, item.peer.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
||||||
if let item = self.layoutParams?.0 {
|
if let item = self.layoutParams?.0 {
|
||||||
item.revealOptions[Int(option.key)].action()
|
item.revealOptions[Int(option.key)].action()
|
||||||
}
|
}
|
||||||
@ -781,4 +780,8 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.setRevealOptionsOpened(false, animated: true)
|
self.setRevealOptionsOpened(false, animated: true)
|
||||||
self.revealOptionsInteractivelyClosed()
|
self.revealOptionsInteractivelyClosed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var preferredAnimationCurve: (CGFloat) -> CGFloat {
|
||||||
|
return listViewAnimationCurveEaseInOut
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import AccountContext
|
|||||||
import StickerResources
|
import StickerResources
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import Markdown
|
import Markdown
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
private let nameFont = Font.medium(14.0)
|
private let nameFont = Font.medium(14.0)
|
||||||
private let inlineBotPrefixFont = Font.regular(14.0)
|
private let inlineBotPrefixFont = Font.regular(14.0)
|
||||||
@ -21,6 +22,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
private let containerNode: ContextControllerSourceNode
|
private let containerNode: ContextControllerSourceNode
|
||||||
let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
|
private var placeholderNode: StickerShimmerEffectNode?
|
||||||
var textNode: TextNode?
|
var textNode: TextNode?
|
||||||
|
|
||||||
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
|
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
|
||||||
@ -50,6 +52,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
|
self.placeholderNode = StickerShimmerEffectNode()
|
||||||
|
self.placeholderNode?.isUserInteractionEnabled = false
|
||||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||||
|
|
||||||
super.init(layerBacked: false)
|
super.init(layerBacked: false)
|
||||||
@ -96,6 +100,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
self.containerNode.addSubnode(self.contextSourceNode)
|
self.containerNode.addSubnode(self.contextSourceNode)
|
||||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.contextSourceNode.contentNode.addSubnode(placeholderNode)
|
||||||
|
}
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
||||||
|
|
||||||
@ -115,6 +122,21 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
self.fetchDisposable.dispose()
|
self.fetchDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func removePlaceholder(animated: Bool) {
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.placeholderNode = nil
|
||||||
|
if !animated {
|
||||||
|
placeholderNode.removeFromSupernode()
|
||||||
|
} else {
|
||||||
|
placeholderNode.alpha = 0.0
|
||||||
|
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||||
|
placeholderNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user