[WIP] Monoforums

This commit is contained in:
Isaac 2025-05-21 00:23:48 +08:00
parent 88e2d839d7
commit f1d0f694f5
43 changed files with 579 additions and 247 deletions

View File

@ -1116,7 +1116,7 @@ public protocol SharedAccountContext: AnyObject {
func navigateToChatController(_ params: NavigateToChatControllerParams)
func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController)
func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError>
func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack, animated: Bool) -> Signal<Never, NoError>
func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal<ChatController, NoError>
func openStorageUsage(context: AccountContext)
func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController)

View File

@ -949,9 +949,11 @@ public let ChatControllerCount = Atomic<Int32>(value: 0)
public final class PeerInfoNavigationSourceTag {
public let peerId: EnginePeer.Id
public let threadId: Int64?
public init(peerId: EnginePeer.Id) {
public init(peerId: EnginePeer.Id, threadId: Int64?) {
self.peerId = peerId
self.threadId = threadId
}
}
@ -1016,6 +1018,13 @@ public protocol ChatControllerCustomNavigationPanelNode: ASDisplayNode {
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, chatController: ChatController) -> LayoutResult
}
public enum ChatControllerAnimateInnerChatSwitchDirection {
case up
case down
case left
case right
}
public protocol ChatController: ViewController {
var chatLocation: ChatLocation { get }
var canReadHistory: ValuePromise<Bool> { get }

View File

@ -1267,7 +1267,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}, openBoostToUnrestrict: {
}, updateVideoTrimRange: { _, _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {
}, updateDisplayHistoryFilterAsList: { _ in
}, requestLayout: { _ in

View File

@ -175,7 +175,7 @@ public final class BrowserBookmarksScreen: ViewController {
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))

View File

@ -814,7 +814,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return false
}
case let .forum(peerId):
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId, threadId: nil)
self.navigationBar?.allowsCustomTransition = { [weak self] in
guard let strongSelf = self else {
return false
@ -1345,7 +1345,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
navigationAnimationOptions = .removeOnMasterDetails
}
if case let .channel(channel) = actualPeer, channel.isForumOrMonoForum, let threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never).startStandalone()
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never, animated: true).startStandalone()
} else {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), purposefulAction: {
if deactivateOnAction {
@ -1378,7 +1378,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
navigationAnimationOptions = .removeOnMasterDetails
}
if case let .channel(channel) = peer, channel.isForumOrMonoForum, let threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never).startStandalone()
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never, animated: true).startStandalone()
} else {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), purposefulAction: { [weak self] in
self?.deactivateSearch(animated: false)
@ -1462,7 +1462,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId)
|> deliverOnMainQueue).startStandalone(next: { topicId in
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never).startStandalone()
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never, animated: true).startStandalone()
}, error: { _ in
controller?.isInProgress = false
})
@ -3762,6 +3762,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})))
}
var needsSeparatorForCreateTopic = true
if let sourceController = sourceController as? ChatController {
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strings.Conversation_Search, icon: { theme in
@ -3771,8 +3772,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
sourceController?.beginMessageSearch("")
})))
} else if channel.hasPermission(.createTopics) {
items.append(.separator)
needsSeparatorForCreateTopic = false
}
if channel.hasPermission(.createTopics) {
if needsSeparatorForCreateTopic {
items.append(.separator)
}
items.append(.action(ContextMenuActionItem(text: strings.Chat_CreateTopic, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
@ -3788,7 +3793,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId)
|> deliverOnMainQueue).startStandalone(next: { topicId in
if let navigationController = (sourceController.navigationController as? NavigationController) {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never).startStandalone()
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never, animated: true).startStandalone()
}
}, error: { _ in
controller?.isInProgress = false

View File

@ -1023,7 +1023,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let titleTopicIconContent: EmojiStatusComponent.Content?
if threadId == 1 {
titleTopicIconContent = .image(image: PresentationResourcesChatList.generalTopicSmallIcon(theme))
titleTopicIconContent = .image(image: PresentationResourcesChatList.generalTopicSmallIcon(theme), tintColor: nil)
} else if let fileId = iconId, fileId != 0 {
titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0))
} else if let iconColor {
@ -3850,7 +3850,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let avatarIconContent: EmojiStatusComponent.Content
if threadInfo.id == 1 {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(item.presentationData.theme))
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(item.presentationData.theme), tintColor: nil)
} else if let fileId = threadInfo.info.icon, fileId != 0 {
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0))
} else {

View File

@ -172,7 +172,7 @@ public final class ChatPanelInterfaceInteraction {
public let openStarsPurchase: (Int64?) -> Void
public let openMessagePayment: () -> Void
public let updateHistoryFilter: ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void
public let updateChatLocationThread: (Int64?) -> Void
public let updateChatLocationThread: (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void
public let toggleChatSidebarMode: () -> Void
public let updateDisplayHistoryFilterAsList: (Bool) -> Void
public let openBoostToUnrestrict: () -> Void
@ -294,7 +294,7 @@ public final class ChatPanelInterfaceInteraction {
openBoostToUnrestrict: @escaping () -> Void,
updateVideoTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
updateChatLocationThread: @escaping (Int64?) -> Void,
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
toggleChatSidebarMode: @escaping () -> Void,
updateDisplayHistoryFilterAsList: @escaping (Bool) -> Void,
requestLayout: @escaping (ContainedViewLayoutTransition) -> Void,
@ -540,7 +540,7 @@ public final class ChatPanelInterfaceInteraction {
}, openBoostToUnrestrict: {
}, updateVideoTrimRange: { _, _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {
}, updateDisplayHistoryFilterAsList: { _ in
}, requestLayout: { _ in

View File

@ -458,6 +458,56 @@ open class BlurredBackgroundView: UIView {
public protocol NavigationBarHeaderView: UIView {
}
private final class NavigationBackgroundCutoutView: UIView {
private let topLayer: SimpleLayer
private let rightLayer: SimpleLayer
weak var targetNode: ASDisplayNode?
override init(frame: CGRect) {
self.topLayer = SimpleLayer()
self.topLayer.backgroundColor = UIColor.white.cgColor
self.rightLayer = SimpleLayer()
self.rightLayer.backgroundColor = UIColor.white.cgColor
super.init(frame: frame)
self.layer.addSublayer(self.topLayer)
self.layer.addSublayer(self.rightLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(size: CGSize, cutout: CGSize?, transition: ContainedViewLayoutTransition) {
let cutout = cutout ?? CGSize()
let topFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: cutout.height))
let rightFrame = CGRect(origin: CGPoint(x: cutout.width, y: 0.0), size: CGSize(width: max(0.0, size.width - cutout.width), height: size.height))
if self.topLayer.frame != topFrame || self.rightLayer.frame != rightFrame {
if cutout.width != 0.0 && cutout.height != 0.0 {
self.targetNode?.view.mask = self
//self.targetNode?.view.addSubview(self)
}
transition.updateFrame(layer: self.topLayer, frame: topFrame)
transition.updateFrame(layer: self.rightLayer, frame: rightFrame, completion: { [weak self] completed in
guard let self, completed else {
return
}
if !(cutout.width != 0.0 && cutout.height != 0.0) {
self.targetNode?.view.mask = nil
//self.removeFromSuperview()
}
})
}
}
}
open class NavigationBar: ASDisplayNode {
public static var defaultSecondaryContentHeight: CGFloat {
return 38.0
@ -489,7 +539,7 @@ open class NavigationBar: ASDisplayNode {
var presentationData: NavigationBarPresentationData
private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool)?
private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool)?
private var requestedLayout: Bool = false
var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in }
@ -1013,6 +1063,8 @@ open class NavigationBar: ASDisplayNode {
public let leftButtonNode: NavigationButtonNode
public let rightButtonNode: NavigationButtonNode
public let additionalContentNode: SparseNode
private let navigationBackgroundCutoutView: NavigationBackgroundCutoutView
public func reattachAdditionalContentNode() {
if self.additionalContentNode.supernode !== self {
@ -1139,6 +1191,9 @@ open class NavigationBar: ASDisplayNode {
self.secondaryContentHeight = NavigationBar.defaultSecondaryContentHeight
self.navigationBackgroundCutoutView = NavigationBackgroundCutoutView(frame: CGRect())
self.navigationBackgroundCutoutView.targetNode = self.backgroundNode
super.init()
self.addSubnode(self.backgroundNode)
@ -1246,22 +1301,25 @@ open class NavigationBar: ASDisplayNode {
if let validLayout = self.validLayout, self.requestedLayout {
self.requestedLayout = false
self.updateLayout(size: validLayout.size, defaultHeight: validLayout.defaultHeight, additionalTopHeight: validLayout.additionalTopHeight, additionalContentHeight: validLayout.additionalContentHeight, additionalBackgroundHeight: validLayout.additionalBackgroundHeight, leftInset: validLayout.leftInset, rightInset: validLayout.rightInset, appearsHidden: validLayout.appearsHidden, isLandscape: validLayout.isLandscape, transition: .immediate)
self.updateLayout(size: validLayout.size, defaultHeight: validLayout.defaultHeight, additionalTopHeight: validLayout.additionalTopHeight, additionalContentHeight: validLayout.additionalContentHeight, additionalBackgroundHeight: validLayout.additionalBackgroundHeight, additionalCutout: validLayout.additionalCutout, leftInset: validLayout.leftInset, rightInset: validLayout.rightInset, appearsHidden: validLayout.appearsHidden, isLandscape: validLayout.isLandscape, transition: .immediate)
}
}
func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) {
if self.layoutSuspended {
return
}
self.validLayout = (size, defaultHeight, additionalTopHeight, additionalContentHeight, additionalBackgroundHeight, leftInset, rightInset, appearsHidden, isLandscape)
self.validLayout = (size, defaultHeight, additionalTopHeight, additionalContentHeight, additionalBackgroundHeight, additionalCutout, leftInset, rightInset, appearsHidden, isLandscape)
let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight))
if self.backgroundNode.frame != backgroundFrame {
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
self.backgroundNode.update(size: backgroundFrame.size, transition: transition)
transition.updateFrame(view: self.navigationBackgroundCutoutView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
}
self.navigationBackgroundCutoutView.update(size: backgroundFrame.size, cutout: additionalCutout, transition: transition)
let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? (self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction) : 0.0
@ -1293,7 +1351,7 @@ open class NavigationBar: ASDisplayNode {
contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
}
transition.updateFrame(node: self.stripeNode, frame: CGRect(x: 0.0, y: size.height + additionalBackgroundHeight, width: size.width, height: UIScreenPixel))
transition.updateFrame(node: self.stripeNode, frame: CGRect(x: (additionalCutout?.width ?? 0.0), y: size.height + additionalBackgroundHeight, width: size.width - (additionalCutout?.width ?? 0.0), height: UIScreenPixel))
let nominalHeight: CGFloat = defaultHeight
let contentVerticalOrigin = additionalTopHeight

View File

@ -451,7 +451,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
navigationBarFrame.size.height += navigationBar.secondaryContentHeight
}
navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition)
navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, additionalCutout: additionalCutout, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition)
if !transition.isAnimated {
navigationBar.layer.removeAnimation(forKey: "bounds")
navigationBar.layer.removeAnimation(forKey: "position")

View File

@ -970,7 +970,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
self.suspendNavigationBarLayout = false
if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout {
self.suspendedNavigationBarLayout = suspendedNavigationBarLayout
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalNavigationBarCutout: self.additionalNavigationBarCutout, transition: transition)
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: self.additionalNavigationBarCutout, transition: transition)
}
self.accessoryPanelContainerHeight = additionalHeight

View File

@ -816,7 +816,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
//let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let currentShareButtonNode = self.shareButtonNode
let currentForwardInfo = self.appliedForwardInfo
@ -1107,7 +1107,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
let threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)? = nil
var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)?
var replyMarkup: ReplyMarkupMessageAttribute?
@ -1173,7 +1173,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
hasReply = false
}
threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
/*threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
@ -1185,7 +1185,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
))
))*/
}
if hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) {

View File

@ -2413,8 +2413,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
var boostNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil })
var viaWidth: CGFloat = 0.0
var threadInfoOriginY: CGFloat = 0.0
var threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil })
let threadInfoOriginY: CGFloat = 0.0
let threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil })
var replyInfoOriginY: CGFloat = 0.0
var replyInfoSizeApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode?) = (CGSize(), { _, _, _ in nil })
@ -2625,8 +2625,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
}
var hasThreadInfo = false
if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1 || item.associatedData.isRecentActions), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isForum, item.message.associatedThreadInfo != nil {
let hasThreadInfo = !"".isEmpty
/*if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1 || item.associatedData.isRecentActions), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isForum, item.message.associatedThreadInfo != nil {
hasThreadInfo = true
} else if case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
@ -2634,7 +2634,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} else {
hasThreadInfo = true
}
}
}*/
var hasReply = replyMessage != nil || replyForward != nil || replyStory != nil
if !isInstantVideo, hasThreadInfo {
@ -2642,7 +2642,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
hasReply = false
}
if !mergedTop.merged {
/*if !mergedTop.merged {
if headerSize.height.isZero {
headerSize.height += 14.0
} else {
@ -2666,7 +2666,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
threadInfoOriginY = headerSize.height
headerSize.width = max(headerSize.width, threadInfoSizeApply.0.width + bubbleWidthInsets)
headerSize.height += threadInfoSizeApply.0.height + 5.0
}
}*/
}
if !isInstantVideo, hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) {
@ -3549,7 +3549,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
guard let strongSelf, let item = strongSelf.item else {
return
}
item.controllerInteraction.updateChatLocationThread(item.content.firstMessage.threadId)
item.controllerInteraction.updateChatLocationThread(item.content.firstMessage.threadId, nil)
}
}
@ -6000,7 +6000,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
}
} else if let channel = item.message.peers[item.message.id.peerId], channel.isMonoForum, case .peer = item.chatLocation {
item.controllerInteraction.updateChatLocationThread(item.message.threadId)
item.controllerInteraction.updateChatLocationThread(item.message.threadId, nil)
} else {
if !self.disablesComments {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {

View File

@ -35,6 +35,8 @@ swift_library(
"//submodules/Components/HierarchyTrackingLayer",
"//submodules/WallpaperBackgroundNode",
"//submodules/AvatarVideoNode",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View File

@ -17,6 +17,8 @@ import ChatControllerInteraction
import AvatarVideoNode
import ChatMessageItem
import AvatarNode
import ComponentFlow
import EmojiStatusComponent
private let timezoneOffset: Int32 = {
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
@ -39,18 +41,23 @@ public final class ChatMessageDateHeader: ListViewItemHeader {
}
}
public final class PeerData {
public let peer: EnginePeer
public final class HeaderData {
public enum Contents {
case peer(EnginePeer)
case thread(id: Int64, info: Message.AssociatedThreadInfo)
}
public init(peer: EnginePeer) {
self.peer = peer
public let contents: Contents
public init(contents: Contents) {
self.contents = contents
}
}
private let timestamp: Int32
private let roundedTimestamp: Int32
private let scheduled: Bool
public let displayPeer: PeerData?
public let displayHeader: HeaderData?
public let id: ListViewItemNode.HeaderId
public let stackingId: ListViewItemNode.HeaderId?
@ -60,16 +67,16 @@ public final class ChatMessageDateHeader: ListViewItemHeader {
public let context: AccountContext
public let action: ((Int32, Bool) -> Void)?
public init(timestamp: Int32, separableThreadId: Int64?, scheduled: Bool, displayPeer: PeerData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) {
public init(timestamp: Int32, separableThreadId: Int64?, scheduled: Bool, displayHeader: HeaderData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) {
self.timestamp = timestamp
self.scheduled = scheduled
self.displayPeer = displayPeer
self.displayHeader = displayHeader
self.presentationData = presentationData
self.controllerInteraction = controllerInteraction
self.context = context
self.action = action
self.roundedTimestamp = dateHeaderTimestampId(timestamp: timestamp)
if let _ = self.displayPeer {
if let _ = self.displayHeader {
self.idValue = ChatMessageDateHeader.Id(roundedTimestamp: 0, separableThreadId: separableThreadId)
self.id = ListViewItemNode.HeaderId(space: 3, id: self.idValue)
self.stackingId = ListViewItemNode.HeaderId(space: 2, id: ChatMessageDateHeader.Id(roundedTimestamp: Int64(self.roundedTimestamp), separableThreadId: nil))
@ -98,7 +105,7 @@ public final class ChatMessageDateHeader: ListViewItemHeader {
}
public func node(synchronousLoad: Bool) -> ListViewItemHeaderNode {
return ChatMessageDateHeaderNodeImpl(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, displayPeer: self.displayPeer, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, context: self.context, action: self.action)
return ChatMessageDateHeaderNodeImpl(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, displayHeader: self.displayHeader, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, context: self.context, action: self.action)
}
public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) {
@ -362,9 +369,11 @@ private final class ChatMessagePeerContentNode: ASDisplayNode {
private let context: AccountContext
private var presentationData: ChatPresentationData
private let controllerInteraction: ChatControllerInteraction?
private let peer: EnginePeer
private let displayHeader: ChatMessageDateHeader.HeaderData
private var avatarNode: AvatarNode?
private var icon: ComponentView<Empty>?
private let avatarNode: AvatarNode
public let labelNode: TextNode
public let backgroundNode: NavigationBackgroundNode
public let stickBackgroundNode: ASImageNode
@ -372,13 +381,11 @@ private final class ChatMessagePeerContentNode: ASDisplayNode {
private var backgroundContent: WallpaperBubbleBackgroundNode?
private var text: String
init(context: AccountContext, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, peer: EnginePeer) {
init(context: AccountContext, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, displayHeader: ChatMessageDateHeader.HeaderData) {
self.context = context
self.presentationData = presentationData
self.controllerInteraction = controllerInteraction
self.peer = peer
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0))
self.displayHeader = displayHeader
self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false
@ -397,7 +404,13 @@ private final class ChatMessagePeerContentNode: ASDisplayNode {
self.stickBackgroundNode.displayWithoutProcessing = true
self.stickBackgroundNode.displaysAsynchronously = false
let text = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let text: String
switch displayHeader.contents {
case let .peer(peer):
text = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
case let .thread(_, info):
text = info.title
}
self.text = text
super.init()
@ -415,7 +428,50 @@ private final class ChatMessagePeerContentNode: ASDisplayNode {
} else {
self.addSubnode(self.backgroundNode)
}
self.addSubnode(self.avatarNode)
switch displayHeader.contents {
case let .peer(peer):
let avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0))
self.avatarNode = avatarNode
self.addSubnode(avatarNode)
if peer.smallProfileImage != nil {
avatarNode.setPeerV2(context: context, theme: presentationData.theme.theme, peer: peer, displayDimensions: CGSize(width: 16.0, height: 16.0))
} else {
avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: peer, displayDimensions: CGSize(width: 16.0, height: 16.0))
}
case let .thread(id, info):
let avatarIconContent: EmojiStatusComponent.Content
if id != 1 {
if let fileId = info.icon, fileId != 0 {
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 16.0, height: 16.0), placeholderColor: presentationData.theme.theme.list.mediaPlaceholderColor, themeColor: presentationData.theme.theme.list.itemAccentColor, loopMode: .count(0))
} else {
avatarIconContent = .topic(title: String(info.title.prefix(1)), color: info.iconColor, size: CGSize(width: 16.0, height: 16.0))
}
} else {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(presentationData.theme.theme)?.withRenderingMode(.alwaysTemplate), tintColor: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.serviceMessage.dateTextColor, wallpaper: presentationData.theme.wallpaper))
}
let avatarIconComponent = EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: avatarIconContent,
isVisibleForAnimations: false,
action: nil
)
let icon = ComponentView<Empty>()
self.icon = icon
let _ = icon.update(
transition: .immediate,
component: AnyComponent(avatarIconComponent),
environment: {},
containerSize: CGSize(width: 16.0, height: 16.0)
)
if let iconView = icon.view {
self.view.addSubview(iconView)
}
}
self.addSubnode(self.labelNode)
let titleFont = Font.medium(min(18.0, floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)))
@ -461,7 +517,13 @@ private final class ChatMessagePeerContentNode: ASDisplayNode {
let chatDateInset: CGFloat = 6.0
let avatarDiameter: CGFloat = 16.0
let avatarInset: CGFloat = 2.0
let avatarInset: CGFloat
switch self.displayHeader.contents {
case .peer:
avatarInset = 2.0
case .thread:
avatarInset = 4.0
}
let avatarSpacing: CGFloat = 4.0
let labelSize = self.labelNode.bounds.size
@ -469,14 +531,14 @@ private final class ChatMessagePeerContentNode: ASDisplayNode {
let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((size.width - leftInset - rightInset - backgroundSize.width) / 2.0), y: (size.height - chatDateSize) / 2.0), size: backgroundSize)
let avatarFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + avatarInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
self.avatarNode.updateSize(size: avatarFrame.size)
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + avatarInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
if self.peer.smallProfileImage != nil {
self.avatarNode.setPeerV2(context: self.context, theme: self.presentationData.theme.theme, peer: self.peer, displayDimensions: avatarFrame.size)
} else {
self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme.theme, peer: self.peer, displayDimensions: avatarFrame.size)
if let avatarNode = self.avatarNode {
transition.updateFrame(node: avatarNode, frame: iconFrame)
avatarNode.updateSize(size: iconFrame.size)
}
if let iconView = self.icon?.view {
transition.updateFrame(view: iconView, frame: iconFrame)
}
transition.updateFrame(node: self.stickBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
@ -517,7 +579,7 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe
private let context: AccountContext
private let localTimestamp: Int32
private let scheduled: Bool
private let displayPeer: ChatMessageDateHeader.PeerData?
private let displayHeader: ChatMessageDateHeader.HeaderData?
private var presentationData: ChatPresentationData
private let controllerInteraction: ChatControllerInteraction?
@ -528,14 +590,14 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe
private var params: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)?
private var absolutePosition: (CGRect, CGSize)?
public init(localTimestamp: Int32, scheduled: Bool, displayPeer: ChatMessageDateHeader.PeerData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) {
public init(localTimestamp: Int32, scheduled: Bool, displayHeader: ChatMessageDateHeader.HeaderData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) {
self.context = context
self.presentationData = presentationData
self.controllerInteraction = controllerInteraction
self.localTimestamp = localTimestamp
self.scheduled = scheduled
self.displayPeer = displayPeer
self.displayHeader = displayHeader
self.action = action
let isRotated = controllerInteraction?.chatIsRotated ?? true
@ -546,13 +608,13 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
}
if let displayPeer {
if let displayHeader {
if self.peerContentNode == nil {
let sectionSeparator = ChatMessageDateSectionSeparatorNode(controllerInteraction: controllerInteraction, presentationData: presentationData)
self.sectionSeparator = sectionSeparator
self.addSubnode(sectionSeparator)
let peerContentNode = ChatMessagePeerContentNode(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, peer: displayPeer.peer)
let peerContentNode = ChatMessagePeerContentNode(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, displayHeader: displayHeader)
self.peerContentNode = peerContentNode
self.addSubnode(peerContentNode)
}

View File

@ -92,10 +92,10 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs
isPaid = true
}
var sameThread = true
if let lhsPeer = lhs.peers[lhs.id.peerId], let rhsPeer = rhs.peers[rhs.id.peerId], arePeersEqual(lhsPeer, rhsPeer), let channel = lhsPeer as? TelegramChannel, channel.isForumOrMonoForum, lhs.threadId != rhs.threadId {
let sameThread = true
/*if let lhsPeer = lhs.peers[lhs.id.peerId], let rhsPeer = rhs.peers[rhs.id.peerId], arePeersEqual(lhsPeer, rhsPeer), let channel = lhsPeer as? TelegramChannel, channel.isForumOrMonoForum, lhs.threadId != rhs.threadId {
sameThread = false
}
}*/
var sameAuthor = false
if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id && lhs.effectivelyIncoming(accountPeerId) == rhs.effectivelyIncoming(accountPeerId) {
@ -288,7 +288,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
let messagePeerId: PeerId = chatLocation.peerId ?? content.firstMessage.id.peerId
var headerSeparableThreadId: Int64?
var headerDisplayPeer: ChatMessageDateHeader.PeerData?
var headerDisplayPeer: ChatMessageDateHeader.HeaderData?
do {
let peerId = messagePeerId
@ -320,15 +320,22 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
}
displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil
if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isMonoForum {
if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isForumOrMonoForum {
if case .replyThread = chatLocation {
displayAuthorInfo = false
} else {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {
headerSeparableThreadId = content.firstMessage.threadId
if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] {
headerDisplayPeer = ChatMessageDateHeader.PeerData(peer: EnginePeer(peer))
if channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {
headerSeparableThreadId = content.firstMessage.threadId
if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] {
headerDisplayPeer = ChatMessageDateHeader.HeaderData(contents: .peer(EnginePeer(peer)))
}
}
} else if let threadId = content.firstMessage.threadId {
if let threadInfo = content.firstMessage.associatedThreadInfo {
headerSeparableThreadId = content.firstMessage.threadId
headerDisplayPeer = ChatMessageDateHeader.HeaderData(contents: .thread(id: threadId, info: threadInfo))
}
}
}
@ -343,7 +350,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
isScheduledMessages = true
}
self.dateHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: nil, scheduled: isScheduledMessages, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { timestamp, alreadyThere in
self.dateHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: nil, scheduled: isScheduledMessages, displayHeader: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { timestamp, alreadyThere in
var calendar = NSCalendar.current
calendar.timeZone = TimeZone(abbreviation: "UTC")!
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
@ -355,8 +362,8 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
})
if let headerSeparableThreadId, let headerDisplayPeer {
self.topicHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: headerSeparableThreadId, scheduled: false, displayPeer: headerDisplayPeer, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { _, _ in
controllerInteraction.updateChatLocationThread(headerSeparableThreadId)
self.topicHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: headerSeparableThreadId, scheduled: false, displayHeader: headerDisplayPeer, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { _, _ in
controllerInteraction.updateChatLocationThread(headerSeparableThreadId, nil)
})
} else {
self.topicHeader = nil

View File

@ -25,7 +25,7 @@ public class ChatReplyCountItem: ListViewItem {
self.isComments = isComments
self.count = count
self.presentationData = presentationData
self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context)
self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayHeader: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context)
self.controllerInteraction = controllerInteraction
}

View File

@ -22,7 +22,7 @@ public class ChatUnreadItem: ListViewItem {
self.index = index
self.presentationData = presentationData
self.controllerInteraction = controllerInteraction
self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context)
self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayHeader: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context)
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {

View File

@ -430,7 +430,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
//let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let currentShareButtonNode = self.shareButtonNode
let currentForwardInfo = self.appliedForwardInfo
@ -669,7 +669,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
let threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)? = nil
var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)?
var replyMarkup: ReplyMarkupMessageAttribute?
@ -739,7 +739,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
hasReply = false
}
threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
/*threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
@ -751,7 +751,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
))
))*/
}
if hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) {

View File

@ -514,7 +514,7 @@ public class ChatMessageThreadInfoNode: ASDisplayNode {
var containerSize: CGSize = CGSize(width: 22.0, height: 22.0)
var iconX: CGFloat = 0.0
if arguments.threadId == 1 {
titleTopicIconContent = .image(image: generalThreadIcon)
titleTopicIconContent = .image(image: generalThreadIcon, tintColor: nil)
containerSize = CGSize(width: 18.0, height: 18.0)
iconX = 3.0
} else if let fileId = topicIconId, fileId != 0 {

View File

@ -526,7 +526,7 @@ final class AvatarComponent: Component {
let avatarIconContent: EmojiStatusComponent.Content
if threadData.id == 1 {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme))
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme), tintColor: nil)
} else if let fileId = threadData.data.info.icon, fileId != 0 {
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0))
} else {

View File

@ -171,7 +171,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
}, openBoostToUnrestrict: {
}, updateVideoTrimRange: { _, _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {
}, updateDisplayHistoryFilterAsList: { _ in
}, requestLayout: { _ in

View File

@ -313,7 +313,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { [weak self] peerId, threadId, _ in
if let context = self?.context, let navigationController = self?.getNavigationController() {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone()
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone()
}
}, tapMessage: nil, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false
}, requestMessageActionCallback: { [weak self] messageId, _, _, _ in
@ -646,7 +646,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode))
self.controllerInteraction = controllerInteraction
@ -1208,7 +1208,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}
case let .replyThread(messageId):
if let navigationController = strongSelf.getNavigationController() {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone()
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone()
}
case let .stickerPack(name, type):
let _ = type

