Monoforums

This commit is contained in:
Isaac 2025-05-23 23:03:52 +08:00
parent 3f2533ae68
commit 3910ecdca2
15 changed files with 196 additions and 115 deletions

View File

@ -1067,6 +1067,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if case let .channel(channel) = peer, channel.flags.contains(.isMonoforum) {
openAsInlineForum = false
} else if case let .channel(channel) = peer, channel.flags.contains(.displayForumAsTabs) {
openAsInlineForum = false
} else {
if let cachedData = cachedDataView.cachedPeerData as? CachedChannelData, case let .known(viewForumAsMessages) = cachedData.viewForumAsMessages, viewForumAsMessages {
openAsInlineForum = false

View File

@ -96,7 +96,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableDebugDataDisplay(Bool)
case rippleEffect(Bool)
case browserExperiment(Bool)
case localTranscription(Bool)
case allForumsHaveTabs(Bool)
case enableReactionOverrides(Bool)
case compressedEmojiCache(Bool)
case storiesJpegExperiment(Bool)
@ -133,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation:
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation:
return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue
@ -226,7 +226,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 39
case .browserExperiment:
return 40
case .localTranscription:
case .allForumsHaveTabs:
return 41
case .enableReactionOverrides:
return 42
@ -1264,12 +1264,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .localTranscription(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Local Transcription", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .allForumsHaveTabs(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Forum Tabs Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.localTranscription = value
settings.allForumsHaveTabs = value
return PreferencesEntry(settings)
})
}).start()
@ -1526,7 +1526,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.browserExperiment(experimentalSettings.browserExperiment))
}
#endif
entries.append(.localTranscription(experimentalSettings.localTranscription))
entries.append(.allForumsHaveTabs(experimentalSettings.allForumsHaveTabs))
if case .internal = sharedContext.applicationBindings.appBuildType {
entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides))
}

View File

