Voice Chat UI improvements

This commit is contained in:
Ilya Laktyushin 2020-12-12 03:40:31 +04:00
parent 57e9aa8123
commit a2a722d87d
8 changed files with 251 additions and 114 deletions

View File

@ -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) {

View File

@ -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
} }

View File

@ -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))
} }

View File

@ -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 {

View File

@ -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
}
} }

View File

@ -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)
} }

View File

@ -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
}
} }

View File

@ -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()