View File

@ -501,7 +501,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode))

View File

@ -40,7 +40,7 @@ public final class ChatSideTopicsPanel: Component {
let isMonoforum: Bool
let topicId: Int64?
let togglePanel: () -> Void
let updateTopicId: (Int64?) -> Void
let updateTopicId: (Int64?, Bool) -> Void
public init(
context: AccountContext,
@ -50,7 +50,7 @@ public final class ChatSideTopicsPanel: Component {
isMonoforum: Bool,
topicId: Int64?,
togglePanel: @escaping () -> Void,
updateTopicId: @escaping (Int64?) -> Void
updateTopicId: @escaping (Int64?, Bool) -> Void
) {
self.context = context
self.theme = theme
@ -196,7 +196,7 @@ public final class ChatSideTopicsPanel: Component {
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
}
} else {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme))
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme), tintColor: theme.rootController.navigationBar.secondaryTextColor)
}
}
@ -801,7 +801,8 @@ public final class ChatSideTopicsPanel: Component {
guard let self, let component = self.component else {
return
}
component.updateTopicId(nil)
component.updateTopicId(nil, false)
})
self.allItemView = itemView
self.scrollView.addSubview(itemView)
@ -851,12 +852,20 @@ public final class ChatSideTopicsPanel: Component {
guard let self, let component = self.component else {
return
}
if case let .forum(topicId) = item.item.id {
component.updateTopicId(topicId)
let topicId: Int64
if case let .forum(topicIdValue) = chatListItem.id {
topicId = topicIdValue
} else {
let topicId = chatListItem.renderedPeer.peerId.toInt64()
component.updateTopicId(topicId)
topicId = chatListItem.renderedPeer.peerId.toInt64()
}
var direction = true
if let lhsIndex = self.topicIndex(threadId: component.topicId), let rhsIndex = self.topicIndex(threadId: topicId) {
direction = lhsIndex < rhsIndex
}
component.updateTopicId(topicId, direction)
}, contextGesture: { gesture, sourceNode in
})
self.itemViews[itemId] = itemView
@ -886,6 +895,9 @@ public final class ChatSideTopicsPanel: Component {
contentSize.height += itemSize.height
}
contentSize.height += 12.0
var removedIds: [Item.Id] = []
for (id, itemView) in self.itemViews {
if !validIds.contains(id) {

View File

@ -280,7 +280,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public let forceUpdateWarpContents: () -> Void
public let playShakeAnimation: () -> Void
public let displayQuickShare: (MessageId, ASDisplayNode, ContextGesture) -> Void
public let updateChatLocationThread: (Int64?) -> Void
public let updateChatLocationThread: (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void
public var canPlayMedia: Bool = false
public var hiddenMedia: [MessageId: [Media]] = [:]
@ -443,7 +443,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
forceUpdateWarpContents: @escaping () -> Void,
playShakeAnimation: @escaping () -> Void,
displayQuickShare: @escaping (MessageId, ASDisplayNode, ContextGesture) -> Void,
updateChatLocationThread: @escaping (Int64?) -> Void,
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
pollActionState: ChatInterfacePollActionState,
stickerSettings: ChatInterfaceStickerSettings,

View File

@ -155,6 +155,13 @@ private enum ChatTitleCredibilityIcon: Equatable {
}
public final class ChatTitleView: UIView, NavigationBarTitleView {
public enum AnimateFromSnapshotDirection {
case up
case down
case left
case right
}
private let context: AccountContext
private var theme: PresentationTheme
@ -1111,25 +1118,40 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
}
}
public func prepareSnapshotState() -> SnapshotState {
let snapshotView = self.snapshotView(afterScreenUpdates: false)!
public func prepareSnapshotState() -> SnapshotState? {
guard let snapshotView = self.snapshotView(afterScreenUpdates: false) else {
return nil
}
return SnapshotState(
snapshotView: snapshotView
)
}
public func animateFromSnapshot(_ snapshotState: SnapshotState) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.layer.animatePosition(from: CGPoint(x: 0.0, y: 20.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true)
public func animateFromSnapshot(_ snapshotState: SnapshotState, direction: AnimateFromSnapshotDirection = .up) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
var offset = CGPoint()
switch direction {
case .up:
offset.y = -20.0
case .down:
offset.y = 20.0
case .left:
offset.x = -20.0
case .right:
offset.x = 20.0
}
self.layer.animatePosition(from: offset, to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true)
snapshotState.snapshotView.frame = self.frame
self.superview?.insertSubview(snapshotState.snapshotView, belowSubview: self)
let snapshotView = snapshotState.snapshotView
snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -offset.x, y: -offset.y), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
}
}

View File

@ -50,7 +50,7 @@ public final class EmojiStatusComponent: Component {
case text(color: UIColor, string: String)
case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode)
case topic(title: String, color: Int32, size: CGSize)
case image(image: UIImage?)
case image(image: UIImage?, tintColor: UIColor?)
}
public let postbox: Postbox
@ -335,8 +335,9 @@ public final class EmojiStatusComponent: Component {
} else {
iconImage = nil
}
case let .image(image):
case let .image(image, tintColor):
iconImage = image
iconTintColor = tintColor
case let .verified(fillColor, foregroundColor, sizeType):
let imageNamePrefix: String
switch sizeType {

View File

@ -169,7 +169,7 @@ private final class TitleFieldComponent: Component {
let iconContent: EmojiStatusComponent.Content
if component.isGeneral {
iconContent = .image(image: generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: component.placeholderColor))
iconContent = .image(image: generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: component.placeholderColor), tintColor: nil)
self.iconButton.isUserInteractionEnabled = false
} else if component.fileId == 0 {
iconContent = .topic(title: String(component.text.prefix(1)), color: component.iconColor, size: CGSize(width: 32.0, height: 32.0))

View File

@ -294,7 +294,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
}
let content: EmojiStatusComponent.Content
if threadId == 1 {
content = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(theme))
content = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(theme), tintColor: nil)
} else if let iconFileId = threadInfo.icon {
content = .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: avatarSize, height: avatarSize), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .forever)
} else {

View File

@ -436,7 +436,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, openBoostToUnrestrict: {
}, updateVideoTrimRange: { _, _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {
}, updateDisplayHistoryFilterAsList: { _ in
}, requestLayout: { _ in
@ -3302,7 +3302,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
c?.dismiss(completion: {
if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
if let channel = currentPeer as? TelegramChannel, channel.isForumOrMonoForum, let threadId = message.threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default).startStandalone()
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone()
} else {
let targetLocation: NavigateToChatControllerParams.Location
if case let .replyThread(message) = strongSelf.chatLocation {
@ -3464,7 +3464,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
c?.dismiss(completion: {
if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
if let channel = currentPeer as? TelegramChannel, channel.isForumOrMonoForum, let threadId = message.threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default).startStandalone()
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone()
} else {
let targetLocation: NavigateToChatControllerParams.Location
if case let .replyThread(message) = strongSelf.chatLocation {
@ -3806,7 +3806,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in
@ -13111,7 +13111,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
if let allowsCustomTransition = other.allowsCustomTransition, !allowsCustomTransition() {
return nil
}
if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId {
if let tag = other.userInfo as? PeerInfoNavigationSourceTag, (tag.peerId == peerId || tag.threadId == peerId.toInt64()) {
return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode)
}
return nil

View File

@ -785,7 +785,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}, openBoostToUnrestrict: {
}, updateVideoTrimRange: { _, _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {
}, updateDisplayHistoryFilterAsList: { _ in
}, requestLayout: { _ in

View File

@ -127,10 +127,21 @@ import PostSuggestionsSettingsScreen
import ChatSendStarsScreen
extension ChatControllerImpl {
func reloadChatLocation() {
func reloadChatLocation(chatLocation: ChatLocation, isReady: Promise<Bool>?) {
self._chatLocationInfoReady.set(.single(false))
self.didSetChatLocationInfoReady = false
if let peerId = chatLocation.peerId, peerId != self.context.account.peerId {
switch subject {
case .pinnedMessages, .scheduledMessages, .messageOptions:
break
default:
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId, threadId: chatLocation.threadId)
}
}
let context = self.context
let chatLocation = self.chatLocation
let chatLocationPeerId: PeerId? = self.chatLocation.peerId
let chatLocationPeerId: PeerId? = chatLocation.peerId
let mode = self.mode
let subject = self.subject
let peerId = chatLocationPeerId
@ -157,7 +168,7 @@ extension ChatControllerImpl {
}
let managingBot: Signal<ChatManagingBot?, NoError>
if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser {
if let peerId = chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser {
managingBot = self.context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.ChatManagingBot(id: peerId)
)
@ -505,7 +516,7 @@ extension ChatControllerImpl {
}))
let threadInfo: Signal<EngineMessageHistoryThread.Info?, NoError>
if let threadId = self.chatLocation.threadId {
if let threadId = chatLocation.threadId {
let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId)
threadInfo = context.account.postbox.combinedView(keys: [viewKey])
|> map { views -> EngineMessageHistoryThread.Info? in
@ -523,9 +534,9 @@ extension ChatControllerImpl {
}
let hasSearchTags: Signal<Bool, NoError>
if let peerId = self.chatLocation.peerId, peerId == context.account.peerId {
if let peerId = chatLocation.peerId, peerId == context.account.peerId {
hasSearchTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: self.chatLocation.threadId)
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId)
)
|> map { tags -> Bool in
return !tags.isEmpty
@ -536,14 +547,14 @@ extension ChatControllerImpl {
}
let hasSavedChats: Signal<Bool, NoError>
if case .peer(context.account.peerId) = self.chatLocation {
if case .peer(context.account.peerId) = chatLocation {
hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved()
} else {
hasSavedChats = .single(false)
}
let isPremiumRequiredForMessaging: Signal<Bool, NoError>
if let peerId = self.chatLocation.peerId {
if let peerId = chatLocation.peerId {
isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId)
|> distinctUntilChanged
} else {
@ -558,7 +569,7 @@ extension ChatControllerImpl {
}
let displayedPeerVerification: Signal<Bool, NoError>
if let peerId = self.chatLocation.peerId {
if let peerId = chatLocation.peerId {
displayedPeerVerification = ApplicationSpecificNotice.displayedPeerVerification(accountManager: context.sharedContext.accountManager, peerId: peerId)
|> take(1)
} else {
@ -906,7 +917,7 @@ extension ChatControllerImpl {
boostsToUnrestrict = cachedChannelData.boostsToUnrestrict
}
if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = strongSelf.chatLocation.peerId {
if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId {
strongSelf.premiumOrStarsRequiredDisposable = ((strongSelf.context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone()
}
@ -992,7 +1003,7 @@ extension ChatControllerImpl {
} else {
isRegularChat = true
}
if strongSelf.nextChannelToReadDisposable == nil, let peerId = strongSelf.chatLocation.peerId, let customChatNavigationStack = strongSelf.customChatNavigationStack {
if strongSelf.nextChannelToReadDisposable == nil, let peerId = chatLocation.peerId, let customChatNavigationStack = strongSelf.customChatNavigationStack {
if let index = customChatNavigationStack.firstIndex(of: peerId), index != customChatNavigationStack.count - 1 {
let nextPeerId = customChatNavigationStack[index + 1]
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
@ -1074,6 +1085,7 @@ extension ChatControllerImpl {
strongSelf.didSetChatLocationInfoReady = true
strongSelf._chatLocationInfoReady.set(.single(true))
}
isReady?.set(.single(true))
strongSelf.updateReminderActivity()
if let upgradedToPeerId = upgradedToPeerId {
if let navigationController = strongSelf.effectiveNavigationController {
@ -1267,9 +1279,9 @@ extension ChatControllerImpl {
}
let hasSearchTags: Signal<Bool, NoError>
if let peerId = self.chatLocation.peerId, peerId == context.account.peerId {
if let peerId = chatLocation.peerId, peerId == context.account.peerId {
hasSearchTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: self.chatLocation.threadId)
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId)
)
|> map { tags -> Bool in
return !tags.isEmpty
@ -1280,14 +1292,14 @@ extension ChatControllerImpl {
}
let hasSavedChats: Signal<Bool, NoError>
if case .peer(context.account.peerId) = self.chatLocation {
if case .peer(context.account.peerId) = chatLocation {
hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved()
} else {
hasSavedChats = .single(false)
}
let isPremiumRequiredForMessaging: Signal<Bool, NoError>
if let peerId = self.chatLocation.peerId {
if let peerId = chatLocation.peerId {
isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId)
|> distinctUntilChanged
} else {
@ -1456,8 +1468,8 @@ extension ChatControllerImpl {
strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true)
let avatarContent: EmojiStatusComponent.Content
if strongSelf.chatLocation.threadId == 1 {
avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme))
if chatLocation.threadId == 1 {
avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme), tintColor: nil)
} else if let fileId = threadInfo.icon {
avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1))
} else {
@ -1633,7 +1645,7 @@ extension ChatControllerImpl {
boostsToUnrestrict = cachedChannelData.boostsToUnrestrict
}
if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = strongSelf.chatLocation.peerId {
if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId {
strongSelf.premiumOrStarsRequiredDisposable = ((strongSelf.context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone()
}
@ -1709,6 +1721,7 @@ extension ChatControllerImpl {
strongSelf.didSetChatLocationInfoReady = true
strongSelf._chatLocationInfoReady.set(.single(true))
}
isReady?.set(.single(true))
}
}))
} else if case .customChatContents = self.chatLocationInfoData {
@ -1785,6 +1798,7 @@ extension ChatControllerImpl {
self.didSetChatLocationInfoReady = true
self._chatLocationInfoReady.set(.single(true))
}
isReady?.set(.single(true))
}))
}
}
@ -6040,44 +6054,14 @@ extension ChatControllerImpl {
} else {
apply()
}
}, updateChatLocationThread: { [weak self] threadId in
}, updateChatLocationThread: { [weak self] threadId, animationDirection in
guard let self else {
return
}
guard let peerId = self.chatLocation.peerId else {
return
let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = self.chatDisplayNode.chatLocationTabSwitchDirection(from: self.chatLocation.threadId, to: threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in
return direction ? .right : .left
}
guard let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer else {
return
}
let updatedChatLocation: ChatLocation
if let threadId {
var isMonoforum = false
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
isMonoforum = true
}
updatedChatLocation = .replyThread(message: ChatReplyThreadMessage(
peerId: peerId,
threadId: threadId,
channelMessageId: nil,
isChannelPost: false,
isForumPost: true,
isMonoforumPost: isMonoforum,
maxMessage: nil,
maxReadIncomingMessageId: nil,
maxReadOutgoingMessageId: nil,
unreadCount: 0,
initialFilledHoles: IndexSet(),
initialAnchor: .automatic,
isNotAvailable: false
))
} else {
updatedChatLocation = .peer(id: peerId)
}
self.updateChatPresentationInterfaceState(animated: true, interactive: false, { presentationInterfaceState in
return presentationInterfaceState.updatedChatLocation(updatedChatLocation)
})
self.updateChatLocationThread(threadId: threadId, animationDirection: animationDirection ?? defaultDirection)
}, toggleChatSidebarMode: { [weak self] in
guard let self else {
return

View File

@ -426,6 +426,23 @@ extension ChatControllerImpl {
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: .forumTopic(thread: replyThreadMessage), avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) {
self.effectiveNavigationController?.pushViewController(infoController)
}
} else if let peer = self.presentationInterfaceState.renderedPeer?.peer, case let .replyThread(replyThreadMessage) = self.chatLocation, peer.isMonoForum {
let context = self.context
if #available(iOS 13.0, *) {
Task { @MainActor [weak self] in
guard let peer = await context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: PeerId(replyThreadMessage.threadId))
).get() else {
return
}
guard let self else {
return
}
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) {
self.effectiveNavigationController?.pushViewController(infoController)
}
}
}
} else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, case let .replyThread(message) = self.chatLocation {
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: channel, mode: .forumTopic(thread: message), avatarInitiallyExpanded: false, fromChat: true, requestsContext: self.inviteRequestsContext) {
self.effectiveNavigationController?.pushViewController(infoController)

View File

@ -437,7 +437,10 @@ func updateChatPresentationInterfaceStateImpl(
selfController.presentationInterfaceState = updatedChatPresentationInterfaceState
if selfController.chatDisplayNode.chatLocation != selfController.presentationInterfaceState.chatLocation {
let tabSwitchDirection = selfController.chatDisplayNode.chatLocationTabSwitchDirection(from: selfController.chatDisplayNode.chatLocation, to: selfController.presentationInterfaceState.chatLocation)
let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = selfController.chatDisplayNode.chatLocationTabSwitchDirection(from: selfController.chatLocation.threadId, to: selfController.presentationInterfaceState.chatLocation.threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in
return direction ? .right : .left
}
let tabSwitchDirection = selfController.currentChatSwitchDirection ?? defaultDirection
selfController.chatDisplayNode.updateChatLocation(chatLocation: selfController.presentationInterfaceState.chatLocation, transition: transition, tabSwitchDirection: tabSwitchDirection)
}
@ -488,17 +491,18 @@ func updateChatPresentationInterfaceStateImpl(
animated = false
}
animated = false
selfController.navigationItem.setLeftBarButton(button.buttonItem, animated: animated)
selfController.navigationItem.setLeftBarButton(button.buttonItem, animated: animated && selfController.currentChatSwitchDirection == nil)
selfController.leftNavigationButton = button
}
} else if let _ = selfController.leftNavigationButton {
selfController.navigationItem.setLeftBarButton(nil, animated: transition.isAnimated)
selfController.navigationItem.setLeftBarButton(nil, animated: transition.isAnimated && selfController.currentChatSwitchDirection == nil)
selfController.leftNavigationButton = nil
}
/*if let channel = selfController.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum {
} else {*/
var buttonsAnimated = transition.isAnimated
if selfController.currentChatSwitchDirection != nil {
buttonsAnimated = false
}
if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) {
if selfController.rightNavigationButton != button {
if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action {
@ -601,7 +605,6 @@ func updateChatPresentationInterfaceStateImpl(
if previousChatLocation != selfController.presentationInterfaceState.chatLocation {
selfController.chatLocation = selfController.presentationInterfaceState.chatLocation
selfController.reloadChatLocation()
selfController.reloadCachedData()
selfController.setupChatHistoryNode()
}

View File

@ -233,7 +233,7 @@ struct ScrolledToMessageId: Equatable {
var allowedReplacementDirection: AllowedReplacementDirections
}
public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate {
public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate {
var validLayout: ContainerViewLayout?
public weak var parentController: ViewController?
@ -2059,7 +2059,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.navigateToMessage(from: nil, to: .id(id, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: false)
}, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in
if let context = self?.context, let navigationController = self?.effectiveNavigationController {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone()
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone()
}
}, tapMessage: nil, clickThroughMessage: { [weak self] view, location in
self?.chatDisplayNode.dismissInput(view: view, location: location)
@ -4828,38 +4828,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
self.displayQuickShare(id: messageId, node: node, gesture: gesture)
}, updateChatLocationThread: { [weak self] threadId in
}, updateChatLocationThread: { [weak self] threadId, animationDirection in
guard let self else {
return
}
self.interfaceInteraction?.updateChatLocationThread(threadId)
let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = self.chatDisplayNode.chatLocationTabSwitchDirection(from: self.chatLocation.threadId, to: self.presentationInterfaceState.chatLocation.threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in
return direction ? .right : .left
}
self.updateChatLocationThread(threadId: threadId, animationDirection: animationDirection ?? defaultDirection)
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency
self.controllerInteraction = controllerInteraction
//if chatLocation.threadId == nil {
if let peerId = chatLocation.peerId, peerId != context.account.peerId {
switch subject {
case .pinnedMessages, .scheduledMessages, .messageOptions:
break
default:
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
}
self.navigationBar?.allowsCustomTransition = { [weak self] in
guard let strongSelf = self else {
return false
}
self.navigationBar?.allowsCustomTransition = { [weak self] in
guard let strongSelf = self else {
return false
}
if strongSelf.navigationBar?.userInfo == nil {
return false
}
if strongSelf.navigationBar?.contentNode != nil {
return false
}
return true
if strongSelf.navigationBar?.userInfo == nil {
return false
}
//}
if strongSelf.navigationBar?.contentNode != nil {
return false
}
return true
}
self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
@ -5306,7 +5299,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
self.reloadChatLocation()
self.reloadChatLocation(chatLocation: self.chatLocation, isReady: nil)
self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get()
|> deliverOnMainQueue).startStrict(next: { [weak self] message in
@ -5906,6 +5899,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.networkSpeedEventsDisposable?.dispose()
self.postedScheduledMessagesEventsDisposable?.dispose()
self.premiumOrStarsRequiredDisposable?.dispose()
self.updateChatLocationThreadDisposable?.dispose()
}
deallocate()
}
@ -9817,6 +9811,122 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
private var updateChatLocationThreadDisposable: Disposable?
private var isUpdatingChatLocationThread: Bool = false
var currentChatSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection?
public func updateChatLocationThread(threadId: Int64?, animationDirection: ChatControllerAnimateInnerChatSwitchDirection? = nil) {
if self.isUpdatingChatLocationThread {
return
}
guard let peerId = self.chatLocation.peerId else {
return
}
if self.chatLocation.threadId == threadId {
return
}
guard let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer else {
return
}
let navigationSnapshot = self.chatTitleView?.prepareSnapshotState()
let rightBarButtonItemSnapshots: [(UIView, CGRect)] = (self.navigationItem.rightBarButtonItems ?? []).compactMap { item -> (UIView, CGRect)? in
guard let view = item.customDisplayNode?.view, let snapshotView = view.snapshotView(afterScreenUpdates: false) else {
return nil
}
return (snapshotView, view.convert(view.bounds, to: self.view))
}
let updatedChatLocation: ChatLocation
if let threadId {
var isMonoforum = false
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
isMonoforum = true
}
updatedChatLocation = .replyThread(message: ChatReplyThreadMessage(
peerId: peerId,
threadId: threadId,
channelMessageId: nil,
isChannelPost: false,
isForumPost: true,
isMonoforumPost: isMonoforum,
maxMessage: nil,
maxReadIncomingMessageId: nil,
maxReadOutgoingMessageId: nil,
unreadCount: 0,
initialFilledHoles: IndexSet(),
initialAnchor: .automatic,
isNotAvailable: false
))
} else {
updatedChatLocation = .peer(id: peerId)
}
let isReady = Promise<Bool>()
self.reloadChatLocation(chatLocation: updatedChatLocation, isReady: isReady)
self.isUpdatingChatLocationThread = true
self.updateChatLocationThreadDisposable?.dispose()
self.updateChatLocationThreadDisposable = (isReady.get()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
guard let self else {
return
}
self.isUpdatingChatLocationThread = false
self.currentChatSwitchDirection = animationDirection
self.updateChatPresentationInterfaceState(animated: animationDirection != nil, interactive: false, { presentationInterfaceState in
return presentationInterfaceState.updatedChatLocation(updatedChatLocation)
})
if let navigationSnapshot, let animationDirection {
let mappedAnimationDirection: ChatTitleView.AnimateFromSnapshotDirection
switch animationDirection {
case .up:
mappedAnimationDirection = .up
case .down:
mappedAnimationDirection = .down
case .left:
mappedAnimationDirection = .left
case .right:
mappedAnimationDirection = .right
}
self.chatTitleView?.animateFromSnapshot(navigationSnapshot, direction: mappedAnimationDirection)
if let rightBarButtonItems = self.navigationItem.rightBarButtonItems {
for i in 0 ..< rightBarButtonItems.count {
let item = rightBarButtonItems[i]
if let customDisplayNode = item.customDisplayNode {
customDisplayNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
//customDisplayNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
let _ = rightBarButtonItemSnapshots
/*if i < rightBarButtonItemSnapshots.count {
let (snapshotItem, snapshotFrame) = rightBarButtonItemSnapshots[i]
if let targetSuperview = customDisplayNode.view.superview {
snapshotItem.frame = targetSuperview.convert(snapshotFrame, from: self.view)
targetSuperview.addSubview(snapshotItem)
snapshotItem.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, completion: { [weak snapshotItem] _ in
snapshotItem?.removeFromSuperview()
})
snapshotItem.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
}*/
}
}
}
}
self.currentChatSwitchDirection = nil
})
}
public var contentContainerNode: ASDisplayNode {
return self.chatDisplayNode.contentContainerNode
}

View File

@ -1391,7 +1391,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
translationPanelHeight = height
if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance {
translationPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
@ -1455,7 +1455,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.titleAccessoryPanelContainer.addSubnode(adPanelNode)
}
let height = adPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState)
let height = adPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState)
if let adMessage = self.chatPresentationInterfaceState.adMessage, let opaqueId = adMessage.adAttribute?.opaqueId {
self.historyNode.markAdAsSeen(opaqueId: opaqueId)
}
@ -1849,7 +1849,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
var extraNavigationBarLeftCutout: CGSize?
extraNavigationBarLeftCutout = CGSize(width: 44.0, height: 30.0)
if let leftPanelSize {
extraNavigationBarLeftCutout = CGSize(width: leftPanelSize.width, height: navigationBarHeight)
} else {
extraNavigationBarLeftCutout = CGSize(width: 0.0, height: navigationBarHeight)
}
updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, extraNavigationBarLeftCutout, extraTransition)
@ -2252,20 +2256,21 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
let leftPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: layout.size.height))
transition.updateFrame(node: self.leftPanelContainer, frame: leftPanelContainerFrame)
if let leftPanel = self.leftPanel, let leftPanelSize {
let _ = leftPanel.view.update(
let leftPanelSize = leftPanel.view.update(
transition: immediatelyLayoutLeftPanelNodeAndAnimateAppearance ? .immediate :ComponentTransition(transition),
component: leftPanel.component.component,
environment: {
ChatSidePanelEnvironment(insets: UIEdgeInsets(
top: sidePanelTopInset,
top: 0.0,
left: leftPanelLeftInset,
bottom: containerInsets.bottom + contentBottomInset,
bottom: 0.0,
right: 0.0
))
},
containerSize: leftPanelSize
containerSize: CGSize(width: leftPanelSize.width, height: leftPanelSize.height - sidePanelTopInset - (containerInsets.bottom + inputPanelsHeight))
)
let leftPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: leftPanelSize)
let leftPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: sidePanelTopInset), size: leftPanelSize)
if let leftPanelView = leftPanel.view.view {
if leftPanelView.superview == nil {
self.leftPanelContainer.view.addSubview(leftPanelView)
@ -2273,7 +2278,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if immediatelyLayoutLeftPanelNodeAndAnimateAppearance {
leftPanelView.frame = leftPanelFrame.offsetBy(dx: -leftPanelSize.width, dy: 0.0)
if self.titleTopicsAccessoryPanelNode != nil {
if self.titleTopicsAccessoryPanelNode != nil || dismissedTitleTopicsAccessoryPanelNode != nil {
if let leftPanelView = leftPanelView as? ChatSideTopicsPanel.View {
leftPanelView.updateGlobalOffset(globalOffset: -leftPanelSize.width, transition: ComponentTransition(transition))
}
@ -2291,15 +2296,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
component: dismissedLeftPanel.component.component,
environment: {
ChatSidePanelEnvironment(insets: UIEdgeInsets(
top: sidePanelTopInset,
top: 0.0,
left: leftPanelLeftInset,
bottom: containerInsets.bottom + contentBottomInset,
bottom: 0.0,
right: 0.0
))
},
containerSize: CGSize(width: defaultLeftPanelWidth, height: layout.size.height)
containerSize: CGSize(width: defaultLeftPanelWidth, height: layout.size.height - sidePanelTopInset - (containerInsets.bottom + inputPanelsHeight))
)
transition.updateFrame(view: dismissedLeftPanelView, frame: CGRect(origin: CGPoint(x: -dismissedLeftPanelSize.width, y: 0.0), size: dismissedLeftPanelSize), completion: { [weak dismissedLeftPanelView] _ in
transition.updateFrame(view: dismissedLeftPanelView, frame: CGRect(origin: CGPoint(x: -dismissedLeftPanelSize.width, y: sidePanelTopInset), size: dismissedLeftPanelSize), completion: { [weak dismissedLeftPanelView] _ in
dismissedLeftPanelView?.removeFromSuperview()
})
if let dismissedLeftPanelView = dismissedLeftPanelView as? ChatSideTopicsPanel.View {
@ -2359,7 +2364,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode, let titleTopicsAccessoryPanelFrame, (immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance || !titleTopicsAccessoryPanelNode.frame.equalTo(titleTopicsAccessoryPanelFrame)) {
if immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance {
titleTopicsAccessoryPanelNode.frame = titleTopicsAccessoryPanelFrame.offsetBy(dx: 0.0, dy: -titleTopicsAccessoryPanelFrame.height)
if self.leftPanel != nil {
if self.leftPanel != nil || dismissedLeftPanel != nil {
titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate)
}
@ -2974,7 +2979,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
self.interfaceInteraction?.updateChatLocationThread(peer.id.toInt64())
self.interfaceInteraction?.updateChatLocationThread(peer.id.toInt64(), nil)
self.controller?.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
return current.updatedSearch(nil)
@ -5012,15 +5017,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
})
}
func chatLocationTabSwitchDirection(from fromLocation: ChatLocation, to toLocation: ChatLocation) -> Bool? {
func chatLocationTabSwitchDirection(from fromLocation: Int64?, to toLocation: Int64?) -> Bool? {
var leftIndex: Int?
var rightIndex: Int?
if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode {
leftIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: fromLocation.threadId)
rightIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: toLocation.threadId)
leftIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: fromLocation)
rightIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: toLocation)
} else if let leftPanelView = self.leftPanel?.view.view as? ChatSideTopicsPanel.View {
leftIndex = leftPanelView.topicIndex(threadId: fromLocation.threadId)
rightIndex = leftPanelView.topicIndex(threadId: toLocation.threadId)
leftIndex = leftPanelView.topicIndex(threadId: fromLocation)
rightIndex = leftPanelView.topicIndex(threadId: toLocation)
}
guard let leftIndex, let rightIndex else {
return nil
@ -5028,7 +5033,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
return leftIndex < rightIndex
}
func updateChatLocation(chatLocation: ChatLocation, transition: ContainedViewLayoutTransition, tabSwitchDirection: Bool?) {
func updateChatLocation(chatLocation: ChatLocation, transition: ContainedViewLayoutTransition, tabSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection?) {
if chatLocation == self.chatLocation {
return
}
@ -5100,10 +5105,25 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if let validLayout = self.validLayout, transition.isAnimated, let tabSwitchDirection {
let offsetMultiplier: CGFloat = tabSwitchDirection ? 1.0 : -1.0
transition.animatePosition(layer: historyNode.layer, from: CGPoint(x: offsetMultiplier * validLayout.0.size.width, y: 0.0), to: CGPoint(), removeOnCompletion: true, additive: true)
transition.animatePosition(layer: previousHistoryNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier * validLayout.0.size.width, y: 0.0), removeOnCompletion: false, additive: true, completion: { [weak previousHistoryNode] _ in
var offsetMultiplier = CGPoint()
switch tabSwitchDirection {
case .up:
offsetMultiplier.y = -1.0
case .down:
offsetMultiplier.y = 1.0
case .left:
offsetMultiplier.x = -1.0
case .right:
offsetMultiplier.x = 1.0
}
previousHistoryNode.clipsToBounds = true
historyNode.clipsToBounds = true
transition.animatePosition(layer: historyNode.layer, from: CGPoint(x: offsetMultiplier.x * validLayout.0.size.width, y: offsetMultiplier.y * validLayout.0.size.height), to: CGPoint(), removeOnCompletion: true, additive: true)
transition.animatePosition(layer: previousHistoryNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier.x * validLayout.0.size.width, y: -offsetMultiplier.y * validLayout.0.size.height), removeOnCompletion: false, additive: true, completion: { [weak previousHistoryNode, weak historyNode] _ in
previousHistoryNode?.removeFromSupernode()
historyNode?.clipsToBounds = false
})
} else {
previousHistoryNode.removeFromSupernode()

View File

@ -279,8 +279,8 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState
togglePanel: { [weak interfaceInteraction] in
interfaceInteraction?.toggleChatSidebarMode()
},
updateTopicId: { [weak interfaceInteraction] topicId in
interfaceInteraction?.updateChatLocationThread(topicId)
updateTopicId: { [weak interfaceInteraction] topicId, direction in
interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up)
}
))
)
@ -300,8 +300,8 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState
togglePanel: { [weak interfaceInteraction] in
interfaceInteraction?.toggleChatSidebarMode()
},
updateTopicId: { [weak interfaceInteraction] topicId in
interfaceInteraction?.updateChatLocationThread(topicId)
updateTopicId: { [weak interfaceInteraction] topicId, direction in
interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up)
}
))
)

View File

@ -330,7 +330,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
}
} else {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme))
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme), tintColor: theme.rootController.navigationBar.secondaryTextColor)
}
}
@ -908,7 +908,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
guard let self else {
return
}
self.interfaceInteraction?.updateChatLocationThread(nil)
self.interfaceInteraction?.updateChatLocationThread(nil, .left)
})
self.allItemView = itemView
self.scrollView.addSubview(itemView)
@ -958,12 +958,20 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
guard let self else {
return
}
if case let .forum(topicId) = chatListItem.id {
self.interfaceInteraction?.updateChatLocationThread(topicId)
let topicId: Int64
if case let .forum(topicIdValue) = chatListItem.id {
topicId = topicIdValue
} else {
let topicId = chatListItem.renderedPeer.peerId.toInt64()
self.interfaceInteraction?.updateChatLocationThread(topicId)
topicId = chatListItem.renderedPeer.peerId.toInt64()
}
var direction = true
if let params = self.params, let lhsIndex = self.topicIndex(threadId: params.interfaceState.chatLocation.threadId), let rhsIndex = self.topicIndex(threadId: topicId) {
direction = lhsIndex < rhsIndex
}
self.interfaceInteraction?.updateChatLocationThread(topicId, direction ? .right : .left)
}, contextGesture: { gesture, sourceNode in
})
self.itemViews[itemId] = itemView

