diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index b7b11cdd4e..0d87fe12f6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1737,7 +1737,7 @@ public final class EngineStoryViewListContext { } init(account: Account, storyId: Int32, views: EngineStoryItem.Views) { - let queue = Queue() + let queue = Queue.mainQueue() self.queue = queue self.impl = QueueLocalObject(queue: queue, generate: { return Impl(queue: queue, account: account, storyId: storyId, views: views) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 968df7dc17..897f3352d7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -861,7 +861,7 @@ public extension TelegramEngine { let _ = accountPeer let _ = storiesStateView - var sortedItems: [(peer: Peer, item: Stories.Item)] = [] + var sortedItems: [(peer: Peer, item: Stories.Item, hasUnseen: Bool, lastTimestamp: Int32)] = [] for peerId in storySubscriptionsView.peerIds { guard let peerView = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView else { @@ -878,21 +878,48 @@ public extension TelegramEngine { } var nextItem: Stories.StoredItem? = itemsView.items.first?.value.get(Stories.StoredItem.self) + let lastTimestamp = itemsView.items.last?.value.get(Stories.StoredItem.self)?.timestamp let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) + var hasUnseen = false if let peerState = peerState { if let item = itemsView.items.first(where: { $0.id > peerState.maxReadId }) { + hasUnseen = true nextItem = item.value.get(Stories.StoredItem.self) } } - if let nextItem = nextItem, case let .item(item) = nextItem { - sortedItems.append((peer, item)) + if let nextItem = nextItem, case let .item(item) = nextItem, let lastTimestamp { + sortedItems.append((peer, item, hasUnseen, lastTimestamp)) } } sortedItems.sort(by: { lhs, rhs in - return lhs.item.timestamp > rhs.item.timestamp + if lhs.hasUnseen != rhs.hasUnseen { + if lhs.hasUnseen { + return true + } else { + return false + } + } + if EnginePeer(lhs.peer).isService != EnginePeer(rhs.peer).isService { + if EnginePeer(lhs.peer).isService { + return true + } else { + return false + } + } + if lhs.peer.isPremium != rhs.peer.isPremium { + if lhs.peer.isPremium { + return true + } else { + return false + } + } + if lhs.lastTimestamp != rhs.lastTimestamp { + return lhs.lastTimestamp > rhs.lastTimestamp + } + return lhs.peer.id < rhs.peer.id }) var nextPriority: Int = 0 diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift index d715c9f2ea..f3473da42d 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift @@ -337,7 +337,7 @@ public final class ChatListNavigationBar: Component { if allowAvatarsExpansion && transition.animation.isImmediate { if self.storiesUnlocked != storiesUnlocked { if storiesUnlocked { - HapticFeedback().impact(.veryLight) + HapticFeedback().tap() } else { HapticFeedback().impact(.veryLight) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 8b680b5284..78c3cb5cad 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -183,6 +183,8 @@ private final class StoryContainerScreenComponent: Component { private var availableReactions: StoryAvailableReactions? + private let sharedViewListsContext = StoryItemSetViewListComponent.SharedListsContext() + private var isAnimatingOut: Bool = false private var didAnimateOut: Bool = false @@ -448,10 +450,10 @@ private final class StoryContainerScreenComponent: Component { self.verticalPanState = nil var updateState = true - if translation.y > 100.0 || velocity.y > 10.0 { + if translation.y > 200.0 || (translation.y > 100.0 && velocity.y > 200.0) { self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) self.environment?.controller()?.dismiss() - } else if translation.y < -100.0 || velocity.y < -40.0 { + } else if translation.y < -200.0 || (translation.y < -100.0 && velocity.y < -100.0) { if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] { if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { if itemSetComponentView.activateInput() { @@ -980,7 +982,8 @@ private final class StoryContainerScreenComponent: Component { } } }, - keyboardInputData: self.inputMediaNodeDataPromise.get() + keyboardInputData: self.inputMediaNodeDataPromise.get(), + sharedViewListsContext: self.sharedViewListsContext )), environment: {}, containerSize: itemSetContainerSize diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index e38061a532..2946b3ae2f 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -98,8 +98,9 @@ public final class StoryItemSetContainerComponent: Component { public let controller: () -> ViewController? public let toggleAmbientMode: () -> Void public let keyboardInputData: Signal + let sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext - public init( + init( context: AccountContext, externalState: ExternalState, storyItemSharedState: StoryContentItem.SharedState, @@ -125,7 +126,8 @@ public final class StoryItemSetContainerComponent: Component { markAsSeen: @escaping (StoryId) -> Void, controller: @escaping () -> ViewController?, toggleAmbientMode: @escaping () -> Void, - keyboardInputData: Signal + keyboardInputData: Signal, + sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext ) { self.context = context self.externalState = externalState @@ -153,6 +155,7 @@ public final class StoryItemSetContainerComponent: Component { self.controller = controller self.toggleAmbientMode = toggleAmbientMode self.keyboardInputData = keyboardInputData + self.sharedViewListsContext = sharedViewListsContext } public static func ==(lhs: StoryItemSetContainerComponent, rhs: StoryItemSetContainerComponent) -> Bool { @@ -1705,12 +1708,16 @@ public final class StoryItemSetContainerComponent: Component { let viewListSize = viewList.view.update( transition: viewListTransition.withUserData(PeerListItemComponent.TransitionHint( synchronousLoad: false + )).withUserData(StoryItemSetViewListComponent.AnimationHint( + synchronous: false )), component: AnyComponent(StoryItemSetViewListComponent( externalState: viewList.externalState, context: component.context, theme: component.theme, strings: component.strings, + sharedListsContext: component.sharedViewListsContext, + peerId: component.slice.peer.id, safeInsets: component.safeInsets, storyItem: component.slice.item.storyItem, outerExpansionFraction: outerExpansionFraction, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index 987543b58b..be2c33fd56 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -4,6 +4,7 @@ import Display import ComponentFlow import MultilineTextComponent import TelegramCore +import Postbox import TelegramPresentationData import ComponentDisplayAdapters import AccountContext @@ -14,6 +15,14 @@ import StoryFooterPanelComponent import PeerListItemComponent final class StoryItemSetViewListComponent: Component { + final class AnimationHint { + let synchronous: Bool + + init(synchronous: Bool) { + self.synchronous = synchronous + } + } + final class ExternalState { fileprivate(set) var minimizedHeight: CGFloat = 0.0 fileprivate(set) var effectiveHeight: CGFloat = 0.0 @@ -22,10 +31,19 @@ final class StoryItemSetViewListComponent: Component { } } + final class SharedListsContext { + var viewLists: [StoryId: EngineStoryViewListContext] = [:] + + init() { + } + } + let externalState: ExternalState let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings + let sharedListsContext: SharedListsContext + let peerId: EnginePeer.Id let safeInsets: UIEdgeInsets let storyItem: EngineStoryItem let outerExpansionFraction: CGFloat @@ -40,6 +58,8 @@ final class StoryItemSetViewListComponent: Component { context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, + sharedListsContext: SharedListsContext, + peerId: EnginePeer.Id, safeInsets: UIEdgeInsets, storyItem: EngineStoryItem, outerExpansionFraction: CGFloat, @@ -53,6 +73,8 @@ final class StoryItemSetViewListComponent: Component { self.context = context self.theme = theme self.strings = strings + self.sharedListsContext = sharedListsContext + self.peerId = peerId self.safeInsets = safeInsets self.storyItem = storyItem self.outerExpansionFraction = outerExpansionFraction @@ -70,6 +92,9 @@ final class StoryItemSetViewListComponent: Component { if lhs.strings !== rhs.strings { return false } + if lhs.peerId != rhs.peerId { + return false + } if lhs.safeInsets != rhs.safeInsets { return false } @@ -175,7 +200,6 @@ final class StoryItemSetViewListComponent: Component { private var ignoreScrolling: Bool = false - private var viewList: EngineStoryViewListContext? private var viewListDisposable: Disposable? private var viewListState: EngineStoryViewListContext.State? private var requestedLoadMoreToken: EngineStoryViewListContext.LoadMoreToken? @@ -495,7 +519,7 @@ final class StoryItemSetViewListComponent: Component { self.visiblePlaceholderViews.removeValue(forKey: id) } - if let viewList = self.viewList, let viewListState = self.viewListState, viewListState.loadMoreToken != nil, visibleBounds.maxY >= self.scrollView.contentSize.height - 200.0 { + if let viewList = component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)], let viewListState = self.viewListState, viewListState.loadMoreToken != nil, visibleBounds.maxY >= self.scrollView.contentSize.height - 200.0 { if self.requestedLoadMoreToken != viewListState.loadMoreToken { self.requestedLoadMoreToken = viewListState.loadMoreToken viewList.loadMore() @@ -510,6 +534,11 @@ final class StoryItemSetViewListComponent: Component { self.component = component self.state = state + var synchronous = false + if let animationHint = transition.userData(AnimationHint.self) { + synchronous = animationHint.synchronous + } + let minimizedHeight = min(availableSize.height, 500.0) if themeUpdated { @@ -520,24 +549,37 @@ final class StoryItemSetViewListComponent: Component { if itemUpdated { self.viewListState = nil - self.viewList = nil self.viewListDisposable?.dispose() if let views = component.storyItem.views { - let viewList = component.context.engine.messages.storyViewList(id: component.storyItem.id, views: views) - self.viewList = viewList + let viewList: EngineStoryViewListContext + if let current = component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] { + viewList = current + } else { + viewList = component.context.engine.messages.storyViewList(id: component.storyItem.id, views: views) + component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList + } + var applyState = false + var firstTime = true self.viewListDisposable = (viewList.state |> deliverOnMainQueue).start(next: { [weak self] listState in guard let self else { return } + if firstTime { + firstTime = false + self.ignoreScrolling = true + self.scrollView.setContentOffset(CGPoint(), animated: false) + self.ignoreScrolling = false + } self.viewListState = listState if applyState { - self.state?.updated(transition: Transition.immediate.withUserData(PeerListItemComponent.TransitionHint(synchronousLoad: true))) + self.state?.updated(transition: Transition.immediate.withUserData(PeerListItemComponent.TransitionHint(synchronousLoad: false))) } }) applyState = true + let _ = synchronous } } @@ -611,15 +653,17 @@ final class StoryItemSetViewListComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width, height: 200.0) ) - if let navigationPanelView = self.navigationPanel.view { + if let navigationPanelView = self.navigationPanel.view as? StoryFooterPanelComponent.View { if navigationPanelView.superview == nil { self.addSubview(navigationPanelView) + self.insertSubview(navigationPanelView.externalContainerView, belowSubview: self.navigationBarBackground) } let expandedNavigationPanelFrame = CGRect(origin: CGPoint(x: navigationBarFrame.minX, y: navigationBarFrame.minY + 4.0), size: navigationPanelSize) let collapsedNavigationPanelFrame = CGRect(origin: CGPoint(x: navigationBarFrame.minX, y: navigationBarFrame.minY - navigationPanelSize.height - component.safeInsets.bottom - 1.0), size: navigationPanelSize) transition.setFrame(view: navigationPanelView, frame: collapsedNavigationPanelFrame.interpolate(to: expandedNavigationPanelFrame, amount: dismissFraction)) + transition.setFrame(view: navigationPanelView.externalContainerView, frame: collapsedNavigationPanelFrame.interpolate(to: expandedNavigationPanelFrame, amount: dismissFraction)) } transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height))) diff --git a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift index 2efabc8e71..e1fe3fbbea 100644 --- a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift @@ -49,7 +49,7 @@ public final class StoryFooterPanelComponent: Component { } public final class View: UIView { - private let viewStatsButton: HighlightableButton + private let viewStatsButton: HighlightTrackingButton private let viewStatsText = ComponentView() private let viewStatsExpandedText = ComponentView() private let deleteButton = ComponentView() @@ -67,18 +67,34 @@ public final class StoryFooterPanelComponent: Component { private var uploadProgress: Float = 0.0 private var uploadProgressDisposable: Disposable? + public let externalContainerView: UIView + override init(frame: CGRect) { - self.viewStatsButton = HighlightableButton() + self.viewStatsButton = HighlightTrackingButton() self.avatarsContext = AnimatedAvatarSetContext() self.avatarsNode = AnimatedAvatarSetNode() + self.externalContainerView = UIView() + super.init(frame: frame) self.avatarsNode.view.isUserInteractionEnabled = false - self.viewStatsButton.addSubview(self.avatarsNode.view) + self.externalContainerView.addSubview(self.avatarsNode.view) self.addSubview(self.viewStatsButton) + self.viewStatsButton.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.avatarsNode.view.alpha = 0.7 + self.viewStatsText.view?.alpha = 0.7 + } else { + self.avatarsNode.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2) + self.viewStatsText.view?.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2) + } + } self.viewStatsButton.addTarget(self, action: #selector(self.viewStatsPressed), for: .touchUpInside) } @@ -224,7 +240,8 @@ public final class StoryFooterPanelComponent: Component { let avatarsSize = self.avatarsNode.update(context: component.context, content: avatarsContent, itemSize: CGSize(width: 30.0, height: 30.0), animated: false, synchronousLoad: true) let avatarsNodeFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - avatarsSize.height) * 0.5)), size: avatarsSize) - self.avatarsNode.frame = avatarsNodeFrame + self.avatarsNode.position = avatarsNodeFrame.center + self.avatarsNode.bounds = CGRect(origin: CGPoint(), size: avatarsNodeFrame.size) transition.setAlpha(view: self.avatarsNode.view, alpha: avatarsAlpha) if !avatarsSize.width.isZero { leftOffset = avatarsNodeFrame.maxX + avatarSpacing @@ -269,7 +286,7 @@ public final class StoryFooterPanelComponent: Component { if let viewStatsTextView = self.viewStatsText.view { if viewStatsTextView.superview == nil { viewStatsTextView.isUserInteractionEnabled = false - self.viewStatsButton.addSubview(viewStatsTextView) + self.externalContainerView.addSubview(viewStatsTextView) } transition.setPosition(view: viewStatsTextView, position: viewStatsTextFrame.center) transition.setBounds(view: viewStatsTextView, bounds: CGRect(origin: CGPoint(), size: viewStatsTextFrame.size)) @@ -281,7 +298,7 @@ public final class StoryFooterPanelComponent: Component { if let viewStatsExpandedTextView = self.viewStatsExpandedText.view { if viewStatsExpandedTextView.superview == nil { viewStatsExpandedTextView.isUserInteractionEnabled = false - self.viewStatsButton.addSubview(viewStatsExpandedTextView) + self.addSubview(viewStatsExpandedTextView) } transition.setPosition(view: viewStatsExpandedTextView, position: viewStatsExpandedTextFrame.center) transition.setBounds(view: viewStatsExpandedTextView, bounds: CGRect(origin: CGPoint(), size: viewStatsExpandedTextFrame.size)) @@ -313,7 +330,7 @@ public final class StoryFooterPanelComponent: Component { ) if let deleteButtonView = self.deleteButton.view { if deleteButtonView.superview == nil { - self.addSubview(deleteButtonView) + self.externalContainerView.addSubview(deleteButtonView) } transition.setFrame(view: deleteButtonView, frame: CGRect(origin: CGPoint(x: rightContentOffset - deleteButtonSize.width, y: floor((size.height - deleteButtonSize.height) * 0.5)), size: deleteButtonSize)) rightContentOffset -= deleteButtonSize.width + 8.0