[WIP] Stories

This commit is contained in:
Ali 2023-06-06 23:23:48 +04:00
parent 7dc3dc078e
commit af19d3f4b5
55 changed files with 1114 additions and 361 deletions

View File

@ -299,6 +299,7 @@ public enum ResolvedUrl {
case invoice(slug: String, invoice: TelegramMediaInvoice?)
case premiumOffer(reference: String?)
case chatFolder(slug: String)
case story(peerId: PeerId, id: Int32)
}
public enum NavigateToChatKeepStack {

View File

@ -326,9 +326,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
case let .known(offset):
let isFirstFilter = strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.chatListFilter == strongSelf.chatListDisplayNode.mainContainerNode.availableFilters.first?.filter
if offset <= navigationBarSearchContentHeight + 1.0 && strongSelf.chatListDisplayNode.inlineStackContainerNode != nil {
if offset <= ChatListNavigationBar.searchScrollHeight + 1.0 && strongSelf.chatListDisplayNode.inlineStackContainerNode != nil {
strongSelf.setInlineChatList(location: nil)
} else if offset <= navigationBarSearchContentHeight + 1.0 && !isFirstFilter {
} else if offset <= ChatListNavigationBar.searchScrollHeight + 1.0 && !isFirstFilter {
let firstFilter = strongSelf.chatListDisplayNode.effectiveContainerNode.availableFilters.first ?? .all
let targetTab: ChatListFilterTabEntryId
switch firstFilter {
@ -1244,7 +1244,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
let storyContent = StoryContentContextImpl(context: self.context, focusedPeerId: peerId)
let storyContent = StoryContentContextImpl(context: self.context, includeHidden: false, focusedPeerId: peerId)
let _ = (storyContent.state
|> filter { $0.slice != nil }
|> take(1)
@ -1696,7 +1696,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.displayNodeDidLoad()
if case .chatList(.root) = self.location {
self.preloadStorySubscriptionsDisposable = (self.context.engine.messages.preloadStorySubscriptions()
self.preloadStorySubscriptionsDisposable = (self.context.engine.messages.preloadStorySubscriptions(includeHidden: false)
|> deliverOnMainQueue).start(next: { [weak self] resources in
guard let self else {
return
@ -1729,7 +1729,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.preloadStoryResourceDisposables.removeValue(forKey: id)
}
})
self.storySubscriptionsDisposable = (self.context.engine.messages.storySubscriptions()
self.storySubscriptionsDisposable = (self.context.engine.messages.storySubscriptions(includeHidden: false)
|> deliverOnMainQueue).start(next: { [weak self] storySubscriptions in
guard let self else {
return
@ -2313,10 +2313,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.validLayout = layout
if let searchTabsNode = self.searchTabsNode {
searchTabsNode.bounds.origin = CGPoint(x: 0.0, y: (layout.statusBarHeight ?? 0.0) + navigationBarSearchContentHeight + 44.0)
}
self.updateLayout(layout: layout, transition: transition)
if layout.inVoiceOver != wasInVoiceOver {
@ -2329,7 +2325,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
let storyContent = StoryContentContextImpl(context: self.context, focusedPeerId: peer?.id)
let storyContent = StoryContentContextImpl(context: self.context, includeHidden: false, focusedPeerId: peer?.id)
let _ = (storyContent.state
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] storyContentState in
@ -2424,15 +2420,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
})
})))
items.append(.action(ContextMenuActionItem(text: "Mute", icon: { theme in
/*items.append(.action(ContextMenuActionItem(text: "Mute", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
})))
})))*/
items.append(.action(ContextMenuActionItem(text: "Archive", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
guard let self else {
return
}
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true)
})))
}

View File

@ -185,7 +185,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let timestamp1: Int32 = 100000
let peers: [EnginePeer.Id: EnginePeer] = [:]
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
@ -376,7 +376,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
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)
if let controller, case .chatList(groupId: .root) = controller.location {
self.listNode.scrollHeightTopInset = navigationBarSearchContentHeight + 79.0
self.listNode.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight + ChatListNavigationBar.storiesScrollHeight
}
super.init()
@ -838,6 +838,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
previousItemNode.listNode.openStories = nil
previousItemNode.listNode.addedVisibleChatsWithPeerIds = nil
previousItemNode.listNode.didBeginSelectingChats = nil
previousItemNode.listNode.canExpandHiddenItems = nil
previousItemNode.accessibilityElementsHidden = true
}
@ -914,7 +915,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
if itemNode.listNode.isTracking {
if case let .known(value) = offset {
if !self.storiesUnlocked {
if value < -50.0 {
if value < -40.0 {
self.storiesUnlocked = true
DispatchQueue.main.async { [weak self] in
guard let self else {
@ -935,7 +936,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
} else if self.storiesUnlocked {
switch offset {
case let .known(value):
if value >= 79.0 {
if value >= ChatListNavigationBar.storiesScrollHeight {
self.storiesUnlocked = false
self.onStoriesLockedUpdated?(false)
}
@ -950,7 +951,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
}
switch self.currentItemNode.visibleContentOffset() {
case let .known(value):
if value > 79.0 {
if value > ChatListNavigationBar.storiesScrollHeight {
if self.storiesUnlocked {
self.storiesUnlocked = false
@ -986,6 +987,12 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
itemNode.listNode.didBeginSelectingChats = { [weak self] in
self?.didBeginSelectingChats?()
}
itemNode.listNode.canExpandHiddenItems = { [weak self] in
guard let self, let canExpandHiddenItems = self.canExpandHiddenItems else {
return false
}
return canExpandHiddenItems()
}
self.currentItemStateValue.set(itemNode.listNode.state |> map { state in
let filterId: Int32?
@ -1047,6 +1054,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
var openStories: ((EnginePeer.Id) -> Void)?
var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
var didBeginSelectingChats: (() -> Void)?
var canExpandHiddenItems: (() -> Bool)?
public var displayFilterLimit: (() -> Void)?
public init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, chatListMode: ChatListNodeMode = .chatList(appendContacts: true), previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) {
@ -1715,8 +1723,23 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
guard let self else {
return
}
//self.controller?.requestLayout(transition: .immediate)
self.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
if isLocked {
self.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
} else {
self.controller?.requestLayout(transition: .immediate)
}
}
self.mainContainerNode.canExpandHiddenItems = { [weak self] in
guard let self, let controller = self.controller else {
return false
}
if let storySubscriptions = controller.storySubscriptions, !storySubscriptions.items.isEmpty, !self.mainContainerNode.storiesUnlocked {
return false
} else {
return true
}
}
let inlineContentPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.inlineContentPanGesture(_:)), allowedDirections: { [weak self] _ in
@ -1852,6 +1875,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
secondaryContent: headerContent?.secondaryContent,
secondaryTransition: self.inlineStackContainerTransitionFraction,
storySubscriptions: self.controller?.storySubscriptions,
storiesIncludeHidden: false,
uploadProgress: self.controller?.storyUploadProgress,
tabsNode: tabsNode,
tabsNodeIsSearch: tabsNodeIsSearch,
@ -1948,7 +1972,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var storiesInset = storiesInset
let navigationBarLayout = self.updateNavigationBar(layout: layout, transition: transition)
self.mainContainerNode.initialScrollingOffset = navigationBarSearchContentHeight + navigationBarLayout.storiesInset
self.mainContainerNode.initialScrollingOffset = ChatListNavigationBar.searchScrollHeight + navigationBarLayout.storiesInset
navigationBarHeight = navigationBarLayout.navigationHeight
visualNavigationHeight = navigationBarLayout.navigationHeight
@ -2146,8 +2170,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.mainContainerNode.accessibilityElementsHidden = false
self.inlineStackContainerNode?.accessibilityElementsHidden = false
return { [weak self] in
if let strongSelf = self, let (layout, _, _, cleanNavigationBarHeight, _) = strongSelf.containerLayout {
return { [weak self, weak placeholderNode] in
if let strongSelf = self, let placeholderNode, let (layout, _, _, cleanNavigationBarHeight, _) = strongSelf.containerLayout {
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
@ -2242,21 +2266,21 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
return true
} else {
let searchScrollOffset = clippedScrollOffset - navigationBarComponentView.effectiveStoriesInsetHeight
if searchScrollOffset > 0.0 && searchScrollOffset < navigationBarSearchContentHeight {
if searchScrollOffset < navigationBarSearchContentHeight * 0.5 {
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 + navigationBarSearchContentHeight, animated: true)
let _ = listView.scrollToOffsetFromTop(navigationBarComponentView.effectiveStoriesInsetHeight + ChatListNavigationBar.searchScrollHeight, animated: true)
}
return true
}
}
} else {
if clippedScrollOffset > 0.0 && clippedScrollOffset < navigationBarSearchContentHeight {
if clippedScrollOffset < navigationBarSearchContentHeight * 0.5 {
if clippedScrollOffset > 0.0 && clippedScrollOffset < ChatListNavigationBar.searchScrollHeight {
if clippedScrollOffset < ChatListNavigationBar.searchScrollHeight * 0.5 {
let _ = listView.scrollToOffsetFromTop(0.0, animated: true)
} else {
let _ = listView.scrollToOffsetFromTop(navigationBarSearchContentHeight, animated: true)
let _ = listView.scrollToOffsetFromTop(ChatListNavigationBar.searchScrollHeight, animated: true)
}
return true
}

View File

@ -3394,7 +3394,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let timestamp1: Int32 = 100000
var peers: [EnginePeer.Id: EnginePeer] = [:]
peers[peer1.id] = peer1

View File

@ -19,6 +19,7 @@ import MultiAnimationRenderer
import Postbox
import ChatFolderLinkPreviewScreen
import StoryContainerScreen
import ChatListHeaderComponent
public enum ChatListNodeMode {
case chatList(appendContacts: Bool)
@ -1154,6 +1155,8 @@ public final class ChatListNode: ListView {
public var isEmptyUpdated: ((ChatListNodeEmptyState, Bool, ContainedViewLayoutTransition) -> Void)?
private var currentIsEmptyState: ChatListNodeEmptyState?
public var canExpandHiddenItems: (() -> Bool)?
public var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
private let currentRemovingItemId = Atomic<ChatListNodeState.ItemId?>(value: nil)
@ -1210,7 +1213,7 @@ public final class ChatListNode: ListView {
self.theme = theme
self.scrollHeightTopInset = navigationBarSearchContentHeight
self.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight
super.init()
@ -2379,11 +2382,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
}
}*/
}
}
}
@ -2717,6 +2720,7 @@ public final class ChatListNode: ListView {
}
}
var startedScrollingAtUpperBound = false
var startedScrollingWithCanExpandHiddenItems = false
self.beganInteractiveDragging = { [weak self] _ in
guard let strongSelf = self else {
@ -2728,6 +2732,13 @@ public final class ChatListNode: ListView {
case let .known(value):
startedScrollingAtUpperBound = value <= 0.0
}
if let canExpandHiddenItems = strongSelf.canExpandHiddenItems {
startedScrollingWithCanExpandHiddenItems = canExpandHiddenItems()
} else {
startedScrollingWithCanExpandHiddenItems = true
}
if strongSelf.currentState.peerIdWithRevealedOptions != nil {
strongSelf.updateState { state in
var state = state
@ -2784,7 +2795,7 @@ public final class ChatListNode: ListView {
atTop = false
case let .known(value):
atTop = value <= 0.0
if startedScrollingAtUpperBound && strongSelf.isTracking {
if startedScrollingAtUpperBound && startedScrollingWithCanExpandHiddenItems && strongSelf.isTracking {
revealHiddenItems = value <= -60.0
}
}

View File

@ -36,7 +36,12 @@ swift_library(
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/QrCodeUI:QrCodeUI",
"//submodules/LocalizedPeerData:LocalizedPeerData"
"//submodules/LocalizedPeerData:LocalizedPeerData",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/Stories/StoryContentComponent",
"//submodules/TelegramUI/Components/Stories/StoryPeerListComponent",
"//submodules/TelegramUI/Components/ChatListTitleView",
"//submodules/ComponentFlow",
],
visibility = [
"//visibility:public",

View File

@ -718,7 +718,7 @@ public final class ContactListNode: ASDisplayNode {
private var indexSections: [String]?
private var queuedTransitions: [ContactsListNodeTransition] = []
private var validLayout: (ContainerViewLayout, UIEdgeInsets)?
private var validLayout: (ContainerViewLayout, UIEdgeInsets, CGFloat)?
private var _ready = ValuePromise<Bool>()
public var ready: Signal<Bool, NoError> {
@ -1369,8 +1369,8 @@ public final class ContactListNode: ASDisplayNode {
}
})
if let (validLayout, headerInsets) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(validLayout, headerInsets: headerInsets, transition: .immediate)
if let (validLayout, headerInsets, storiesInset) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(validLayout, headerInsets: headerInsets, storiesInset: storiesInset, transition: .immediate)
}
}
}
@ -1433,9 +1433,12 @@ public final class ContactListNode: ASDisplayNode {
}
}
public func containerLayoutUpdated(_ layout: ContainerViewLayout, headerInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
private var previousStoriesInset: CGFloat?
public var ignoreStoryInsetAdjustment: Bool = false
public func containerLayoutUpdated(_ layout: ContainerViewLayout, headerInsets: UIEdgeInsets, storiesInset: CGFloat, transition: ContainedViewLayoutTransition) {
let hadValidLayout = self.validLayout != nil
self.validLayout = (layout, headerInsets)
self.validLayout = (layout, headerInsets, storiesInset)
var insets = layout.insets(options: [.input])
insets.left = layout.safeInsets.left
@ -1446,12 +1449,22 @@ public final class ContactListNode: ASDisplayNode {
headerInsets.top -= navigationBarSearchContentHeight
}
var additionalScrollDistance: CGFloat = 0.0
if let previousStoriesInset = self.previousStoriesInset {
if self.ignoreStoryInsetAdjustment {
} else {
additionalScrollDistance += previousStoriesInset - storiesInset
}
}
self.previousStoriesInset = storiesInset
transition.updateFrame(node: self.listNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if let indexSections = self.indexSections {
var insets = layout.insets(options: [.input])
if let inputHeight = layout.inputHeight {
@ -1506,7 +1519,7 @@ public final class ContactListNode: ASDisplayNode {
options.insert(.AnimateCrossfade)
}
}
if let (layout, _) = self.validLayout {
if let (layout, _, _) = self.validLayout {
self.indexSections = transition.indexSections
var insets = layout.insets(options: [.input])

View File

@ -19,18 +19,21 @@ import AppBundle
import StickerResources
import ContextUI
import QrCodeUI
import StoryContainerScreen
import StoryContentComponent
import ChatListHeaderComponent
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceNode: ContextReferenceContentNode
private let sourceView: UIView
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
init(controller: ViewController, sourceView: UIView) {
self.controller = controller
self.sourceNode = sourceNode
self.sourceView = sourceView
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}
@ -96,48 +99,6 @@ private final class SortHeaderButton: HighlightableButtonNode {
}
}
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
if listNode.scroller.isDragging {
return false
}
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
let offset: CGFloat
if searchNode.expansionProgress < 0.6 {
offset = navigationBarSearchContentHeight
} else {
offset = 0.0
}
let _ = listNode.scrollToOffsetFromTop(offset, animated: true)
return true
} else if searchNode.expansionProgress == 1.0 {
var sortItemNode: ListViewItemNode?
var nextItemNode: ListViewItemNode?
listNode.forEachItemNode({ itemNode in
if sortItemNode == nil, let itemNode = itemNode as? ContactListActionItemNode {
sortItemNode = itemNode
} else if sortItemNode != nil && nextItemNode == nil {
nextItemNode = itemNode as? ListViewItemNode
}
})
if false, let sortItemNode = sortItemNode {
let itemFrame = sortItemNode.apparentFrame
if itemFrame.contains(CGPoint(x: 0.0, y: listNode.insets.top)) {
var scrollToItem: ListViewScrollToItem?
if itemFrame.minY + itemFrame.height * 0.6 < listNode.insets.top {
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-76.0), animated: true, curve: .Default(duration: 0.3), directionHint: .Up)
} else {
scrollToItem = ListViewScrollToItem(index: 0, position: .top(0), animated: true, curve: .Default(duration: 0.3), directionHint: .Up)
}
listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
return true
}
}
}
return false
}
public class ContactsController: ViewController {
private let context: AccountContext
@ -159,8 +120,6 @@ public class ContactsController: ViewController {
private let sortOrderPromise = Promise<ContactsSortOrder>()
private let isInVoiceOver = ValuePromise<Bool>(false)
private var searchContentNode: NavigationBarSearchContentNode?
public var switchToChatsController: (() -> Void)?
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
@ -178,7 +137,8 @@ public class ContactsController: ViewController {
self.sortButton = SortHeaderButton(presentationData: self.presentationData)
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
//super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
super.init(navigationBarPresentationData: nil)
self.tabBarItemContextActionType = .always
@ -209,9 +169,6 @@ public class ContactsController: ViewController {
self.scrollToTop = { [weak self] in
if let strongSelf = self {
if let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateExpansionProgress(1.0, animated: true)
}
strongSelf.contactsNode.scrollToTop()
}
}
@ -264,12 +221,6 @@ public class ContactsController: ViewController {
})
}
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in
self?.contactsNode.contactListNode.listNode.cancelTracking()
self?.activateSearch()
})
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
self.sortButton.addTarget(self, action: #selector(self.sortPressed), forControlEvents: .touchUpInside)
}
@ -286,7 +237,7 @@ public class ContactsController: ViewController {
self.sortButton.update(theme: self.presentationData.theme, strings: self.presentationData.strings)
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
self.title = self.presentationData.strings.Contacts_Title
self.tabBarItem.title = self.presentationData.strings.Contacts_Title
if !self.presentationData.reduceMotion {
@ -313,7 +264,15 @@ public class ContactsController: ViewController {
self.displayNode = ContactsControllerNode(context: self.context, sortOrder: sortOrderSignal |> distinctUntilChanged, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}, controller: self)
self._ready.set(self.contactsNode.contactListNode.ready)
self._ready.set(combineLatest(queue: .mainQueue(),
self.contactsNode.contactListNode.ready,
self.contactsNode.storiesReady.get()
)
|> filter { a, b in
return a && b
}
|> take(1)
|> map { _ -> Bool in true })
self.contactsNode.navigationBar = self.navigationBar
@ -505,26 +464,8 @@ public class ContactsController: ViewController {
}
}
self.contactsNode.contactListNode.contentOffsetChanged = { [weak self] offset in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout {
var offset = offset
if validLayout.inVoiceOver {
offset = .known(0.0)
}
searchContentNode.updateListVisibleContentOffset(offset)
}
}
self.contactsNode.contactListNode.contentScrollingEnded = { [weak self] listView in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
return fixListNodeScrolling(listView, searchNode: searchContentNode)
} else {
return false
}
}
self.sortButton.contextAction = { [weak self] sourceNode, gesture in
self?.presentSortMenu(sourceNode: sourceNode, gesture: gesture)
self?.presentSortMenu(sourceView: sourceNode.view, gesture: gesture)
}
self.displayNodeDidLoad()
@ -533,6 +474,7 @@ public class ContactsController: ViewController {
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.contactsNode.didAppear = true
self.contactsNode.contactListNode.enableUpdates = true
}
@ -542,6 +484,22 @@ public class ContactsController: ViewController {
self.contactsNode.contactListNode.enableUpdates = false
}
private func searchContentNode() -> NavigationBarSearchContentNode? {
if let navigationBarView = self.contactsNode.navigationBarView.view as? ChatListNavigationBar.View {
return navigationBarView.searchContentNode
}
return nil
}
private func chatListHeaderView() -> ChatListHeaderComponent.View? {
if let navigationBarView = self.contactsNode.navigationBarView.view as? ChatListNavigationBar.View {
if let componentView = navigationBarView.headerContent.view as? ChatListHeaderComponent.View {
return componentView
}
}
return nil
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
@ -550,6 +508,124 @@ public class ContactsController: ViewController {
self.validLayout = layout
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, actualNavigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
if let componentView = self.chatListHeaderView(), componentView.storyPeerAction == nil {
componentView.storyPeerAction = { [weak self] peer in
guard let self else {
return
}
let storyContent = StoryContentContextImpl(context: self.context, includeHidden: false, focusedPeerId: peer?.id)
let _ = (storyContent.state
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] storyContentState in
guard let self else {
return
}
if let peer, peer.id == self.context.account.peerId, storyContentState.slice == nil {
return
}
var transitionIn: StoryContainerScreen.TransitionIn?
if let peer, let componentView = self.chatListHeaderView() {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peer.id) {
transitionIn = StoryContainerScreen.TransitionIn(
sourceView: transitionView,
sourceRect: transitionView.bounds,
sourceCornerRadius: transitionView.bounds.height * 0.5
)
}
}
let storyContainerScreen = StoryContainerScreen(
context: self.context,
content: storyContent,
transitionIn: transitionIn,
transitionOut: { [weak self] peerId, _ in
guard let self else {
return nil
}
if let componentView = self.chatListHeaderView() {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) {
return StoryContainerScreen.TransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height * 0.5,
destinationIsAvatar: true,
completed: {}
)
}
}
return nil
}
)
self.push(storyContainerScreen)
})
}
componentView.storyContextPeerAction = { [weak self] sourceNode, gesture, peer in
guard let self else {
return
}
var items: [ContextMenuItem] = []
//TODO:localize
if peer.id == self.context.account.peerId {
} else {
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c.dismiss(completion: {
guard let self else {
return
}
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)
)
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self else {
return
}
guard let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
(self.navigationController as? NavigationController)?.pushViewController(controller)
})
})
})))
/*items.append(.action(ContextMenuActionItem(text: "Mute", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
})))*/
if case let .user(user) = peer, let storiesHidden = user.storiesHidden, storiesHidden {
items.append(.action(ContextMenuActionItem(text: "Unarchive", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false)
})))
}
}
if items.isEmpty {
return
}
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
}
}
@objc private func sortPressed() {
@ -557,24 +633,20 @@ public class ContactsController: ViewController {
}
private func activateSearch() {
if self.displayNavigationBar {
if let searchContentNode = self.searchContentNode {
self.contactsNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
}
self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
if let searchContentNode = self.searchContentNode() {
self.contactsNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
}
self.requestLayout(transition: .animated(duration: 0.5, curve: .spring))
}
private func deactivateSearch(animated: Bool) {
if !self.displayNavigationBar {
self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
if let searchContentNode = self.searchContentNode {
self.contactsNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated)
}
if let searchContentNode = self.searchContentNode() {
self.contactsNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated)
self.requestLayout(transition: .animated(duration: 0.5, curve: .spring))
}
}
private func presentSortMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
func presentSortMenu(sourceView: UIView, gesture: ContextGesture?) {
let updateSortOrder: (ContactsSortOrder) -> Void = { [weak self] sortOrder in
if let strongSelf = self {
strongSelf.sortOrderPromise.set(.single(sortOrder))
@ -609,7 +681,7 @@ public class ContactsController: ViewController {
})))
return items
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceNode: self.sortButton.referenceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
self.presentInGlobalOverlay(contextController)
}
@ -708,3 +780,26 @@ private final class ContactsTabBarContextExtractedContentSource: ContextExtracte
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
}
}
private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool
let ignoreContentTouches: Bool = true
let blurBackground: Bool = true
private let controller: ViewController
private let sourceNode: ContextExtractedContentContainingNode
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool) {
self.controller = controller
self.sourceNode = sourceNode
self.keepInPlace = keepInPlace
}
func takeView() -> ContextControllerTakeViewInfo? {
return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds)
}
func putBack() -> ContextControllerPutBackViewInfo? {
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -12,6 +12,9 @@ import SearchBarNode
import SearchUI
import AppBundle
import ContextUI
import ChatListHeaderComponent
import ChatListTitleView
import ComponentFlow
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
@ -46,10 +49,13 @@ final class ContactsControllerNode: ASDisplayNode {
private let context: AccountContext
private(set) var searchDisplayController: SearchDisplayController?
private var isSearchDisplayControllerActive: Bool = false
private var storiesUnlocked: Bool = false
private var containerLayout: (ContainerViewLayout, CGFloat)?
var navigationBar: NavigationBar?
let navigationBarView = ComponentView<Empty>()
var requestDeactivateSearch: (() -> Void)?
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
@ -64,6 +70,18 @@ final class ContactsControllerNode: ASDisplayNode {
weak var controller: ContactsController?
private var initialScrollingOffset: CGFloat?
private var isSettingUpContentOffset: Bool = false
private var didSetupContentOffset: Bool = false
private var contentOffset: ListViewVisibleContentOffset?
private var ignoreStoryInsetAdjustment: Bool = false
var didAppear: Bool = false
private(set) var storySubscriptions: EngineStorySubscriptions?
private var storySubscriptionsDisposable: Disposable?
let storiesReady = Promise<Bool>()
init(context: AccountContext, sortOrder: Signal<ContactsSortOrder, NoError>, present: @escaping (ViewController, Any?) -> Void, controller: ContactsController) {
self.context = context
self.controller = controller
@ -139,10 +157,127 @@ final class ContactsControllerNode: ASDisplayNode {
contextAction = { [weak self] peer, node, gesture, location in
self?.contextAction(peer: peer, node: node, gesture: gesture, location: location)
}
self.contactListNode.contentOffsetChanged = { [weak self] offset in
guard let self else {
return
}
if self.isSettingUpContentOffset {
return
}
if !self.didSetupContentOffset, let initialScrollingOffset = self.initialScrollingOffset {
self.initialScrollingOffset = nil
self.didSetupContentOffset = true
self.isSettingUpContentOffset = true
let _ = self.contactListNode.listNode.scrollToOffsetFromTop(initialScrollingOffset, animated: false)
let offset = self.contactListNode.listNode.visibleContentOffset()
self.contentOffset = offset
self.contentOffsetChanged(offset: offset)
self.isSettingUpContentOffset = false
return
}
self.contentOffset = offset
self.contentOffsetChanged(offset: offset)
if self.contactListNode.listNode.isTracking {
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.contactListNode.ignoreStoryInsetAdjustment = true
self.contactListNode.listNode.allowInsetFixWhileTracking = true
self.onStoriesLockedUpdated(isLocked: true)
self.contactListNode.ignoreStoryInsetAdjustment = false
self.contactListNode.listNode.allowInsetFixWhileTracking = false
}
}
}
}
} else if self.storiesUnlocked {
switch offset {
case let .known(value):
if value >= ChatListNavigationBar.storiesScrollHeight {
self.storiesUnlocked = false
DispatchQueue.main.async { [weak self] in
self?.onStoriesLockedUpdated(isLocked: false)
}
}
default:
break
}
}
}
self.contactListNode.contentScrollingEnded = { [weak self] listView in
guard let self else {
return false
}
return self.contentScrollingEnded(listView: listView)
}
self.storySubscriptionsDisposable = (self.context.engine.messages.storySubscriptions(includeHidden: true)
|> deliverOnMainQueue).start(next: { [weak self] storySubscriptions in
guard let self else {
return
}
var wasEmpty = true
if let storySubscriptions = self.storySubscriptions, !storySubscriptions.items.isEmpty {
wasEmpty = false
}
self.storySubscriptions = storySubscriptions
let isEmpty = storySubscriptions.items.isEmpty
let transition: ContainedViewLayoutTransition
if self.didAppear {
transition = .animated(duration: 0.4, curve: .spring)
} else {
transition = .immediate
}
let _ = wasEmpty
let _ = isEmpty
//self.chatListDisplayNode.temporaryContentOffsetChangeTransition = transition
self.controller?.requestLayout(transition: transition)
//self.chatListDisplayNode.temporaryContentOffsetChangeTransition = nil
/*self.chatListDisplayNode.mainContainerNode.currentItemNode.updateState { chatListState in
var chatListState = chatListState
var peersWithNewStories = Set<EnginePeer.Id>()
for item in storySubscriptions.items {
if item.peer.id == self.context.account.peerId {
continue
}
if item.hasUnseen {
peersWithNewStories.insert(item.peer.id)
}
}
chatListState.peersWithNewStories = peersWithNewStories
return chatListState
}*/
self.storiesReady.set(.single(true))
})
}
deinit {
self.presentationDataDisposable?.dispose()
self.storySubscriptionsDisposable?.dispose()
}
private func updateThemeAndStrings() {
@ -158,22 +293,180 @@ final class ContactsControllerNode: ASDisplayNode {
}
}
private func onStoriesLockedUpdated(isLocked: Bool) {
self.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
private func contentOffsetChanged(offset: ListViewVisibleContentOffset) {
self.updateNavigationScrolling(transition: .immediate)
}
private func contentScrollingEnded(listView: ListView) -> Bool {
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
}
}
}
}
return false
}
private func updateNavigationBar(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) {
let tabsNode: ASDisplayNode? = nil
let tabsNodeIsSearch = false
//TODO:localize
let primaryContent = ChatListHeaderComponent.Content(
title: "Contacts",
navigationBackTitle: nil,
titleComponent: nil,
chatListTitle: NetworkStatusTitle(text: "Contacts", activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil),
leftButton: AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent(
content: .text(title: self.presentationData.strings.Contacts_Sort, isBold: false),
pressed: { [weak self] sourceView in
guard let self else {
return
}
self.controller?.presentSortMenu(sourceView: sourceView, gesture: nil)
}
))),
rightButtons: [AnyComponentWithIdentity(id: "add", component: AnyComponent(NavigationButtonComponent(
content: .icon(imageName: "Chat List/AddIcon"),
pressed: { [weak self] _ in
guard let self else {
return
}
self.controller?.addPressed()
}
)))],
backTitle: nil,
backPressed: nil
)
let navigationBarSize = self.navigationBarView.update(
transition: Transition(transition),
component: AnyComponent(ChatListNavigationBar(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
statusBarHeight: layout.statusBarHeight ?? 0.0,
sideInset: layout.safeInsets.left,
isSearchActive: self.isSearchDisplayControllerActive,
storiesUnlocked: self.storiesUnlocked,
primaryContent: primaryContent,
secondaryContent: nil,
secondaryTransition: 0.0,
storySubscriptions: self.storySubscriptions,
storiesIncludeHidden: true,
uploadProgress: nil,
tabsNode: tabsNode,
tabsNodeIsSearch: tabsNodeIsSearch,
activateSearch: { [weak self] searchContentNode in
guard let self else {
return
}
self.contactListNode.activateSearch?()
},
openStatusSetup: { _ in
}
)),
environment: {},
containerSize: layout.size
)
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
navigationBarComponentView.deferScrollApplication = true
if navigationBarComponentView.superview == nil {
self.view.addSubview(navigationBarComponentView)
}
transition.updateFrame(view: navigationBarComponentView, frame: CGRect(origin: CGPoint(), size: navigationBarSize))
return (navigationBarSize.height, navigationBarComponentView.effectiveStoriesInsetHeight)
} else {
return (0.0, 0.0)
}
}
private func getEffectiveNavigationScrollingOffset() -> CGFloat {
let mainOffset: CGFloat
if let contentOffset = self.contentOffset, case let .known(value) = contentOffset {
mainOffset = value
} else {
mainOffset = 1000.0
}
return mainOffset
}
private func updateNavigationScrolling(transition: ContainedViewLayoutTransition) {
var offset = self.getEffectiveNavigationScrollingOffset()
if self.isSearchDisplayControllerActive {
offset = 0.0
}
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
navigationBarComponentView.applyScroll(offset: offset, transition: Transition(transition))
}
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.containerLayout = (layout, navigationBarHeight)
let navigationBarLayout = self.updateNavigationBar(layout: layout, transition: transition)
self.initialScrollingOffset = 0.0//ChatListNavigationBar.searchScrollHeight + navigationBarLayout.storiesInset
var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight
insets.top += navigationBarLayout.navigationHeight
var headerInsets = layout.insets(options: [.input])
headerInsets.top += actualNavigationBarHeight
headerInsets.top = navigationBarLayout.navigationHeight - navigationBarLayout.storiesInset - ChatListNavigationBar.searchScrollHeight
let innerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
searchDisplayController.containerLayoutUpdated(innerLayout, navigationBarHeight: navigationBarLayout.navigationHeight, transition: transition)
}
self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
self.contactListNode.containerLayoutUpdated(innerLayout, headerInsets: headerInsets, storiesInset: navigationBarLayout.storiesInset, transition: transition)
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.updateNavigationScrolling(transition: transition)
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
navigationBarComponentView.deferScrollApplication = false
navigationBarComponentView.applyCurrentScroll(transition: Transition(transition))
}
}
private func contextAction(peer: EnginePeer, node: ASDisplayNode?, gesture: ContextGesture?, location: CGPoint?) {
@ -187,11 +480,14 @@ final class ContactsControllerNode: ASDisplayNode {
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
guard let (containerLayout, navigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
guard let (containerLayout, navigationBarHeight) = self.containerLayout, self.searchDisplayController == nil else {
return
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global, .deviceContacts], addContact: { [weak self] phoneNumber in
self.isSearchDisplayControllerActive = true
self.storiesUnlocked = false
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global, .deviceContacts], addContact: { [weak self] phoneNumber in
if let requestAddContact = self?.requestAddContact {
requestAddContact(phoneNumber)
}
@ -208,21 +504,29 @@ final class ContactsControllerNode: ASDisplayNode {
})
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in
if let strongSelf = self {
if isSearchBar {
strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode)
if let navigationBarComponentView = strongSelf.navigationBarView.view as? ChatListNavigationBar.View {
navigationBarComponentView.addSubnode(subnode)
}
} else {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
strongSelf.insertSubnode(subnode, aboveSubnode: strongSelf.contactListNode)
}
}
}, placeholder: placeholderNode)
}
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) {
self.isSearchDisplayControllerActive = false
if let searchDisplayController = self.searchDisplayController {
let previousFrame = placeholderNode.frame
placeholderNode.frame = previousFrame.offsetBy(dx: 0.0, dy: 54.0)
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
self.searchDisplayController = nil
placeholderNode.frame = previousFrame
}
}
}