View File

@ -138,7 +138,15 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
isFirst = false
continue
}
if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && controller.chatLocation.threadId == params.chatLocation.asChatLocation.threadId && (controller.subject != .scheduledMessages || controller.subject == params.subject) {
var canMatchThread = controller.chatLocation.threadId == params.chatLocation.asChatLocation.threadId
var switchToThread = false
if !canMatchThread && controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && controller.subject == nil {
canMatchThread = true
switchToThread = true
}
if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && canMatchThread && (controller.subject != .scheduledMessages || controller.subject == params.subject) {
if let updateTextInputState = params.updateTextInputState {
controller.updateTextInputState(updateTextInputState)
}
@ -164,6 +172,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
controller.beginReportSelection(reason: reportReason)
}
if switchToThread {
controller.updateChatLocationThread(threadId: params.chatLocation.threadId, animationDirection: nil)
}
if popAndComplete {
if let _ = params.navigationController.viewControllers.last as? AttachmentController, let controller = params.navigationController.viewControllers[params.navigationController.viewControllers.count - 2] as? ChatControllerImpl, controller.chatLocation == params.chatLocation.asChatLocation {
@ -376,7 +388,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll
return false
}
public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError> {
public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack, animated: Bool) -> Signal<Never, NoError> {
return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: messageId, preload: false)
|> deliverOnMainQueue
|> beforeNext { [weak context, weak navigationController] result in
@ -399,7 +411,7 @@ public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePee
activateInput: actualActivateInput,
keepStack: keepStack,
scrollToEndIfExists: scrollToEndIfExists,
animated: !scrollToEndIfExists
animated: !scrollToEndIfExists && animated
)
)
}