@ -65,7 +65,11 @@ private func mappedChatListFilterPredicate(postbox: PostboxImpl, currentTransact
if let cachedPeerData = postbox.cachedPeerDataTable.get(peer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true
}
if postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value && !displayAsRegularChat {
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value
if !isThreadBased {
displayAsRegularChat = true
}
if isThreadBased && !displayAsRegularChat {
isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.effectiveUnreadCount ?? 0) > 0
} else {
isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false
@ -435,13 +439,17 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(peer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true
}
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value
if !isThreadBased {
displayAsRegularChat = true
}
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId))
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, threadId: nil, calculation: filterPredicate.messageTagSummary)
var isUnread: Bool
if postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value && !displayAsRegularChat {
if isThreadBased && !displayAsRegularChat {
isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.effectiveUnreadCount ?? 0) > 0
} else {
isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false
@ -569,9 +577,13 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(entryPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true
}
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer).value
if !isThreadBased {
displayAsRegularChat = true
}
var isUnread: Bool
if postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer).value && !displayAsRegularChat {
if isThreadBased && !displayAsRegularChat {
isUnread = (postbox.peerThreadsSummaryTable.get(peerId: entryPeer.id)?.effectiveUnreadCount ?? 0) > 0
} else {
isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false
@ -618,9 +630,13 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(mainPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true
}
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer).value
if !isThreadBased {
displayAsRegularChat = true
}
var isUnread: Bool
if postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer).value && !displayAsRegularChat {
if isThreadBased && !displayAsRegularChat {
isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0) > 0
} else {
isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
@ -800,9 +816,13 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(entryPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true
}
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer).value
if !isThreadBased {
displayAsRegularChat = true
}
var isUnread: Bool
if postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer).value && !displayAsRegularChat {
if isThreadBased && !displayAsRegularChat {
isUnread = (postbox.peerThreadsSummaryTable.get(peerId: entryPeer.id)?.effectiveUnreadCount ?? 0) > 0
} else {
isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false
@ -858,9 +878,13 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(mainPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true
}
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer).value
if !isThreadBased {
displayAsRegularChat = true
}
var isUnread: Bool
if postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer).value && !displayAsRegularChat {
if isThreadBased && !displayAsRegularChat {
isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0) > 0
} else {
isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
@ -952,10 +976,19 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(entryData.index.messageIndex.id.peerId), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true
}
let isThreadBased: Bool
if let peer = postbox.peerTable.get(entryData.index.messageIndex.id.peerId) {
isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value
} else {
isThreadBased = false
}
if !isThreadBased {
displayAsRegularChat = true
}
var updatedReadState = entryData.readState
if let peer = postbox.peerTable.get(entryData.index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value, !displayAsRegularChat {
let summary = postbox.peerThreadsSummaryTable.get(peerId: peer.id)
if isThreadBased, !displayAsRegularChat {
let summary = postbox.peerThreadsSummaryTable.get(peerId: entryData.index.messageIndex.id.peerId)
var count: Int32 = 0
var isMuted: Bool = false
@ -1605,6 +1638,9 @@ struct ChatListViewState {
autoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData)
displayAsRegularChat = postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedData)
}
if !isThreadBased {
displayAsRegularChat = true
}
var topForumTopics: [ChatListForumTopicData] = []
let readState: ChatListViewReadState?

View File

@ -76,7 +76,11 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
peerSummaryIsThreadBased: { peer in
if let channel = peer as? TelegramChannel {
if channel.flags.contains(.isForum) {
return (true, false)
if channel.flags.contains(.displayForumAsTabs) {
return (false, false)
} else {
return (true, false)
}
} else if channel.flags.contains(.isMonoforum) {
return (true, true)
} else {

View File

@ -208,7 +208,9 @@ func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, netwo
}
var displayAsRegularChat: Bool = false
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData {
if let channel = peer as? TelegramChannel, channel.flags.contains(.displayForumAsTabs) {
displayAsRegularChat = true
} else if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData {
displayAsRegularChat = cachedData.viewForumAsMessages.knownValue ?? false
}

View File

@ -26,7 +26,7 @@ import LottieComponent
import BundleIconComponent
private protocol ChatEmptyNodeContent {
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize
}
private let titleFont = Font.semibold(15.0)
@ -46,7 +46,7 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod
self.addSubnode(self.textNode)
}
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
self.currentTheme = interfaceState.theme
self.currentStrings = interfaceState.strings
@ -155,7 +155,7 @@ public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNod
let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file._parse()), false, self.view, self.stickerNode.bounds, nil, [])
}
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
let isFirstTime = self.currentTheme == nil
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
@ -363,7 +363,7 @@ public final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeS
let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file._parse()), false, self.view, self.stickerNode.bounds, nil, [])
}
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
self.currentTheme = interfaceState.theme
self.currentStrings = interfaceState.strings
@ -496,7 +496,7 @@ private final class ChatEmptyNodeSecretChatContent: ASDisplayNode, ChatEmptyNode
self.addSubnode(self.subtitleNode)
}
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
self.currentTheme = interfaceState.theme
self.currentStrings = interfaceState.strings
@ -630,7 +630,7 @@ private final class ChatEmptyNodeGroupChatContent: ASDisplayNode, ChatEmptyNodeC
self.addSubnode(self.subtitleNode)
}
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
self.currentTheme = interfaceState.theme
self.currentStrings = interfaceState.strings
@ -759,7 +759,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC
self.shareBusinessLink?(businessLink.url)
}
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
var maxWidth: CGFloat = size.width
var centerText = false
@ -1125,7 +1125,7 @@ public final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeCo
self.addSubnode(self.textNode)
}
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
self.currentTheme = interfaceState.theme
@ -1266,7 +1266,7 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
}
}
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
let maxWidth = min(270.0, size.width)
@ -1778,7 +1778,7 @@ public final class ChatEmptyNode: ASDisplayNode {
}
}
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.wallpaperBackgroundNode = backgroundNode
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
@ -1912,14 +1912,14 @@ public final class ChatEmptyNode: ASDisplayNode {
var contentSize = CGSize()
if let contentNode = self.content?.1 {
contentSize = contentNode.updateLayout(interfaceState: interfaceState, subject: subject, size: displayRect.size, transition: contentTransition)
contentSize = contentNode.updateLayout(interfaceState: interfaceState, subject: subject, size: displayRect.size, leftInset: leftInset, rightInset: rightInset, transition: contentTransition)
if updateGreetingSticker {
self.context.prefetchManager?.prepareNextGreetingSticker()
}
}
let contentFrame = CGRect(origin: CGPoint(x: displayRect.minX + floor((displayRect.width - contentSize.width) / 2.0), y: displayRect.minY + floor((displayRect.height - contentSize.height) / 2.0)), size: contentSize)
let contentFrame = CGRect(origin: CGPoint(x: displayRect.minX + leftInset + floor((displayRect.width - leftInset - rightInset - contentSize.width) / 2.0), y: displayRect.minY + floor((displayRect.height - contentSize.height) / 2.0)), size: contentSize)
if let contentNode = self.content?.1 {
contentTransition.updateFrame(node: contentNode, frame: contentFrame)
}
@ -1980,7 +1980,7 @@ public final class ChatEmptyNode: ASDisplayNode {
wallpaperBackgroundNode: backgroundNode,
constrainedSize: CGSize(width: size.width - insets.left - insets.right, height: 200.0)
)
let attachedDescriptionFrame = CGRect(origin: CGPoint(x: floor((size.width - attachedDescriptionSize.width) * 0.5), y: contentFrame.maxY + 4.0), size: attachedDescriptionSize)
let attachedDescriptionFrame = CGRect(origin: CGPoint(x: leftInset + floor((size.width - leftInset - rightInset - attachedDescriptionSize.width) * 0.5), y: contentFrame.maxY + 4.0), size: attachedDescriptionSize)
transition.updateFrame(node: attachedDescriptionNode, frame: attachedDescriptionFrame)
if let (rect, containerSize) = self.absolutePosition {

View File

@ -126,7 +126,7 @@ public final class ChatLoadingPlaceholderMessageContainer {
}
if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode {
let avatarFrame = CGRect(origin: CGPoint(x: 3.0, y: rect.maxY + 1.0 - avatarSize.height), size: avatarSize)
let avatarFrame = CGRect(origin: CGPoint(x: rect.minX + 3.0, y: rect.maxY + 1.0 - avatarSize.height), size: avatarSize)
transition.updateFrame(node: avatarNode, frame: avatarFrame)
transition.updateFrame(node: avatarBorderNode, frame: avatarFrame)
@ -134,7 +134,7 @@ public final class ChatLoadingPlaceholderMessageContainer {
avatarOffset += avatarSize.width - 1.0
}
let bubbleFrame = CGRect(origin: CGPoint(x: 3.0 + avatarOffset, y: rect.origin.y), size: CGSize(width: rect.width, height: rect.height))
let bubbleFrame = CGRect(origin: CGPoint(x: rect.minX + 3.0 + avatarOffset, y: rect.origin.y), size: CGSize(width: rect.width, height: rect.height))
transition.updateFrame(node: self.bubbleNode, frame: bubbleFrame)
transition.updateFrame(node: self.bubbleBorderNode, frame: bubbleFrame)
}
@ -484,7 +484,7 @@ public final class ChatLoadingPlaceholderNode: ASDisplayNode {
for messageContainer in self.messageContainers {
let messageSize = dimensions[index % 14]
messageContainer.update(size: bounds.size, hasAvatar: self.chatType != .channel && self.chatType != .user, rect: CGRect(origin: CGPoint(x: 0.0, y: bounds.size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition)
messageContainer.update(size: bounds.size, hasAvatar: self.chatType != .channel && self.chatType != .user, rect: CGRect(origin: CGPoint(x: insets.left, y: bounds.size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition)
offset += messageSize.height
index += 1
}

View File

@ -590,6 +590,8 @@ public final class ChatSideTopicsPanel: Component {
public final class View: UIView {
private let scrollView: ScrollView
private let scrollContainerView: UIView
private let scrollViewMask: UIImageView
private let background = ComponentView<Empty>()
private let separatorLayer: SimpleLayer
@ -612,13 +614,20 @@ public final class ChatSideTopicsPanel: Component {
self.selectedLineView = UIImageView()
self.scrollView = ScrollView(frame: CGRect())
self.scrollContainerView = UIView()
self.scrollViewMask = UIImageView(image: generateGradientImage(size: CGSize(width: 8.0, height: 8.0), colors: [
UIColor(white: 1.0, alpha: 0.0),
UIColor(white: 1.0, alpha: 1.0)
], locations: [0.0, 1.0], direction: .vertical)?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 8))
self.scrollContainerView.mask = self.scrollViewMask
self.separatorLayer = SimpleLayer()
super.init(frame: frame)
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
self.scrollView.clipsToBounds = false
self.scrollView.clipsToBounds = true
self.scrollView.contentInsetAdjustmentBehavior = .never
if #available(iOS 13.0, *) {
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
@ -629,7 +638,8 @@ public final class ChatSideTopicsPanel: Component {
self.scrollView.alwaysBounceVertical = false
self.scrollView.scrollsToTop = false
self.addSubview(self.scrollView)
self.addSubview(self.scrollContainerView)
self.scrollContainerView.addSubview(self.scrollView)
self.scrollView.addSubview(self.selectedLineView)
}
@ -709,7 +719,7 @@ public final class ChatSideTopicsPanel: Component {
if let backgroundView = self.background.view {
if backgroundView.superview == nil {
self.insertSubview(backgroundView, belowSubview: self.scrollView)
self.insertSubview(backgroundView, at: 0)
}
transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(), size: availableSize))
}
@ -735,20 +745,9 @@ public final class ChatSideTopicsPanel: Component {
let itemSpacing: CGFloat = 24.0
var contentSize = CGSize(width: panelWidth, height: 0.0)
contentSize.height += containerInsets.top
var validIds: [Item.Id] = []
var isFirst = true
var selectedItemFrame: CGRect?
var topContainerInset: CGFloat = containerInsets.top
do {
if isFirst {
isFirst = false
} else {
contentSize.height += itemSpacing
}
var itemTransition = transition
var animateIn = false
let itemView: TabItemView
@ -764,11 +763,11 @@ public final class ChatSideTopicsPanel: Component {
component.togglePanel()
})
self.tabItemView = itemView
self.scrollView.addSubview(itemView)
self.addSubview(itemView)
}
let itemSize = itemView.update(context: component.context, theme: component.theme, width: panelWidth, transition: .immediate)
let itemFrame = CGRect(origin: CGPoint(x: containerInsets.left, y: contentSize.height), size: itemSize)
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: topContainerInset), size: itemSize)
itemTransition.setPosition(layer: itemView.layer, position: itemFrame.center)
itemTransition.setBounds(layer: itemView.layer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
@ -778,10 +777,17 @@ public final class ChatSideTopicsPanel: Component {
transition.containedViewLayoutTransition.animateTransformScale(view: itemView, from: 0.001)
}
contentSize.height += itemSize.height
contentSize.height -= 20.0
topContainerInset += itemSize.height
topContainerInset -= 24.0
}
var contentSize = CGSize(width: panelWidth, height: 0.0)
contentSize.height += 36.0
var validIds: [Item.Id] = []
var isFirst = true
var selectedItemFrame: CGRect?
do {
if isFirst {
isFirst = false
@ -931,7 +937,11 @@ public final class ChatSideTopicsPanel: Component {
contentSize.height += containerInsets.bottom
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height - topContainerInset)
self.scrollContainerView.frame = CGRect(origin: CGPoint(x: 0.0, y: topContainerInset), size: scrollSize)
self.scrollViewMask.frame = CGRect(origin: CGPoint(x: 0.0, y: topContainerInset), size: scrollSize)
if self.scrollView.bounds.size != scrollSize {
self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize)
}

View File

@ -145,6 +145,8 @@ final class ChatIntroItemComponent: Component {
backgroundNode: backgroundNode,
size: size,
insets: UIEdgeInsets(),
leftInset: 0.0,
rightInset: 0.0,
transition: .immediate
)

View File

@ -195,7 +195,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
private var emptyNode: ChatEmptyNode?
private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType?
private var didDisplayEmptyGreeting = false
private var validEmptyNodeLayout: (CGSize, UIEdgeInsets)?
private var validEmptyNodeLayout: (CGSize, UIEdgeInsets, CGFloat, CGFloat)?
var restrictedNode: ChatRecentActionsEmptyNode?
private(set) var validLayout: (ContainerViewLayout, CGFloat)?
@ -1019,7 +1019,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.contentContainerNode.contentNode.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer)
}
if let (size, insets) = self.validEmptyNodeLayout {
if let (size, insets, leftInset, rightInset) = self.validEmptyNodeLayout {
let mappedType: ChatEmptyNode.Subject.EmptyType
switch emptyType {
case .generic:
@ -1033,7 +1033,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
case .botInfo:
mappedType = .botInfo
}
emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(mappedType), loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate)
emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(mappedType), loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
emptyNode.frame = CGRect(origin: CGPoint(), size: size)
}
if animated {
@ -2199,28 +2199,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
var emptyNodeInsets = insets
emptyNodeInsets.bottom += inputPanelsHeight
self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets)
if let emptyNode = self.emptyNode, let emptyType = self.emptyType {
let mappedType: ChatEmptyNode.Subject.EmptyType
switch emptyType {
case .generic:
mappedType = .generic
case .joined:
mappedType = .joined
case .clearedHistory:
mappedType = .clearedHistory
case .topic:
mappedType = .topic
case .botInfo:
mappedType = .botInfo
}
emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(mappedType), loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition)
transition.updateFrame(node: emptyNode, frame: contentBounds)
emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
}
var contentBottomInset: CGFloat = inputPanelsHeight + 4.0
if let scrollContainerNode = self.scrollContainerNode {
@ -2239,10 +2217,17 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
let visibleAreaInset = UIEdgeInsets(top: containerInsets.top, left: 0.0, bottom: containerInsets.bottom + inputPanelsHeight, right: 0.0)
self.visibleAreaInset = visibleAreaInset
self.loadingNode.updateLayout(size: contentBounds.size, insets: visibleAreaInset, transition: transition)
var loadingNodeInsets = visibleAreaInset
loadingNodeInsets.left = layout.safeInsets.left
loadingNodeInsets.right = layout.safeInsets.right
if let leftPanelSize {
loadingNodeInsets.left += leftPanelSize.width
}
self.loadingNode.updateLayout(size: contentBounds.size, insets: loadingNodeInsets, transition: transition)
if let loadingPlaceholderNode = self.loadingPlaceholderNode {
loadingPlaceholderNode.updateLayout(size: contentBounds.size, insets: visibleAreaInset, metrics: layout.metrics, transition: transition)
loadingPlaceholderNode.updateLayout(size: contentBounds.size, insets: loadingNodeInsets, metrics: layout.metrics, transition: transition)
loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
}
@ -2293,6 +2278,28 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
listInsets.left += leftPanelSize.width
}
var emptyNodeInsets = insets
emptyNodeInsets.bottom += inputPanelsHeight
self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets, listInsets.left, listInsets.right)
if let emptyNode = self.emptyNode, let emptyType = self.emptyType {
let mappedType: ChatEmptyNode.Subject.EmptyType
switch emptyType {
case .generic:
mappedType = .generic
case .joined:
mappedType = .joined
case .clearedHistory:
mappedType = .clearedHistory
case .topic:
mappedType = .topic
case .botInfo:
mappedType = .botInfo
}
emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(mappedType), loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, leftInset: listInsets.left, rightInset: listInsets.right, transition: transition)
transition.updateFrame(node: emptyNode, frame: contentBounds)
emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
}
var displayTopDimNode = false
let ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil
var expandTopDimNode = false

View File

@ -241,7 +241,7 @@ func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfa
return panel
}
}
} else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForum, chatPresentationInterfaceState.search == nil {
} else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForum, (channel.flags.contains(.displayForumAsTabs) || context.sharedContext.immediateExperimentalUISettings.allForumsHaveTabs), chatPresentationInterfaceState.search == nil {
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId {
if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode {

View File

@ -182,7 +182,7 @@ private final class CustomBadgeComponent: Component {
}
}
final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, ChatControllerCustomNavigationPanelNode, ASScrollViewDelegate {
final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, ChatControllerCustomNavigationPanelNode {
private struct Params: Equatable {
var width: CGFloat
var leftInset: CGFloat
@ -736,6 +736,8 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
private let isMonoforum: Bool
private let scrollView: ScrollView
private let scrollViewContainer: UIView
private let scrollViewMask: UIImageView
private var params: Params?
@ -756,12 +758,18 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
self.selectedLineView = UIImageView()
self.scrollView = ScrollView(frame: CGRect())
self.scrollViewMask = UIImageView(image: generateGradientImage(size: CGSize(width: 8.0, height: 8.0), colors: [
UIColor(white: 1.0, alpha: 0.0),
UIColor(white: 1.0, alpha: 1.0)
], locations: [0.0, 1.0], direction: .horizontal)?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 0))
self.scrollViewContainer = UIView()
super.init()
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
self.scrollView.clipsToBounds = false
self.scrollView.clipsToBounds = true
self.scrollView.contentInsetAdjustmentBehavior = .never
if #available(iOS 13.0, *) {
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
@ -771,9 +779,11 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
self.scrollView.alwaysBounceHorizontal = false
self.scrollView.alwaysBounceVertical = false
self.scrollView.scrollsToTop = false
self.scrollView.delegate = self.wrappedScrollViewDelegate
self.view.addSubview(self.scrollView)
self.scrollViewContainer.addSubview(self.scrollView)
self.scrollViewContainer.mask = self.scrollViewMask
self.view.addSubview(self.scrollViewContainer)
self.scrollView.addSubview(self.selectedLineView)
@ -843,20 +853,9 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
let containerInsets = UIEdgeInsets(top: 0.0, left: params.leftInset + 16.0, bottom: 0.0, right: params.rightInset + 16.0)
let itemSpacing: CGFloat = 24.0
var contentSize = CGSize(width: 0.0, height: panelHeight)
contentSize.width += containerInsets.left + 8.0
var validIds: [Item.Id] = []
var isFirst = true
var selectedItemFrame: CGRect?
var leftContentInset: CGFloat = containerInsets.left + 8.0
do {
if isFirst {
isFirst = false
} else {
contentSize.width += itemSpacing
}
var itemTransition = transition
var animateIn = false
let itemView: TabItemView
@ -872,11 +871,11 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
self.interfaceInteraction?.toggleChatSidebarMode()
})
self.tabItemView = itemView
self.scrollView.addSubview(itemView)
self.view.addSubview(itemView)
}
let itemSize = itemView.update(context: self.context, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate)
let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize)
let itemFrame = CGRect(origin: CGPoint(x: leftContentInset, y: -5.0), size: itemSize)
itemTransition.updatePosition(layer: itemView.layer, position: itemFrame.center)
itemTransition.updateBounds(layer: itemView.layer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
@ -886,9 +885,15 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
transition.animateTransformScale(view: itemView, from: 0.001)
}
contentSize.width += itemSize.width
leftContentInset += itemSize.width + 8.0
}
var contentSize = CGSize(width: itemSpacing - 8.0, height: panelHeight)
var validIds: [Item.Id] = []
var isFirst = true
var selectedItemFrame: CGRect?
do {
if isFirst {
isFirst = false
@ -1034,9 +1039,12 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
contentSize.width += containerInsets.right
let scrollSize = CGSize(width: params.width, height: contentSize.height)
let scrollSize = CGSize(width: params.width - leftContentInset, height: contentSize.height)
self.scrollViewContainer.frame = CGRect(origin: CGPoint(x: leftContentInset, y: 0.0), size: scrollSize)
self.scrollViewMask.frame = CGRect(origin: CGPoint(), size: scrollSize)
if self.scrollView.bounds.size != scrollSize {
self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize)
self.scrollView.frame = CGRect(origin: CGPoint(), size: scrollSize)
}
if self.scrollView.contentSize != contentSize {
self.scrollView.contentSize = contentSize

View File

@ -42,16 +42,20 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
viewForumAsMessages = .single(false)
}
} else if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
viewForumAsMessages = params.context.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peer.id)])
|> take(1)
|> map { combinedView in
guard let cachedDataView = combinedView.views[.cachedPeerData(peerId: peer.id)] as? CachedPeerDataView else {
return false
}
if let cachedData = cachedDataView.cachedPeerData as? CachedChannelData, case let .known(viewForumAsMessages) = cachedData.viewForumAsMessages, viewForumAsMessages {
return true
} else {
return false
if channel.flags.contains(.displayForumAsTabs) {
viewForumAsMessages = .single(true)
} else {
viewForumAsMessages = params.context.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peer.id)])
|> take(1)
|> map { combinedView in
guard let cachedDataView = combinedView.views[.cachedPeerData(peerId: peer.id)] as? CachedPeerDataView else {
return false
}
if let cachedData = cachedDataView.cachedPeerData as? CachedChannelData, case let .known(viewForumAsMessages) = cachedData.viewForumAsMessages, viewForumAsMessages {
return true
} else {
return false
}
}
}
} else if case let .peer(peer) = params.chatLocation, peer.id == params.context.account.peerId {

View File

@ -64,7 +64,7 @@ private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceh
self.wallpaperBackgroundNode.updateLayout(size: size, displayMode: needsTiling ? .aspectFit : .aspectFill, transition: transition)
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: contentBounds)
self.emptyNode.updateLayout(interfaceState: self.presentationInterfaceState, subject: .detailsPlaceholder, loadingNode: nil, backgroundNode: self.wallpaperBackgroundNode, size: contentBounds.size, insets: .zero, transition: transition)
self.emptyNode.updateLayout(interfaceState: self.presentationInterfaceState, subject: .detailsPlaceholder, loadingNode: nil, backgroundNode: self.wallpaperBackgroundNode, size: contentBounds.size, insets: .zero, leftInset: 0.0, rightInset: 0.0, transition: transition)
transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: .zero, size: size))
self.emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
}