View File

@ -56,7 +56,7 @@ private enum InviteContactsEntry: Comparable, Identifiable {
} else {
status = .none
}
let peer: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: contact.firstName, lastName: contact.lastName, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: contact.firstName, lastName: contact.lastName, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: status, enabled: true, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in
interaction.toggleContact(id)
})

View File

@ -1412,7 +1412,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}, openUrl: { _ in }, openPeer: { _ in
}, showAll: false)
let peer = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
let peer = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer, text: "", attributes: [], media: [map], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let controller = LocationViewController(context: self.context, subject: EngineMessage(message), params: controllerParams)

View File

@ -726,7 +726,7 @@ public final class InviteLinkViewController: ViewController {
if requestsState.importers.isEmpty && requestsState.isLoadingMore {
count = min(4, state.count)
loading = true
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
for i in 0 ..< count {
entries.append(.request(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, true))
}
@ -760,7 +760,7 @@ public final class InviteLinkViewController: ViewController {
if state.importers.isEmpty && state.isLoadingMore {
count = min(4, state.count)
loading = true
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
for i in 0 ..< count {
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, false, true))
}

View File

@ -855,7 +855,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
var peers = SimpleDictionary<PeerId, Peer>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview

View File

@ -663,7 +663,7 @@ private func deviceContactInfoEntries(account: Account, engine: TelegramEngine,
firstName = presentationData.strings.Message_Contact
}
entries.append(.info(entries.count, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: peer ?? EnginePeer.user(TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: firstName, lastName: isOrganization ? nil : personName.1, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])), state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), job: isOrganization ? nil : jobSummary, isPlain: !isShare, hiddenAvatar: hiddenAvatar))
entries.append(.info(entries.count, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: peer ?? EnginePeer.user(TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: firstName, lastName: isOrganization ? nil : personName.1, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)), state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), job: isOrganization ? nil : jobSummary, isPlain: !isShare, hiddenAvatar: hiddenAvatar))
if !selecting {
if let _ = peer {

View File

@ -1256,24 +1256,29 @@ public final class Transaction {
return self.postbox!.removeChatHidden(peerId: peerId, index: index)
}
public func getAllStorySubscriptions() -> (state: CodableEntry?, peerIds: [PeerId]) {
public func getAllStorySubscriptions(key: PostboxStorySubscriptionsKey) -> (state: CodableEntry?, peerIds: [PeerId]) {
assert(!self.disposed)
return self.postbox!.getAllStorySubscriptions()
return self.postbox!.getAllStorySubscriptions(key: key)
}
public func replaceAllStorySubscriptions(state: CodableEntry?, peerIds: [PeerId]) {
public func storySubscriptionsContains(key: PostboxStorySubscriptionsKey, peerId: PeerId) -> Bool {
assert(!self.disposed)
self.postbox!.replaceAllStorySubscriptions(state: state, peerIds: peerIds)
return self.postbox!.storySubscriptionsContains(key: key, peerId: peerId)
}
public func getSubscriptionsStoriesState() -> CodableEntry? {
public func replaceAllStorySubscriptions(key: PostboxStorySubscriptionsKey, state: CodableEntry?, peerIds: [PeerId]) {
assert(!self.disposed)
return self.postbox!.getSubscriptionsStoriesState()
self.postbox!.replaceAllStorySubscriptions(key: key, state: state, peerIds: peerIds)
}
public func setSubscriptionsStoriesState(state: CodableEntry?) {
public func getSubscriptionsStoriesState(key: PostboxStorySubscriptionsKey) -> CodableEntry? {
assert(!self.disposed)
self.postbox!.setSubscriptionsStoriesState(state: state)
return self.postbox!.getSubscriptionsStoriesState(key: key)
}
public func setSubscriptionsStoriesState(key: PostboxStorySubscriptionsKey, state: CodableEntry?) {
assert(!self.disposed)
self.postbox!.setSubscriptionsStoriesState(key: key, state: state)
}
public func getLocalStoryState() -> CodableEntry? {
@ -1767,7 +1772,7 @@ final class PostboxImpl {
self.deviceContactImportInfoTable = DeviceContactImportInfoTable(valueBox: self.valueBox, table: DeviceContactImportInfoTable.tableSpec(54), useCaches: useCaches)
self.groupMessageStatsTable = GroupMessageStatsTable(valueBox: self.valueBox, table: GroupMessageStatsTable.tableSpec(58), useCaches: useCaches)
self.storyStatesTable = StoryStatesTable(valueBox: self.valueBox, table: StoryStatesTable.tableSpec(65), useCaches: useCaches)
self.storySubscriptionsTable = StorySubscriptionsTable(valueBox: self.valueBox, table: StorySubscriptionsTable.tableSpec(66), useCaches: useCaches)
self.storySubscriptionsTable = StorySubscriptionsTable(valueBox: self.valueBox, table: StorySubscriptionsTable.tableSpec(78), useCaches: useCaches)
self.storyItemsTable = StoryItemsTable(valueBox: self.valueBox, table: StoryItemsTable.tableSpec(69), useCaches: useCaches)
self.storyTable = StoryTable(valueBox: self.valueBox, table: StoryTable.tableSpec(70), useCaches: useCaches)
@ -2179,28 +2184,32 @@ final class PostboxImpl {
self.synchronizeGroupMessageStatsTable.set(groupId: groupId, namespace: namespace, needsValidation: false, operations: &self.currentUpdatedGroupSummarySynchronizeOperations)
}
fileprivate func getAllStorySubscriptions() -> (state: CodableEntry?, peerIds: [PeerId]) {
fileprivate func getAllStorySubscriptions(key: PostboxStorySubscriptionsKey) -> (state: CodableEntry?, peerIds: [PeerId]) {
return (
self.storyStatesTable.get(key: .subscriptions),
self.storySubscriptionsTable.getAll()
self.storyStatesTable.get(key: .subscriptions(key)),
self.storySubscriptionsTable.getAll(subscriptionsKey: key)
)
}
fileprivate func replaceAllStorySubscriptions(state: CodableEntry?, peerIds: [PeerId]) {
self.storyStatesTable.set(key: .subscriptions, value: state, events: &self.currentStoryStatesEvents)
self.storySubscriptionsTable.replaceAll(peerIds: peerIds, events: &self.currentStorySubscriptionsEvents)
fileprivate func storySubscriptionsContains(key: PostboxStorySubscriptionsKey, peerId: PeerId) -> Bool {
return self.storySubscriptionsTable.contains(subscriptionsKey: key, peerId: peerId)
}
fileprivate func replaceAllStorySubscriptions(key: PostboxStorySubscriptionsKey, state: CodableEntry?, peerIds: [PeerId]) {
self.storyStatesTable.set(key: .subscriptions(key), value: state, events: &self.currentStoryStatesEvents)
self.storySubscriptionsTable.replaceAll(subscriptionsKey: key, peerIds: peerIds, events: &self.currentStorySubscriptionsEvents)
}
fileprivate func getLocalStoryState() -> CodableEntry? {
return self.storyStatesTable.get(key: .local)
}
fileprivate func getSubscriptionsStoriesState() -> CodableEntry? {
return self.storyStatesTable.get(key: .subscriptions)
fileprivate func getSubscriptionsStoriesState(key: PostboxStorySubscriptionsKey) -> CodableEntry? {
return self.storyStatesTable.get(key: .subscriptions(key))
}
fileprivate func setSubscriptionsStoriesState(state: CodableEntry?) {
self.storyStatesTable.set(key: .subscriptions, value: state, events: &self.currentStoryStatesEvents)
fileprivate func setSubscriptionsStoriesState(key: PostboxStorySubscriptionsKey, state: CodableEntry?) {
self.storyStatesTable.set(key: .subscriptions(key), value: state, events: &self.currentStoryStatesEvents)
}
fileprivate func setLocalStoryState(state: CodableEntry?) {

View File

@ -7,15 +7,21 @@ final class StoryStatesTable: Table {
enum Key: Hashable {
case local
case subscriptions
case subscriptions(PostboxStorySubscriptionsKey)
case peer(PeerId)
init(key: ValueBoxKey) {
init?(key: ValueBoxKey) {
switch key.getUInt8(0) {
case 0:
self = .local
case 1:
self = .subscriptions
if key.length != 1 + 4 {
return nil
}
guard let subscriptionsKey = PostboxStorySubscriptionsKey(rawValue: key.getInt32(1)) else {
return nil
}
self = .subscriptions(subscriptionsKey)
case 2:
self = .peer(PeerId(key.getInt64(1)))
default:
@ -30,9 +36,10 @@ final class StoryStatesTable: Table {
let key = ValueBoxKey(length: 1)
key.setUInt8(0, value: 0)
return key
case .subscriptions:
let key = ValueBoxKey(length: 1)
case let .subscriptions(subscriptionsKey):
let key = ValueBoxKey(length: 1 + 4)
key.setUInt8(0, value: 1)
key.setInt32(1, value: subscriptionsKey.rawValue)
return key
case let .peer(peerId):
let key = ValueBoxKey(length: 1 + 8)

View File

@ -2,7 +2,7 @@ import Foundation
public enum PostboxStoryStatesKey: Hashable {
case local
case subscriptions
case subscriptions(PostboxStorySubscriptionsKey)
case peer(PeerId)
}
@ -11,8 +11,8 @@ private extension PostboxStoryStatesKey {
switch tableKey {
case .local:
self = .local
case .subscriptions:
self = .subscriptions
case let .subscriptions(key):
self = .subscriptions(key)
case let .peer(peerId):
self = .peer(peerId)
}
@ -22,8 +22,8 @@ private extension PostboxStoryStatesKey {
switch self {
case .local:
return .local
case .subscriptions:
return .subscriptions
case let .subscriptions(key):
return .subscriptions(key)
case let .peer(peerId):
return .peer(peerId)
}

View File

@ -2,10 +2,11 @@ import Foundation
final class StorySubscriptionsTable: Table {
enum Event {
case replaceAll
case replaceAll(key: PostboxStorySubscriptionsKey)
}
private struct Key: Hashable {
var subscriptionsKey: PostboxStorySubscriptionsKey
var peerId: PeerId
}
@ -13,20 +14,27 @@ final class StorySubscriptionsTable: Table {
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
}
private let sharedKey = ValueBoxKey(length: 8)
private let sharedKey = ValueBoxKey(length: 4 + 8)
private func key(_ key: Key) -> ValueBoxKey {
self.sharedKey.setInt64(0, value: key.peerId.toInt64())
self.sharedKey.setInt32(0, value: key.subscriptionsKey.rawValue)
self.sharedKey.setInt64(4, value: key.peerId.toInt64())
return self.sharedKey
}
private func getAllKeys() -> [Key] {
private func getAllKeys(subscriptionsKey: PostboxStorySubscriptionsKey) -> [Key] {
var result: [Key] = []
self.valueBox.scan(self.table, keys: { key in
let peerId = PeerId(key.getInt64(0))
result.append(Key(peerId: peerId))
if key.length != 4 + 8 {
return true
}
if let readSubscriptionsKey = PostboxStorySubscriptionsKey(rawValue: key.getInt32(0)) {
if readSubscriptionsKey == subscriptionsKey {
let peerId = PeerId(key.getInt64(4))
result.append(Key(subscriptionsKey: subscriptionsKey, peerId: peerId))
}
}
return true
})
@ -34,12 +42,19 @@ final class StorySubscriptionsTable: Table {
return result
}
public func getAll() -> [PeerId] {
public func getAll(subscriptionsKey: PostboxStorySubscriptionsKey) -> [PeerId] {
var result: [PeerId] = []
self.valueBox.scan(self.table, keys: { key in
let peerId = PeerId(key.getInt64(0))
result.append(peerId)
if key.length != 4 + 8 {
return true
}
if let readSubscriptionsKey = PostboxStorySubscriptionsKey(rawValue: key.getInt32(0)) {
if readSubscriptionsKey == subscriptionsKey {
let peerId = PeerId(key.getInt64(4))
result.append(peerId)
}
}
return true
})
@ -47,16 +62,24 @@ final class StorySubscriptionsTable: Table {
return result
}
public func replaceAll(peerIds: [PeerId], events: inout [Event]) {
for key in self.getAllKeys() {
public func contains(subscriptionsKey: PostboxStorySubscriptionsKey, peerId: PeerId) -> Bool {
if let _ = self.valueBox.get(self.table, key: self.key(Key(subscriptionsKey: subscriptionsKey, peerId: peerId))) {
return true
} else {
return false
}
}
public func replaceAll(subscriptionsKey: PostboxStorySubscriptionsKey, peerIds: [PeerId], events: inout [Event]) {
for key in self.getAllKeys(subscriptionsKey: subscriptionsKey) {
self.valueBox.remove(self.table, key: self.key(key), secure: true)
}
for peerId in peerIds {
self.valueBox.set(self.table, key: self.key(Key(peerId: peerId)), value: MemoryBuffer())
self.valueBox.set(self.table, key: self.key(Key(subscriptionsKey: subscriptionsKey, peerId: peerId)), value: MemoryBuffer())
}
events.append(.replaceAll)
events.append(.replaceAll(key: subscriptionsKey))
}
override func clearMemoryCache() {

View File

@ -1,10 +1,17 @@
import Foundation
public enum PostboxStorySubscriptionsKey: Int32 {
case all = 0
case filtered = 1
}
final class MutableStorySubscriptionsView: MutablePostboxView {
private let key: PostboxStorySubscriptionsKey
var peerIds: [PeerId]
init(postbox: PostboxImpl) {
self.peerIds = postbox.storySubscriptionsTable.getAll()
init(postbox: PostboxImpl, key: PostboxStorySubscriptionsKey) {
self.key = key
self.peerIds = postbox.storySubscriptionsTable.getAll(subscriptionsKey: self.key)
}
func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool {
@ -12,14 +19,16 @@ final class MutableStorySubscriptionsView: MutablePostboxView {
if !transaction.storySubscriptionsEvents.isEmpty {
loop: for event in transaction.storySubscriptionsEvents {
switch event {
case .replaceAll:
let peerIds = postbox.storySubscriptionsTable.getAll()
if self.peerIds != peerIds {
updated = true
self.peerIds = peerIds
case let .replaceAll(key):
if key == self.key {
let peerIds = postbox.storySubscriptionsTable.getAll(subscriptionsKey: self.key)
if self.peerIds != peerIds {
updated = true
self.peerIds = peerIds
}
break loop
}
break loop
}
}
}
@ -27,7 +36,7 @@ final class MutableStorySubscriptionsView: MutablePostboxView {
}
func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool {
let peerIds = postbox.storySubscriptionsTable.getAll()
let peerIds = postbox.storySubscriptionsTable.getAll(subscriptionsKey: self.key)
if self.peerIds != peerIds {
self.peerIds = peerIds

View File

@ -40,7 +40,7 @@ public enum PostboxViewKey: Hashable {
case peerTimeoutAttributes
case messageHistoryThreadIndex(id: PeerId, summaryComponents: ChatListEntrySummaryComponents)
case messageHistoryThreadInfo(peerId: PeerId, threadId: Int64)
case storySubscriptions
case storySubscriptions(key: PostboxStorySubscriptionsKey)
case storiesState(key: PostboxStoryStatesKey)
case storyItems(peerId: PeerId)
@ -137,8 +137,9 @@ public enum PostboxViewKey: Hashable {
case let .messageHistoryThreadInfo(peerId, threadId):
hasher.combine(peerId)
hasher.combine(threadId)
case .storySubscriptions:
case let .storySubscriptions(key):
hasher.combine(18)
hasher.combine(key)
case let .storiesState(key):
hasher.combine(key)
case let .storyItems(peerId):
@ -382,8 +383,8 @@ public enum PostboxViewKey: Hashable {
} else {
return false
}
case .storySubscriptions:
if case .storySubscriptions = rhs {
case let .storySubscriptions(key):
if case .storySubscriptions(key) = rhs {
return true
} else {
return false
@ -484,8 +485,8 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost
return MutableMessageHistoryThreadIndexView(postbox: postbox, peerId: id, summaryComponents: summaryComponents)
case let .messageHistoryThreadInfo(peerId, threadId):
return MutableMessageHistoryThreadInfoView(postbox: postbox, peerId: peerId, threadId: threadId)
case .storySubscriptions:
return MutableStorySubscriptionsView(postbox: postbox)
case let .storySubscriptions(key):
return MutableStorySubscriptionsView(postbox: postbox, key: key)
case let .storiesState(key):
return MutableStoryStatesView(postbox: postbox, key: key)
case let .storyItems(peerId):

View File

@ -163,8 +163,8 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel
let otherPeerId = self.context.account.peerId
var peers = SimpleDictionary<PeerId, Peer>()
var messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3)
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])

View File

@ -145,7 +145,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
var peers = SimpleDictionary<PeerId, Peer>()
let messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: item.peerName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: item.peerName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName, psaType: nil, flags: [])

View File

@ -274,7 +274,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
var peers = SimpleDictionary<PeerId, Peer>()
let messages = SimpleDictionary<MessageId, Message>()
peers[userPeerId] = TelegramUser(id: userPeerId, accessHash: nil, firstName: item.strings.Settings_QuickReactionSetup_DemoMessageAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[userPeerId] = TelegramUser(id: userPeerId, accessHash: nil, firstName: item.strings.Settings_QuickReactionSetup_DemoMessageAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
let messageText = item.strings.Settings_QuickReactionSetup_DemoMessageText

View File

@ -302,14 +302,14 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
)
}
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: []))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: []))
let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let timestamp = self.referenceTimestamp
@ -425,8 +425,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
let otherPeerId = self.context.account.peerId
var peers = SimpleDictionary<PeerId, Peer>()
var messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3)
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])

View File

@ -935,12 +935,12 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
)
}
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: []))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let timestamp = self.referenceTimestamp
@ -1028,8 +1028,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
let otherPeerId = self.context.account.peerId
var peers = SimpleDictionary<PeerId, Peer>()
var messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
var sampleMessages: [Message] = []

View File

@ -450,15 +450,15 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
let chatListPresentationData = ChatListPresentationData(theme: self.previewTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: []))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: []))
let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer7: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(6)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []))
let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let peer7: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(6)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
let timestamp = self.referenceTimestamp
@ -579,8 +579,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
let otherPeerId = self.context.account.peerId
var peers = SimpleDictionary<PeerId, Peer>()
var messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
var sampleMessages: [Message] = []

View File

@ -155,11 +155,11 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3)
if let (author, text) = messageItem.reply {
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: author, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: author, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
}
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, isCentered: false))
}

View File

@ -1485,8 +1485,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let otherPeerId = self.context.account.peerId
var peers = SimpleDictionary<PeerId, Peer>()
let messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
var topMessageText = ""
var bottomMessageText = ""

View File

@ -973,7 +973,8 @@ public class Account {
let networkStatsContext: NetworkStatsContext
public let storySubscriptionsContext: StorySubscriptionsContext?
public let filteredStorySubscriptionsContext: StorySubscriptionsContext?
public let allStorySubscriptionsContext: StorySubscriptionsContext?
public init(accountManager: AccountManager<TelegramAccountManagerTypes>, id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, supplementary: Bool) {
self.accountManager = accountManager
@ -993,9 +994,11 @@ public class Account {
self.peerInputActivityManager = PeerInputActivityManager()
if !supplementary {
self.storySubscriptionsContext = StorySubscriptionsContext(accountPeerId: peerId, postbox: postbox, network: network)
self.filteredStorySubscriptionsContext = StorySubscriptionsContext(accountPeerId: peerId, postbox: postbox, network: network, includesHidden: false)
self.allStorySubscriptionsContext = StorySubscriptionsContext(accountPeerId: peerId, postbox: postbox, network: network, includesHidden: true)
} else {
self.storySubscriptionsContext = nil
self.filteredStorySubscriptionsContext = nil
self.allStorySubscriptionsContext = nil
}
self.callSessionManager = CallSessionManager(postbox: postbox, network: network, maxLayer: networkArguments.voipMaxLayer, versions: networkArguments.voipVersions, addUpdates: { [weak self] updates in

View File

@ -68,6 +68,11 @@ extension TelegramUser {
userFlags.insert(.isCloseFriend)
}
var storiesHidden: Bool?
if !isMin {
storiesHidden = (flags2 & (1 << 5)) != 0
}
var botInfo: BotUserInfo?
if (flags & (1 << 14)) != 0 {
var botFlags = BotUserInfoFlags()
@ -91,9 +96,9 @@ extension TelegramUser {
let restrictionInfo: PeerAccessRestrictionInfo? = restrictionReason.flatMap(PeerAccessRestrictionInfo.init(apiReasons:))
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, firstName: firstName, lastName: lastName, username: username, phone: phone, photo: representations, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [])
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, firstName: firstName, lastName: lastName, username: username, phone: phone, photo: representations, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden)
case let .userEmpty(id):
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)), accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)), accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
}
}
@ -174,7 +179,7 @@ extension TelegramUser {
accessHash = lhs.accessHash ?? rhsAccessHashValue
}
return TelegramUser(id: lhs.id, accessHash: accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: lhs.username, phone: lhs.phone, photo: telegramPhoto, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), usernames: lhs.usernames)
return TelegramUser(id: lhs.id, accessHash: accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: lhs.username, phone: lhs.phone, photo: telegramPhoto, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), usernames: lhs.usernames, storiesHidden: lhs.storiesHidden)
} else {
return TelegramUser(user: rhs)
}
@ -228,7 +233,14 @@ extension TelegramUser {
photo = rhs.photo
}
return TelegramUser(id: lhs.id, accessHash: accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: lhs.username, phone: lhs.phone, photo: photo, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus, usernames: lhs.usernames)
var storiesHidden: Bool?
if let value = rhs.storiesHidden {
storiesHidden = value
} else {
storiesHidden = lhs.storiesHidden
}
return TelegramUser(id: lhs.id, accessHash: accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: lhs.username, phone: lhs.phone, photo: photo, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus, usernames: lhs.usernames, storiesHidden: storiesHidden)
}
}
}

View File

@ -1080,7 +1080,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
if updatedState.peers[peerId] == nil {
updatedState.updatePeer(peerId, { peer in
if peer == nil {
return TelegramUser(id: peerId, accessHash: nil, firstName: "Telegram Notifications", lastName: nil, username: nil, phone: nil, photo: [], botInfo: BotUserInfo(flags: [], inlinePlaceholder: nil), restrictionInfo: nil, flags: [.isVerified], emojiStatus: nil, usernames: [])
return TelegramUser(id: peerId, accessHash: nil, firstName: "Telegram Notifications", lastName: nil, username: nil, phone: nil, photo: [], botInfo: BotUserInfo(flags: [], inlinePlaceholder: nil), restrictionInfo: nil, flags: [.isVerified], emojiStatus: nil, usernames: [], storiesHidden: nil)
} else {
return peer
}
@ -4351,9 +4351,9 @@ func replayFinalState(
}
}
var subscriptionsOpaqueState: String?
if let state = transaction.getSubscriptionsStoriesState()?.get(Stories.SubscriptionsState.self) {
subscriptionsOpaqueState = state.opaqueState
var filteredSubscriptionsOpaqueState: String?
if let state = transaction.getSubscriptionsStoriesState(key: .filtered)?.get(Stories.SubscriptionsState.self) {
filteredSubscriptionsOpaqueState = state.opaqueState
}
var appliedMaxReadId: Int32?
if let currentState = transaction.getPeerStoryState(peerId: peerId)?.get(Stories.PeerState.self) {
@ -4366,7 +4366,7 @@ func replayFinalState(
transaction.setStoryItems(peerId: peerId, items: updatedPeerEntries)
transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState(
subscriptionsOpaqueState: subscriptionsOpaqueState,
subscriptionsOpaqueState: filteredSubscriptionsOpaqueState,
maxReadId: appliedMaxReadId ?? 0
)))
@ -4381,12 +4381,12 @@ func replayFinalState(
appliedMaxReadId = max(appliedMaxReadId, currentState.maxReadId)
}
var subscriptionsOpaqueState: String?
if let state = transaction.getSubscriptionsStoriesState()?.get(Stories.SubscriptionsState.self) {
subscriptionsOpaqueState = state.opaqueState
var filteredSubscriptionsOpaqueState: String?
if let state = transaction.getSubscriptionsStoriesState(key: .filtered)?.get(Stories.SubscriptionsState.self) {
filteredSubscriptionsOpaqueState = state.opaqueState
}
transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState(
subscriptionsOpaqueState: subscriptionsOpaqueState,
subscriptionsOpaqueState: filteredSubscriptionsOpaqueState,
maxReadId: appliedMaxReadId
)))

View File

@ -109,6 +109,7 @@ public final class TelegramUser: Peer, Equatable {
public let flags: UserInfoFlags
public let emojiStatus: PeerEmojiStatus?
public let usernames: [TelegramPeerUsername]
public let storiesHidden: Bool?
public var nameOrPhone: String {
if let firstName = self.firstName {
@ -169,7 +170,7 @@ public final class TelegramUser: Peer, Equatable {
}
}
public init(id: PeerId, accessHash: TelegramPeerAccessHash?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: [TelegramMediaImageRepresentation], botInfo: BotUserInfo?, restrictionInfo: PeerAccessRestrictionInfo?, flags: UserInfoFlags, emojiStatus: PeerEmojiStatus?, usernames: [TelegramPeerUsername]) {
public init(id: PeerId, accessHash: TelegramPeerAccessHash?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: [TelegramMediaImageRepresentation], botInfo: BotUserInfo?, restrictionInfo: PeerAccessRestrictionInfo?, flags: UserInfoFlags, emojiStatus: PeerEmojiStatus?, usernames: [TelegramPeerUsername], storiesHidden: Bool?) {
self.id = id
self.accessHash = accessHash
self.firstName = firstName
@ -182,6 +183,7 @@ public final class TelegramUser: Peer, Equatable {
self.flags = flags
self.emojiStatus = emojiStatus
self.usernames = usernames
self.storiesHidden = storiesHidden
}
public init(decoder: PostboxDecoder) {
@ -220,6 +222,7 @@ public final class TelegramUser: Peer, Equatable {
self.emojiStatus = decoder.decode(PeerEmojiStatus.self, forKey: "emjs")
self.usernames = decoder.decodeObjectArrayForKey("uns")
self.storiesHidden = decoder.decodeOptionalBoolForKey("sth")
}
public func encode(_ encoder: PostboxEncoder) {
@ -273,6 +276,12 @@ public final class TelegramUser: Peer, Equatable {
}
encoder.encodeObjectArray(self.usernames, forKey: "uns")
if let storiesHidden = self.storiesHidden {
encoder.encodeBool(storiesHidden, forKey: "sth")
} else {
encoder.encodeNil(forKey: "sth")
}
}
public func isEqual(_ other: Peer) -> Bool {
@ -325,35 +334,42 @@ public final class TelegramUser: Peer, Equatable {
if lhs.usernames != rhs.usernames {
return false
}
if lhs.storiesHidden != rhs.storiesHidden {
return false
}
return true
}
public func withUpdatedUsername(_ username: String?) -> TelegramUser {
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames)
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames, storiesHidden: self.storiesHidden)
}
public func withUpdatedUsernames(_ usernames: [TelegramPeerUsername]) -> TelegramUser {
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: usernames)
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: usernames, storiesHidden: self.storiesHidden)
}
public func withUpdatedNames(firstName: String?, lastName: String?) -> TelegramUser {
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: firstName, lastName: lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames)
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: firstName, lastName: lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames, storiesHidden: self.storiesHidden)
}
public func withUpdatedPhone(_ phone: String?) -> TelegramUser {
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames)
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames, storiesHidden: self.storiesHidden)
}
public func withUpdatedPhoto(_ representations: [TelegramMediaImageRepresentation]) -> TelegramUser {
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: phone, photo: representations, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames)
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: phone, photo: representations, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames, storiesHidden: self.storiesHidden)
}
public func withUpdatedEmojiStatus(_ emojiStatus: PeerEmojiStatus?) -> TelegramUser {
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: emojiStatus, usernames: self.usernames)
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: emojiStatus, usernames: self.usernames, storiesHidden: self.storiesHidden)
}
public func withUpdatedFlags(_ flags: UserInfoFlags) -> TelegramUser {
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: emojiStatus, usernames: self.usernames)
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: emojiStatus, usernames: self.usernames, storiesHidden: self.storiesHidden)
}
public func withUpdatedStoriesHidden(_ storiesHidden: Bool?) -> TelegramUser {
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: self.emojiStatus, usernames: self.usernames, storiesHidden: storiesHidden)
}
}

View File

@ -1290,3 +1290,53 @@ public final class EngineStoryViewListContext {
}
}
}
func _internal_updatePeerStoriesHidden(account: Account, id: PeerId, isHidden: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Api.InputUser? in
guard let peer = transaction.getPeer(id) else {
return nil
}
guard let user = peer as? TelegramUser else {
return nil
}
updatePeers(transaction: transaction, peers: [user.withUpdatedStoriesHidden(isHidden)], update: { _, updated in
return updated
})
return apiInputUser(peer)
}
|> mapToSignal { inputUser -> Signal<Never, NoError> in
guard let inputUser = inputUser else {
return .complete()
}
return account.network.request(Api.functions.contacts.toggleStoriesHidden(id: inputUser, hidden: isHidden ? .boolTrue : .boolFalse))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
}
}
func _internal_exportStoryLink(account: Account, peerId: EnginePeer.Id, id: Int32) -> Signal<String?, NoError> {
return account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
|> mapToSignal { inputUser -> Signal<String?, NoError> in
guard let inputUser = inputUser else {
return .single(nil)
}
return account.network.request(Api.functions.stories.exportStoryLink(userId: inputUser, id: id))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.ExportedStoryLink?, NoError> in
return .single(nil)
}
|> map { result -> String? in
guard let result = result else {
return nil
}
switch result {
case let .exportedStoryLink(link):
return link
}
}
}
}