View File

@ -218,7 +218,7 @@ func openResolvedUrlImpl(
}
case let .replyThread(messageId):
if let navigationController = navigationController {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone()
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone()
}
case let .stickerPack(name, _):
dismissInput()

View File

@ -193,7 +193,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
self.dimNode = ASDisplayNode()

View File

@ -2126,8 +2126,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
navigateToForumChannelImpl(context: context, peerId: peerId, navigationController: navigationController)
}
public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError> {
return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput, scrollToEndIfExists: scrollToEndIfExists, keepStack: keepStack)
public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack, animated: Bool) -> Signal<Never, NoError> {
return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput, scrollToEndIfExists: scrollToEndIfExists, keepStack: keepStack, animated: animated)
}
public func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal<ChatController, NoError> {
@ -2395,7 +2395,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, updateChatLocationThread: { _, _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode as? WallpaperBackgroundNode))
@ -2416,7 +2416,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader {
return ChatMessageDateHeader(timestamp: timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), controllerInteraction: nil, context: context)
return ChatMessageDateHeader(timestamp: timestamp, separableThreadId: nil, scheduled: false, displayHeader: nil, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), controllerInteraction: nil, context: context)
}
public func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: Peer, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader {

View File

@ -91,7 +91,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n
}
case let .replyThread(messageId):
if let navigationController = controller.navigationController as? NavigationController {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).start()
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).start()
}
case let .stickerPack(name, _):
let packReference: StickerPackReference = .name(name)