mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Story header view
This commit is contained in:
parent
e4dfc81876
commit
f8a1457732
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user