View File

@ -111,6 +111,7 @@ public final class StorySubscriptionsContext {
private let queue: Queue
private let postbox: Postbox
private let network: Network
private let includesHidden: Bool
private var taskState = TaskState()
@ -121,11 +122,12 @@ public final class StorySubscriptionsContext {
private let loadMoreDisposable = MetaDisposable()
private let refreshTimerDisposable = MetaDisposable()
init(queue: Queue, accountPeerId: PeerId, postbox: Postbox, network: Network) {
init(queue: Queue, accountPeerId: PeerId, postbox: Postbox, network: Network, includesHidden: Bool) {
self.accountPeerId = accountPeerId
self.queue = queue
self.postbox = postbox
self.network = network
self.includesHidden = includesHidden
self.taskState.isRefreshScheduled = true
@ -148,16 +150,18 @@ public final class StorySubscriptionsContext {
return
}
let subscriptionsKey: PostboxStorySubscriptionsKey = self.includesHidden ? .all : .filtered
if self.taskState.isRefreshScheduled {
self.isLoading = true
self.stateDisposable = (postbox.combinedView(keys: [PostboxViewKey.storiesState(key: .subscriptions)])
self.stateDisposable = (postbox.combinedView(keys: [PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey))])
|> take(1)
|> deliverOn(self.queue)).start(next: { [weak self] views in
guard let `self` = self else {
return
}
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else {
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey))] as? StoryStatesView else {
return
}
@ -173,13 +177,13 @@ public final class StorySubscriptionsContext {
} else if self.taskState.isLoadMoreScheduled {
self.isLoading = true
self.stateDisposable = (postbox.combinedView(keys: [PostboxViewKey.storiesState(key: .subscriptions)])
self.stateDisposable = (postbox.combinedView(keys: [PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey))])
|> take(1)
|> deliverOn(self.queue)).start(next: { [weak self] views in
guard let `self` = self else {
return
}
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else {
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey))] as? StoryStatesView else {
return
}
@ -206,6 +210,11 @@ public final class StorySubscriptionsContext {
private func loadImpl(isRefresh: Bool, stateMark: OpaqueStateMark) {
var flags: Int32 = 0
if self.includesHidden {
flags |= 1 << 2
}
var state: String?
switch stateMark {
case .empty:
@ -228,6 +237,9 @@ public final class StorySubscriptionsContext {
let accountPeerId = self.accountPeerId
let includesHidden = self.includesHidden
let subscriptionsKey: PostboxStorySubscriptionsKey = self.includesHidden ? .all : .filtered
self.loadMoreDisposable.set((self.network.request(Api.functions.stories.getAllStories(flags: flags, state: state))
|> deliverOn(self.queue)).start(next: { [weak self] result in
guard let `self` = self else {
@ -238,7 +250,7 @@ public final class StorySubscriptionsContext {
switch result {
case let .allStoriesNotModified(state):
self.loadedStateMark = .value(state)
let (currentStateValue, _) = transaction.getAllStorySubscriptions()
let (currentStateValue, _) = transaction.getAllStorySubscriptions(key: subscriptionsKey)
let currentState = currentStateValue.flatMap { $0.get(Stories.SubscriptionsState.self) }
var hasMore = false
@ -246,7 +258,7 @@ public final class StorySubscriptionsContext {
hasMore = currentState.hasMore
}
transaction.setSubscriptionsStoriesState(state: CodableEntry(Stories.SubscriptionsState(
transaction.setSubscriptionsStoriesState(key: subscriptionsKey, state: CodableEntry(Stories.SubscriptionsState(
opaqueState: state,
refreshId: currentState?.refreshId ?? UInt64.random(in: 0 ... UInt64.max),
hasMore: hasMore
@ -263,14 +275,9 @@ public final class StorySubscriptionsContext {
peerPresences[telegramUser.id] = user
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
let hasMore: Bool = (flags & (1 << 0)) != 0
let (_, currentPeerItems) = transaction.getAllStorySubscriptions()
let (_, currentPeerItems) = transaction.getAllStorySubscriptions(key: subscriptionsKey)
var peerEntries: [PeerId] = []
for userStorySet in userStories {
@ -312,18 +319,29 @@ public final class StorySubscriptionsContext {
}
}
if !isRefresh {
if isRefresh {
if !includesHidden {
if !peerEntries.contains(where: { $0 == accountPeerId }) {
transaction.setStoryItems(peerId: accountPeerId, items: [])
}
}
} else {
let leftPeerIds = currentPeerItems.filter({ !peerEntries.contains($0) })
if !leftPeerIds.isEmpty {
peerEntries = leftPeerIds + peerEntries
}
}
transaction.replaceAllStorySubscriptions(state: CodableEntry(Stories.SubscriptionsState(
transaction.replaceAllStorySubscriptions(key: subscriptionsKey, state: CodableEntry(Stories.SubscriptionsState(
opaqueState: state,
refreshId: UInt64.random(in: 0 ... UInt64.max),
hasMore: hasMore
)), peerIds: peerEntries)
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
}
}
|> deliverOn(self.queue)).start(completed: { [weak self] in
@ -355,10 +373,10 @@ public final class StorySubscriptionsContext {
private let queue = Queue(name: "StorySubscriptionsContext")
private let impl: QueueLocalObject<Impl>
init(accountPeerId: PeerId, postbox: Postbox, network: Network) {
init(accountPeerId: PeerId, postbox: Postbox, network: Network, includesHidden: Bool) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
Impl(queue: queue, accountPeerId: accountPeerId, postbox: postbox, network: network)
Impl(queue: queue, accountPeerId: accountPeerId, postbox: postbox, network: network, includesHidden: includesHidden)
})
}

View File

@ -592,7 +592,7 @@ public extension TelegramEngine {
}).start()
}
public func storySubscriptions() -> Signal<EngineStorySubscriptions, NoError> {
public func storySubscriptions(includeHidden: Bool) -> Signal<EngineStorySubscriptions, NoError> {
let debugTimerSignal: Signal<Bool, NoError>
#if DEBUG && false
debugTimerSignal = Signal<Bool, NoError>.single(true)
@ -609,21 +609,24 @@ public extension TelegramEngine {
debugTimerSignal = .single(true)
#endif
let subscriptionsKey: PostboxStorySubscriptionsKey = includeHidden ? .all : .filtered
let basicPeerKey = PostboxViewKey.basicPeer(self.account.peerId)
let storySubscriptionsKey = PostboxViewKey.storySubscriptions(key: subscriptionsKey)
return combineLatest(debugTimerSignal |> distinctUntilChanged,
self.account.postbox.combinedView(keys: [
basicPeerKey,
PostboxViewKey.storySubscriptions,
PostboxViewKey.storiesState(key: .subscriptions)
storySubscriptionsKey,
PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey))
]))
|> mapToSignal { debugTimer, views -> Signal<EngineStorySubscriptions, NoError> in
guard let basicPeerView = views.views[basicPeerKey] as? BasicPeerView, let accountPeer = basicPeerView.peer else {
return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil))
}
guard let storySubscriptionsView = views.views[PostboxViewKey.storySubscriptions] as? StorySubscriptionsView else {
guard let storySubscriptionsView = views.views[storySubscriptionsKey] as? StorySubscriptionsView else {
return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil))
}
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else {
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey))] as? StoryStatesView else {
return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil))
}
@ -756,21 +759,23 @@ public extension TelegramEngine {
}
}
public func preloadStorySubscriptions() -> Signal<[EngineMediaResource.Id: StoryPreloadInfo], NoError> {
public func preloadStorySubscriptions(includeHidden: Bool) -> Signal<[EngineMediaResource.Id: StoryPreloadInfo], NoError> {
let basicPeerKey = PostboxViewKey.basicPeer(self.account.peerId)
let subscriptionsKey: PostboxStorySubscriptionsKey = includeHidden ? .all : .filtered
let storySubscriptionsKey = PostboxViewKey.storySubscriptions(key: subscriptionsKey)
return self.account.postbox.combinedView(keys: [
basicPeerKey,
PostboxViewKey.storySubscriptions,
PostboxViewKey.storiesState(key: .subscriptions)
storySubscriptionsKey,
PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey))
])
|> mapToSignal { views -> Signal<[EngineMediaResource.Id: StoryPreloadInfo], NoError> in
guard let basicPeerView = views.views[basicPeerKey] as? BasicPeerView, let accountPeer = basicPeerView.peer else {
return .single([:])
}
guard let storySubscriptionsView = views.views[PostboxViewKey.storySubscriptions] as? StorySubscriptionsView else {
guard let storySubscriptionsView = views.views[storySubscriptionsKey] as? StorySubscriptionsView else {
return .single([:])
}
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else {
guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey))] as? StoryStatesView else {
return .single([:])
}
@ -899,5 +904,9 @@ public extension TelegramEngine {
public func storyViewList(id: Int32, views: EngineStoryItem.Views) -> EngineStoryViewListContext {
return EngineStoryViewListContext(account: self.account, storyId: id, views: views)
}
public func exportStoryLink(peerId: EnginePeer.Id, id: Int32) -> Signal<String?, NoError> {
return _internal_exportStoryLink(account: self.account, peerId: peerId, id: id)
}
}
}

View File

@ -1132,6 +1132,10 @@ public extension TelegramEngine {
public func tokenizeSearchString(string: String, transliteration: EngineStringIndexTokenTransliteration) -> [EngineDataBuffer] {
return stringIndexTokens(string, transliteration: transliteration)
}
public func updatePeerStoriesHidden(id: PeerId, isHidden: Bool) {
let _ = _internal_updatePeerStoriesHidden(account: self.account, id: id, isHidden: isHidden).start()
}
}
}

View File

@ -53,7 +53,22 @@ public func updatePeers(transaction: Transaction, peers: [Peer], update: (Peer?,
switch peerId.namespace {
case Namespaces.Peer.CloudUser:
break
if let updated = updated as? TelegramUser, let storiesHidden = updated.storiesHidden {
if storiesHidden {
if transaction.storySubscriptionsContains(key: .filtered, peerId: updated.id) {
var (state, peerIds) = transaction.getAllStorySubscriptions(key: .filtered)
peerIds.removeAll(where: { $0 == updated.id })
transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds)
}
} else {
if transaction.storySubscriptionsContains(key: .all, peerId: updated.id) && !transaction.storySubscriptionsContains(key: .filtered, peerId: updated.id) {
var (state, peerIds) = transaction.getAllStorySubscriptions(key: .all)
peerIds.removeAll(where: { $0 == updated.id })
peerIds.append(updated.id)
transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds)
}
}
}
case Namespaces.Peer.CloudGroup:
if let group = updated as? TelegramGroup {
if group.flags.contains(.deactivated) {

View File

@ -133,7 +133,9 @@ public final class ChatListHeaderComponent: Component {
public let secondaryTransition: CGFloat
public let networkStatus: HeaderNetworkStatusComponent.Content?
public let storySubscriptions: EngineStorySubscriptions?
public let storiesIncludeHidden: Bool
public let storiesFraction: CGFloat
public let storiesUnlockedFraction: CGFloat
public let uploadProgress: Float?
public let context: AccountContext
public let theme: PresentationTheme
@ -149,7 +151,9 @@ public final class ChatListHeaderComponent: Component {
secondaryTransition: CGFloat,
networkStatus: HeaderNetworkStatusComponent.Content?,
storySubscriptions: EngineStorySubscriptions?,
storiesIncludeHidden: Bool,
storiesFraction: CGFloat,
storiesUnlockedFraction: CGFloat,
uploadProgress: Float?,
context: AccountContext,
theme: PresentationTheme,
@ -164,7 +168,9 @@ public final class ChatListHeaderComponent: Component {
self.context = context
self.networkStatus = networkStatus
self.storySubscriptions = storySubscriptions
self.storiesIncludeHidden = storiesIncludeHidden
self.storiesFraction = storiesFraction
self.storiesUnlockedFraction = storiesUnlockedFraction
self.uploadProgress = uploadProgress
self.theme = theme
self.strings = strings
@ -191,9 +197,15 @@ public final class ChatListHeaderComponent: Component {
if lhs.storySubscriptions != rhs.storySubscriptions {
return false
}
if lhs.storiesIncludeHidden != rhs.storiesIncludeHidden {
return false
}
if lhs.storiesFraction != rhs.storiesFraction {
return false
}
if lhs.storiesUnlockedFraction != rhs.storiesUnlockedFraction {
return false
}
if lhs.uploadProgress != rhs.uploadProgress {
return false
}
@ -799,12 +811,6 @@ public final class ChatListHeaderComponent: Component {
var storyListTransition = transition
if let storySubscriptions = component.storySubscriptions {
var storyOffsetFraction: CGFloat = 1.0
storyOffsetFraction = component.storiesFraction
let _ = storyOffsetFraction
//self.storyOffsetFraction = storyOffsetFraction
let storyPeerList: ComponentView<Empty>
if let current = self.storyPeerList {
storyPeerList = current
@ -821,8 +827,10 @@ public final class ChatListHeaderComponent: Component {
context: component.context,
theme: component.theme,
strings: component.strings,
includesHidden: 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 {
@ -971,8 +979,8 @@ public final class ChatListHeaderComponent: Component {
self.addSubview(storyPeerListComponentView)
}
let storyPeerListMinOffset: CGFloat = -8.0
let storyPeerListMaxOffset: CGFloat = availableSize.height + 2.0
let storyPeerListMinOffset: CGFloat = -7.0
let storyPeerListMaxOffset: CGFloat = availableSize.height + 8.0
let storyPeerListPosition: CGFloat = storyPeerListMinOffset * (1.0 - component.storiesFraction) + storyPeerListMaxOffset * component.storiesFraction

View File

@ -21,6 +21,7 @@ public final class ChatListNavigationBar: Component {
public let secondaryContent: ChatListHeaderComponent.Content?
public let secondaryTransition: CGFloat
public let storySubscriptions: EngineStorySubscriptions?
public let storiesIncludeHidden: Bool
public let uploadProgress: Float?
public let tabsNode: ASDisplayNode?
public let tabsNodeIsSearch: Bool
@ -39,6 +40,7 @@ public final class ChatListNavigationBar: Component {
secondaryContent: ChatListHeaderComponent.Content?,
secondaryTransition: CGFloat,
storySubscriptions: EngineStorySubscriptions?,
storiesIncludeHidden: Bool,
uploadProgress: Float?,
tabsNode: ASDisplayNode?,
tabsNodeIsSearch: Bool,
@ -56,6 +58,7 @@ public final class ChatListNavigationBar: Component {
self.secondaryContent = secondaryContent
self.secondaryTransition = secondaryTransition
self.storySubscriptions = storySubscriptions
self.storiesIncludeHidden = storiesIncludeHidden
self.uploadProgress = uploadProgress
self.tabsNode = tabsNode
self.tabsNodeIsSearch = tabsNodeIsSearch
@ -97,6 +100,9 @@ public final class ChatListNavigationBar: Component {
if lhs.storySubscriptions != rhs.storySubscriptions {
return false
}
if lhs.storiesIncludeHidden != rhs.storiesIncludeHidden {
return false
}
if lhs.uploadProgress != rhs.uploadProgress {
return false
}
@ -116,6 +122,9 @@ public final class ChatListNavigationBar: Component {
self.size = size
}
}
public static let searchScrollHeight: CGFloat = 52.0
public static let storiesScrollHeight: CGFloat = 94.0
public final class View: UIView {
private let backgroundView: BlurredBackgroundView
@ -142,7 +151,9 @@ public final class ChatListNavigationBar: Component {
private var applyScrollFractionAnimator: DisplayLinkAnimator?
private var applyScrollFraction: CGFloat = 1.0
private var applyScrollUnlockedFraction: CGFloat = 1.0
private var applyScrollStartFraction: CGFloat = 0.0
private var storiesOffsetFraction: CGFloat = 0.0
private var tabsNode: ASDisplayNode?
private var tabsNodeIsSearch: Bool = false
@ -201,11 +212,11 @@ public final class ChatListNavigationBar: Component {
self.scrollTheme = component.theme
self.scrollStrings = component.strings
let searchOffsetDistance: CGFloat = navigationBarSearchContentHeight
let defaultStoriesOffsetDistance: CGFloat = 79.0
let searchOffsetDistance: CGFloat = ChatListNavigationBar.searchScrollHeight
let defaultStoriesOffsetDistance: CGFloat = ChatListNavigationBar.storiesScrollHeight
let effectiveStoriesOffsetDistance: CGFloat
var minContentOffset: CGFloat = navigationBarSearchContentHeight
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
@ -269,22 +280,32 @@ public final class ChatListNavigationBar: Component {
self.addSubview(searchContentNode.view)
}
let clippedStoriesOverscrollOffset = -min(0.0, clippedScrollOffset)
let clippedStoriesOffset = max(0.0, min(clippedScrollOffset, defaultStoriesOffsetDistance))
var storiesOffsetFraction: CGFloat
if !component.isSearchActive, component.secondaryTransition == 0.0, let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty, component.storiesUnlocked {
storiesOffsetFraction = clippedStoriesOffset / defaultStoriesOffsetDistance
var storiesUnlockedOffsetFraction: CGFloat
if !component.isSearchActive, component.secondaryTransition == 0.0, let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty {
if component.storiesUnlocked {
storiesOffsetFraction = clippedStoriesOffset / defaultStoriesOffsetDistance
storiesUnlockedOffsetFraction = 1.0
} else {
storiesOffsetFraction = 1.0 - (clippedStoriesOverscrollOffset / defaultStoriesOffsetDistance)
storiesUnlockedOffsetFraction = 0.0
}
} else {
storiesOffsetFraction = 1.0
storiesUnlockedOffsetFraction = 1.0
}
if self.applyScrollFractionAnimator != nil {
storiesOffsetFraction = self.applyScrollFraction * storiesOffsetFraction + (1.0 - self.applyScrollFraction) * 1.0
storiesUnlockedOffsetFraction = self.applyScrollUnlockedFraction * storiesUnlockedOffsetFraction + (1.0 - self.applyScrollUnlockedFraction) * 1.0
}
let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight)
var searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - searchSize.height), size: searchSize)
if component.tabsNode != nil {
searchFrame.origin.y -= 46.0
searchFrame.origin.y -= 40.0
}
let clippedSearchOffset = max(0.0, min(clippedScrollOffset - effectiveStoriesOffsetDistance, searchOffsetDistance))
@ -300,6 +321,7 @@ public final class ChatListNavigationBar: Component {
headerTransition = .immediate
}
self.storiesOffsetFraction = storiesOffsetFraction
let headerContentSize = self.headerContent.update(
transition: headerTransition,
component: AnyComponent(ChatListHeaderComponent(
@ -309,7 +331,9 @@ public final class ChatListNavigationBar: Component {
secondaryTransition: component.secondaryTransition,
networkStatus: nil,
storySubscriptions: component.storySubscriptions,
storiesIncludeHidden: component.storiesIncludeHidden,
storiesFraction: 1.0 - storiesOffsetFraction,
storiesUnlockedFraction: 1.0 - storiesUnlockedOffsetFraction,
uploadProgress: component.uploadProgress,
context: component.context,
theme: component.theme,
@ -375,7 +399,7 @@ public final class ChatListNavigationBar: Component {
if let disappearingTabsView = self.disappearingTabsView {
disappearingTabsView.layer.anchorPoint = CGPoint()
transition.setFrameWithAdditivePosition(view: disappearingTabsView, frame: tabsFrame.offsetBy(dx: 0.0, dy: self.disappearingTabsViewSearch ? -56.0 : 0.0))
transition.setFrameWithAdditivePosition(view: disappearingTabsView, frame: tabsFrame.offsetBy(dx: 0.0, dy: self.disappearingTabsViewSearch ? (-currentLayout.size.height + 2.0) : 0.0))
}
if let tabsNode = component.tabsNode {
@ -401,7 +425,7 @@ public final class ChatListNavigationBar: Component {
transition.setAlpha(view: tabsNode.view, alpha: 1.0)
}
tabsNodeTransition.setFrameWithAdditivePosition(view: tabsNode.view, frame: tabsFrame)
tabsNodeTransition.setFrameWithAdditivePosition(view: tabsNode.view, frame: tabsFrame.offsetBy(dx: 0.0, dy: component.tabsNodeIsSearch ? (-currentLayout.size.height + 2.0) : 0.0))
}
}
@ -441,7 +465,7 @@ public final class ChatListNavigationBar: Component {
self.effectiveStoriesInsetHeight = 0.0
} else {
if let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty, component.storiesUnlocked {
let storiesHeight: CGFloat = 79.0 * (1.0 - component.secondaryTransition)
let storiesHeight: CGFloat = ChatListNavigationBar.storiesScrollHeight * (1.0 - component.secondaryTransition)
contentHeight += storiesHeight
self.effectiveStoriesInsetHeight = storiesHeight
} else {
@ -452,7 +476,7 @@ public final class ChatListNavigationBar: Component {
}
if component.tabsNode != nil {
contentHeight += 46.0
contentHeight += 40.0
}
let size = CGSize(width: availableSize.width, height: contentHeight)
@ -467,13 +491,18 @@ public final class ChatListNavigationBar: Component {
}
if storiesUnlockedUpdated && component.storiesUnlocked {
self.applyScrollFraction = 0.0
self.applyScrollStartFraction = 0.0
let startFraction = self.storiesOffsetFraction
self.applyScrollFraction = (1.0 - 0.0) * startFraction + 0.0 * 1.0
self.applyScrollUnlockedFraction = 0.0
self.applyScrollFractionAnimator = DisplayLinkAnimator(duration: 0.3, from: 0.0, to: 1.0, update: { [weak self] value in
guard let self else {
return
}
self.applyScrollFraction = listViewAnimationCurveSystem(value)
let t = listViewAnimationCurveSystem(value)
self.applyScrollFraction = (1.0 - t) * startFraction + t * 1.0
self.applyScrollUnlockedFraction = t
if let rawScrollOffset = self.rawScrollOffset {
self.hasDeferredScrollOffset = true

View File

@ -372,9 +372,10 @@ private final class StoryContainerScreenComponent: Component {
func animateOut(completion: @escaping () -> Void) {
self.isAnimatingOut = true
self.state?.updated(transition: .immediate)
if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View, let transitionOut = component.transitionOut(slice.peer.id, slice.item.id) {
self.state?.updated(transition: .immediate)
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
transition.setAlpha(layer: self.backgroundLayer, alpha: 0.0)
transition.setAlpha(view: self.backgroundEffectView, alpha: 0.0)
@ -385,10 +386,14 @@ private final class StoryContainerScreenComponent: Component {
transitionOutCompleted()
})
} else {
self.layer.allowsGroupOpacity = true
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
self.dismissPanState = ItemSetPanState(fraction: 1.0, didBegin: true)
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
transition.setAlpha(layer: self.backgroundLayer, alpha: 0.0, completion: { _ in
completion()
})
transition.setAlpha(view: self.backgroundEffectView, alpha: 0.0)
}
self.didAnimateOut = true

View File

@ -1061,8 +1061,31 @@ public final class StoryItemSetContainerComponent: Component {
if component.slice.item.storyItem.isPublic {
items.append(.action(ContextMenuActionItem(text: "Copy link", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
}, action: { _, a in
}, action: { [weak self] _, a in
a(.default)
guard let self, let component = self.component else {
return
}
let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id)
|> deliverOnMainQueue).start(next: { [weak self] link in
guard let self, let component = self.component else {
return
}
if let link {
UIPasteboard.general.string = link
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
component.presentController(UndoOverlayController(
presentationData: presentationData,
content: .linkCopied(text: "Link copied."),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
))
}
})
})))
items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
@ -1652,9 +1675,9 @@ public final class StoryItemSetContainerComponent: Component {
)
if let inlineActionsView = self.inlineActions.view {
if inlineActionsView.superview == nil {
//self.contentContainerView.addSubview(inlineActionsView)
self.contentContainerView.addSubview(inlineActionsView)
}
transition.setFrame(view: inlineActionsView, frame: CGRect(origin: CGPoint(x: contentFrame.width - 10.0 - inlineActionsSize.width, y: contentFrame.height - 20.0 - inlineActionsSize.height), size: inlineActionsSize))
transition.setFrame(view: inlineActionsView, frame: CGRect(origin: CGPoint(x: contentFrame.width - 10.0 - inlineActionsSize.width, y: contentFrame.height - 10.0 - inlineActionsSize.height), size: inlineActionsSize))
var inlineActionsAlpha: CGFloat = inputPanelIsOverlay ? 0.0 : 1.0
if self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil {
@ -1666,6 +1689,9 @@ public final class StoryItemSetContainerComponent: Component {
if component.hideUI || self.displayViewList {
inlineActionsAlpha = 0.0
}
if !component.slice.item.storyItem.isPublic {
inlineActionsAlpha = 0.0
}
transition.setAlpha(view: inlineActionsView, alpha: inlineActionsAlpha)
}

View File

@ -273,40 +273,26 @@ final class StoryItemSetContainerSendMessage {
}
func performInlineAction(view: StoryItemSetContainerComponent.View, item: StoryActionsComponent.Item) {
/*guard let component = view.component else {
guard let component = view.component else {
return
}
guard let focusedItemId = view.focusedItemId, let focusedItem = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else {
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return
}
switch item.kind {
case .like:
if item.isActivated {
component.context.engine.messages.setMessageReactions(
id: targetMessageId,
reactions: [
]
)
} else {
component.context.engine.messages.setMessageReactions(
id: targetMessageId,
reactions: [
.builtin("")
]
)
}
return
case .share:
let _ = (component.context.engine.data.get(
TelegramEngine.EngineData.Item.Messages.Message(id: targetMessageId)
)
|> deliverOnMainQueue).start(next: { [weak view] message in
guard let view, let message, let component = view.component, let controller = component.controller() else {
let _ = (component.context.engine.messages.exportStoryLink(peerId: peerId, id: focusedItem.storyItem.id)
|> deliverOnMainQueue).start(next: { [weak view] link in
guard let view, let link, let component = view.component, let controller = component.controller() else {
return
}
let shareController = ShareController(
context: component.context,
subject: .messages([message._asMessage()]),
subject: .url(link),
externalShare: false,
immediateExternalShare: false,
updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }),
@ -314,7 +300,7 @@ final class StoryItemSetContainerSendMessage {
)
controller.present(shareController, in: .window(.root))
})
}*/
}
}
private func clearInputText(view: StoryItemSetContainerComponent.View) {

View File

@ -310,6 +310,7 @@ public final class StoryContentContextImpl: StoryContentContext {
}
private let context: AccountContext
private let includeHidden: Bool
public private(set) var stateValue: StoryContentContextState?
public var state: Signal<StoryContentContextState, NoError> {
@ -342,14 +343,16 @@ public final class StoryContentContextImpl: StoryContentContext {
public init(
context: AccountContext,
includeHidden: Bool,
focusedPeerId: EnginePeer.Id?
) {
self.context = context
self.includeHidden = includeHidden
if let focusedPeerId {
self.focusedItem = (focusedPeerId, nil)
}
self.storySubscriptionsDisposable = (context.engine.messages.storySubscriptions()
self.storySubscriptionsDisposable = (context.engine.messages.storySubscriptions(includeHidden: includeHidden)
|> deliverOnMainQueue).start(next: { [weak self] storySubscriptions in
guard let self else {
return
@ -741,6 +744,16 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
guard let self else {
return
}
if item == nil {
let storyKey = StoryKey(peerId: storyId.peerId, id: storyId.id)
if !self.requestedStoryKeys.contains(storyKey) {
self.requestedStoryKeys.insert(storyKey)
self.requestStoryDisposables.add(self.context.engine.messages.refreshStories(peerId: storyId.peerId, ids: [storyId.id]).start())
}
}
if let item, case let .item(itemValue) = item, let media = itemValue.media, let peer {
let mappedItem = EngineStoryItem(
id: itemValue.id,

View File

@ -21,8 +21,10 @@ public final class StoryPeerListComponent: Component {
public let context: AccountContext
public let theme: PresentationTheme
public let strings: PresentationStrings
public let includesHidden: Bool
public let storySubscriptions: EngineStorySubscriptions?
public let collapseFraction: CGFloat
public let unlockedFraction: CGFloat
public let uploadProgress: Float?
public let peerAction: (EnginePeer?) -> Void
public let contextPeerAction: (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void
@ -32,8 +34,10 @@ public final class StoryPeerListComponent: Component {
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
includesHidden: Bool,
storySubscriptions: EngineStorySubscriptions?,
collapseFraction: CGFloat,
unlockedFraction: CGFloat,
uploadProgress: Float?,
peerAction: @escaping (EnginePeer?) -> Void,
contextPeerAction: @escaping (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void
@ -42,8 +46,10 @@ public final class StoryPeerListComponent: Component {
self.context = context
self.theme = theme
self.strings = strings
self.includesHidden = includesHidden
self.storySubscriptions = storySubscriptions
self.collapseFraction = collapseFraction
self.unlockedFraction = unlockedFraction
self.uploadProgress = uploadProgress
self.peerAction = peerAction
self.contextPeerAction = contextPeerAction
@ -59,12 +65,18 @@ public final class StoryPeerListComponent: Component {
if lhs.strings !== rhs.strings {
return false
}
if lhs.includesHidden != rhs.includesHidden {
return false
}
if lhs.storySubscriptions != rhs.storySubscriptions {
return false
}
if lhs.collapseFraction != rhs.collapseFraction {
return false
}
if lhs.unlockedFraction != rhs.unlockedFraction {
return false
}
if lhs.uploadProgress != rhs.uploadProgress {
return false
}
@ -211,7 +223,7 @@ public final class StoryPeerListComponent: Component {
}
let _ = hasStories
let collapseStartIndex = 1
let collapseStartIndex = component.includesHidden ? 0 : 1
let collapsedItemWidth: CGFloat = 24.0
let collapsedItemDistance: CGFloat = 14.0
@ -290,7 +302,14 @@ public final class StoryPeerListComponent: Component {
let itemFrame: CGRect
if isReallyVisible {
itemFrame = regularItemFrame.interpolate(to: collapsedItemFrame, amount: component.collapseFraction)
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
}
@ -315,7 +334,7 @@ public final class StoryPeerListComponent: Component {
rightItemFrame = regularRightItemFrame.interpolate(to: collapsedRightItemFrame, amount: component.collapseFraction)
}
} else {
if component.collapseFraction == 1.0 {
if component.collapseFraction == 1.0 || component.unlockedFraction == 0.0 {
itemAlpha = 0.0
} else {
itemAlpha = 1.0
@ -440,8 +459,15 @@ public final class StoryPeerListComponent: Component {
if let storySubscriptions = component.storySubscriptions, let hasMoreToken = storySubscriptions.hasMoreToken {
if self.requestedLoadMoreToken != hasMoreToken {
self.requestedLoadMoreToken = hasMoreToken
if let storySubscriptionsContext = component.context.account.storySubscriptionsContext {
storySubscriptionsContext.loadMore()
if component.includesHidden {
if let storySubscriptionsContext = component.context.account.filteredStorySubscriptionsContext {
storySubscriptionsContext.loadMore()
}
} else {
if let storySubscriptionsContext = component.context.account.allStorySubscriptionsContext {
storySubscriptionsContext.loadMore()
}
}
}
}
@ -450,7 +476,7 @@ public final class StoryPeerListComponent: Component {
self.sortedItems.removeAll(keepingCapacity: true)
if let storySubscriptions = component.storySubscriptions {
if let accountItem = storySubscriptions.accountItem {
if !component.includesHidden, let accountItem = storySubscriptions.accountItem {
self.sortedItems.append(accountItem)
}

View File

@ -388,7 +388,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) + (25.0 / 52.0) * component.collapsedScaleFactor * component.collapseFraction
let effectiveScale: CGFloat = 1.0 * (1.0 - component.collapseFraction) + (24.0 / 52.0) * component.collapsedScaleFactor * component.collapseFraction
let avatarNode: AvatarNode
if let current = self.avatarNode {
@ -577,7 +577,7 @@ public final class StoryPeerListItemComponent: Component {
environment: {},
containerSize: CGSize(width: availableSize.width + 4.0, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5) + (effectiveWidth - availableSize.width) * 0.5, y: indicatorFrame.midY + (indicatorFrame.height * 0.5 + 3.0) * effectiveScale), size: titleSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5) + (effectiveWidth - availableSize.width) * 0.5, y: indicatorFrame.midY + (indicatorFrame.height * 0.5 + 2.0) * effectiveScale), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)

View File

@ -1183,7 +1183,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
ignoreForward = true
effectiveAuthor = forwardInfo.author
if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature {
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
}
}
displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil
@ -1199,7 +1199,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
displayAuthorInfo = !mergedTop.merged && incoming
} else if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature {
ignoreForward = true
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
displayAuthorInfo = !mergedTop.merged && incoming
} else if let adAttribute = item.content.firstMessage.adAttribute, let author = item.content.firstMessage.author {
ignoreForward = true

View File

@ -338,7 +338,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
if let forwardInfo = content.firstMessage.forwardInfo {
effectiveAuthor = forwardInfo.author
if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature {
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
}
}
displayAuthorInfo = incoming && effectiveAuthor != nil

View File

@ -978,6 +978,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break
case .gameStart:
break
case .story:
break
case let .channelMessage(peer, messageId, timecode):
if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), subject: .message(id: .id(messageId), highlight: true, timecode: timecode)))

View File

@ -109,7 +109,7 @@ final class ComposeControllerNode: ASDisplayNode {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
}
self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition)
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
}

View File

@ -231,7 +231,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
var headerInsets = layout.insets(options: [.input])
headerInsets.top += actualNavigationBarHeight
headerInsets.top += strongSelf.tokenListNode.bounds.size.height
searchResultsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: .immediate)
searchResultsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: .immediate)
searchResultsNode.frame = CGRect(origin: CGPoint(), size: layout.size)
}
@ -285,7 +285,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
switch self.contentNode {
case let .contacts(contactsNode):
contactsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
contactsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition)
case let .chats(chatsNode):
var combinedInsets = insets
combinedInsets.left += layout.safeInsets.left
@ -297,7 +297,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
self.contentNode.node.frame = CGRect(origin: CGPoint(), size: layout.size)
if let searchResultsNode = self.searchResultsNode {
searchResultsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
searchResultsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition)
searchResultsNode.frame = CGRect(origin: CGPoint(), size: layout.size)
}

View File

@ -144,7 +144,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
}
self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition)
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)

View File

@ -30,6 +30,8 @@ import BotPaymentsUI
import PremiumUI
import AuthorizationUI
import ChatFolderLinkPreviewScreen
import StoryContainerScreen
import StoryContentComponent
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
if case .default = navigation {
@ -803,5 +805,24 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}))
dismissInput()
}
case let .story(peerId, id):
let storyContent = SingleStoryContentContextImpl(context: context, storyId: StoryId(peerId: peerId, id: id))
let _ = (storyContent.state
|> take(1)
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in
let transitionIn: StoryContainerScreen.TransitionIn? = nil
let storyContainerScreen = StoryContainerScreen(
context: context,
content: storyContent,
transitionIn: transitionIn,
transitionOut: { _, _ in
let transitionOut: StoryContainerScreen.TransitionOut? = nil
return transitionOut
}
)
navigationController?.pushViewController(storyContainerScreen)
})
}
}

View File

@ -1069,7 +1069,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
contactListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
contactListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition)
}
if let searchDisplayController = self.searchDisplayController {

View File

@ -250,7 +250,7 @@ private func pollResultsControllerEntries(presentationData: PresentationData, po
displayCount = Int(voterCount)
}
for peerIndex in 0 ..< displayCount {
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)
let peer = EngineRenderedPeer(peer: EnginePeer(fakeUser))
entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: optionTextHeader, optionAdditionalText: optionAdditionalTextHeader, optionCount: voterCount, optionExpanded: false, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: peerIndex % 2, isFirstInOption: peerIndex == 0))
}

View File

@ -72,6 +72,7 @@ public enum ParsedInternalPeerUrlParameter {
case replyThread(Int32, Int32)
case voiceChat(String?)
case appStart(String, String?)
case story(Int32)
}
public enum ParsedInternalUrl {
@ -246,6 +247,10 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
}
}
return .startAttach(peerName, value, choose)
} else if queryItem.name == "story" {
if let id = Int32(value) {
return .peer(.name(peerName), .story(id))
}
}
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
return .peer(.name(peerName), .voiceChat(nil))
@ -697,6 +702,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
}
case let .voiceChat(invite):
return .single(.joinVoiceChat(peer.id, invite))
case let .story(id):
return .single(.story(peerId: peer.id, id: id))
}
} else {
return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)))