Story header view

This commit is contained in:
Ali 2023-06-23 00:10:59 +03:00
parent e4dfc81876
commit f8a1457732
12 changed files with 803 additions and 361 deletions

View File

@ -326,7 +326,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
switch strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.visibleContentOffset() {
case .none, .unknown:
strongSelf.chatListDisplayNode.willScrollToTop()
strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.scrollToPosition(.top)
strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.scrollToPosition(.top(adjustForTempInset: false))
case let .known(offset):
let isFirstFilter = strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.chatListFilter == strongSelf.chatListDisplayNode.mainContainerNode.availableFilters.first?.filter
@ -349,9 +349,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.chatListDisplayNode.willScrollToTop()
if let inlineStackContainerNode = strongSelf.chatListDisplayNode.inlineStackContainerNode {
inlineStackContainerNode.currentItemNode.scrollToPosition(.top)
inlineStackContainerNode.currentItemNode.scrollToPosition(.top(adjustForTempInset: false))
} else {
strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.scrollToPosition(.top)
strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.scrollToPosition(.top(adjustForTempInset: false))
}
}
}
@ -2391,7 +2391,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: peer?.id, singlePeer: false)
guard let peer else {
self.chatListDisplayNode.scrollToStories(animated: true)
return
}
let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: peer.id, singlePeer: false)
let _ = (storyContent.state
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] storyContentState in
@ -2399,13 +2404,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
if let peer, peer.id == self.context.account.peerId, storyContentState.slice == nil {
if peer.id == self.context.account.peerId, storyContentState.slice == nil {
self.openStoryCamera()
return
}
var transitionIn: StoryContainerScreen.TransitionIn?
if let peer, let componentView = self.chatListHeaderView() {
if let componentView = self.chatListHeaderView() {
if let (transitionView, _) = componentView.storyPeerListView()?.transitionViewForItem(peerId: peer.id) {
transitionIn = StoryContainerScreen.TransitionIn(
sourceView: transitionView,
@ -2581,7 +2586,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
public func scrollToStories() {
self.chatListDisplayNode.scrollToStories()
self.chatListDisplayNode.scrollToStories(animated: false)
}
private func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -3410,9 +3415,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
if let scrollToTop = strongSelf.scrollToTop {
/*if let scrollToTop = strongSelf.scrollToTop {
scrollToTop()
}
}*/
let tabsIsEmpty: Bool
if let (resolvedItems, displayTabsAtBottom, _) = strongSelf.tabContainerData {

View File

@ -361,7 +361,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
private(set) var validLayout: (size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, storiesInset: CGFloat)?
init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) {
init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void, autoSetReady: Bool) {
self.context = context
self.controller = controller
self.location = location
@ -373,7 +373,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
self.secondaryEmptyAction = secondaryEmptyAction
self.isInlineMode = isInlineMode
self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: chatListMode, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true, isInlineMode: isInlineMode)
self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: chatListMode, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true, isInlineMode: isInlineMode, autoSetReady: autoSetReady)
if let controller, case .chatList(groupId: .root) = controller.location {
self.listNode.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight + ChatListNavigationBar.storiesScrollHeight
@ -772,8 +772,20 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
private var filtersLimit: Int32? = nil
private var selectedId: ChatListFilterTabEntryId
var storiesUnlocked: Bool = false
var hintUpdatedStoryExpansion: Bool = false
var ignoreStoryUnlockedScrolling: Bool = false
var tempTopInset: CGFloat = 0.0 {
didSet {
if self.tempTopInset != oldValue {
for (_, itemNode) in self.itemNodes {
itemNode.listNode.tempTopInset = self.tempTopInset
}
if let pendingItemNode = self.pendingItemNode {
pendingItemNode.1.listNode.tempTopInset = self.tempTopInset
}
}
}
}
var initialScrollingOffset: CGFloat?
@ -910,64 +922,61 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
self.isSettingUpContentOffset = false
return
}
self.contentOffset = offset
self.contentOffsetChanged?(offset)
if itemNode.listNode.isTracking {
if itemNode.listNode.isTracking && !self.currentItemNode.startedScrollingAtUpperBound && self.tempTopInset == 0.0 {
if case let .known(value) = offset {
if !self.storiesUnlocked {
if value < -40.0 {
self.storiesUnlocked = true
DispatchQueue.main.async { [weak self] in
guard let self else {
return
}
HapticFeedback().impact()
self.currentItemNode.ignoreStoryInsetAdjustment = true
self.currentItemNode.allowInsetFixWhileTracking = true
self.onStoriesLockedUpdated?(true)
self.currentItemNode.ignoreStoryInsetAdjustment = false
self.currentItemNode.allowInsetFixWhileTracking = false
}
if value < -1.0 {
if let storySubscriptions = self.controller?.storySubscriptions, !storySubscriptions.items.isEmpty {
self.currentItemNode.startedScrollingAtUpperBound = true
self.tempTopInset = ChatListNavigationBar.storiesScrollHeight
}
}
}
} else if self.storiesUnlocked {
switch offset {
case let .known(value):
if value >= ChatListNavigationBar.storiesScrollHeight && !self.ignoreStoryUnlockedScrolling {
self.storiesUnlocked = false
self.onStoriesLockedUpdated?(false)
}
self.contentOffset = offset
self.contentOffsetChanged?(offset)
if self.currentItemNode.startedScrollingAtUpperBound && self.tempTopInset != 0.0 {
if case let .known(value) = offset {
if value > 4.0 {
self.currentItemNode.startedScrollingAtUpperBound = false
self.tempTopInset = 0.0
} else if value <= -94.0 {
} else if value > -82.0 {
}
default:
break
} else if case .unknown = offset {
self.currentItemNode.startedScrollingAtUpperBound = false
self.tempTopInset = 0.0
}
}
}
itemNode.listNode.didBeginInteractiveDragging = { [weak self] _ in
guard let self else {
return
}
let tempTopInset: CGFloat
if self.currentItemNode.startedScrollingAtUpperBound {
if let storySubscriptions = self.controller?.storySubscriptions, !storySubscriptions.items.isEmpty {
tempTopInset = ChatListNavigationBar.storiesScrollHeight
} else {
tempTopInset = 0.0
}
} else {
tempTopInset = 0.0
}
if self.tempTopInset != tempTopInset {
self.tempTopInset = tempTopInset
self.hintUpdatedStoryExpansion = true
self.currentItemNode.contentOffsetChanged?(self.currentItemNode.visibleContentOffset())
self.hintUpdatedStoryExpansion = false
}
}
itemNode.listNode.endedInteractiveDragging = { [weak self] _ in
guard let self else {
return
}
switch self.currentItemNode.visibleContentOffset() {
case let .known(value):
if value > ChatListNavigationBar.storiesScrollHeight {
if self.storiesUnlocked {
self.storiesUnlocked = false
DispatchQueue.main.async { [weak self] in
guard let self else {
return
}
self.onStoriesLockedUpdated?(false)
let _ = self.contentScrollingEnded?(self.currentItemNode)
}
}
}
default:
break
}
let _ = self
}
itemNode.listNode.contentScrollingEnded = { [weak self] listView in
guard let self else {
@ -975,6 +984,11 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
}
return self.contentScrollingEnded?(listView) ?? false
//DispatchQueue.main.async { [weak self] in
// let _ = self?.contentScrollingEnded?(listView)
//}
//return false
}
itemNode.listNode.activateChatPreview = { [weak self] item, threadId, sourceNode, gesture, location in
self?.activateChatPreview?(item, threadId, sourceNode, gesture, location)
@ -1090,7 +1104,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
self?.filterEmptyAction(filter)
}, secondaryEmptyAction: { [weak self] in
self?.secondaryEmptyAction()
})
}, autoSetReady: true)
self.itemNodes[.all] = itemNode
self.addSubnode(itemNode)
@ -1104,7 +1118,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
}
switch strongSelf.currentItemNode.visibleContentOffset() {
case let .known(value):
if value < -1.0 {
if value < -strongSelf.currentItemNode.tempTopInset {
return []
}
case .none, .unknown:
@ -1158,7 +1172,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
itemNode.emptyNode?.restartAnimation()
if let controller = self.controller, let chatListDisplayNode = controller.displayNode as? ChatListControllerNode, let navigationBarComponentView = chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset {
let scrollOffset = max(0.0, clippedScrollOffset - navigationBarComponentView.effectiveStoriesInsetHeight)
let scrollOffset = clippedScrollOffset
let _ = itemNode.listNode.scrollToOffsetFromTop(scrollOffset, animated: false)
}
@ -1331,9 +1345,9 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
}
}
public func scrollToTop(animated: Bool) {
public func scrollToTop(animated: Bool, adjustForTempInset: Bool) {
if let itemNode = self.itemNodes[self.selectedId] {
itemNode.listNode.scrollToPosition(.top, animated: animated)
itemNode.listNode.scrollToPosition(.top(adjustForTempInset: adjustForTempInset), animated: animated)
}
}
@ -1401,10 +1415,14 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
guard let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = self.validLayout else {
return
}
self.selectedId = id
if let currentItemNode = self.currentItemNodeValue {
itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: currentItemNode.listNode.isNavigationHidden)
if let controller = self.controller, let chatListDisplayNode = controller.displayNode as? ChatListControllerNode, let navigationBarComponentView = chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset {
let scrollOffset = clippedScrollOffset
let _ = itemNode.listNode.scrollToOffsetFromTop(scrollOffset, animated: false)
}
self.selectedId = id
self.applyItemNodeAsCurrent(id: id, itemNode: itemNode)
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: transition)
@ -1418,7 +1436,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
self?.filterEmptyAction(filter)
}, secondaryEmptyAction: { [weak self] in
self?.secondaryEmptyAction()
})
}, autoSetReady: false)
let disposable = MetaDisposable()
self.pendingItemNode = (id, itemNode, disposable)
@ -1436,6 +1454,13 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
}
strongSelf.pendingItemNode = nil
itemNode.listNode.tempTopInset = strongSelf.tempTopInset
if let controller = strongSelf.controller, let chatListDisplayNode = controller.displayNode as? ChatListControllerNode, let navigationBarComponentView = chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset {
let scrollOffset = clippedScrollOffset
let _ = itemNode.listNode.scrollToOffsetFromTop(scrollOffset, animated: false)
}
guard let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = strongSelf.validLayout else {
strongSelf.itemNodes[id] = itemNode
@ -1501,6 +1526,11 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
completion?()
}))
if let (layout, _, visualNavigationHeight, originalNavigationHeight, _, insets, _, _, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = self.validLayout {
itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate)
return
}
}
}
}
@ -1536,7 +1566,8 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
self?.filterEmptyAction(filter)
}, secondaryEmptyAction: { [weak self] in
self?.secondaryEmptyAction()
})
}, autoSetReady: false)
itemNode.listNode.tempTopInset = self.tempTopInset
self.itemNodes[id] = itemNode
}
}
@ -1638,6 +1669,10 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var didBeginSelectingChatsWhileEditing: Bool = false
var isEditing: Bool = false
private var tempAllowAvatarExpansion: Bool = false
private var tempDisableStoriesAnimations: Bool = false
private var tempNavigationScrollingTransition: ContainedViewLayoutTransition?
private var containerLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, storiesInset: CGFloat)?
var contentScrollingEnded: ((ListView) -> Bool)?
@ -1730,6 +1765,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
if isLocked {
self.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
//self.controller?.requestLayout(transition: .immediate)
} else {
self.controller?.requestLayout(transition: .immediate)
}
@ -1740,7 +1776,12 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
return false
}
if let storySubscriptions = controller.storySubscriptions, !storySubscriptions.items.isEmpty, !self.mainContainerNode.storiesUnlocked {
if let storySubscriptions = controller.storySubscriptions, !storySubscriptions.items.isEmpty {
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
if navigationBarComponentView.storiesUnlocked {
return true
}
}
return false
} else {
return true
@ -1875,7 +1916,6 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
statusBarHeight: layout.statusBarHeight ?? 0.0,
sideInset: layout.safeInsets.left,
isSearchActive: self.isSearchDisplayControllerActive,
storiesUnlocked: self.mainContainerNode.storiesUnlocked,
primaryContent: headerContent?.primaryContent,
secondaryContent: headerContent?.secondaryContent,
secondaryTransition: self.inlineStackContainerTransitionFraction,
@ -1923,7 +1963,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
transition.updateFrame(view: navigationBarComponentView, frame: CGRect(origin: CGPoint(), size: navigationBarSize))
return (navigationBarSize.height, navigationBarComponentView.effectiveStoriesInsetHeight)
return (navigationBarSize.height, 0.0)
} else {
return (0.0, 0.0)
}
@ -1961,12 +2001,16 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
var allowAvatarsExpansion: Bool = true
if !self.mainContainerNode.currentItemNode.startedScrollingAtUpperBound && !self.mainContainerNode.storiesUnlocked {
allowAvatarsExpansion = false
if !self.mainContainerNode.currentItemNode.startedScrollingAtUpperBound && !self.tempAllowAvatarExpansion {
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
if !navigationBarComponentView.storiesUnlocked {
allowAvatarsExpansion = false
}
}
}
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
navigationBarComponentView.applyScroll(offset: offset, allowAvatarsExpansion: allowAvatarsExpansion, transition: Transition(transition))
navigationBarComponentView.applyScroll(offset: offset, allowAvatarsExpansion: allowAvatarsExpansion, forceUpdate: false, transition: Transition(transition).withUserData(ChatListNavigationBar.AnimationHint(disableStoriesAnimations: self.tempDisableStoriesAnimations)))
}
}
@ -1977,16 +2021,26 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
let _ = self.updateNavigationBar(layout: layout, transition: transition)
}
func scrollToStories() {
func scrollToStories(animated: Bool) {
if self.inlineStackContainerNode != nil {
return
}
self.mainContainerNode.scrollToTop(animated: false)
self.mainContainerNode.storiesUnlocked = true
if let storySubscriptions = self.controller?.storySubscriptions, !storySubscriptions.items.isEmpty {
self.tempAllowAvatarExpansion = true
self.tempDisableStoriesAnimations = !animated
self.tempNavigationScrollingTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate
self.mainContainerNode.scrollToTop(animated: animated, adjustForTempInset: true)
self.tempAllowAvatarExpansion = false
self.tempDisableStoriesAnimations = false
tempNavigationScrollingTransition = nil
}
/*self.mainContainerNode.scrollToTop(animated: false)
self.mainContainerNode.ignoreStoryUnlockedScrolling = true
self.controller?.requestLayout(transition: .immediate)
self.mainContainerNode.scrollToTop(animated: false)
self.mainContainerNode.ignoreStoryUnlockedScrolling = false
self.mainContainerNode.ignoreStoryUnlockedScrolling = false*/
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, storiesInset: CGFloat, transition: ContainedViewLayoutTransition) {
@ -2166,7 +2220,6 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
strongSelf.isSearchDisplayControllerActive = true
strongSelf.mainContainerNode.storiesUnlocked = false
strongSelf.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
strongSelf.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in
@ -2230,7 +2283,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
let _ = inlineStackContainerNode
}
self.updateNavigationScrolling(transition: .immediate)
self.updateNavigationScrolling(transition: self.tempNavigationScrollingTransition ?? .immediate)
/*if !isPrimary {
self.contentOffsetChanged?(offset)
@ -2278,56 +2331,34 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private func contentScrollingEnded(listView: ListView, isPrimary: Bool) -> Bool {
if !isPrimary || self.inlineStackContainerNode == nil {
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
if let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset {
if navigationBarComponentView.effectiveStoriesInsetHeight > 0.0 {
if clippedScrollOffset > 0.0 && clippedScrollOffset < navigationBarComponentView.effectiveStoriesInsetHeight {
if clippedScrollOffset < navigationBarComponentView.effectiveStoriesInsetHeight * 0.5 {
let _ = listView.scrollToOffsetFromTop(0.0, animated: true)
} else {
let _ = listView.scrollToOffsetFromTop(navigationBarComponentView.effectiveStoriesInsetHeight, animated: true)
}
return true
} else {
let searchScrollOffset = clippedScrollOffset - navigationBarComponentView.effectiveStoriesInsetHeight
if searchScrollOffset > 0.0 && searchScrollOffset < ChatListNavigationBar.searchScrollHeight {
if searchScrollOffset < ChatListNavigationBar.searchScrollHeight * 0.5 {
let _ = listView.scrollToOffsetFromTop(navigationBarComponentView.effectiveStoriesInsetHeight, animated: true)
} else {
let _ = listView.scrollToOffsetFromTop(navigationBarComponentView.effectiveStoriesInsetHeight + ChatListNavigationBar.searchScrollHeight, animated: true)
}
return true
}
}
} else {
if clippedScrollOffset > 0.0 && clippedScrollOffset < ChatListNavigationBar.searchScrollHeight {
if clippedScrollOffset < ChatListNavigationBar.searchScrollHeight * 0.5 {
let _ = listView.scrollToOffsetFromTop(0.0, animated: true)
} else {
let _ = listView.scrollToOffsetFromTop(ChatListNavigationBar.searchScrollHeight, animated: true)
}
return true
}
}
} else {
return false
}
guard let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View else {
return false
}
if let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset {
let searchScrollOffset = clippedScrollOffset
if searchScrollOffset > 0.0 && searchScrollOffset < ChatListNavigationBar.searchScrollHeight {
if searchScrollOffset < ChatListNavigationBar.searchScrollHeight * 0.5 {
let _ = listView.scrollToOffsetFromTop(0.0, animated: true)
} else {
let _ = listView.scrollToOffsetFromTop(ChatListNavigationBar.searchScrollHeight, animated: true)
}
return true
} else if clippedScrollOffset < 0.0 && clippedScrollOffset > -listView.tempTopInset {
if navigationBarComponentView.storiesUnlocked {
let _ = listView.scrollToOffsetFromTop(-listView.tempTopInset, animated: true)
} else {
let _ = listView.scrollToOffsetFromTop(0.0, animated: true)
}
return true
}
}
return false
/*guard let inlineStackContainerNode = self.inlineStackContainerNode else {
return self.contentScrollingEnded?(listView) ?? false
}
self.contentOffsetSyncLockedIn = false
if isPrimary {
return false
}
let _ = inlineStackContainerNode
return self.contentScrollingEnded?(listView) ?? false*/
}
func makeInlineChatList(location: ChatListControllerLocation) -> ChatListContainerNode {
@ -2343,7 +2374,11 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
func setInlineChatList(inlineStackContainerNode: ChatListContainerNode?) {
if let inlineStackContainerNode = inlineStackContainerNode {
if self.inlineStackContainerNode !== inlineStackContainerNode {
self.mainContainerNode.storiesUnlocked = false
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
if navigationBarComponentView.storiesUnlocked {
let _ = self.mainContainerNode.currentItemNode.scrollToOffsetFromTop(self.mainContainerNode.currentItemNode.tempTopInset, animated: true)
}
}
inlineStackContainerNode.leftSeparatorLayer.isHidden = false
@ -2375,7 +2410,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
let previousInlineStackContainerNode = self.inlineStackContainerNode
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View, let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset {
let scrollOffset = max(0.0, clippedScrollOffset - navigationBarComponentView.effectiveStoriesInsetHeight)
let scrollOffset = max(0.0, clippedScrollOffset)
inlineStackContainerNode.initialScrollingOffset = scrollOffset
}
@ -2426,9 +2461,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.contentNode.scrollToTop()
} else if let inlineStackContainerNode = self.inlineStackContainerNode {
inlineStackContainerNode.scrollToTop(animated: true)
inlineStackContainerNode.scrollToTop(animated: true, adjustForTempInset: false)
} else {
self.mainContainerNode.scrollToTop(animated: true)
self.mainContainerNode.scrollToTop(animated: true, adjustForTempInset: false)
}
}
}