View File

@ -66,6 +66,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var fakeAds: Bool
public var conferenceDebug: Bool
public var checkSerializedData: Bool
public var allForumsHaveTabs: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -109,7 +110,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
devRequests: false,
fakeAds: false,
conferenceDebug: false,
checkSerializedData: false
checkSerializedData: false,
allForumsHaveTabs: false
)
}
@ -154,7 +156,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
devRequests: Bool,
fakeAds: Bool,
conferenceDebug: Bool,
checkSerializedData: Bool
checkSerializedData: Bool,
allForumsHaveTabs: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -197,6 +200,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.fakeAds = fakeAds
self.conferenceDebug = conferenceDebug
self.checkSerializedData = checkSerializedData
self.allForumsHaveTabs = allForumsHaveTabs
}
public init(from decoder: Decoder) throws {
@ -243,6 +247,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.fakeAds = try container.decodeIfPresent(Bool.self, forKey: "fakeAds") ?? false
self.conferenceDebug = try container.decodeIfPresent(Bool.self, forKey: "conferenceDebug") ?? false
self.checkSerializedData = try container.decodeIfPresent(Bool.self, forKey: "checkSerializedData") ?? false
self.allForumsHaveTabs = try container.decodeIfPresent(Bool.self, forKey: "allForumsHaveTabs") ?? false
}
public func encode(to encoder: Encoder) throws {
@ -289,6 +294,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encodeIfPresent(self.fakeAds, forKey: "fakeAds")
try container.encodeIfPresent(self.conferenceDebug, forKey: "conferenceDebug")
try container.encodeIfPresent(self.checkSerializedData, forKey: "checkSerializedData")
try container.encodeIfPresent(self.allForumsHaveTabs, forKey: "allForumsHaveTabs")
}
}