View File

@ -1040,7 +1040,7 @@ public enum ChatListGlobalScrollOption {
}
public enum ChatListNodeScrollPosition {
case top
case top(adjustForTempInset: Bool)
}
public enum ChatListNodeEmptyState: Equatable {
@ -1151,6 +1151,7 @@ public final class ChatListNode: ListView {
public var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
public var contentScrollingEnded: ((ListView) -> Bool)?
public var didBeginInteractiveDragging: ((ListView) -> Void)?
public var isEmptyUpdated: ((ChatListNodeEmptyState, Bool, ContainedViewLayoutTransition) -> Void)?
private var currentIsEmptyState: ChatListNodeEmptyState?
@ -1193,9 +1194,11 @@ public final class ChatListNode: ListView {
}
}
public private(set) var startedScrollingAtUpperBound: Bool = false
public var startedScrollingAtUpperBound: Bool = false
public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) {
private let autoSetReady: Bool
public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool, autoSetReady: Bool) {
self.context = context
self.location = location
self.chatListFilter = chatListFilter
@ -1204,6 +1207,7 @@ public final class ChatListNode: ListView {
self.mode = mode
self.animationCache = animationCache
self.animationRenderer = animationRenderer
self.autoSetReady = autoSetReady
var isSelecting = false
if case .peers(_, true, _, _, _, _) = mode {
@ -2363,7 +2367,7 @@ public final class ChatListNode: ListView {
var isHiddenItemVisible = false
if let range = range.visibleRange {
let entryCount = chatListView.filteredEntries.count
for i in range.firstIndex ..< range.lastIndex {
for i in max(0, range.firstIndex - 1) ..< range.lastIndex {
if i < 0 || i >= entryCount {
assertionFailure()
continue
@ -2384,11 +2388,11 @@ public final class ChatListNode: ListView {
}
}
if !isHiddenItemVisible && strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed {
/*strongSelf.updateState { state in
strongSelf.updateState { state in
var state = state
state.hiddenItemShouldBeTemporaryRevealed = false
return state
}*/
}
}
}
}
@ -2731,7 +2735,7 @@ public final class ChatListNode: ListView {
case .none, .unknown:
strongSelf.startedScrollingAtUpperBound = false
case let .known(value):
strongSelf.startedScrollingAtUpperBound = value <= 0.0
strongSelf.startedScrollingAtUpperBound = value <= 0.001
}
if let canExpandHiddenItems = strongSelf.canExpandHiddenItems {
@ -2747,27 +2751,28 @@ public final class ChatListNode: ListView {
return state
}
}
strongSelf.didBeginInteractiveDragging?(strongSelf)
}
self.didEndScrolling = { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.startedScrollingAtUpperBound = false
let _ = strongSelf.contentScrollingEnded?(strongSelf)
let revealHiddenItems: Bool
switch strongSelf.visibleContentOffset() {
case .none, .unknown:
revealHiddenItems = false
case let .known(value):
revealHiddenItems = value <= 54.0
revealHiddenItems = value <= -strongSelf.tempTopInset - 60.0
}
if !revealHiddenItems && strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed {
strongSelf.updateState { state in
/*strongSelf.updateState { state in
var state = state
state.hiddenItemShouldBeTemporaryRevealed = false
return state
}
}*/
}
}
@ -2795,9 +2800,9 @@ public final class ChatListNode: ListView {
case .none, .unknown:
atTop = false
case let .known(value):
atTop = value <= 0.0
atTop = value <= -strongSelf.tempTopInset
if strongSelf.startedScrollingAtUpperBound && startedScrollingWithCanExpandHiddenItems && strongSelf.isTracking {
revealHiddenItems = value <= -60.0
revealHiddenItems = value <= -strongSelf.tempTopInset - 60.0
}
}
strongSelf.scrolledAtTopValue = atTop
@ -2939,7 +2944,7 @@ public final class ChatListNode: ListView {
if strongSelf.isNodeLoaded, strongSelf.dequeuedInitialTransitionOnLayout {
strongSelf.dequeueTransition()
} else {
if !strongSelf.didSetReady {
if !strongSelf.didSetReady && strongSelf.autoSetReady {
strongSelf.didSetReady = true
strongSelf._ready.set(true)
}
@ -3282,6 +3287,7 @@ public final class ChatListNode: ListView {
default:
break
}
additionalScrollDistance = 0.0
} else {
additionalScrollDistance += previousStoriesInset - storiesInset
}
@ -3311,15 +3317,24 @@ public final class ChatListNode: ListView {
}
public func scrollToPosition(_ position: ChatListNodeScrollPosition, animated: Bool = true) {
var additionalDelta: CGFloat = 0.0
switch position {
case let .top(adjustForTempInset):
if adjustForTempInset {
additionalDelta = ChatListNavigationBar.storiesScrollHeight
self.tempTopInset = ChatListNavigationBar.storiesScrollHeight
}
}
if let list = self.chatListView?.originalList {
if !list.hasLater {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: animated, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: ListViewScrollToItem(index: 0, position: .top(additionalDelta), animated: animated, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} else {
let location: ChatListNodeLocation = .scroll(index: .chatList(.absoluteUpperBound), sourceIndex: .chatList(.absoluteLowerBound), scrollPosition: .top(0.0), animated: animated, filter: self.chatListFilter)
let location: ChatListNodeLocation = .scroll(index: .chatList(.absoluteUpperBound), sourceIndex: .chatList(.absoluteLowerBound), scrollPosition: .top(additionalDelta), animated: animated, filter: self.chatListFilter)
self.setChatListLocation(location)
}
} else {
let location: ChatListNodeLocation = .scroll(index: .chatList(.absoluteUpperBound), sourceIndex: .chatList(.absoluteLowerBound), scrollPosition: .top(0.0), animated: animated, filter: self.chatListFilter)
let location: ChatListNodeLocation = .scroll(index: .chatList(.absoluteUpperBound), sourceIndex: .chatList(.absoluteLowerBound), scrollPosition: .top(additionalDelta), animated: animated, filter: self.chatListFilter)
self.setChatListLocation(location)
}
}

View File

@ -315,7 +315,11 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
private func contentScrollingEnded(listView: ListView) -> Bool {
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
if "".isEmpty {
return false
}
/*if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
if let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset {
if navigationBarComponentView.effectiveStoriesInsetHeight > 0.0 {
if clippedScrollOffset > 0.0 && clippedScrollOffset < navigationBarComponentView.effectiveStoriesInsetHeight {
@ -347,7 +351,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
}
}
}
}*/
return false
}
@ -394,7 +398,6 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
statusBarHeight: layout.statusBarHeight ?? 0.0,
sideInset: layout.safeInsets.left,
isSearchActive: self.isSearchDisplayControllerActive,
storiesUnlocked: self.storiesUnlocked,
primaryContent: primaryContent,
secondaryContent: nil,
secondaryTransition: 0.0,
@ -426,7 +429,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
transition.updateFrame(view: navigationBarComponentView, frame: CGRect(origin: CGPoint(), size: navigationBarSize))
return (navigationBarSize.height, navigationBarComponentView.effectiveStoriesInsetHeight)
return (navigationBarSize.height, 0.0)
} else {
return (0.0, 0.0)
}

View File

@ -871,10 +871,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.resetScrollIndicatorFlashTimer(start: true)
self.lastContentOffsetTimestamp = 0.0
self.didEndScrolling?(false)
self.isAuxiliaryDisplayLinkEnabled = false
}
self.ignoreScrollingEvents = true
self.ignoreScrollingEvents = false
self.endedInteractiveDragging(self.touchesPosition)
if !decelerate {
self.didEndScrolling?(false)
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
@ -943,6 +947,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
private var generalAccumulatedDeltaY: CGFloat = 0.0
private var previousDidScrollTimestamp: Double = 0.0
private var ignoreNextScrollAdjustment: Bool = false
private func updateScrollViewDidScroll(_ scrollView: UIScrollView, synchronous: Bool) {
if self.ignoreScrollingEvents || scroller !== self.scroller {
return
@ -965,6 +971,16 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let deltaY = scrollView.contentOffset.y - self.lastContentOffset.y
/*if self.ignoreNextScrollAdjustment {
self.ignoreNextScrollAdjustment = false
self.lastContentOffset = scrollView.contentOffset
return
}*/
//if abs(deltaY) > 30.0 {
// print("deltaY: \(deltaY)")
//}
self.generalAccumulatedDeltaY += deltaY
if abs(self.generalAccumulatedDeltaY) > 14.0 {
let direction: GeneralScrollDirection = self.generalAccumulatedDeltaY < 0 ? .up : .down
@ -976,6 +992,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
self.lastContentOffset = scrollView.contentOffset
//print("lastContentOffset9 = \(self.lastContentOffset.y)")
if !self.lastContentOffsetTimestamp.isZero {
self.lastContentOffsetTimestamp = CACurrentMediaTime()
}
@ -1141,6 +1158,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
return false
}
public var tempTopInset: CGFloat = 0.0 {
didSet {
if self.tempTopInset != oldValue {
self.updateScroller(transition: .immediate)
}
}
}
private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, scrollToItem: ListViewScrollToItem? = nil, isExperimentalSnapToScrollToItem: Bool = false, insetDeltaOffsetFix: CGFloat) -> (snappedTopInset: CGFloat, offset: CGFloat) {
if self.itemNodes.count == 0 {
return (0.0, 0.0)
@ -1175,7 +1200,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
if topItemFound {
topItemEdge = self.itemNodes[0].apparentFrame.origin.y
topItemEdge = self.itemNodes[0].apparentFrame.origin.y - self.tempTopInset
}
var bottomItemNode: ListViewItemNode?
@ -1643,31 +1668,39 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.ignoreScrollingEvents = true
if topItemFound && bottomItemFound {
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight)
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge + self.tempTopInset)
//print("lastContentOffset1 = \(self.lastContentOffset.y)")
self.scroller.contentOffset = self.lastContentOffset
} else if topItemFound {
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge + self.tempTopInset)
//print("lastContentOffset2 = \(self.lastContentOffset.y), ignoreNextScrollAdjustment: \(self.ignoreNextScrollAdjustment)")
if self.scroller.contentOffset != self.lastContentOffset {
self.scroller.contentOffset = self.lastContentOffset
}
self.lastContentOffset = self.scroller.contentOffset
//print("lastContentOffset2.1 = \(self.lastContentOffset.y), ignoreNextScrollAdjustment: \(self.ignoreNextScrollAdjustment)")
} else if bottomItemFound {
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize * 2.0 - bottomItemEdge)
//print("lastContentOffset3 = \(self.lastContentOffset.y)")
self.scroller.contentOffset = self.lastContentOffset
} else if self.itemNodes.isEmpty {
self.scroller.contentSize = self.visibleSize
if self.lastContentOffset.y == infiniteScrollSize && self.scroller.contentOffset.y.isZero {
self.scroller.contentOffset = .zero
self.lastContentOffset = .zero
//print("lastContentOffset4 = \(self.lastContentOffset.y)")
}
} else {
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
if abs(self.scroller.contentOffset.y - infiniteScrollSize) > infiniteScrollSize / 2.0 {
self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize)
//print("lastContentOffset5 = \(self.lastContentOffset.y)")
self.scroller.contentOffset = self.lastContentOffset
} else {
self.lastContentOffset = self.scroller.contentOffset
//print("lastContentOffset6 = \(self.lastContentOffset.y)")
}
}
self.ignoreScrollingEvents = wasIgnoringScrollingEvents
@ -1805,9 +1838,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let wasIgnoringScrollingEvents = self.ignoreScrollingEvents
self.ignoreScrollingEvents = true
self.scroller.frame = CGRect(origin: CGPoint(), size: updateSizeAndInsets.size)
self.scroller.contentSize = CGSize(width: updateSizeAndInsets.size.width, height: infiniteScrollSize * 2.0)
self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize)
self.scroller.contentOffset = self.lastContentOffset
//self.scroller.contentSize = CGSize(width: updateSizeAndInsets.size.width, height: infiniteScrollSize * 2.0)
//self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize)
//print("lastContentOffset7 = \(self.lastContentOffset.y)")
//self.scroller.contentOffset = self.lastContentOffset
self.ignoreScrollingEvents = wasIgnoringScrollingEvents
self.updateScroller(transition: .immediate)
@ -2984,6 +3018,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
offsetFix += additionalScrollDistance
/*if let topItemNode = self.itemNodes.first(where: { $0.index == 0 }) {
let topEdge = self.scroller.contentOffset.y + updateSizeAndInsets.insets.top
offsetFix = -(topEdge - topItemNode.apparentFrame.minY)
}*/
self.insets = updateSizeAndInsets.insets
self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets
self.scrollIndicatorInsets = updateSizeAndInsets.scrollIndicatorInsets ?? self.insets
@ -3137,10 +3176,17 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let wasIgnoringScrollingEvents = self.ignoreScrollingEvents
self.ignoreScrollingEvents = true
self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize)
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize)
self.scroller.contentOffset = self.lastContentOffset
if self.scroller.bounds.size != self.visibleSize {
self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize)
}
//self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
//self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize)
//self.scroller.contentOffset = self.lastContentOffset
//self.lastContentOffset = self.scroller.contentOffset
//print("lastContentOffset8 = \(self.lastContentOffset.y)")
self.ignoreScrollingEvents = wasIgnoringScrollingEvents
} else {
let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil && scrollToItem?.directionHint != .Down, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, scrollToItem: scrollToItem, insetDeltaOffsetFix: 0.0)
@ -4879,9 +4925,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
for itemNode in self.itemNodes {
if itemNode.index == 0 {
if animated {
self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset), animated: animated)
self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset + self.tempTopInset), animated: animated)
} else {
self.scroller.contentOffset = CGPoint(x: 0.0, y: offset)
self.scroller.contentOffset = CGPoint(x: 0.0, y: offset + self.tempTopInset)
}
return true
}

View File

@ -5,9 +5,7 @@ public final class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate {
super.init(frame: frame)
self.scrollsToTop = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.contentInsetAdjustmentBehavior = .never
}
self.contentInsetAdjustmentBehavior = .never
}
required public init?(coder aDecoder: NSCoder) {

View File

@ -136,7 +136,7 @@ public final class ChatListHeaderComponent: Component {
public let storySubscriptions: EngineStorySubscriptions?
public let storiesIncludeHidden: Bool
public let storiesFraction: CGFloat
public let storiesUnlockedFraction: CGFloat
public let storiesUnlocked: Bool
public let uploadProgress: Float?
public let context: AccountContext
public let theme: PresentationTheme
@ -154,7 +154,7 @@ public final class ChatListHeaderComponent: Component {
storySubscriptions: EngineStorySubscriptions?,
storiesIncludeHidden: Bool,
storiesFraction: CGFloat,
storiesUnlockedFraction: CGFloat,
storiesUnlocked: Bool,
uploadProgress: Float?,
context: AccountContext,
theme: PresentationTheme,
@ -171,7 +171,7 @@ public final class ChatListHeaderComponent: Component {
self.storySubscriptions = storySubscriptions
self.storiesIncludeHidden = storiesIncludeHidden
self.storiesFraction = storiesFraction
self.storiesUnlockedFraction = storiesUnlockedFraction
self.storiesUnlocked = storiesUnlocked
self.uploadProgress = uploadProgress
self.theme = theme
self.strings = strings
@ -204,7 +204,7 @@ public final class ChatListHeaderComponent: Component {
if lhs.storiesFraction != rhs.storiesFraction {
return false
}
if lhs.storiesUnlockedFraction != rhs.storiesUnlockedFraction {
if lhs.storiesUnlocked != rhs.storiesUnlocked {
return false
}
if lhs.uploadProgress != rhs.uploadProgress {
@ -312,6 +312,8 @@ public final class ChatListHeaderComponent: Component {
var contentOffsetFraction: CGFloat = 0.0
private(set) var centerContentWidth: CGFloat = 0.0
private(set) var centerContentRightInset: CGFloat = 0.0
private(set) var centerContentOffsetX: CGFloat = 0.0
private(set) var centerContentOrigin: CGFloat = 0.0
@ -637,6 +639,9 @@ public final class ChatListHeaderComponent: Component {
}
}
var centerContentRightInset: CGFloat = 0.0
centerContentRightInset = size.width - rightOffset + 16.0
var centerContentWidth: CGFloat = 0.0
var centerContentOffsetX: CGFloat = 0.0
var centerContentOrigin: CGFloat = 0.0
@ -699,6 +704,7 @@ public final class ChatListHeaderComponent: Component {
self.centerContentWidth = centerContentWidth
self.centerContentOffsetX = centerContentOffsetX
self.centerContentOrigin = centerContentOrigin
self.centerContentRightInset = centerContentRightInset
}
}
@ -809,49 +815,6 @@ public final class ChatListHeaderComponent: Component {
let previousComponent = self.component
self.component = component
var storyListTransition = transition
if let storySubscriptions = component.storySubscriptions {
let storyPeerList: ComponentView<Empty>
if let current = self.storyPeerList {
storyPeerList = current
} else {
storyListTransition = .immediate
storyPeerList = ComponentView()
self.storyPeerList = storyPeerList
}
let _ = storyPeerList.update(
transition: storyListTransition,
component: AnyComponent(StoryPeerListComponent(
externalState: self.storyPeerListExternalState,
context: component.context,
theme: component.theme,
strings: component.strings,
sideInset: component.sideInset,
useHiddenList: component.storiesIncludeHidden,
storySubscriptions: storySubscriptions,
collapseFraction: 1.0 - component.storiesFraction,
unlockedFraction: 1.0 - component.storiesUnlockedFraction,
uploadProgress: component.uploadProgress,
peerAction: { [weak self] peer in
guard let self else {
return
}
self.storyPeerAction?(peer)
},
contextPeerAction: { [weak self] sourceNode, gesture, peer in
guard let self else {
return
}
self.storyContextPeerAction?(sourceNode, gesture, peer)
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 94.0)
)
}
if let primaryContent = component.primaryContent {
var primaryContentTransition = transition
let primaryContentView: ContentView
@ -883,15 +846,15 @@ public final class ChatListHeaderComponent: Component {
self.addSubview(primaryContentView)
}
var sideContentWidth: CGFloat = 0.0
if let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty {
let sideContentWidth: CGFloat = 0.0
/*if let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty {
sideContentWidth = self.storyPeerListExternalState.collapsedWidth + 12.0
}
if let chatListTitle = primaryContent.chatListTitle {
if chatListTitle.activity {
sideContentWidth = 0.0
}
}
}*/
primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, backTitle: primaryContent.backTitle, sideInset: component.sideInset, sideContentWidth: sideContentWidth, sideContentFraction: (1.0 - component.storiesFraction), size: availableSize, transition: primaryContentTransition)
primaryContentTransition.setFrame(view: primaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
@ -902,6 +865,59 @@ public final class ChatListHeaderComponent: Component {
primaryContentView.removeFromSuperview()
}
var storyListTransition = transition
if let storySubscriptions = component.storySubscriptions {
let storyPeerList: ComponentView<Empty>
if let current = self.storyPeerList {
storyPeerList = current
} else {
storyListTransition = .immediate
storyPeerList = ComponentView()
self.storyPeerList = storyPeerList
}
let _ = storyPeerList.update(
transition: storyListTransition,
component: AnyComponent(StoryPeerListComponent(
externalState: self.storyPeerListExternalState,
context: component.context,
theme: component.theme,
strings: component.strings,
sideInset: component.sideInset,
titleContentWidth: self.primaryContentView?.centerContentWidth ?? 0.0,
maxTitleX: availableSize.width - (self.primaryContentView?.centerContentRightInset ?? 0.0),
useHiddenList: component.storiesIncludeHidden,
storySubscriptions: storySubscriptions,
collapseFraction: 1.0 - component.storiesFraction,
unlocked: component.storiesUnlocked,
uploadProgress: component.uploadProgress,
peerAction: { [weak self] peer in
guard let self else {
return
}
self.storyPeerAction?(peer)
},
contextPeerAction: { [weak self] sourceNode, gesture, peer in
guard let self else {
return
}
self.storyContextPeerAction?(sourceNode, gesture, peer)
},
updateTitleContentOffset: { [weak self] offset, transition in
guard let self, let primaryContentView = self.primaryContentView else {
return
}
guard let chatListTitleView = primaryContentView.chatListTitleView else {
return
}
transition.setSublayerTransform(view: chatListTitleView, transform: CATransform3DMakeTranslation(offset, 0.0, 0.0))
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 94.0)
)
}
if let secondaryContent = component.secondaryContent {
var secondaryContentTransition = transition
let secondaryContentView: ContentView
@ -981,17 +997,18 @@ public final class ChatListHeaderComponent: Component {
self.addSubview(storyPeerListComponentView)
}
let storyPeerListMinOffset: CGFloat = -7.0
//let storyPeerListMinOffset: CGFloat = -7.0
let storyPeerListMaxOffset: CGFloat = availableSize.height + 8.0
let storyPeerListPosition: CGFloat = storyPeerListMinOffset * (1.0 - component.storiesFraction) + storyPeerListMaxOffset * component.storiesFraction
//let storyPeerListPosition: CGFloat = storyPeerListMinOffset * (1.0 - component.storiesFraction) + storyPeerListMaxOffset * component.storiesFraction
var defaultStoryListX: CGFloat = 0.0
if let primaryContentView = self.primaryContentView {
defaultStoryListX = primaryContentView.centerContentOrigin - (self.storyPeerListExternalState.collapsedWidth * 0.5 + 12.0) - availableSize.width * 0.5
}
let _ = defaultStoryListX
storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: -1.0 * availableSize.width * component.secondaryTransition + (1.0 - component.storiesFraction) * defaultStoryListX, y: storyPeerListPosition), size: CGSize(width: availableSize.width, height: 79.0)))
storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: -1.0 * availableSize.width * component.secondaryTransition + 0.0, y: storyPeerListMaxOffset), size: CGSize(width: availableSize.width, height: 79.0)))
var storyListNormalAlpha: CGFloat = 1.0
if let chatListTitle = component.primaryContent?.chatListTitle {

View File

@ -8,15 +8,23 @@ import ComponentDisplayAdapters
import SearchUI
import AccountContext
import TelegramCore
import StoryPeerListComponent
public final class ChatListNavigationBar: Component {
public final class AnimationHint {
let disableStoriesAnimations: Bool
public init(disableStoriesAnimations: Bool) {
self.disableStoriesAnimations = disableStoriesAnimations
}
}
public let context: AccountContext
public let theme: PresentationTheme
public let strings: PresentationStrings
public let statusBarHeight: CGFloat
public let sideInset: CGFloat
public let isSearchActive: Bool
public let storiesUnlocked: Bool
public let primaryContent: ChatListHeaderComponent.Content?
public let secondaryContent: ChatListHeaderComponent.Content?
public let secondaryTransition: CGFloat
@ -37,7 +45,6 @@ public final class ChatListNavigationBar: Component {
statusBarHeight: CGFloat,
sideInset: CGFloat,
isSearchActive: Bool,
storiesUnlocked: Bool,
primaryContent: ChatListHeaderComponent.Content?,
secondaryContent: ChatListHeaderComponent.Content?,
secondaryTransition: CGFloat,
@ -57,7 +64,6 @@ public final class ChatListNavigationBar: Component {
self.statusBarHeight = statusBarHeight
self.sideInset = sideInset
self.isSearchActive = isSearchActive
self.storiesUnlocked = storiesUnlocked
self.primaryContent = primaryContent
self.secondaryContent = secondaryContent
self.secondaryTransition = secondaryTransition
@ -91,9 +97,6 @@ public final class ChatListNavigationBar: Component {
if lhs.isSearchActive != rhs.isSearchActive {
return false
}
if lhs.storiesUnlocked != rhs.storiesUnlocked {
return false
}
if lhs.primaryContent != rhs.primaryContent {
return false
}
@ -160,15 +163,7 @@ public final class ChatListNavigationBar: Component {
public var deferScrollApplication: Bool = false
private var hasDeferredScrollOffset: Bool = false
public private(set) var effectiveStoriesInsetHeight: CGFloat = 0.0
private var applyScrollFractionAnimator: DisplayLinkAnimator?
private var applyScrollFraction: CGFloat = 1.0
private var storiesOffsetStartFraction: CGFloat = 1.0
private var applyScrollUnlockedFraction: CGFloat = 1.0
private var storiesOffsetFraction: CGFloat = 0.0
private var storiesUnlockedFraction: CGFloat = 0.0
private var storiesUnlockedStartFraction: CGFloat = 1.0
public private(set) var storiesUnlocked: Bool = false
private var tabsNode: ASDisplayNode?
private var tabsNodeIsSearch: Bool = false
@ -198,9 +193,16 @@ public final class ChatListNavigationBar: Component {
return nil
}
guard let result = super.hitTest(point, with: event) else {
if self.alpha.isZero {
return nil
}
for view in self.subviews.reversed() {
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
return result
}
}
let result = super.hitTest(point, with: event)
return result
}
@ -211,13 +213,10 @@ public final class ChatListNavigationBar: Component {
}
public func applyScroll(offset: CGFloat, allowAvatarsExpansion: Bool, forceUpdate: Bool = false, transition: Transition) {
if self.currentAllowAvatarsExpansion != allowAvatarsExpansion, allowAvatarsExpansion, !transition.animation.isImmediate {
self.addStoriesUnlockedAnimation(duration: 0.3, animateScrollUnlocked: false)
}
let transition = transition
self.rawScrollOffset = offset
let allowAvatarsExpansionUpdated = self.currentAllowAvatarsExpansion != allowAvatarsExpansion
self.currentAllowAvatarsExpansion = allowAvatarsExpansion
if self.deferScrollApplication && !forceUpdate {
@ -235,19 +234,11 @@ public final class ChatListNavigationBar: Component {
self.scrollStrings = component.strings
let searchOffsetDistance: CGFloat = ChatListNavigationBar.searchScrollHeight
let defaultStoriesOffsetDistance: CGFloat = ChatListNavigationBar.storiesScrollHeight
let effectiveStoriesOffsetDistance: CGFloat
var minContentOffset: CGFloat = ChatListNavigationBar.searchScrollHeight
if !component.isSearchActive, let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty, component.storiesUnlocked {
effectiveStoriesOffsetDistance = defaultStoriesOffsetDistance * (1.0 - component.secondaryTransition)
minContentOffset += effectiveStoriesOffsetDistance
} else {
effectiveStoriesOffsetDistance = 0.0
}
let minContentOffset: CGFloat = ChatListNavigationBar.searchScrollHeight
let clippedScrollOffset = min(minContentOffset, offset)
if self.clippedScrollOffset == clippedScrollOffset && !self.hasDeferredScrollOffset && !forceUpdate {
if self.clippedScrollOffset == clippedScrollOffset && !self.hasDeferredScrollOffset && !forceUpdate && !allowAvatarsExpansionUpdated {
return
}
self.hasDeferredScrollOffset = false
@ -302,7 +293,7 @@ public final class ChatListNavigationBar: Component {
self.addSubview(searchContentNode.view)
}
let clippedStoriesOverscrollOffset = -min(0.0, clippedScrollOffset)
/*let clippedStoriesOverscrollOffset = -min(0.0, clippedScrollOffset)
let clippedStoriesOffset = max(0.0, min(clippedScrollOffset, defaultStoriesOffsetDistance))
var storiesOffsetFraction: CGFloat
var storiesUnlockedOffsetFraction: CGFloat
@ -322,7 +313,7 @@ public final class ChatListNavigationBar: Component {
if self.applyScrollFractionAnimator != nil {
storiesOffsetFraction = self.applyScrollFraction * storiesOffsetFraction + (1.0 - self.applyScrollFraction) * self.storiesOffsetStartFraction
storiesUnlockedOffsetFraction = self.applyScrollUnlockedFraction * storiesUnlockedOffsetFraction + (1.0 - self.applyScrollUnlockedFraction) * self.storiesUnlockedStartFraction
}
}*/
let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight)
var searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - searchSize.height), size: searchSize)
@ -333,7 +324,7 @@ public final class ChatListNavigationBar: Component {
searchFrame.origin.y -= component.accessoryPanelContainerHeight
}
let clippedSearchOffset = max(0.0, min(clippedScrollOffset - effectiveStoriesOffsetDistance, searchOffsetDistance))
let clippedSearchOffset = max(0.0, min(clippedScrollOffset, searchOffsetDistance))
let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance
searchContentNode.expansionProgress = 1.0 - searchOffsetFraction
@ -341,13 +332,38 @@ public final class ChatListNavigationBar: Component {
searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition)
var headerTransition = transition
if self.applyScrollFractionAnimator != nil {
let headerTransition = transition
/*if self.applyScrollFractionAnimator != nil {
headerTransition = .immediate
}*/
let storiesOffsetFraction: CGFloat
let storiesUnlocked: Bool
if allowAvatarsExpansion {
storiesOffsetFraction = max(0.0, min(1.0, -offset / ChatListNavigationBar.storiesScrollHeight))
if offset <= -80.0 {
storiesUnlocked = true
} else if offset >= -66.0 {
storiesUnlocked = false
} else {
storiesUnlocked = self.storiesUnlocked
}
} else {
storiesOffsetFraction = 0.0
storiesUnlocked = false
}
self.storiesOffsetFraction = storiesOffsetFraction
self.storiesUnlockedFraction = storiesUnlockedOffsetFraction
if allowAvatarsExpansion && transition.animation.isImmediate {
if self.storiesUnlocked != storiesUnlocked {
if storiesUnlocked {
HapticFeedback().impact()
} else {
HapticFeedback().tap()
}
}
}
self.storiesUnlocked = storiesUnlocked
let headerComponent = ChatListHeaderComponent(
sideInset: component.sideInset + 16.0,
@ -357,8 +373,8 @@ public final class ChatListNavigationBar: Component {
networkStatus: nil,
storySubscriptions: component.storySubscriptions,
storiesIncludeHidden: component.storiesIncludeHidden,
storiesFraction: 1.0 - storiesOffsetFraction,
storiesUnlockedFraction: 1.0 - storiesUnlockedOffsetFraction,
storiesFraction: storiesOffsetFraction,
storiesUnlocked: storiesUnlocked,
uploadProgress: component.uploadProgress,
context: component.context,
theme: component.theme,
@ -376,16 +392,29 @@ public final class ChatListNavigationBar: Component {
component.context.sharedContext.appLockContext.lock()
}
)
let animationHint = transition.userData(AnimationHint.self)
var animationDuration: Double?
if case let .curve(duration, _) = transition.animation {
animationDuration = duration
}
self.currentHeaderComponent = headerComponent
let headerContentSize = self.headerContent.update(
transition: headerTransition,
transition: headerTransition.withUserData(StoryPeerListComponent.AnimationHint(
duration: animationDuration,
allowAvatarsExpansionUpdated: allowAvatarsExpansionUpdated && allowAvatarsExpansion,
bounce: transition.animation.isImmediate,
disableAnimations: animationHint?.disableStoriesAnimations ?? false
)),
component: AnyComponent(headerComponent),
environment: {},
containerSize: CGSize(width: currentLayout.size.width, height: 44.0)
)
let headerContentY: CGFloat
if component.isSearchActive {
headerContentY = -headerContentSize.height - effectiveStoriesOffsetDistance
headerContentY = -headerContentSize.height
} else {
if component.statusBarHeight < 1.0 {
headerContentY = 0.0
@ -496,7 +525,6 @@ public final class ChatListNavigationBar: Component {
statusBarHeight: component.statusBarHeight,
sideInset: component.sideInset,
isSearchActive: component.isSearchActive,
storiesUnlocked: component.storiesUnlocked,
primaryContent: component.primaryContent,
secondaryContent: component.secondaryContent,
secondaryTransition: component.secondaryTransition,
@ -520,7 +548,7 @@ public final class ChatListNavigationBar: Component {
storySubscriptions: headerComponent.storySubscriptions,
storiesIncludeHidden: headerComponent.storiesIncludeHidden,
storiesFraction: headerComponent.storiesFraction,
storiesUnlockedFraction: headerComponent.storiesUnlockedFraction,
storiesUnlocked: headerComponent.storiesUnlocked,
uploadProgress: storyUploadProgress,
context: headerComponent.context,
theme: headerComponent.theme,
@ -543,12 +571,8 @@ public final class ChatListNavigationBar: Component {
func update(component: ChatListNavigationBar, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
var storiesUnlockedUpdated = false
var uploadProgressUpdated = false
if let previousComponent = self.component {
if previousComponent.storiesUnlocked != component.storiesUnlocked {
storiesUnlockedUpdated = true
}
if previousComponent.uploadProgress != component.uploadProgress {
uploadProgressUpdated = true
}
@ -573,16 +597,7 @@ public final class ChatListNavigationBar: Component {
if component.statusBarHeight < 1.0 {
contentHeight += 8.0
}
self.effectiveStoriesInsetHeight = 0.0
} else {
if let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty, component.storiesUnlocked {
let storiesHeight: CGFloat = ChatListNavigationBar.storiesScrollHeight * (1.0 - component.secondaryTransition)
contentHeight += storiesHeight
self.effectiveStoriesInsetHeight = storiesHeight
} else {
self.effectiveStoriesInsetHeight = 0.0
}
contentHeight += navigationBarSearchContentHeight
}
@ -605,14 +620,10 @@ public final class ChatListNavigationBar: Component {
}
}
if storiesUnlockedUpdated, case let .curve(duration, _) = transition.animation {
self.addStoriesUnlockedAnimation(duration: duration, animateScrollUnlocked: true)
}
return size
}
private func addStoriesUnlockedAnimation(duration: Double, animateScrollUnlocked: Bool) {
/*private func addStoriesUnlockedAnimation(duration: Double, animateScrollUnlocked: Bool) {
guard let component = self.component else {
return
}
@ -648,7 +659,7 @@ public final class ChatListNavigationBar: Component {
self.applyScrollFractionAnimator?.invalidate()
self.applyScrollFractionAnimator = nil
})
}
}*/
}
public func makeView() -> View {

View File

@ -11,6 +11,32 @@ import SwiftSignalKit
import TelegramPresentationData
import StoryContainerScreen
private func solveParabolicMotion(from sourcePoint: CGPoint, to targetPosition: CGPoint, progress: CGFloat) -> CGPoint {
if sourcePoint.y == targetPosition.y {
return sourcePoint.interpolate(to: targetPosition, amount: progress)
}
//(x - h)² + (y - k)² = r²
//(x1 - h) * (x1 - h) + (y1 - k) * (y1 - k) = r * r
//(x2 - h) * (x2 - h) + (y2 - k) * (y2 - k) = r * r
let x1 = sourcePoint.y
let y1 = sourcePoint.x
let x2 = targetPosition.y
let y2 = targetPosition.x
let b = (x1 * x1 * y2 - x2 * x2 * y1) / (x1 * x1 - x2 * x2)
let k = (y1 - y2) / (x1 * x1 - x2 * x2)
let x = sourcePoint.y.interpolate(to: targetPosition.y, amount: progress)
let y = k * x * x + b
return CGPoint(x: y, y: x)
}
private let modelSpringAnimation: CABasicAnimation = {
return makeSpringBounceAnimation("", 0.0, 88.0)
}()
public final class StoryPeerListComponent: Component {
public final class ExternalState {
public fileprivate(set) var collapsedWidth: CGFloat = 0.0
@ -19,18 +45,35 @@ public final class StoryPeerListComponent: Component {
}
}
public final class AnimationHint {
let duration: Double?
let allowAvatarsExpansionUpdated: Bool
let bounce: Bool
let disableAnimations: Bool
public init(duration: Double?, allowAvatarsExpansionUpdated: Bool, bounce: Bool, disableAnimations: Bool) {
self.duration = duration
self.allowAvatarsExpansionUpdated = allowAvatarsExpansionUpdated
self.bounce = bounce
self.disableAnimations = disableAnimations
}
}
public let externalState: ExternalState
public let context: AccountContext
public let theme: PresentationTheme
public let strings: PresentationStrings
public let sideInset: CGFloat
public let titleContentWidth: CGFloat
public let maxTitleX: CGFloat
public let useHiddenList: Bool
public let storySubscriptions: EngineStorySubscriptions?
public let collapseFraction: CGFloat
public let unlockedFraction: CGFloat
public let unlocked: Bool
public let uploadProgress: Float?
public let peerAction: (EnginePeer?) -> Void
public let contextPeerAction: (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void
public let updateTitleContentOffset: (CGFloat, Transition) -> Void
public init(
externalState: ExternalState,
@ -38,26 +81,32 @@ public final class StoryPeerListComponent: Component {
theme: PresentationTheme,
strings: PresentationStrings,
sideInset: CGFloat,
titleContentWidth: CGFloat,
maxTitleX: CGFloat,
useHiddenList: Bool,
storySubscriptions: EngineStorySubscriptions?,
collapseFraction: CGFloat,
unlockedFraction: CGFloat,
unlocked: Bool,
uploadProgress: Float?,
peerAction: @escaping (EnginePeer?) -> Void,
contextPeerAction: @escaping (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void
contextPeerAction: @escaping (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void,
updateTitleContentOffset: @escaping (CGFloat, Transition) -> Void
) {
self.externalState = externalState
self.context = context
self.theme = theme
self.strings = strings
self.sideInset = sideInset
self.titleContentWidth = titleContentWidth
self.maxTitleX = maxTitleX
self.useHiddenList = useHiddenList
self.storySubscriptions = storySubscriptions
self.collapseFraction = collapseFraction
self.unlockedFraction = unlockedFraction
self.unlocked = unlocked
self.uploadProgress = uploadProgress
self.peerAction = peerAction
self.contextPeerAction = contextPeerAction
self.updateTitleContentOffset = updateTitleContentOffset
}
public static func ==(lhs: StoryPeerListComponent, rhs: StoryPeerListComponent) -> Bool {
@ -73,6 +122,12 @@ public final class StoryPeerListComponent: Component {
if lhs.sideInset != rhs.sideInset {
return false
}
if lhs.titleContentWidth != rhs.titleContentWidth {
return false
}
if lhs.maxTitleX != rhs.maxTitleX {
return false
}
if lhs.useHiddenList != rhs.useHiddenList {
return false
}
@ -82,7 +137,7 @@ public final class StoryPeerListComponent: Component {
if lhs.collapseFraction != rhs.collapseFraction {
return false
}
if lhs.unlockedFraction != rhs.unlockedFraction {
if lhs.unlocked != rhs.unlocked {
return false
}
if lhs.uploadProgress != rhs.uploadProgress {
@ -134,6 +189,44 @@ public final class StoryPeerListComponent: Component {
}
}
private final class AnimationState {
let duration: Double
let fromIsUnlocked: Bool
let fromFraction: CGFloat
let startTime: Double
let bounce: Bool
init(
duration: Double,
fromIsUnlocked: Bool,
fromFraction: CGFloat,
startTime: Double,
bounce: Bool
) {
self.duration = duration
self.fromIsUnlocked = fromIsUnlocked
self.fromFraction = fromFraction
self.startTime = startTime
self.bounce = bounce
}
func interpolatedFraction(at timestamp: Double, effectiveFromFraction: CGFloat, toFraction: CGFloat) -> CGFloat {
var rawProgress = CGFloat((timestamp - self.startTime) / self.duration)
rawProgress = max(0.0, min(1.0, rawProgress))
let progress = listViewAnimationCurveSystem(rawProgress)
return effectiveFromFraction * (1.0 - progress) + toFraction * progress
}
func isFinished(at timestamp: Double) -> Bool {
if timestamp > self.startTime + self.duration {
return true
} else {
return false
}
}
}
public final class View: UIView, UIScrollViewDelegate {
private let collapsedButton: HighlightableButton
private let scrollView: ScrollView
@ -153,6 +246,11 @@ public final class StoryPeerListComponent: Component {
private var previewedItemDisposable: Disposable?
private var previewedItemId: EnginePeer.Id?
private var animationState: AnimationState?
private var animator: ConstantDisplayLinkAnimator?
private var currentFraction: CGFloat = 0.0
public override init(frame: CGRect) {
self.collapsedButton = HighlightableButton()
@ -165,6 +263,7 @@ public final class StoryPeerListComponent: Component {
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceVertical = false
self.scrollView.alwaysBounceHorizontal = true
self.scrollView.clipsToBounds = false
super.init(frame: frame)
@ -281,19 +380,198 @@ public final class StoryPeerListComponent: Component {
let collapseEndIndex = collapseStartIndex + max(0, Int(collapsedItemCount) - 1)
let collapsedContentOrigin: CGFloat
var collapsedContentOrigin: CGFloat
let collapsedItemOffsetY: CGFloat
let itemScale: CGFloat
collapsedContentOrigin = floor((itemLayout.containerSize.width - collapsedContentWidth) * 0.5)
itemScale = 1.0
collapsedItemOffsetY = 0.0
let titleContentSpacing: CGFloat = 8.0
var combinedTitleContentWidth = component.titleContentWidth
if !combinedTitleContentWidth.isZero {
combinedTitleContentWidth += titleContentSpacing
}
let centralContentWidth: CGFloat = collapsedContentWidth + combinedTitleContentWidth
collapsedContentOrigin = floor((itemLayout.containerSize.width - centralContentWidth) * 0.5)
collapsedContentOrigin = min(collapsedContentOrigin, component.maxTitleX - centralContentWidth - 4.0)
collapsedItemOffsetY = -59.0
struct CollapseState {
var globalFraction: CGFloat
var scaleFraction: CGFloat
var minFraction: CGFloat
var maxFraction: CGFloat
var sideAlphaFraction: CGFloat
}
/*let calculateCollapedFraction: (CGFloat) -> CGFloat = { t in
let offset = scrollingRubberBandingOffset(offset: (1.0 - t) * 94.0, bandingStart: 0.0, range: 400.0, coefficient: 0.4)
return 1.0 - max(0.0, min(1.0, offset / 94.0))
}*/
let targetExpandedFraction = component.collapseFraction
let targetFraction: CGFloat = component.collapseFraction
let targetScaleFraction: CGFloat
let targetMinFraction: CGFloat
let targetMaxFraction: CGFloat
let targetSideAlphaFraction: CGFloat
if component.unlocked {
targetScaleFraction = targetExpandedFraction
targetMinFraction = 0.0
targetMaxFraction = 1.0 - targetExpandedFraction
targetSideAlphaFraction = 1.0
} else {
targetScaleFraction = 1.0
targetMinFraction = 1.0 - targetExpandedFraction
targetMaxFraction = 0.0
targetSideAlphaFraction = 0.0
}
let collapsedState: CollapseState
let expandBoundsFraction: CGFloat
if let animationState = self.animationState {
let effectiveFromScaleFraction: CGFloat
if animationState.fromIsUnlocked {
effectiveFromScaleFraction = animationState.fromFraction
} else {
effectiveFromScaleFraction = 1.0
}
let effectiveFromMinFraction: CGFloat
let effectiveFromMaxFraction: CGFloat
if animationState.fromIsUnlocked {
effectiveFromMinFraction = 0.0
effectiveFromMaxFraction = 1.0 - animationState.fromFraction
} else {
effectiveFromMinFraction = 1.0 - animationState.fromFraction
effectiveFromMaxFraction = 0.0
}
let effectiveFromSideAlphaFraction: CGFloat
if animationState.fromIsUnlocked {
effectiveFromSideAlphaFraction = 1.0
} else {
effectiveFromSideAlphaFraction = 0.0
}
let timestamp = CACurrentMediaTime()
let animatedGlobalFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: animationState.fromFraction, toFraction: targetFraction)
let animatedScaleFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromScaleFraction, toFraction: targetScaleFraction)
let animatedMinFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromMinFraction, toFraction: targetMinFraction)
let animatedMaxFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromMaxFraction, toFraction: targetMaxFraction)
let animatedSideAlphaFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromSideAlphaFraction, toFraction: targetSideAlphaFraction)
collapsedState = CollapseState(
globalFraction: animatedGlobalFraction,
scaleFraction: animatedScaleFraction,
minFraction: animatedMinFraction,
maxFraction: animatedMaxFraction,
sideAlphaFraction: animatedSideAlphaFraction
)
var rawProgress = CGFloat((timestamp - animationState.startTime) / animationState.duration)
rawProgress = max(0.0, min(1.0, rawProgress))
if !animationState.fromIsUnlocked && animationState.bounce {
expandBoundsFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: 1.0, toFraction: 0.0)
} else {
expandBoundsFraction = 0.0
}
} else {
collapsedState = CollapseState(
globalFraction: component.collapseFraction,
scaleFraction: targetScaleFraction,
minFraction: targetMinFraction,
maxFraction: targetMaxFraction,
sideAlphaFraction: targetSideAlphaFraction
)
expandBoundsFraction = 0.0
}
let defaultCollapsedTitleOffset = floor((itemLayout.containerSize.width - component.titleContentWidth) * 0.5)
let targetCollapsedTitleOffset: CGFloat = collapsedContentOrigin + collapsedContentWidth + titleContentSpacing
let collapsedTitleOffset = targetCollapsedTitleOffset - defaultCollapsedTitleOffset
let titleMinContentOffset: CGFloat = collapsedTitleOffset.interpolate(to: collapsedTitleOffset + 12.0, amount: collapsedState.minFraction)
let titleContentOffset: CGFloat = titleMinContentOffset.interpolate(to: 0.0 as CGFloat, amount: collapsedState.maxFraction)
component.updateTitleContentOffset(titleContentOffset, transition)
self.currentFraction = collapsedState.globalFraction
component.externalState.collapsedWidth = collapsedContentWidth
let effectiveVisibleBounds = self.scrollView.bounds
let visibleBounds = effectiveVisibleBounds.insetBy(dx: -200.0, dy: 0.0)
struct MeasuredItem {
var itemFrame: CGRect
var itemScale: CGFloat
}
let calculateItem: (Int) -> MeasuredItem = { i in
let regularItemFrame = itemLayout.frame(at: i)
let isReallyVisible = effectiveVisibleBounds.intersects(regularItemFrame)
let collapsedItemX: CGFloat
if i < collapseStartIndex {
collapsedItemX = collapsedContentOrigin
} else if i > collapseEndIndex {
collapsedItemX = collapsedContentOrigin + CGFloat(collapseEndIndex) * collapsedItemDistance - collapsedItemWidth * 0.5
} else {
collapsedItemX = collapsedContentOrigin + CGFloat(i - collapseStartIndex) * collapsedItemDistance
}
let collapsedItemFrame = CGRect(origin: CGPoint(x: collapsedItemX, y: regularItemFrame.minY + collapsedItemOffsetY), size: CGSize(width: collapsedItemWidth, height: regularItemFrame.height))
var collapsedMaxItemFrame = collapsedItemFrame
var collapseDistance: CGFloat = CGFloat(i - collapseStartIndex) / CGFloat(collapseEndIndex - collapseStartIndex)
collapseDistance = max(0.0, min(1.0, collapseDistance))
collapsedMaxItemFrame.origin.x -= collapsedState.minFraction * 4.0
collapsedMaxItemFrame.origin.x += collapseDistance * 20.0
collapsedMaxItemFrame.origin.y += collapseDistance * 20.0
collapsedMaxItemFrame.origin.y += collapsedState.minFraction * 10.0
let minimizedItemScale: CGFloat = 24.0 / 52.0
let minimizedMaxItemScale: CGFloat = (24.0 + 4.0) / 52.0
let maximizedItemScale: CGFloat = 1.0
let minItemScale = minimizedItemScale.interpolate(to: minimizedMaxItemScale, amount: collapsedState.minFraction)
let itemScale: CGFloat = minItemScale.interpolate(to: maximizedItemScale, amount: collapsedState.maxFraction)
let itemFrame: CGRect
if isReallyVisible {
var adjustedRegularFrame = regularItemFrame
if i < collapseStartIndex {
adjustedRegularFrame = adjustedRegularFrame.interpolate(to: itemLayout.frame(at: collapseStartIndex), amount: 0.0)
} else if i > collapseEndIndex {
adjustedRegularFrame = adjustedRegularFrame.interpolate(to: itemLayout.frame(at: collapseEndIndex), amount: 0.0)
}
let collapsedItemPosition: CGPoint = collapsedItemFrame.center.interpolate(to: collapsedMaxItemFrame.center, amount: collapsedState.minFraction)
var itemPosition = collapsedItemPosition.interpolate(to: adjustedRegularFrame.center, amount: collapsedState.maxFraction)
var bounceOffsetFraction = (adjustedRegularFrame.midX - itemLayout.frame(at: collapseStartIndex).midX) / itemLayout.containerSize.width
bounceOffsetFraction = max(-1.0, min(1.0, bounceOffsetFraction))
itemPosition.x += min(10.0, expandBoundsFraction * collapsedState.maxFraction * 1200.0) * bounceOffsetFraction
//let itemPosition = solveParabolicMotion(from: collapsedItemPosition, to: adjustedRegularFrame.center, progress: collapsedState.maxFraction)
let itemSize = CGSize(width: adjustedRegularFrame.width * itemScale, height: adjustedRegularFrame.height)
itemFrame = itemSize.centered(around: itemPosition)
} else {
itemFrame = regularItemFrame
}
return MeasuredItem(
itemFrame: itemFrame,
itemScale: itemScale
)
}
var validIds: [EnginePeer.Id] = []
for i in 0 ..< self.sortedItems.count {
let itemSet = self.sortedItems[i]
@ -334,33 +612,7 @@ public final class StoryPeerListComponent: Component {
}
}
let collapsedItemX: CGFloat
let collapsedItemScaleFactor: CGFloat
if i < collapseStartIndex {
collapsedItemX = collapsedContentOrigin
collapsedItemScaleFactor = 0.1
} else if i > collapseEndIndex {
collapsedItemX = collapsedContentOrigin + CGFloat(collapseEndIndex) * collapsedItemDistance - collapsedItemWidth * 0.5
collapsedItemScaleFactor = 0.1
} else {
collapsedItemX = collapsedContentOrigin + CGFloat(i - collapseStartIndex) * collapsedItemDistance
collapsedItemScaleFactor = 1.0
}
let collapsedItemFrame = CGRect(origin: CGPoint(x: collapsedItemX, y: regularItemFrame.minY + collapsedItemOffsetY), size: CGSize(width: collapsedItemWidth, height: regularItemFrame.height))
let itemFrame: CGRect
if isReallyVisible {
var adjustedRegularFrame = regularItemFrame
if i < collapseStartIndex {
adjustedRegularFrame = adjustedRegularFrame.interpolate(to: itemLayout.frame(at: collapseStartIndex), amount: 1.0 - component.unlockedFraction)
} else if i > collapseEndIndex {
adjustedRegularFrame = adjustedRegularFrame.interpolate(to: itemLayout.frame(at: collapseEndIndex), amount: 1.0 - component.unlockedFraction)
}
itemFrame = adjustedRegularFrame.interpolate(to: collapsedItemFrame, amount: component.collapseFraction)
} else {
itemFrame = regularItemFrame
}
let measuredItem = calculateItem(i)
var leftItemFrame: CGRect?
var rightItemFrame: CGRect?
@ -372,31 +624,23 @@ public final class StoryPeerListComponent: Component {
isCollapsable = true
if i != collapseStartIndex {
let regularLeftItemFrame = itemLayout.frame(at: i - 1)
let collapsedLeftItemFrame = CGRect(origin: CGPoint(x: collapsedContentOrigin + CGFloat(i - collapseStartIndex - 1) * collapsedItemDistance, y: regularLeftItemFrame.minY), size: CGSize(width: collapsedItemWidth, height: regularLeftItemFrame.height))
leftItemFrame = regularLeftItemFrame.interpolate(to: collapsedLeftItemFrame, amount: component.collapseFraction)
leftItemFrame = calculateItem(i - 1).itemFrame
}
if i != collapseEndIndex {
let regularRightItemFrame = itemLayout.frame(at: i - 1)
let collapsedRightItemFrame = CGRect(origin: CGPoint(x: collapsedContentOrigin + CGFloat(i - collapseStartIndex - 1) * collapsedItemDistance, y: regularRightItemFrame.minY), size: CGSize(width: collapsedItemWidth, height: regularRightItemFrame.height))
rightItemFrame = regularRightItemFrame.interpolate(to: collapsedRightItemFrame, amount: component.collapseFraction)
rightItemFrame = calculateItem(i + 1).itemFrame
}
} else {
if component.collapseFraction == 1.0 || component.unlockedFraction == 0.0 {
itemAlpha = 0.0
} else {
itemAlpha = 1.0
}
itemAlpha = collapsedState.sideAlphaFraction
}
var leftNeighborDistance: CGFloat?
var rightNeighborDistance: CGFloat?
var leftNeighborDistance: CGPoint?
var rightNeighborDistance: CGPoint?
if let leftItemFrame {
leftNeighborDistance = abs(leftItemFrame.midX - itemFrame.midX)
leftNeighborDistance = CGPoint(x: abs(leftItemFrame.midX - measuredItem.itemFrame.midX), y: leftItemFrame.minY - measuredItem.itemFrame.minY)
}
if let rightItemFrame {
rightNeighborDistance = abs(rightItemFrame.midX - itemFrame.midX)
rightNeighborDistance = CGPoint(x: abs(rightItemFrame.midX - measuredItem.itemFrame.midX), y: rightItemFrame.minY - measuredItem.itemFrame.minY)
}
let _ = visibleItem.view.update(
@ -409,9 +653,10 @@ public final class StoryPeerListComponent: Component {
hasUnseen: hasUnseen,
hasItems: hasItems,
ringAnimation: itemRingAnimation,
collapseFraction: isReallyVisible ? component.collapseFraction : 0.0,
collapsedScaleFactor: collapsedItemScaleFactor,
collapseFraction: isReallyVisible ? (1.0 - collapsedState.maxFraction) : 0.0,
scale: measuredItem.itemScale,
collapsedWidth: collapsedItemWidth,
expandedAlphaFraction: collapsedState.sideAlphaFraction,
leftNeighborDistance: leftNeighborDistance,
rightNeighborDistance: rightNeighborDistance,
action: component.peerAction,
@ -435,13 +680,13 @@ public final class StoryPeerListComponent: Component {
itemView.backgroundContainer.layer.zPosition = 0.0
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
itemTransition.setFrame(view: itemView, frame: measuredItem.itemFrame)
itemTransition.setAlpha(view: itemView, alpha: itemAlpha)
itemTransition.setScale(view: itemView, scale: itemScale)
itemTransition.setScale(view: itemView, scale: 1.0)
itemTransition.setFrame(view: itemView.backgroundContainer, frame: itemFrame)
itemTransition.setFrame(view: itemView.backgroundContainer, frame: measuredItem.itemFrame)
itemTransition.setAlpha(view: itemView.backgroundContainer, alpha: itemAlpha)
itemTransition.setScale(view: itemView.backgroundContainer, scale: itemScale)
itemTransition.setScale(view: itemView.backgroundContainer, scale: 1.0)
itemView.updateIsPreviewing(isPreviewing: self.previewedItemId == itemSet.peer.id)
}
@ -469,13 +714,24 @@ public final class StoryPeerListComponent: Component {
self.visibleItems.removeValue(forKey: id)
}
transition.setFrame(view: self.collapsedButton, frame: CGRect(origin: CGPoint(x: collapsedContentOrigin, y: 6.0), size: CGSize(width: collapsedContentWidth, height: 44.0 - 4.0)))
transition.setFrame(view: self.collapsedButton, frame: CGRect(origin: CGPoint(x: collapsedContentOrigin - 4.0, y: 6.0 - 59.0), size: CGSize(width: collapsedContentWidth + 4.0, height: 44.0)))
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let result = super.hitTest(point, with: event) else {
if self.alpha.isZero {
return nil
}
var result: UIView?
for view in self.subviews.reversed() {
if let resultValue = view.hitTest(self.convert(point, to: view), with: event), resultValue.isUserInteractionEnabled {
result = resultValue
}
}
guard let result else {
return nil
}
if self.collapsedButton.isUserInteractionEnabled {
if result !== self.collapsedButton {
return nil
@ -489,6 +745,9 @@ public final class StoryPeerListComponent: Component {
}
func update(component: StoryPeerListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
var transition = transition
transition.animation = .none
if self.component != nil {
if component.collapseFraction != 0.0 && self.scrollView.bounds.minX != 0.0 {
self.ignoreScrolling = true
@ -503,6 +762,53 @@ public final class StoryPeerListComponent: Component {
}
}
let animationHint = transition.userData(AnimationHint.self)
var useAnimation = false
if let previousComponent = self.component, component.unlocked != previousComponent.unlocked {
useAnimation = true
} else if let animationHint, animationHint.allowAvatarsExpansionUpdated {
useAnimation = true
}
if let animationHint, animationHint.disableAnimations {
useAnimation = false
self.animationState = nil
}
let timestamp = CACurrentMediaTime()
if let previousComponent = self.component, useAnimation {
let duration: Double
if let durationValue = animationHint?.duration {
duration = durationValue
} else if component.unlocked {
duration = 0.3
} else {
duration = 0.25
}
self.animationState = AnimationState(duration: duration * UIView.animationDurationFactor(), fromIsUnlocked: previousComponent.unlocked, fromFraction: self.currentFraction, startTime: timestamp, bounce: animationHint?.bounce ?? true)
}
if let animationState = self.animationState {
if animationState.isFinished(at: timestamp) {
self.animationState = nil
}
}
if let _ = self.animationState {
if self.animator == nil {
let animator = ConstantDisplayLinkAnimator(update: { [weak self] in
guard let self else {
return
}
self.state?.updated(transition: .immediate)
})
self.animator = animator
animator.isPaused = false
}
} else if let animator = self.animator {
self.animator = nil
animator.invalidate()
}
self.component = component
self.state = state
@ -522,7 +828,7 @@ public final class StoryPeerListComponent: Component {
}
}
self.collapsedButton.isUserInteractionEnabled = component.collapseFraction >= 1.0 - .ulpOfOne
self.collapsedButton.isUserInteractionEnabled = !component.unlocked
self.sortedItems.removeAll(keepingCapacity: true)
if let storySubscriptions = component.storySubscriptions {

View File

@ -269,10 +269,11 @@ public final class StoryPeerListItemComponent: Component {
public let hasItems: Bool
public let ringAnimation: RingAnimation?
public let collapseFraction: CGFloat
public let collapsedScaleFactor: CGFloat
public let scale: CGFloat
public let collapsedWidth: CGFloat
public let leftNeighborDistance: CGFloat?
public let rightNeighborDistance: CGFloat?
public let expandedAlphaFraction: CGFloat
public let leftNeighborDistance: CGPoint?
public let rightNeighborDistance: CGPoint?
public let action: (EnginePeer) -> Void
public let contextGesture: (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void
@ -285,10 +286,11 @@ public final class StoryPeerListItemComponent: Component {
hasItems: Bool,
ringAnimation: RingAnimation?,
collapseFraction: CGFloat,
collapsedScaleFactor: CGFloat,
scale: CGFloat,
collapsedWidth: CGFloat,
leftNeighborDistance: CGFloat?,
rightNeighborDistance: CGFloat?,
expandedAlphaFraction: CGFloat,
leftNeighborDistance: CGPoint?,
rightNeighborDistance: CGPoint?,
action: @escaping (EnginePeer) -> Void,
contextGesture: @escaping (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void
) {
@ -300,8 +302,9 @@ public final class StoryPeerListItemComponent: Component {
self.hasItems = hasItems
self.ringAnimation = ringAnimation
self.collapseFraction = collapseFraction
self.collapsedScaleFactor = collapsedScaleFactor
self.scale = scale
self.collapsedWidth = collapsedWidth
self.expandedAlphaFraction = expandedAlphaFraction
self.leftNeighborDistance = leftNeighborDistance
self.rightNeighborDistance = rightNeighborDistance
self.action = action
@ -333,12 +336,15 @@ public final class StoryPeerListItemComponent: Component {
if lhs.collapseFraction != rhs.collapseFraction {
return false
}
if lhs.collapsedScaleFactor != rhs.collapsedScaleFactor {
if lhs.scale != rhs.scale {
return false
}
if lhs.collapsedWidth != rhs.collapsedWidth {
return false
}
if lhs.expandedAlphaFraction != rhs.expandedAlphaFraction {
return false
}
if lhs.leftNeighborDistance != rhs.leftNeighborDistance {
return false
}
@ -516,7 +522,7 @@ public final class StoryPeerListItemComponent: Component {
let effectiveWidth: CGFloat = (1.0 - component.collapseFraction) * availableSize.width + component.collapseFraction * component.collapsedWidth
let effectiveScale: CGFloat = 1.0 * (1.0 - component.collapseFraction) + (24.0 / 52.0) * component.collapsedScaleFactor * component.collapseFraction
let effectiveScale: CGFloat = component.scale
let avatarNode: AvatarNode
if let current = self.avatarNode {
@ -665,10 +671,10 @@ public final class StoryPeerListItemComponent: Component {
var mappedRightCenter: CGPoint?
if let leftNeighborDistance = component.leftNeighborDistance {
mappedLeftCenter = CGPoint(x: indicatorCenter.x - leftNeighborDistance * (1.0 / effectiveScale), y: indicatorCenter.y)
mappedLeftCenter = CGPoint(x: indicatorCenter.x - leftNeighborDistance.x * (1.0 / effectiveScale), y: indicatorCenter.y + leftNeighborDistance.y * (1.0 / effectiveScale))
}
if let rightNeighborDistance = component.rightNeighborDistance {
mappedRightCenter = CGPoint(x: indicatorCenter.x + rightNeighborDistance * (1.0 / effectiveScale), y: indicatorCenter.y)
mappedRightCenter = CGPoint(x: indicatorCenter.x + rightNeighborDistance.x * (1.0 / effectiveScale), y: indicatorCenter.y + rightNeighborDistance.y * (1.0 / effectiveScale))
}
let avatarPath = CGMutablePath()
@ -677,7 +683,7 @@ public final class StoryPeerListItemComponent: Component {
let cutoutSize: CGFloat = 18.0 + UIScreenPixel * 2.0
avatarPath.addEllipse(in: CGRect(origin: CGPoint(x: avatarSize.width - cutoutSize + UIScreenPixel, y: avatarSize.height - 1.0 - cutoutSize + UIScreenPixel), size: CGSize(width: cutoutSize, height: cutoutSize)))
} else if let mappedLeftCenter {
avatarPath.addEllipse(in: CGRect(origin: CGPoint(), size: avatarSize).insetBy(dx: -indicatorLineWidth, dy: -indicatorLineWidth).offsetBy(dx: -abs(indicatorCenter.x - mappedLeftCenter.x), dy: 0.0))
avatarPath.addEllipse(in: CGRect(origin: CGPoint(), size: avatarSize).insetBy(dx: -indicatorLineWidth, dy: -indicatorLineWidth).offsetBy(dx: -abs(indicatorCenter.x - mappedLeftCenter.x), dy: -abs(indicatorCenter.y - mappedLeftCenter.y)))
}
Transition.immediate.setShapeLayerPath(layer: self.avatarShapeLayer, path: avatarPath)
@ -700,10 +706,10 @@ public final class StoryPeerListItemComponent: Component {
if let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) {
self.button.addSubview(snapshotView)
snapshotView.frame = titleView.frame
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView.layer.animateAlpha(from: component.expandedAlphaFraction, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
titleView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
titleView.layer.animateAlpha(from: 0.0, to: component.expandedAlphaFraction, duration: 0.25)
}
titleTransition = .immediate
@ -741,7 +747,7 @@ public final class StoryPeerListItemComponent: Component {
titleTransition.setPosition(view: titleView, position: titleFrame.center)
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
titleTransition.setScale(view: titleView, scale: effectiveScale)
titleTransition.setAlpha(view: titleView, alpha: 1.0 - component.collapseFraction)
titleTransition.setAlpha(view: titleView, alpha: component.expandedAlphaFraction)
}
if let ringAnimation = component.ringAnimation {

View File

@ -105,7 +105,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
let chatListFilters = chatSelection.chatListFilters
placeholder = placeholderValue
let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters, displayAutoremoveTimeout: chatSelection.displayAutoremoveTimeout, displayPresence: chatSelection.displayPresence), isPeerEnabled: isPeerEnabled, theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters, displayAutoremoveTimeout: chatSelection.displayAutoremoveTimeout, displayPresence: chatSelection.displayPresence), isPeerEnabled: isPeerEnabled, theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false, autoSetReady: true)
chatListNode.passthroughPeerSelection = true
chatListNode.disabledPeerSelected = { peer, _ in
attemptDisabledItemSelection?(peer)
@ -263,7 +263,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
case let .contacts(contactsNode):
contactsNode.scrollToTop()
case let .chats(chatsNode):
chatsNode.scrollToPosition(.top)
chatsNode.scrollToPosition(.top(adjustForTempInset: false))
}
}

View File

@ -210,7 +210,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.chatListNode = nil
} else {
self.mainContainerNode = nil
self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: chatListMode, theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: chatListMode, theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false, autoSetReady: true)
}
super.init()
@ -1291,9 +1291,9 @@ final class PeerSelectionControllerNode: ASDisplayNode {
func scrollToTop() {
if self.mainContainerNode?.supernode != nil {
self.mainContainerNode?.scrollToTop(animated: true)
self.mainContainerNode?.scrollToTop(animated: true, adjustForTempInset: false)
} else if self.chatListNode?.supernode != nil {
self.chatListNode?.scrollToPosition(.top)
self.chatListNode?.scrollToPosition(.top(adjustForTempInset: false))
} else if let contactListNode = self.contactListNode, contactListNode.supernode != nil {
//contactListNode.scrollToTop()
}