[WIP] Topics

This commit is contained in:
Ali 2022-10-04 18:19:39 +04:00
parent 533085c77a
commit 62697d4ffb
83 changed files with 1974 additions and 1650 deletions

View File

@ -271,11 +271,11 @@ public enum StickerPackUrlType {
public enum ResolvedUrl {
case externalUrl(String)
case urlAuth(String)
case peer(PeerId?, ChatControllerInteractionNavigateToPeer)
case peer(Peer?, ChatControllerInteractionNavigateToPeer)
case inaccessiblePeer
case botStart(peerId: PeerId, payload: String)
case botStart(peer: Peer, payload: String)
case groupBotStart(peerId: PeerId, payload: String, adminRights: ResolvedBotAdminRights?)
case channelMessage(peerId: PeerId, messageId: MessageId, timecode: Double?)
case channelMessage(peer: Peer, messageId: MessageId, timecode: Double?)
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
case stickerPack(name: String, type: StickerPackUrlType)
case instantView(TelegramMediaWebpage, String?)
@ -718,7 +718,7 @@ public protocol SharedAccountContext: AnyObject {
func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
func makePeersNearbyController(context: AccountContext) -> ViewController
func makeComposeController(context: AccountContext) -> ViewController
func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController
func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController
func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode) -> ChatController
func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, isCentered: Bool) -> ListViewItem
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
@ -740,7 +740,7 @@ public protocol SharedAccountContext: AnyObject {
func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set<EngineMessage.Id>) -> Signal<ChatAvailableMessageActions, NoError>
func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set<EngineMessage.Id>, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal<ChatAvailableMessageActions, NoError>
func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError>
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?)
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?)
func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)

View File

@ -14,14 +14,14 @@ public final class GalleryControllerActionInteraction {
public let openUrl: (String, Bool) -> Void
public let openUrlIn: (String) -> Void
public let openPeerMention: (String) -> Void
public let openPeer: (PeerId) -> Void
public let openPeer: (EnginePeer) -> Void
public let openHashtag: (String?, String) -> Void
public let openBotCommand: (String) -> Void
public let addContact: (String) -> Void
public let storeMediaPlaybackState: (MessageId, Double?, Double) -> Void
public let editMedia: (MessageId, [UIView], @escaping () -> Void) -> Void
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, editMedia: @escaping (MessageId, [UIView], @escaping () -> Void) -> Void) {
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, editMedia: @escaping (MessageId, [UIView], @escaping () -> Void) -> Void) {
self.openUrl = openUrl
self.openUrlIn = openUrlIn
self.openPeerMention = openPeerMention

View File

@ -70,7 +70,7 @@ public enum AvatarNodeExplicitIcon {
private enum AvatarNodeState: Equatable {
case empty
case peerAvatar(EnginePeer.Id, [String], TelegramMediaImageRepresentation?)
case peerAvatar(EnginePeer.Id, [String], TelegramMediaImageRepresentation?, AvatarNodeClipStyle)
case custom(letter: [String], explicitColorIndex: Int?, explicitIcon: AvatarNodeExplicitIcon?)
}
@ -78,8 +78,8 @@ private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool {
switch (lhs, rhs) {
case (.empty, .empty):
return true
case let (.peerAvatar(lhsPeerId, lhsLetters, lhsPhotoRepresentations), .peerAvatar(rhsPeerId, rhsLetters, rhsPhotoRepresentations)):
return lhsPeerId == rhsPeerId && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations
case let (.peerAvatar(lhsPeerId, lhsLetters, lhsPhotoRepresentations, lhsClipStyle), .peerAvatar(rhsPeerId, rhsLetters, rhsPhotoRepresentations, rhsClipStyle)):
return lhsPeerId == rhsPeerId && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations && lhsClipStyle == rhsClipStyle
case let (.custom(lhsLetters, lhsIndex, lhsIcon), .custom(rhsLetters, rhsIndex, rhsIcon)):
return lhsLetters == rhsLetters && lhsIndex == rhsIndex && lhsIcon == rhsIcon
default:
@ -321,7 +321,7 @@ public final class AvatarNode: ASDisplayNode {
} else if peer?.restrictionText(platform: "ios", contentSettings: genericContext.currentContentSettings.with { $0 }) == nil {
representation = peer?.smallProfileImage
}
let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? EnginePeer.Id(0), peer?.displayLetters ?? [], representation)
let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? EnginePeer.Id(0), peer?.displayLetters ?? [], representation, clipStyle)
if updatedState != self.state || overrideImage != self.overrideImage || theme !== self.theme {
self.state = updatedState
self.overrideImage = overrideImage
@ -588,9 +588,8 @@ public final class AvatarNode: ASDisplayNode {
let currentState = node?.state
let createNode = node == nil
return { [weak node] context, peer, font in
let state: AvatarNodeState = .peerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage)
let state: AvatarNodeState = .peerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage, .round)
if currentState != state {
}
var createdNode: AvatarNode?
if createNode {

View File

@ -79,6 +79,7 @@ swift_library(
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
"//submodules/TelegramUI/Components/EntityKeyboard",
"//submodules/TelegramUI/Components/ForumCreateTopicScreen:ForumCreateTopicScreen",
"//submodules/TelegramUI/Components/ChatTitleView",
"//submodules/AnimationUI:AnimationUI",
],
visibility = [

View File

@ -38,6 +38,7 @@ import EntityKeyboard
import TelegramStringFormatting
import ForumCreateTopicScreen
import AnimationUI
import ChatTitleView
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
if listNode.scroller.isDragging {
@ -110,22 +111,22 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
}
}
private final class MoreHeaderButton: HighlightableButtonNode {
enum Content {
public final class MoreHeaderButton: HighlightableButtonNode {
public enum Content {
case image(UIImage?)
case more(UIImage?)
}
let referenceNode: ContextReferenceContentNode
let containerNode: ContextControllerSourceNode
public let referenceNode: ContextReferenceContentNode
public let containerNode: ContextControllerSourceNode
private let iconNode: ASImageNode
private var animationNode: AnimationNode?
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
public var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
private var color: UIColor
init(color: UIColor) {
public init(color: UIColor) {
self.color = color
self.referenceNode = ContextReferenceContentNode()
@ -158,7 +159,7 @@ private final class MoreHeaderButton: HighlightableButtonNode {
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0))
self.referenceNode.frame = self.containerNode.bounds
self.iconNode.image = optionsCircleImage(color: color)
self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color)
if let image = self.iconNode.image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
}
@ -167,7 +168,7 @@ private final class MoreHeaderButton: HighlightableButtonNode {
}
private var content: Content?
func setContent(_ content: Content, animated: Bool = false) {
public func setContent(_ content: Content, animated: Bool = false) {
if case .more = content, self.animationNode == nil {
let iconColor = self.color
let animationNode = AnimationNode(animation: "anim_profilemore", colors: ["Point 2.Group 1.Fill 1": iconColor,
@ -236,33 +237,33 @@ private final class MoreHeaderButton: HighlightableButtonNode {
}
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
self.view.isOpaque = false
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 22.0, height: 44.0)
}
func onLayout() {
public func onLayout() {
}
func play() {
public func play() {
self.animationNode?.playOnce()
}
}
public static func optionsCircleImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
private func optionsCircleImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
let lineWidth: CGFloat = 1.3
context.setLineWidth(lineWidth)
context.setStrokeColor(color.cgColor)
let lineWidth: CGFloat = 1.3
context.setLineWidth(lineWidth)
context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth, dy: lineWidth))
})
context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth, dy: lineWidth))
})
}
}
public class ChatListControllerImpl: TelegramBaseController, ChatListController {
@ -284,11 +285,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return super.displayNode as! ChatListControllerNode
}
private let titleView: ChatListTitleView
private var titleView: ChatListTitleView?
private var chatTitleView: ChatTitleView?
private let infoReady = Promise<Bool>()
private var proxyUnavailableTooltipController: TooltipController?
private var didShowProxyUnavailableTooltipController = false
private var titleDisposable: Disposable?
private var chatTitleDisposable: Disposable?
private var badgeDisposable: Disposable?
private var badgeIconDisposable: Disposable?
@ -358,17 +363,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.animationCache = context.animationCache
self.animationRenderer = context.animationRenderer
self.titleView = ChatListTitleView(
context: context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer
)
switch self.location {
case .chatList:
self.titleView = ChatListTitleView(
context: context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer
)
case .forum:
self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer)
}
self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor)
self.moreBarButton.isUserInteractionEnabled = true
self.moreBarButton.setContent(.more(optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor)))
self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor)))
self.moreBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
@ -401,16 +411,96 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
guard case let .forum(peerId) = self.location else {
return
}
ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, sourceView: sourceNode.view, gesture: gesture)
ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: true, sourceView: sourceNode.view, gesture: gesture)
}
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
}
self.titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil)
self.navigationItem.titleView = self.titleView
self.titleView.openStatusSetup = { [weak self] sourceView in
self?.openStatusSetup(sourceView: sourceView)
switch self.location {
case .chatList:
if let titleView = self.titleView {
titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil)
self.navigationItem.titleView = titleView
titleView.openStatusSetup = { [weak self] sourceView in
self?.openStatusSetup(sourceView: sourceView)
}
}
self.infoReady.set(.single(true))
case let .forum(peerId):
if let chatTitleView = self.chatTitleView {
self.navigationItem.titleView = chatTitleView
chatTitleView.pressed = { [weak self] in
guard let self = self else {
return
}
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
)
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self = self, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
(self.navigationController as? NavigationController)?.pushViewController(controller)
})
}
let peerView = Promise<PeerView>()
peerView.set(context.account.viewTracker.peerView(peerId))
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
let recentOnlineSignal: Signal<Int32?, NoError> = peerView.get()
|> map { view -> Bool? in
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
if case .broadcast = peer.info {
return nil
} else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 {
return true
} else {
return false
}
} else {
return false
}
}
|> distinctUntilChanged
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
if let isLarge = isLarge {
if isLarge {
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init)
} else {
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init)
}
} else {
return .single(nil)
}
}
onlineMemberCount = recentOnlineSignal
self.chatTitleDisposable = (combineLatest(queue: Queue.mainQueue(),
peerView.get(),
onlineMemberCount
)
|> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount in
guard let strongSelf = self, let chatTitleView = strongSelf.chatTitleView else {
return
}
chatTitleView.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false)
strongSelf.infoReady.set(.single(true))
if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, !channel.flags.contains(.isForum) {
if let navigationController = strongSelf.navigationController as? NavigationController {
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false))
navigationController.replaceController(strongSelf, with: chatController, animated: true)
}
}
})
}
}
if !previewing {
@ -581,7 +671,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
animated = true
}
}
strongSelf.titleView.setTitle(NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus), animated: animated)
strongSelf.titleView?.setTitle(NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus), animated: animated)
} else if isReorderingTabs {
if case .chatList(.root) = strongSelf.location {
strongSelf.navigationItem.setRightBarButton(nil, animated: true)
@ -592,17 +682,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let (_, connectsViaProxy) = proxy
switch networkState {
case .waitingForNetwork:
strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus)
strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus)
case let .connecting(proxy):
var text = strongSelf.presentationData.strings.State_Connecting
if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 {
text = strongSelf.presentationData.strings.State_ConnectingToProxy
}
strongSelf.titleView.title = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus)
strongSelf.titleView?.title = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus)
case .updating:
strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus)
strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus)
case .online:
strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus)
strongSelf.titleView?.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus)
}
} else {
var isRoot = false
@ -654,7 +744,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var checkProxy = false
switch networkState {
case .waitingForNetwork:
strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus)
strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus)
case let .connecting(proxy):
var text = strongSelf.presentationData.strings.State_Connecting
if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 {
@ -663,11 +753,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if let proxy = proxy, proxy.hasConnectionIssues {
checkProxy = true
}
strongSelf.titleView.title = NetworkStatusTitle(text: text, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus)
strongSelf.titleView?.title = NetworkStatusTitle(text: text, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus)
case .updating:
strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus)
strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus)
case .online:
strongSelf.titleView.setTitle(NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus), animated: (previousEditingAndNetworkState?.0 ?? false) != stateAndFilterId.state.editing)
strongSelf.titleView?.setTitle(NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus), animated: (previousEditingAndNetworkState?.0 ?? false) != stateAndFilterId.state.editing)
}
if case .chatList(.root) = location, checkProxy {
if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil && strongSelf.navigationController?.topViewController === self {
@ -680,8 +770,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: {
if let strongSelf = self, let rect = strongSelf.titleView.proxyButtonFrame {
return (strongSelf.titleView, rect.insetBy(dx: 0.0, dy: -4.0))
if let strongSelf = self, let titleView = strongSelf.titleView, let rect = titleView.proxyButtonFrame {
return (titleView, rect.insetBy(dx: 0.0, dy: -4.0))
}
return nil
}))
@ -708,13 +798,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
})
self.titleView.toggleIsLocked = { [weak self] in
self.titleView?.toggleIsLocked = { [weak self] in
if let strongSelf = self {
strongSelf.context.sharedContext.appLockContext.lock()
}
}
self.titleView.openProxySettings = { [weak self] in
self.titleView?.openProxySettings = { [weak self] in
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.pushViewController(context.sharedContext.makeProxySettingsController(context: context))
}
@ -1044,6 +1134,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
deinit {
self.openMessageFromSearchDisposable.dispose()
self.titleDisposable?.dispose()
self.chatTitleDisposable?.dispose()
self.badgeDisposable?.dispose()
self.badgeIconDisposable?.dispose()
self.passcodeLockTooltipDisposable.dispose()
@ -1064,7 +1155,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var selectedItems = Set<MediaId>()
var topStatusTitle = self.presentationData.strings.PeerStatusSetup_NoTimerTitle
var currentSelection: Int64?
if let peerStatus = self.titleView.title.peerStatus, case let .emoji(emojiStatus) = peerStatus {
if let peerStatus = self.titleView?.title.peerStatus, case let .emoji(emojiStatus) = peerStatus {
selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId))
currentSelection = emojiStatus.fileId
@ -1141,8 +1232,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.navigationItem.rightBarButtonItem = editItem
}
self.titleView.theme = self.presentationData.theme
self.titleView.strings = self.presentationData.strings
self.titleView?.theme = self.presentationData.theme
self.titleView?.strings = self.presentationData.strings
self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, hasEmbeddedTitleContent: false)
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
@ -1825,7 +1918,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if case .chatList(.root) = self.location {
self.ready.set(.never())
} else {
self.ready.set(self.chatListDisplayNode.containerNode.ready)
self.ready.set(combineLatest([
self.chatListDisplayNode.containerNode.ready,
self.infoReady.get()
])
|> map { values -> Bool in
return !values.contains(where: { !$0 })
}
|> filter { $0 })
}
self.displayNodeDidLoad()
@ -1898,7 +1998,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
#endif
if let lockViewFrame = self.titleView.lockViewFrame, !self.didShowPasscodeLockTooltipController {
if let lockViewFrame = self.titleView?.lockViewFrame, !self.didShowPasscodeLockTooltipController {
self.passcodeLockTooltipDisposable.set(combineLatest(queue: .mainQueue(), ApplicationSpecificNotice.getPasscodeLockTips(accountManager: self.context.sharedContext.accountManager), self.context.sharedContext.accountManager.accessChallengeData() |> take(1)).start(next: { [weak self] tooltipValue, passcodeView in
if let strongSelf = self {
if !tooltipValue {
@ -1908,8 +2008,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.DialogList_PasscodeLockHelp), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, dismissByTapOutside: true)
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { [weak self] in
if let strongSelf = self {
return (strongSelf.titleView, lockViewFrame.offsetBy(dx: 4.0, dy: 14.0))
if let strongSelf = self, let titleView = strongSelf.titleView {
return (titleView, lockViewFrame.offsetBy(dx: 4.0, dy: 14.0))
}
return nil
}))
@ -2322,9 +2422,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
}
public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, sourceView: UIView, gesture: ContextGesture?) {
let isViewingAsTopics: Bool = true
public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) {
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: "View as Topics", icon: { theme in
@ -2332,8 +2430,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return nil
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { _, a in
}, action: { [weak sourceController] _, a in
a(.default)
guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else {
return
}
let chatController = context.sharedContext.makeChatListController(context: context, location: .forum(peerId: peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
navigationController.replaceController(sourceController, with: chatController, animated: false)
})))
items.append(.action(ContextMenuActionItem(text: "View as Messages", icon: { theme in
if isViewingAsTopics {
@ -2394,7 +2499,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}, action: { action in
action.dismissWithResult(.default)
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: "Topic#\(Int.random(in: 0 ..< 100000))", iconFileId: nil)
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: "Topic#\(Int.random(in: 0 ..< 100000)) very long title to fill two lines", iconFileId: nil)
|> deliverOnMainQueue).start(next: { topicId in
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [.message(text: "First Message", attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(topicId)), localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start()
})
@ -3673,8 +3778,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
public var lockViewFrame: CGRect? {
if let lockViewFrame = self.titleView.lockViewFrame {
return self.titleView.convert(lockViewFrame, to: self.view)
if let titleView = self.titleView, let lockViewFrame = titleView.lockViewFrame {
return titleView.convert(lockViewFrame, to: self.view)
} else {
return nil
}

View File

@ -29,6 +29,16 @@ import ComponentFlow
import EmojiStatusComponent
public enum ChatListItemContent {
public struct ThreadInfo: Equatable {
public var id: Int64
public var info: EngineMessageHistoryThread.Info
public init(id: Int64, info: EngineMessageHistoryThread.Info) {
self.id = id
self.info = info
}
}
public final class DraftState: Equatable {
let text: String
let entities: [MessageTextEntity]
@ -49,7 +59,7 @@ public enum ChatListItemContent {
}
}
case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThread.Info?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumThreadTitle: String?)
case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: ThreadInfo?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumThreadTitle: String?)
case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool)
public var chatLocation: ChatLocation? {
@ -1011,7 +1021,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let promoInfo: ChatListNodeEntryPromoInfo?
let displayAsMessage: Bool
let hasFailedMessages: Bool
var threadInfo: EngineMessageHistoryThread.Info?
var threadInfo: ChatListItemContent.ThreadInfo?
var forumThreadTitle: String?
var groupHiddenByDefault = false
@ -1150,7 +1160,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let leftInset: CGFloat = params.leftInset + avatarLeftInset
enum ContentData {
case chat(itemPeer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThread.Info?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?)
case chat(itemPeer: EngineRenderedPeer, threadInfo: ChatListItemContent.ThreadInfo?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?)
case group(peers: [EngineChatList.GroupItem.Item])
}
@ -1238,8 +1248,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
}
if let forumThreadTitle = forumThreadTitle, let peerTextValue = peerText {
peerText = "\(peerTextValue)\(forumThreadTitle)"
if let peerTextValue = peerText, case let .channel(channel) = itemPeer.chatMainPeer, channel.flags.contains(.isForum), threadInfo == nil {
if let forumThreadTitle = forumThreadTitle {
peerText = "\(peerTextValue)\(forumThreadTitle)"
} else {
//TODO:localize
peerText = "\(peerTextValue) → General"
}
}
let messageText: String
@ -1432,7 +1447,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
switch contentData {
case let .chat(itemPeer, threadInfo, _, _, _, _, _):
if let threadInfo = threadInfo {
titleAttributedString = NSAttributedString(string: threadInfo.title, font: titleFont, textColor: theme.titleColor)
titleAttributedString = NSAttributedString(string: threadInfo.info.title, font: titleFont, textColor: theme.titleColor)
} else if let message = messages.last, case let .user(author) = message.author, displayAsMessage {
titleAttributedString = NSAttributedString(string: author.id == account.peerId ? item.presentationData.strings.DialogList_You : EnginePeer.user(author).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: titleFont, textColor: theme.titleColor)
} else if isPeerGroup {
@ -1687,9 +1702,17 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil)
}
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
let maxTitleLines: Int
switch item.index {
case .forum:
maxTitleLines = 2
case .chatList:
maxTitleLines = 1
}
let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: maxTitleLines, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var inputActivitiesSize: CGSize?
var inputActivitiesApply: (() -> Void)?
@ -1777,6 +1800,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let titleSpacing: CGFloat = -1.0
let authorSpacing: CGFloat = -3.0
var itemHeight: CGFloat = 8.0 * 2.0 + 1.0
itemHeight -= 21.0
itemHeight += titleLayout.size.height
itemHeight += measureLayout.size.height * 3.0
itemHeight += titleSpacing
itemHeight += authorSpacing
@ -1892,7 +1917,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame)
strongSelf.updateVideoVisibility()
if let iconFileId = threadInfo?.icon {
if let threadInfo = threadInfo {
let avatarIconView: ComponentHostView<Empty>
if let current = strongSelf.avatarIconView {
avatarIconView = current
@ -1902,11 +1927,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.contextContainer.view.addSubview(avatarIconView)
}
let avatarIconContent: EmojiStatusComponent.Content
if let fileId = threadInfo.info.icon {
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 40.0, height: 40.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever)
} else {
avatarIconContent = .topic(title: String(threadInfo.info.title.prefix(1)), colorIndex: Int(clamping: abs(threadInfo.id)))
}
let avatarIconComponent = EmojiStatusComponent(
context: item.context,
animationCache: item.interaction.animationCache,
animationRenderer: item.interaction.animationRenderer,
content: .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: 40.0, height: 40.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever),
content: avatarIconContent,
isVisibleForAnimations: strongSelf.visibilityStatus,
action: nil
)

View File

@ -55,7 +55,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
isRemovedFromTotalUnreadCount: Bool,
draftState: ChatListItemContent.DraftState?,
peer: EngineRenderedPeer,
threadInfo: EngineMessageHistoryThread.Info?,
threadInfo: ChatListItemContent.ThreadInfo?,
presence: EnginePeer.Presence?,
hasUnseenMentions: Bool,
hasUnseenReactions: Bool,
@ -377,8 +377,16 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
if let peerId {
inputActivities = state.peerInputActivities?.activities[peerId]
}
var threadId: Int64 = 0
switch entry.index {
case let .forum(_, threadIdValue, _, _):
threadId = threadIdValue
default:
break
}
result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumThreadTitle: entry.forumTopicTitle))
result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0) }, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumThreadTitle: entry.forumTopicTitle))
}
if !view.hasLater {
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
@ -440,6 +448,14 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
let peerId = index.messageIndex.id.peerId
let isSelected = state.selectedPeerIds.contains(peerId)
var threadId: Int64 = 0
switch item.item.index {
case let .forum(_, threadIdValue, _, _):
threadId = threadIdValue
default:
break
}
result.append(.PeerEntry(
index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: index.messageIndex)),
presentationData: state.presentationData,
@ -448,7 +464,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
isRemovedFromTotalUnreadCount: item.item.isMuted,
draftState: draftState,
peer: item.item.renderedPeer,
threadInfo: item.item.threadInfo,
threadInfo: item.item.threadInfo.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0) },
presence: item.item.presence,
hasUnseenMentions: item.item.hasUnseenMentions,
hasUnseenReactions: item.item.hasUnseenReactions,

View File

@ -667,7 +667,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
let reaction: MessageReaction.Reaction?
private let requestUpdate: (ReactionsTabNode, ContainedViewLayoutTransition) -> Void
private let requestUpdateApparentHeight: (ReactionsTabNode, ContainedViewLayoutTransition) -> Void
private let openPeer: (PeerId) -> Void
private let openPeer: (EnginePeer) -> Void
private var hasMore: Bool = false
@ -699,7 +699,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
readStats: MessageReadStats?,
requestUpdate: @escaping (ReactionsTabNode, ContainedViewLayoutTransition) -> Void,
requestUpdateApparentHeight: @escaping (ReactionsTabNode, ContainedViewLayoutTransition) -> Void,
openPeer: @escaping (PeerId) -> Void
openPeer: @escaping (EnginePeer) -> Void
) {
self.context = context
self.availableReactions = availableReactions
@ -800,9 +800,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
itemNode = current
} else {
let openPeer = self.openPeer
let peerId = item.peer.id
let peer = item.peer
itemNode = ItemNode(context: self.context, availableReactions: self.availableReactions, animationCache: self.animationCache, animationRenderer: self.animationRenderer, action: {
openPeer(peerId)
openPeer(peer)
})
self.itemNodes[index] = itemNode
self.scrollNode.addSubnode(itemNode)
@ -972,7 +972,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
}
private var interactiveTransitionState: InteractiveTransitionState?
private let openPeer: (PeerId) -> Void
private let openPeer: (EnginePeer) -> Void
private(set) var apparentHeight: CGFloat = 0.0
@ -987,7 +987,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void,
requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void,
back: (() -> Void)?,
openPeer: @escaping (PeerId) -> Void
openPeer: @escaping (EnginePeer) -> Void
) {
self.context = context
self.availableReactions = availableReactions
@ -1334,7 +1334,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
let reaction: MessageReaction.Reaction?
let readStats: MessageReadStats?
let back: (() -> Void)?
let openPeer: (PeerId) -> Void
let openPeer: (EnginePeer) -> Void
public init(
context: AccountContext,
@ -1345,7 +1345,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
reaction: MessageReaction.Reaction?,
readStats: MessageReadStats?,
back: (() -> Void)?,
openPeer: @escaping (PeerId) -> Void
openPeer: @escaping (EnginePeer) -> Void
) {
self.context = context
self.availableReactions = availableReactions

View File

@ -669,7 +669,12 @@ public class GalleryController: ViewController, StandalonePresentableController,
case let .textMention(mention):
strongSelf.actionInteraction?.openPeerMention(mention)
case let .peerMention(peerId, _):
strongSelf.actionInteraction?.openPeer(peerId)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.actionInteraction?.openPeer(peer)
}
})
case let .botCommand(command):
strongSelf.actionInteraction?.openBotCommand(command)
case let .hashtag(peerName, hashtag):
@ -775,7 +780,13 @@ public class GalleryController: ViewController, StandalonePresentableController,
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.dismiss(forceAway: false)
strongSelf.actionInteraction?.openPeer(peerId)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.actionInteraction?.openPeer(peer)
}
})
}
}))
if !mention.isEmpty {

View File

@ -28,7 +28,7 @@ final class InstantPageAnchorItem: InstantPageItem {
func drawInTile(context: CGContext) {
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return nil
}

View File

@ -35,7 +35,7 @@ final class InstantPageArticleItem: InstantPageItem {
self.hasRTL = hasRTL
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return InstantPageArticleNode(context: context, item: self, webPage: self.webPage, strings: strings, theme: theme, contentItems: self.contentItems, contentSize: self.contentSize, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl)
}

View File

@ -24,7 +24,7 @@ final class InstantPageAudioItem: InstantPageItem {
self.medias = [media]
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return InstantPageAudioNode(context: context, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia)
}

View File

@ -18,7 +18,7 @@ final class InstantPageContentNode : ASDisplayNode {
private let openMedia: (InstantPageMedia) -> Void
private let longPressMedia: (InstantPageMedia) -> Void
private let openPeer: (PeerId) -> Void
private let openPeer: (EnginePeer) -> Void
private let openUrl: (InstantPageUrlItem) -> Void
var currentLayoutTiles: [InstantPageTile] = []
@ -40,7 +40,7 @@ final class InstantPageContentNode : ASDisplayNode {
private var previousVisibleBounds: CGRect?
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
self.context = context
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder

View File

@ -151,9 +151,9 @@ public final class InstantPageController: ViewController {
self?.present(c, in: .window(.root), with: a, blockInteraction: true)
}, pushController: { [weak self] c in
(self?.navigationController as? NavigationController)?.pushViewController(c)
}, openPeer: { [weak self] peerId in
}, openPeer: { [weak self] peer in
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), animated: true))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), animated: true))
}
}, navigateBack: { [weak self] in
if let strongSelf = self, let controllers = strongSelf.navigationController?.viewControllers.reversed() {

View File

@ -34,7 +34,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
private let getNavigationController: () -> NavigationController?
private let present: (ViewController, Any?) -> Void
private let pushController: (ViewController) -> Void
private let openPeer: (PeerId) -> Void
private let openPeer: (EnginePeer) -> Void
private var webPage: TelegramMediaWebpage?
private var initialAnchor: String?
@ -92,7 +92,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return InstantPageStoredState(contentOffset: Double(self.scrollNode.view.contentOffset.y), details: details)
}
init(controller: InstantPageController, context: AccountContext, settings: InstantPagePresentationSettings?, themeSettings: PresentationThemeSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, autoNightModeTriggered: Bool, statusBar: StatusBar, sourcePeerType: MediaAutoDownloadPeerType, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) {
init(controller: InstantPageController, context: AccountContext, settings: InstantPagePresentationSettings?, themeSettings: PresentationThemeSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, autoNightModeTriggered: Bool, statusBar: StatusBar, sourcePeerType: MediaAutoDownloadPeerType, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (EnginePeer) -> Void, navigateBack: @escaping () -> Void) {
self.controller = controller
self.context = context
self.presentationTheme = presentationTheme
@ -1326,22 +1326,22 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
default:
strongSelf.loadProgress.set(1.0)
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), forceExternal: false, openPeer: { peerId, navigation in
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), forceExternal: false, openPeer: { peer, navigation in
switch navigation {
case let .chat(_, subject, peekData):
if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: subject, peekData: peekData))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, peekData: peekData))
}
case let .withBotStartPayload(botStart):
if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: botStart, keepStack: .always))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), botStart: botStart, keepStack: .always))
}
case let .withAttachBot(attachBotStart):
if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), attachBotStart: attachBotStart))
}
case .info:
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peerId)
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id)
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self {
if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {

View File

@ -40,7 +40,7 @@ final class InstantPageDetailsItem: InstantPageItem {
self.index = index
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
var expanded: Bool?
if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] {
expanded = currentlyExpanded

View File

@ -35,7 +35,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
var requestLayoutUpdate: ((Bool) -> Void)?
init(context: AccountContext, sourcePeerType: MediaAutoDownloadPeerType, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) {
init(context: AccountContext, sourcePeerType: MediaAutoDownloadPeerType, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) {
self.context = context
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder

View File

@ -21,7 +21,7 @@ final class InstantPageFeedbackItem: InstantPageItem {
self.webPage = webPage
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return InstantPageFeedbackNode(context: context, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl)
}

View File

@ -45,7 +45,7 @@ final class InstantPageImageItem: InstantPageItem {
self.fit = fit
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return InstantPageImageNode(context: context, sourcePeerType: sourcePeerType, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished)
}

View File

@ -16,7 +16,7 @@ protocol InstantPageItem {
func matchesAnchor(_ anchor: String) -> Bool
func drawInTile(context: CGContext)
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode?
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode?
func matchesNode(_ node: InstantPageNode) -> Bool
func linkSelectionRects(at point: CGPoint) -> [CGRect]

View File

@ -27,7 +27,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem {
self.rtl = rtl
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return InstantPagePeerReferenceNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, transparent: self.transparent, rtl: self.rtl, openPeer: openPeer)
}

View File

@ -55,7 +55,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
private var strings: PresentationStrings
private var nameDisplayOrder: PresentationPersonNameOrder
private var theme: InstantPageTheme
private let openPeer: (PeerId) -> Void
private let openPeer: (EnginePeer) -> Void
private let highlightedBackgroundNode: ASDisplayNode
private let buttonNode: HighlightableButtonNode
@ -70,7 +70,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
private let joinDisposable = MetaDisposable()
private var joinState: JoinState = .none
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (PeerId) -> Void) {
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (EnginePeer) -> Void) {
self.context = context
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder
@ -300,7 +300,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
@objc func buttonPressed() {
if let peer = self.peer {
self.openPeer(peer.id)
self.openPeer(EnginePeer(peer))
}
}

View File

@ -29,7 +29,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem {
self.interactive = interactive
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return InstantPagePlayableVideoNode(context: context, webPage: self.webPage, theme: theme, media: self.media, interactive: self.interactive, openMedia: openMedia)
}

View File

@ -62,7 +62,7 @@ final class InstantPageShapeItem: InstantPageItem {
return false
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return nil
}

View File

@ -21,7 +21,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
self.medias = medias
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return InstantPageSlideshowNode(context: context, sourcePeerType: sourcePeerType, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia)
}

View File

@ -18,7 +18,7 @@ final class InstantPageSubContentNode : ASDisplayNode {
private let openMedia: (InstantPageMedia) -> Void
private let longPressMedia: (InstantPageMedia) -> Void
private let openPeer: (PeerId) -> Void
private let openPeer: (EnginePeer) -> Void
private let openUrl: (InstantPageUrlItem) -> Void
var currentLayoutTiles: [InstantPageTile] = []
@ -40,7 +40,7 @@ final class InstantPageSubContentNode : ASDisplayNode {
private var previousVisibleBounds: CGRect?
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
self.context = context
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder

View File

@ -200,7 +200,7 @@ final class InstantPageTableItem: InstantPageScrollableItem {
return false
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
var additionalNodes: [InstantPageNode] = []
for cell in self.cells {
for item in cell.additionalItems {

View File

@ -436,7 +436,7 @@ final class InstantPageTextItem: InstantPageItem {
return false
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return nil
}
@ -485,7 +485,7 @@ final class InstantPageScrollableTextItem: InstantPageScrollableItem {
context.restoreGState()
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
var additionalNodes: [InstantPageNode] = []
for item in additionalItems {
if item.wantsNode {

View File

@ -25,7 +25,7 @@ final class InstantPageWebEmbedItem: InstantPageItem {
self.enableScrolling = enableScrolling
}
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight)
}

View File

@ -22,14 +22,14 @@ public final class JoinLinkPreviewController: ViewController {
private let link: String
private var isRequest = false
private var isGroup = false
private let navigateToPeer: (EnginePeer.Id, ChatPeekTimeout?) -> Void
private let navigateToPeer: (EnginePeer, ChatPeekTimeout?) -> Void
private let parentNavigationController: NavigationController?
private var resolvedState: ExternalJoiningChatState?
private var presentationData: PresentationData
private let disposable = MetaDisposable()
public init(context: AccountContext, link: String, navigateToPeer: @escaping (EnginePeer.Id, ChatPeekTimeout?) -> Void, parentNavigationController: NavigationController?, resolvedState: ExternalJoiningChatState? = nil) {
public init(context: AccountContext, link: String, navigateToPeer: @escaping (EnginePeer, ChatPeekTimeout?) -> Void, parentNavigationController: NavigationController?, resolvedState: ExternalJoiningChatState? = nil) {
self.context = context
self.link = link
self.navigateToPeer = navigateToPeer
@ -87,11 +87,11 @@ public final class JoinLinkPreviewController: ViewController {
let data = JoinLinkPreviewData(isGroup: invite.participants != nil, isJoined: false)
strongSelf.controllerNode.setInvitePeer(image: invite.photoRepresentation, title: invite.title, memberCount: invite.participantsCount, members: invite.participants?.map({ $0 }) ?? [], data: data)
}
case let .alreadyJoined(peerId):
strongSelf.navigateToPeer(peerId, nil)
case let .alreadyJoined(peer):
strongSelf.navigateToPeer(peer, nil)
strongSelf.dismiss()
case let .peek(peerId, deadline):
strongSelf.navigateToPeer(peerId, ChatPeekTimeout(deadline: deadline, linkData: strongSelf.link))
case let .peek(peer, deadline):
strongSelf.navigateToPeer(peer, ChatPeekTimeout(deadline: deadline, linkData: strongSelf.link))
strongSelf.dismiss()
case .invalidHash:
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
@ -139,13 +139,13 @@ public final class JoinLinkPreviewController: ViewController {
}
private func join() {
self.disposable.set((self.context.engine.peers.joinChatInteractively(with: self.link) |> deliverOnMainQueue).start(next: { [weak self] peerId in
self.disposable.set((self.context.engine.peers.joinChatInteractively(with: self.link) |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self {
if strongSelf.isRequest {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .inviteRequestSent(title: strongSelf.presentationData.strings.MemberRequests_RequestToJoinSent, text: strongSelf.isGroup ? strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup : strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel ), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
} else {
if let peerId = peerId {
strongSelf.navigateToPeer(peerId, nil)
if let peer = peer {
strongSelf.navigateToPeer(peer, nil)
}
}
strongSelf.dismiss()

View File

@ -692,11 +692,11 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
guard let navigationController = self.controller?.navigationController as? NavigationController else {
return false
}
self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { [weak self] peerId, navigation in
self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { [weak self] peer, navigation in
guard let strongSelf = self else {
return
}
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, keepStack: .always, peekData: nil, completion: { [weak navigationController] _ in
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: nil, keepStack: .always, peekData: nil, completion: { [weak navigationController] _ in
if let navigationController = navigationController {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in

View File

@ -18,9 +18,9 @@ private final class SelectivePrivacyPeersControllerArguments {
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let removePeer: (PeerId) -> Void
let addPeer: () -> Void
let openPeer: (PeerId) -> Void
let openPeer: (EnginePeer) -> Void
init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (PeerId) -> Void) {
init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void) {
self.context = context
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.removePeer = removePeer
@ -141,7 +141,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry {
}
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: {
arguments.openPeer(peer.peer.id)
arguments.openPeer(EnginePeer(peer.peer))
}, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id)
}, removePeer: { peerId in
@ -323,14 +323,11 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
controller?.dismiss()
}))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, openPeer: { peerId in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
pushControllerImpl?(controller)
})
}, openPeer: { peer in
guard let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
pushControllerImpl?(controller)
})
var previousPeers: [SelectivePrivacyPeer]?

View File

@ -22,7 +22,7 @@ private let maxUsersDisplayedHighLimit: Int32 = 12
private final class GroupStatsControllerArguments {
let context: AccountContext
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
let openPeer: (PeerId) -> Void
let openPeer: (EnginePeer) -> Void
let openPeerHistory: (PeerId) -> Void
let openPeerAdminActions: (PeerId) -> Void
let promotePeer: (PeerId) -> Void
@ -33,7 +33,7 @@ private final class GroupStatsControllerArguments {
let setAdminsPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let setInvitersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPeer: @escaping (PeerId) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPeer: @escaping (EnginePeer) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
self.context = context
self.loadDetailedGraph = loadDetailedGraph
self.openPeer = openPeer
@ -412,7 +412,7 @@ private enum StatsEntry: ItemListNodeEntry {
}
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id)
arguments.openPeer(EnginePeer(peer))
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in })
@ -443,7 +443,7 @@ private enum StatsEntry: ItemListNodeEntry {
}
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id)
arguments.openPeer(EnginePeer(peer))
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in })
@ -466,7 +466,7 @@ private enum StatsEntry: ItemListNodeEntry {
}
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id)
arguments.openPeer(EnginePeer(peer))
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in })
@ -721,7 +721,7 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat
let datacenterId: Int32 = statsDatacenterId ?? 0
var openPeerImpl: ((PeerId) -> Void)?
var openPeerImpl: ((EnginePeer) -> Void)?
var openPeerHistoryImpl: ((PeerId) -> Void)?
var openPeerAdminActionsImpl: ((PeerId) -> Void)?
var promotePeerImpl: ((PeerId) -> Void)?
@ -779,8 +779,8 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat
let arguments = GroupStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
return statsContext.loadDetailedGraph(graph, x: x)
}, openPeer: { peerId in
openPeerImpl?(peerId)
}, openPeer: { peer in
openPeerImpl?(peer)
}, openPeerHistory: { peerId in
openPeerHistoryImpl?(peerId)
}, openPeerAdminActions: { peerId in
@ -864,9 +864,9 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat
controller.didDisappear = { [weak controller] _ in
controller?.clearItemNodesHighlight(animated: true)
}
openPeerImpl = { [weak controller] peerId in
openPeerImpl = { [weak controller] peer in
if let navigationController = controller?.navigationController as? NavigationController {
let _ = (context.account.postbox.loadedPeerWithId(peerId)
let _ = (context.account.postbox.loadedPeerWithId(peer.id)
|> take(1)
|> deliverOnMainQueue).start(next: { peer in
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {

View File

@ -4,7 +4,6 @@ public enum Api {
public enum auth {}
public enum channels {}
public enum contacts {}
public enum feed {}
public enum help {}
public enum messages {}
public enum payments {}
@ -22,7 +21,6 @@ public enum Api {
public enum bots {}
public enum channels {}
public enum contacts {}
public enum feed {}
public enum folders {}
public enum help {}
public enum langpack {}
@ -221,11 +219,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[179611673] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) }
dict[-317687113] = { return Api.ExportedChatInvite.parse_chatInvitePublicJoinRequests($0) }
dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) }
dict[1348066419] = { return Api.FeedPosition.parse_feedPosition($0) }
dict[-207944868] = { return Api.FileHash.parse_fileHash($0) }
dict[-11252123] = { return Api.Folder.parse_folder($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
dict[1885902651] = { return Api.ForumTopic.parse_forumTopic($0) }
dict[792599046] = { return Api.ForumTopic.parse_forumTopic($0) }
dict[-1107729093] = { return Api.Game.parse_game($0) }
dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) }
dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) }
@ -772,7 +769,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-761649164] = { return Api.Update.parse_updateChannelMessageForwards($0) }
dict[-232346616] = { return Api.Update.parse_updateChannelMessageViews($0) }
dict[-1738720581] = { return Api.Update.parse_updateChannelParticipant($0) }
dict[1153291573] = { return Api.Update.parse_updateChannelReadMessagesContents($0) }
dict[-366410403] = { return Api.Update.parse_updateChannelReadMessagesContents($0) }
dict[277713951] = { return Api.Update.parse_updateChannelTooLong($0) }
dict[-1937192669] = { return Api.Update.parse_updateChannelUserTyping($0) }
dict[791390623] = { return Api.Update.parse_updateChannelWebPage($0) }
@ -841,7 +838,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1218471511] = { return Api.Update.parse_updateReadChannelOutbox($0) }
dict[-78886548] = { return Api.Update.parse_updateReadFeaturedEmojiStickers($0) }
dict[1461528386] = { return Api.Update.parse_updateReadFeaturedStickers($0) }
dict[1951948721] = { return Api.Update.parse_updateReadFeed($0) }
dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) }
dict[791617983] = { return Api.Update.parse_updateReadHistoryOutbox($0) }
dict[1757493555] = { return Api.Update.parse_updateReadMessagesContents($0) }
@ -961,8 +957,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1891070632] = { return Api.contacts.TopPeers.parse_topPeers($0) }
dict[-1255369827] = { return Api.contacts.TopPeers.parse_topPeersDisabled($0) }
dict[-567906571] = { return Api.contacts.TopPeers.parse_topPeersNotModified($0) }
dict[-587770695] = { return Api.feed.FeedMessages.parse_feedMessages($0) }
dict[-619039485] = { return Api.feed.FeedMessages.parse_feedMessagesNotModified($0) }
dict[-860107216] = { return Api.help.AppUpdate.parse_appUpdate($0) }
dict[-1000708810] = { return Api.help.AppUpdate.parse_noAppUpdate($0) }
dict[-2016381538] = { return Api.help.CountriesList.parse_countriesList($0) }
@ -1287,8 +1281,6 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.ExportedMessageLink:
_1.serialize(buffer, boxed)
case let _1 as Api.FeedPosition:
_1.serialize(buffer, boxed)
case let _1 as Api.FileHash:
_1.serialize(buffer, boxed)
case let _1 as Api.Folder:
@ -1727,8 +1719,6 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.contacts.TopPeers:
_1.serialize(buffer, boxed)
case let _1 as Api.feed.FeedMessages:
_1.serialize(buffer, boxed)
case let _1 as Api.help.AppUpdate:
_1.serialize(buffer, boxed)
case let _1 as Api.help.CountriesList:

View File

@ -671,7 +671,7 @@ public extension Api {
case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32)
case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32)
case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32)
case updateChannelReadMessagesContents(channelId: Int64, messages: [Int32])
case updateChannelReadMessagesContents(flags: Int32, channelId: Int64, topMsgId: Int32?, messages: [Int32])
case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?)
case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction)
case updateChannelWebPage(channelId: Int64, webpage: Api.WebPage, pts: Int32, ptsCount: Int32)
@ -740,7 +740,6 @@ public extension Api {
case updateReadChannelOutbox(channelId: Int64, maxId: Int32)
case updateReadFeaturedEmojiStickers
case updateReadFeaturedStickers
case updateReadFeed(flags: Int32, filterId: Int32, maxPosition: Api.FeedPosition, unreadCount: Int32?, unreadMutedCount: Int32?)
case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32)
case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32)
case updateReadMessagesContents(messages: [Int32], pts: Int32, ptsCount: Int32)
@ -925,11 +924,13 @@ public extension Api {
if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)}
serializeInt32(qts, buffer: buffer, boxed: false)
break
case .updateChannelReadMessagesContents(let channelId, let messages):
case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages):
if boxed {
buffer.appendInt32(1153291573)
buffer.appendInt32(-366410403)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(channelId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
@ -1510,16 +1511,6 @@ public extension Api {
buffer.appendInt32(1461528386)
}
break
case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount):
if boxed {
buffer.appendInt32(1951948721)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(filterId, buffer: buffer, boxed: false)
maxPosition.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(unreadCount!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(unreadMutedCount!, buffer: buffer, boxed: false)}
break
case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount):
if boxed {
@ -1731,8 +1722,8 @@ public extension Api {
return ("updateChannelMessageViews", [("channelId", String(describing: channelId)), ("id", String(describing: id)), ("views", String(describing: views))])
case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts):
return ("updateChannelParticipant", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("date", String(describing: date)), ("actorId", String(describing: actorId)), ("userId", String(describing: userId)), ("prevParticipant", String(describing: prevParticipant)), ("newParticipant", String(describing: newParticipant)), ("invite", String(describing: invite)), ("qts", String(describing: qts))])
case .updateChannelReadMessagesContents(let channelId, let messages):
return ("updateChannelReadMessagesContents", [("channelId", String(describing: channelId)), ("messages", String(describing: messages))])
case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages):
return ("updateChannelReadMessagesContents", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("topMsgId", String(describing: topMsgId)), ("messages", String(describing: messages))])
case .updateChannelTooLong(let flags, let channelId, let pts):
return ("updateChannelTooLong", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("pts", String(describing: pts))])
case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let fromId, let action):
@ -1869,8 +1860,6 @@ public extension Api {
return ("updateReadFeaturedEmojiStickers", [])
case .updateReadFeaturedStickers:
return ("updateReadFeaturedStickers", [])
case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount):
return ("updateReadFeed", [("flags", String(describing: flags)), ("filterId", String(describing: filterId)), ("maxPosition", String(describing: maxPosition)), ("unreadCount", String(describing: unreadCount)), ("unreadMutedCount", String(describing: unreadMutedCount))])
case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount):
return ("updateReadHistoryInbox", [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("stillUnreadCount", String(describing: stillUnreadCount)), ("pts", String(describing: pts)), ("ptsCount", String(describing: ptsCount))])
case .updateReadHistoryOutbox(let peer, let maxId, let pts, let ptsCount):
@ -2294,16 +2283,22 @@ public extension Api {
}
}
public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Int32]?
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: [Int32]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.Update.updateChannelReadMessagesContents(channelId: _1!, messages: _2!)
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.Update.updateChannelReadMessagesContents(flags: _1!, channelId: _2!, topMsgId: _3, messages: _4!)
}
else {
return nil
@ -3452,31 +3447,6 @@ public extension Api {
public static func parse_updateReadFeaturedStickers(_ reader: BufferReader) -> Update? {
return Api.Update.updateReadFeaturedStickers
}
public static func parse_updateReadFeed(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.FeedPosition?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.FeedPosition
}
var _4: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() }
var _5: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.Update.updateReadFeed(flags: _1!, filterId: _2!, maxPosition: _3!, unreadCount: _4, unreadMutedCount: _5)
}
else {
return nil
}
}
public static func parse_updateReadHistoryInbox(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()

View File

@ -1,99 +1,3 @@
public extension Api.feed {
enum FeedMessages: TypeConstructorDescription {
case feedMessages(flags: Int32, maxPosition: Api.FeedPosition?, minPosition: Api.FeedPosition?, readMaxPosition: Api.FeedPosition?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
case feedMessagesNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .feedMessages(let flags, let maxPosition, let minPosition, let readMaxPosition, let messages, let chats, let users):
if boxed {
buffer.appendInt32(-587770695)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {maxPosition!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {minPosition!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {readMaxPosition!.serialize(buffer, true)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .feedMessagesNotModified:
if boxed {
buffer.appendInt32(-619039485)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .feedMessages(let flags, let maxPosition, let minPosition, let readMaxPosition, let messages, let chats, let users):
return ("feedMessages", [("flags", String(describing: flags)), ("maxPosition", String(describing: maxPosition)), ("minPosition", String(describing: minPosition)), ("readMaxPosition", String(describing: readMaxPosition)), ("messages", String(describing: messages)), ("chats", String(describing: chats)), ("users", String(describing: users))])
case .feedMessagesNotModified:
return ("feedMessagesNotModified", [])
}
}
public static func parse_feedMessages(_ reader: BufferReader) -> FeedMessages? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.FeedPosition?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.FeedPosition
} }
var _3: Api.FeedPosition?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.FeedPosition
} }
var _4: Api.FeedPosition?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.FeedPosition
} }
var _5: [Api.Message]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _6: [Api.Chat]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _7: [Api.User]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.feed.FeedMessages.feedMessages(flags: _1!, maxPosition: _2, minPosition: _3, readMaxPosition: _4, messages: _5!, chats: _6!, users: _7!)
}
else {
return nil
}
}
public static func parse_feedMessagesNotModified(_ reader: BufferReader) -> FeedMessages? {
return Api.feed.FeedMessages.feedMessagesNotModified
}
}
}
public extension Api.help {
enum AppUpdate: TypeConstructorDescription {
case appUpdate(flags: Int32, id: Int32, version: String, text: String, entities: [Api.MessageEntity], document: Api.Document?, url: String?, sticker: Api.Document?)
@ -1316,3 +1220,125 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum BotCallbackAnswer: TypeConstructorDescription {
case botCallbackAnswer(flags: Int32, message: String?, url: String?, cacheTime: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .botCallbackAnswer(let flags, let message, let url, let cacheTime):
if boxed {
buffer.appendInt32(911761060)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)}
serializeInt32(cacheTime, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .botCallbackAnswer(let flags, let message, let url, let cacheTime):
return ("botCallbackAnswer", [("flags", String(describing: flags)), ("message", String(describing: message)), ("url", String(describing: url)), ("cacheTime", String(describing: cacheTime))])
}
}
public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) }
var _3: String?
if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) }
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum BotResults: TypeConstructorDescription {
case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users):
if boxed {
buffer.appendInt32(-1803769784)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(queryId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {switchPm!.serialize(buffer, true)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(results.count))
for item in results {
item.serialize(buffer, true)
}
serializeInt32(cacheTime, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users):
return ("botResults", [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("nextOffset", String(describing: nextOffset)), ("switchPm", String(describing: switchPm)), ("results", String(describing: results)), ("cacheTime", String(describing: cacheTime)), ("users", String(describing: users))])
}
}
public static func parse_botResults(_ reader: BufferReader) -> BotResults? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: String?
if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) }
var _4: Api.InlineBotSwitchPM?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM
} }
var _5: [Api.BotInlineResult]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self)
}
var _6: Int32?
_6 = reader.readInt32()
var _7: [Api.User]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, results: _5!, cacheTime: _6!, users: _7!)
}
else {
return nil
}
}
}
}

View File

@ -1,125 +1,3 @@
public extension Api.messages {
enum BotCallbackAnswer: TypeConstructorDescription {
case botCallbackAnswer(flags: Int32, message: String?, url: String?, cacheTime: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .botCallbackAnswer(let flags, let message, let url, let cacheTime):
if boxed {
buffer.appendInt32(911761060)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)}
serializeInt32(cacheTime, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .botCallbackAnswer(let flags, let message, let url, let cacheTime):
return ("botCallbackAnswer", [("flags", String(describing: flags)), ("message", String(describing: message)), ("url", String(describing: url)), ("cacheTime", String(describing: cacheTime))])
}
}
public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) }
var _3: String?
if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) }
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum BotResults: TypeConstructorDescription {
case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users):
if boxed {
buffer.appendInt32(-1803769784)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(queryId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {switchPm!.serialize(buffer, true)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(results.count))
for item in results {
item.serialize(buffer, true)
}
serializeInt32(cacheTime, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users):
return ("botResults", [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("nextOffset", String(describing: nextOffset)), ("switchPm", String(describing: switchPm)), ("results", String(describing: results)), ("cacheTime", String(describing: cacheTime)), ("users", String(describing: users))])
}
}
public static func parse_botResults(_ reader: BufferReader) -> BotResults? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: String?
if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) }
var _4: Api.InlineBotSwitchPM?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM
} }
var _5: [Api.BotInlineResult]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self)
}
var _6: Int32?
_6 = reader.readInt32()
var _7: [Api.User]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, results: _5!, cacheTime: _6!, users: _7!)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum ChatAdminsWithInvites: TypeConstructorDescription {
case chatAdminsWithInvites(admins: [Api.ChatAdminWithInvites], users: [Api.User])

View File

@ -2983,44 +2983,6 @@ public extension Api.functions.contacts {
})
}
}
public extension Api.functions.feed {
static func getFeed(flags: Int32, filterId: Int32, offsetPosition: Api.FeedPosition?, addOffset: Int32, limit: Int32, maxPosition: Api.FeedPosition?, minPosition: Api.FeedPosition?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.feed.FeedMessages>) {
let buffer = Buffer()
buffer.appendInt32(2121717715)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(filterId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {offsetPosition!.serialize(buffer, true)}
serializeInt32(addOffset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {maxPosition!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {minPosition!.serialize(buffer, true)}
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "feed.getFeed", parameters: [("flags", String(describing: flags)), ("filterId", String(describing: filterId)), ("offsetPosition", String(describing: offsetPosition)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxPosition", String(describing: maxPosition)), ("minPosition", String(describing: minPosition)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.feed.FeedMessages? in
let reader = BufferReader(buffer)
var result: Api.feed.FeedMessages?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.feed.FeedMessages
}
return result
})
}
}
public extension Api.functions.feed {
static func readFeed(filterId: Int32, maxPosition: Api.FeedPosition) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-1271479809)
serializeInt32(filterId, buffer: buffer, boxed: false)
maxPosition.serialize(buffer, true)
return (FunctionDescription(name: "feed.readFeed", parameters: [("filterId", String(describing: filterId)), ("maxPosition", String(describing: maxPosition))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.folders {
static func deleteFolder(folderId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
@ -5155,16 +5117,18 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.messages {
static func getUnreadMentions(peer: Api.InputPeer, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
static func getUnreadMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
buffer.appendInt32(1180140658)
buffer.appendInt32(-251140208)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
serializeInt32(offsetId, buffer: buffer, boxed: false)
serializeInt32(addOffset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
serializeInt32(maxId, buffer: buffer, boxed: false)
serializeInt32(minId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getUnreadMentions", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
return (FunctionDescription(name: "messages.getUnreadMentions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
let reader = BufferReader(buffer)
var result: Api.messages.Messages?
if let signature = reader.readInt32() {
@ -5175,16 +5139,18 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.messages {
static func getUnreadReactions(peer: Api.InputPeer, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
static func getUnreadReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
buffer.appendInt32(-396644838)
buffer.appendInt32(841173339)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
serializeInt32(offsetId, buffer: buffer, boxed: false)
serializeInt32(addOffset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
serializeInt32(maxId, buffer: buffer, boxed: false)
serializeInt32(minId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
let reader = BufferReader(buffer)
var result: Api.messages.Messages?
if let signature = reader.readInt32() {
@ -5466,11 +5432,13 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.messages {
static func readMentions(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
static func readMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
let buffer = Buffer()
buffer.appendInt32(251759059)
buffer.appendInt32(921026381)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
return (FunctionDescription(name: "messages.readMentions", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "messages.readMentions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in
let reader = BufferReader(buffer)
var result: Api.messages.AffectedHistory?
if let signature = reader.readInt32() {

View File

@ -956,52 +956,6 @@ public extension Api {
}
}
public extension Api {
enum FeedPosition: TypeConstructorDescription {
case feedPosition(date: Int32, peer: Api.Peer, id: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .feedPosition(let date, let peer, let id):
if boxed {
buffer.appendInt32(1348066419)
}
serializeInt32(date, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(id, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .feedPosition(let date, let peer, let id):
return ("feedPosition", [("date", String(describing: date)), ("peer", String(describing: peer)), ("id", String(describing: id))])
}
}
public static func parse_feedPosition(_ reader: BufferReader) -> FeedPosition? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.FeedPosition.feedPosition(date: _1!, peer: _2!, id: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum FileHash: TypeConstructorDescription {
case fileHash(offset: Int64, limit: Int32, hash: Buffer)
@ -1138,3 +1092,79 @@ public extension Api {
}
}
public extension Api {
enum ForumTopic: TypeConstructorDescription {
case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount):
if boxed {
buffer.appendInt32(792599046)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)}
serializeInt32(topMessage, buffer: buffer, boxed: false)
serializeInt32(readInboxMaxId, buffer: buffer, boxed: false)
serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false)
serializeInt32(unreadCount, buffer: buffer, boxed: false)
serializeInt32(unreadMentionsCount, buffer: buffer, boxed: false)
serializeInt32(unreadReactionsCount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount):
return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount)), ("unreadMentionsCount", String(describing: unreadMentionsCount)), ("unreadReactionsCount", String(describing: unreadReactionsCount))])
}
}
public static func parse_forumTopic(_ reader: BufferReader) -> ForumTopic? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: String?
_4 = parseString(reader)
var _5: Int64?
if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() }
var _6: Int32?
_6 = reader.readInt32()
var _7: Int32?
_7 = reader.readInt32()
var _8: Int32?
_8 = reader.readInt32()
var _9: Int32?
_9 = reader.readInt32()
var _10: Int32?
_10 = reader.readInt32()
var _11: Int32?
_11 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = _10 != nil
let _c11 = _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconEmojiId: _5, topMessage: _6!, readInboxMaxId: _7!, readOutboxMaxId: _8!, unreadCount: _9!, unreadMentionsCount: _10!, unreadReactionsCount: _11!)
}
else {
return nil
}
}
}
}

View File

@ -1,71 +1,3 @@
public extension Api {
enum ForumTopic: TypeConstructorDescription {
case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount):
if boxed {
buffer.appendInt32(1885902651)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)}
serializeInt32(topMessage, buffer: buffer, boxed: false)
serializeInt32(readInboxMaxId, buffer: buffer, boxed: false)
serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false)
serializeInt32(unreadCount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount):
return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount))])
}
}
public static func parse_forumTopic(_ reader: BufferReader) -> ForumTopic? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: String?
_4 = parseString(reader)
var _5: Int64?
if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() }
var _6: Int32?
_6 = reader.readInt32()
var _7: Int32?
_7 = reader.readInt32()
var _8: Int32?
_8 = reader.readInt32()
var _9: Int32?
_9 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconEmojiId: _5, topMessage: _6!, readInboxMaxId: _7!, readOutboxMaxId: _8!, unreadCount: _9!)
}
else {
return nil
}
}
}
}
public extension Api {
enum Game: TypeConstructorDescription {
case game(flags: Int32, id: Int64, accessHash: Int64, shortName: String, title: String, description: String, photo: Api.Photo, document: Api.Document?)

View File

@ -193,7 +193,7 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si
for topic in topics {
switch topic {
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount):
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _):
let data = MessageHistoryThreadData(
info: EngineMessageHistoryThread.Info(
title: title,

View File

@ -1374,7 +1374,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
}
case let .updateReadMessagesContents(messages, _, _):
updatedState.addReadMessagesContents((nil, messages))
case let .updateChannelReadMessagesContents(channelId, messages):
case let .updateChannelReadMessagesContents(_, channelId, topMsgId, messages):
let _ = topMsgId
updatedState.addReadMessagesContents((PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), messages))
case let .updateChannelMessageViews(channelId, id, views):
updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: id), count: views)
@ -1693,7 +1694,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, state: AccountMutab
for topic in topics {
switch topic {
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount):
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _):
state.operations.append(.ResetForumTopic(topicId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), data: MessageHistoryThreadData(
info: EngineMessageHistoryThread.Info(
title: title,
@ -1785,7 +1786,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe
for topic in topics {
switch topic {
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount):
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _):
fetchedChatList.threadInfos[MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)] = MessageHistoryThreadData(
info: EngineMessageHistoryThread.Info(
title: title,
@ -2429,7 +2430,8 @@ private func pollChannel(postbox: Postbox, network: Network, peer: Peer, state:
updatedState.updateMessagesPinned(ids: messages.map { id in
MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: id)
}, pinned: (flags & (1 << 0)) != 0)
case let .updateChannelReadMessagesContents(_, messages):
case let .updateChannelReadMessagesContents(_, _, topMsgId, messages):
let _ = topMsgId
updatedState.addReadMessagesContents((peer.id, messages))
case let .updateChannelMessageViews(_, id, views):
updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)

View File

@ -493,9 +493,9 @@ private func validateChannelMessagesBatch(postbox: Postbox, network: Network, ac
let requestSignal: Signal<Api.messages.Messages, MTRpcError>
if let tag = tag {
if tag == MessageTags.unseenPersonalMessage {
requestSignal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1))
requestSignal = network.request(Api.functions.messages.getUnreadMentions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1))
} else if tag == MessageTags.unseenReaction {
requestSignal = network.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1))
requestSignal = network.request(Api.functions.messages.getUnreadReactions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1))
} else if let filter = messageFilterForTagMask(tag) {
requestSignal = network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash))
} else {

View File

@ -406,7 +406,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
minMaxRange = 1 ... Int32.max - 1
}
request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
request = source.request(Api.functions.messages.getUnreadMentions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
} else if tag == .unseenReaction {
let offsetId: Int32
let addOffset: Int32
@ -454,7 +454,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
minMaxRange = 1 ... Int32.max - 1
}
request = source.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
request = source.request(Api.functions.messages.getUnreadReactions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
} else if tag == .liveLocation {
let selectedLimit = count

View File

@ -123,7 +123,7 @@ private func synchronizeMarkAllUnseen(transaction: Transaction, postbox: Postbox
let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel)
let limit: Int32 = 100
let oneOperation: (Int32) -> Signal<Int32?, MTRpcError> = { maxId in
return network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: maxId, addOffset: maxId == 0 ? 0 : -1, limit: limit, maxId: maxId == 0 ? 0 : (maxId + 1), minId: 1))
return network.request(Api.functions.messages.getUnreadMentions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: maxId, addOffset: maxId == 0 ? 0 : -1, limit: limit, maxId: maxId == 0 ? 0 : (maxId + 1), minId: 1))
|> mapToSignal { result -> Signal<[MessageId], MTRpcError> in
switch result {
case let .messages(messages, _, _):

View File

@ -7,6 +7,7 @@ private struct DiscussionMessage {
var messageId: MessageId
var channelMessageId: MessageId?
var isChannelPost: Bool
var isForumPost: Bool
var maxMessage: MessageId?
var maxReadIncomingMessageId: MessageId?
var maxReadOutgoingMessageId: MessageId?
@ -242,10 +243,16 @@ private class ReplyThreadHistoryContextImpl {
})
}
var isForumPost = false
if let channel = transaction.getPeer(parsedIndex.id.peerId) as? TelegramChannel, channel.flags.contains(.isForum) {
isForumPost = true
}
return .single(DiscussionMessage(
messageId: parsedIndex.id,
channelMessageId: channelMessageId,
isChannelPost: isChannelPost,
isForumPost: isForumPost,
maxMessage: resolvedMaxMessage,
maxReadIncomingMessageId: maxReadIncomingMessageId,
maxReadOutgoingMessageId: readOutboxMaxId.flatMap { readMaxId in
@ -554,6 +561,7 @@ public struct ChatReplyThreadMessage: Equatable {
public var messageId: MessageId
public var channelMessageId: MessageId?
public var isChannelPost: Bool
public var isForumPost: Bool
public var maxMessage: MessageId?
public var maxReadIncomingMessageId: MessageId?
public var maxReadOutgoingMessageId: MessageId?
@ -562,10 +570,11 @@ public struct ChatReplyThreadMessage: Equatable {
public var initialAnchor: Anchor
public var isNotAvailable: Bool
fileprivate init(messageId: MessageId, channelMessageId: MessageId?, isChannelPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, unreadCount: Int, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) {
fileprivate init(messageId: MessageId, channelMessageId: MessageId?, isChannelPost: Bool, isForumPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, unreadCount: Int, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) {
self.messageId = messageId
self.channelMessageId = channelMessageId
self.isChannelPost = isChannelPost
self.isForumPost = isForumPost
self.maxMessage = maxMessage
self.maxReadIncomingMessageId = maxReadIncomingMessageId
self.maxReadOutgoingMessageId = maxReadOutgoingMessageId
@ -664,10 +673,16 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
}
}
var isForumPost = false
if let channel = transaction.getPeer(parsedIndex.id.peerId) as? TelegramChannel, channel.flags.contains(.isForum) {
isForumPost = true
}
return DiscussionMessage(
messageId: parsedIndex.id,
channelMessageId: channelMessageId,
isChannelPost: isChannelPost,
isForumPost: isForumPost,
maxMessage: resolvedMaxMessage,
maxReadIncomingMessageId: readInboxMaxId.flatMap { readMaxId in
MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId)
@ -691,6 +706,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
messageId: messageId,
channelMessageId: nil,
isChannelPost: false,
isForumPost: true,
maxMessage: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxKnownMessageId),
maxReadIncomingMessageId: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxIncomingReadId),
maxReadOutgoingMessageId: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxOutgoingReadId),
@ -929,6 +945,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
messageId: discussionMessage.messageId,
channelMessageId: discussionMessage.channelMessageId,
isChannelPost: discussionMessage.isChannelPost,
isForumPost: discussionMessage.isForumPost,
maxMessage: discussionMessage.maxMessage,
maxReadIncomingMessageId: discussionMessage.maxReadIncomingMessageId,
maxReadOutgoingMessageId: discussionMessage.maxReadOutgoingMessageId,

View File

@ -47,9 +47,9 @@ public enum ExternalJoiningChatState {
}
case invite(Invite)
case alreadyJoined(PeerId)
case alreadyJoined(EnginePeer)
case invalidHash
case peek(PeerId, Int32)
case peek(EnginePeer, Int32)
}
func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal<PeerId?, JoinLinkError> {
@ -112,7 +112,7 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
return updated
})
return .alreadyJoined(peer.id)
return .alreadyJoined(EnginePeer(peer))
})
|> castError(JoinLinkInfoError.self)
}
@ -124,7 +124,7 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
return updated
})
return .peek(peer.id, expires)
return .peek(EnginePeer(peer), expires)
})
|> castError(JoinLinkInfoError.self)
}

View File

@ -524,8 +524,18 @@ public extension TelegramEngine {
}
}
public func joinChatInteractively(with hash: String) -> Signal <PeerId?, JoinLinkError> {
public func joinChatInteractively(with hash: String) -> Signal <EnginePeer?, JoinLinkError> {
let account = self.account
return _internal_joinChatInteractively(with: hash, account: self.account)
|> mapToSignal { id -> Signal <EnginePeer?, JoinLinkError> in
guard let id = id else {
return .single(nil)
}
return account.postbox.transaction { transaction -> EnginePeer? in
return transaction.getPeer(id).flatMap(EnginePeer.init)
}
|> castError(JoinLinkError.self)
}
}
public func joinLinkInformation(_ hash: String) -> Signal<ExternalJoiningChatState, JoinLinkInfoError> {

View File

@ -299,6 +299,7 @@ swift_library(
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
"//submodules/TelegramUI/Components/ForumTopicListScreen:ForumTopicListScreen",
"//submodules/TelegramUI/Components/ForumCreateTopicScreen:ForumCreateTopicScreen",
"//submodules/TelegramUI/Components/ChatTitleView",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -0,0 +1,37 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatTitleView",
module_name = "ChatTitleView",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/ActivityIndicator:ActivityIndicator",
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager",
"//submodules/ChatTitleActivityNode",
"//submodules/LocalizedPeerData",
"//submodules/PhoneNumberFormat",
"//submodules/AccountContext",
"//submodules/ComponentFlow",
"//submodules/AnimatedCountLabelNode",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,848 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import SwiftSignalKit
import LegacyComponents
import TelegramPresentationData
import TelegramUIPreferences
import ActivityIndicator
import TelegramStringFormatting
import PeerPresenceStatusManager
import ChatTitleActivityNode
import LocalizedPeerData
import PhoneNumberFormat
import ChatTitleActivityNode
import AnimatedCountLabelNode
import AccountContext
import ComponentFlow
import EmojiStatusComponent
import AnimationCache
import MultiAnimationRenderer
private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers])
private let subtitleFont = Font.regular(13.0)
public enum ChatTitleContent {
public enum ReplyThreadType {
case comments
case replies
}
case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool)
case replyThread(type: ReplyThreadType, count: Int)
case custom(String, String?, Bool)
}
private enum ChatTitleIcon {
case none
case lock
case mute
}
private enum ChatTitleCredibilityIcon: Equatable {
case none
case fake
case scam
case verified
case premium
case emojiStatus(PeerEmojiStatus)
}
public final class ChatTitleView: UIView, NavigationBarTitleView {
private let context: AccountContext
private var theme: PresentationTheme
private var hasEmbeddedTitleContent: Bool = false
private var strings: PresentationStrings
private var dateTimeFormat: PresentationDateTimeFormat
private var nameDisplayOrder: PresentationPersonNameOrder
private let animationCache: AnimationCache
private let animationRenderer: MultiAnimationRenderer
private let contentContainer: ASDisplayNode
public let titleContainerView: PortalSourceView
public let titleTextNode: ImmediateAnimatedCountLabelNode
public let titleLeftIconNode: ASImageNode
public let titleRightIconNode: ASImageNode
public let titleCredibilityIconView: ComponentHostView<Empty>
public let activityNode: ChatTitleActivityNode
private let button: HighlightTrackingButtonNode
private var validLayout: (CGSize, CGRect)?
private var titleLeftIcon: ChatTitleIcon = .none
private var titleRightIcon: ChatTitleIcon = .none
private var titleCredibilityIcon: ChatTitleCredibilityIcon = .none
private var presenceManager: PeerPresenceStatusManager?
private var pointerInteraction: PointerInteraction?
public var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? {
didSet {
let _ = self.updateStatus()
}
}
private func updateNetworkStatusNode(networkState: AccountNetworkState, layout: ContainerViewLayout?) {
self.setNeedsLayout()
}
public var networkState: AccountNetworkState = .online(proxy: nil) {
didSet {
if self.networkState != oldValue {
updateNetworkStatusNode(networkState: self.networkState, layout: self.layout)
let _ = self.updateStatus()
}
}
}
public var layout: ContainerViewLayout? {
didSet {
if self.layout != oldValue {
updateNetworkStatusNode(networkState: self.networkState, layout: self.layout)
}
}
}
public var pressed: (() -> Void)?
public var longPressed: (() -> Void)?
public var titleContent: ChatTitleContent? {
didSet {
if let titleContent = self.titleContent {
let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme
var segments: [AnimatedCountLabelNode.Segment] = []
var titleLeftIcon: ChatTitleIcon = .none
var titleRightIcon: ChatTitleIcon = .none
var titleCredibilityIcon: ChatTitleCredibilityIcon = .none
var isEnabled = true
switch titleContent {
case let .peer(peerView, customTitle, _, isScheduledMessages):
if peerView.peerId.isReplies {
let typeText: String = self.strings.DialogList_Replies
segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
isEnabled = false
} else if isScheduledMessages {
if peerView.peerId == self.context.account.peerId {
segments = [.text(0, NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
} else {
segments = [.text(0, NSAttributedString(string: self.strings.ScheduledMessages_Title, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
}
isEnabled = false
} else {
if let peer = peerViewMainPeer(peerView) {
if let customTitle = customTitle {
segments = [.text(0, NSAttributedString(string: customTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
} else if peerView.peerId == self.context.account.peerId {
segments = [.text(0, NSAttributedString(string: self.strings.Conversation_SavedMessages, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
} else {
if !peerView.peerIsContact, let user = peer as? TelegramUser, !user.flags.contains(.isSupport), user.botInfo == nil, let phone = user.phone, !phone.isEmpty {
segments = [.text(0, NSAttributedString(string: formatPhoneNumber(phone), font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
} else {
segments = [.text(0, NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
}
}
if peer.id != self.context.account.peerId {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
if peer.isFake {
titleCredibilityIcon = .fake
} else if peer.isScam {
titleCredibilityIcon = .scam
} else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
titleCredibilityIcon = .emojiStatus(emojiStatus)
} else if peer.isVerified {
titleCredibilityIcon = .verified
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
titleCredibilityIcon = .premium
}
}
}
if peerView.peerId.namespace == Namespaces.Peer.SecretChat {
titleLeftIcon = .lock
}
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
if titleCredibilityIcon != .verified {
titleRightIcon = .mute
}
}
}
}
case let .replyThread(type, count):
let textFont = titleFont
let textColor = titleTheme.rootController.navigationBar.primaryTextColor
if count > 0 {
var commentsPart: String
switch type {
case .comments:
commentsPart = self.strings.Conversation_TitleComments(Int32(count))
case .replies:
commentsPart = self.strings.Conversation_TitleReplies(Int32(count))
}
if commentsPart.contains("[") && commentsPart.contains("]") {
if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") {
commentsPart.removeSubrange(startIndex ... endIndex)
}
} else {
commentsPart = commentsPart.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,."))
}
let rawTextAndRanges: PresentationStrings.FormattedString
switch type {
case .comments:
rawTextAndRanges = self.strings.Conversation_TitleCommentsFormat("\(count)", commentsPart)
case .replies:
rawTextAndRanges = self.strings.Conversation_TitleRepliesFormat("\(count)", commentsPart)
}
let rawText = rawTextAndRanges.string
var textIndex = 0
var latestIndex = 0
for indexAndRange in rawTextAndRanges.ranges {
let index = indexAndRange.index
let range = indexAndRange.range
var lowerSegmentIndex = range.lowerBound
if index != 0 {
lowerSegmentIndex = min(lowerSegmentIndex, latestIndex)
} else {
if latestIndex < range.lowerBound {
let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex) ..< rawText.index(rawText.startIndex, offsetBy: range.lowerBound)])
segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor)))
textIndex += 1
}
}
latestIndex = range.upperBound
let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: lowerSegmentIndex) ..< rawText.index(rawText.startIndex, offsetBy: min(rawText.count, range.upperBound))])
if index == 0 {
segments.append(.number(count, NSAttributedString(string: part, font: textFont, textColor: textColor)))
} else {
segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor)))
textIndex += 1
}
}
if latestIndex < rawText.count {
let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex)...])
segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor)))
textIndex += 1
}
} else {
switch type {
case .comments:
segments = [.text(0, NSAttributedString(string: strings.Conversation_TitleCommentsEmpty, font: textFont, textColor: textColor))]
case .replies:
segments = [.text(0, NSAttributedString(string: strings.Conversation_TitleRepliesEmpty, font: textFont, textColor: textColor))]
}
}
isEnabled = false
case let .custom(text, _, enabled):
segments = [.text(0, NSAttributedString(string: text, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
isEnabled = enabled
}
var updated = false
if self.titleTextNode.segments != segments {
self.titleTextNode.segments = segments
updated = true
}
if titleLeftIcon != self.titleLeftIcon {
self.titleLeftIcon = titleLeftIcon
switch titleLeftIcon {
case .lock:
self.titleLeftIconNode.image = PresentationResourcesChat.chatTitleLockIcon(titleTheme)
default:
self.titleLeftIconNode.image = nil
}
updated = true
}
if titleCredibilityIcon != self.titleCredibilityIcon {
self.titleCredibilityIcon = titleCredibilityIcon
/*switch titleCredibilityIcon {
case .none:
self.titleCredibilityIconNode.image = nil
case .fake:
self.titleCredibilityIconNode.image = PresentationResourcesChatList.fakeIcon(titleTheme, strings: self.strings, type: .regular)
case .scam:
self.titleCredibilityIconNode.image = PresentationResourcesChatList.scamIcon(titleTheme, strings: self.strings, type: .regular)
case .verified:
self.titleCredibilityIconNode.image = PresentationResourcesChatList.verifiedIcon(titleTheme)
case .premium:
self.titleCredibilityIconNode.image = PresentationResourcesChatList.premiumIcon(titleTheme)
}*/
updated = true
}
if titleRightIcon != self.titleRightIcon {
self.titleRightIcon = titleRightIcon
switch titleRightIcon {
case .mute:
self.titleRightIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(titleTheme)
default:
self.titleRightIconNode.image = nil
}
updated = true
}
self.isUserInteractionEnabled = isEnabled
self.button.isUserInteractionEnabled = isEnabled
if !self.updateStatus() {
if updated {
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
}
}
}
}
private func updateStatus() -> Bool {
var inputActivitiesAllowed = true
if let titleContent = self.titleContent {
switch titleContent {
case let .peer(peerView, _, _, isScheduledMessages):
if let peer = peerViewMainPeer(peerView) {
if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies {
inputActivitiesAllowed = false
}
}
case .replyThread:
inputActivitiesAllowed = true
default:
inputActivitiesAllowed = false
}
}
let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme
var state = ChatTitleActivityNodeState.none
switch self.networkState {
case .waitingForNetwork, .connecting, .updating:
var infoText: String
switch self.networkState {
case .waitingForNetwork:
infoText = self.strings.ChatState_WaitingForNetwork
case .connecting:
infoText = self.strings.ChatState_Connecting
case .updating:
infoText = self.strings.ChatState_Updating
case .online:
infoText = ""
}
state = .info(NSAttributedString(string: infoText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor), .generic)
case .online:
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed {
var stringValue = ""
var mergedActivity = inputActivities[0].1
for (_, activity) in inputActivities {
if activity != mergedActivity {
mergedActivity = .typingText
break
}
}
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat {
switch mergedActivity {
case .typingText:
stringValue = strings.Conversation_typing
case .uploadingFile:
stringValue = strings.Activity_UploadingDocument
case .recordingVoice:
stringValue = strings.Activity_RecordingAudio
case .uploadingPhoto:
stringValue = strings.Activity_UploadingPhoto
case .uploadingVideo:
stringValue = strings.Activity_UploadingVideo
case .playingGame:
stringValue = strings.Activity_PlayingGame
case .recordingInstantVideo:
stringValue = strings.Activity_RecordingVideoMessage
case .uploadingInstantVideo:
stringValue = strings.Activity_UploadingVideoMessage
case .choosingSticker:
stringValue = strings.Activity_ChoosingSticker
case let .seeingEmojiInteraction(emoticon):
stringValue = strings.Activity_EnjoyingAnimations(emoticon).string
case .speakingInGroupCall, .interactingWithEmoji:
stringValue = ""
}
} else {
if inputActivities.count > 1 {
let peerTitle = EnginePeer(inputActivities[0].0).compactDisplayTitle
if inputActivities.count == 2 {
let secondPeerTitle = EnginePeer(inputActivities[1].0).compactDisplayTitle
stringValue = strings.Chat_MultipleTypingPair(peerTitle, secondPeerTitle).string
} else {
stringValue = strings.Chat_MultipleTypingMore(peerTitle, String(inputActivities.count - 1)).string
}
} else if let (peer, _) = inputActivities.first {
stringValue = EnginePeer(peer).compactDisplayTitle
}
}
let color = titleTheme.rootController.navigationBar.accentTextColor
let string = NSAttributedString(string: stringValue, font: subtitleFont, textColor: color)
switch mergedActivity {
case .typingText:
state = .typingText(string, color)
case .recordingVoice:
state = .recordingVoice(string, color)
case .recordingInstantVideo:
state = .recordingVideo(string, color)
case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo:
state = .uploading(string, color)
case .playingGame:
state = .playingGame(string, color)
case .speakingInGroupCall, .interactingWithEmoji:
state = .typingText(string, color)
case .choosingSticker:
state = .choosingSticker(string, color)
case .seeingEmojiInteraction:
state = .choosingSticker(string, color)
}
} else {
if let titleContent = self.titleContent {
switch titleContent {
case let .peer(peerView, _, onlineMemberCount, isScheduledMessages):
if let peer = peerViewMainPeer(peerView) {
let servicePeer = isServicePeer(peer)
if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies {
let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let user = peer as? TelegramUser {
if user.isDeleted {
state = .none
} else if servicePeer {
let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if user.flags.contains(.isSupport) {
let statusText = self.strings.Bot_GenericSupportStatus
let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let _ = user.botInfo {
let statusText = self.strings.Bot_GenericBotStatus
let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let peer = peerViewMainPeer(peerView) {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let userPresence: TelegramUserPresence
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence {
userPresence = presence
self.presenceManager?.reset(presence: EnginePeer.Presence(presence))
} else {
userPresence = TelegramUserPresence(status: .none, lastActivity: 0)
}
let (string, activity) = stringAndActivityForUserPresence(strings: self.strings, dateTimeFormat: self.dateTimeFormat, presence: EnginePeer.Presence(userPresence), relativeTo: Int32(timestamp))
let attributedString = NSAttributedString(string: string, font: subtitleFont, textColor: activity ? titleTheme.rootController.navigationBar.accentTextColor : titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(attributedString, activity ? .online : .lastSeenTime)
} else {
let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
} else if let group = peer as? TelegramGroup {
var onlineCount = 0
if let cachedGroupData = peerView.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
for participant in participants.participants {
if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence {
let relativeStatus = relativeUserPresenceStatus(EnginePeer.Presence(presence), relativeTo: Int32(timestamp))
switch relativeStatus {
case .online:
onlineCount += 1
default:
break
}
}
}
}
if onlineCount > 1 {
let string = NSMutableAttributedString()
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
state = .info(string, .generic)
} else {
let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
} else if let channel = peer as? TelegramChannel {
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
if memberCount == 0 {
let string: NSAttributedString
if case .group = channel.info {
string = NSAttributedString(string: strings.Group_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
} else {
string = NSAttributedString(string: strings.Channel_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
}
state = .info(string, .generic)
} else {
if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
let string = NSMutableAttributedString()
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
state = .info(string, .generic)
} else {
let membersString: String
if case .group = channel.info {
membersString = strings.Conversation_StatusMembers(memberCount)
} else {
membersString = strings.Conversation_StatusSubscribers(memberCount)
}
let string = NSAttributedString(string: membersString, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
}
} else {
switch channel.info {
case .group:
let string = NSAttributedString(string: strings.Group_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
case .broadcast:
let string = NSAttributedString(string: strings.Channel_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
}
}
}
case let .custom(_, subtitle?, _):
let string = NSAttributedString(string: subtitle, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
default:
break
}
var accessibilityText = ""
for segment in self.titleTextNode.segments {
switch segment {
case let .number(_, string):
accessibilityText.append(string.string)
case let .text(_, string):
accessibilityText.append(string.string)
}
}
self.accessibilityLabel = accessibilityText
self.accessibilityValue = state.string
} else {
self.accessibilityLabel = nil
}
}
}
if self.activityNode.transitionToState(state, animation: .slide) {
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.3, curve: .spring))
}
return true
} else {
return false
}
}
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) {
self.context = context
self.theme = theme
self.strings = strings
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.animationCache = animationCache
self.animationRenderer = animationRenderer
self.contentContainer = ASDisplayNode()
self.titleContainerView = PortalSourceView()
self.titleTextNode = ImmediateAnimatedCountLabelNode()
self.titleLeftIconNode = ASImageNode()
self.titleLeftIconNode.isLayerBacked = true
self.titleLeftIconNode.displayWithoutProcessing = true
self.titleLeftIconNode.displaysAsynchronously = false
self.titleRightIconNode = ASImageNode()
self.titleRightIconNode.isLayerBacked = true
self.titleRightIconNode.displayWithoutProcessing = true
self.titleRightIconNode.displaysAsynchronously = false
self.titleCredibilityIconView = ComponentHostView()
self.titleCredibilityIconView.isUserInteractionEnabled = false
self.activityNode = ChatTitleActivityNode()
self.button = HighlightTrackingButtonNode()
super.init(frame: CGRect())
self.isAccessibilityElement = true
self.accessibilityTraits = .header
self.addSubnode(self.contentContainer)
self.titleContainerView.addSubnode(self.titleTextNode)
self.contentContainer.view.addSubview(self.titleContainerView)
self.contentContainer.addSubnode(self.activityNode)
self.addSubnode(self.button)
self.presenceManager = PeerPresenceStatusManager(update: { [weak self] in
let _ = self?.updateStatus()
})
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: [.touchUpInside])
self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.titleTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.activityNode.layer.removeAnimation(forKey: "opacity")
strongSelf.titleCredibilityIconView.layer.removeAnimation(forKey: "opacity")
strongSelf.titleTextNode.alpha = 0.4
strongSelf.activityNode.alpha = 0.4
strongSelf.titleCredibilityIconView.alpha = 0.4
} else {
strongSelf.titleTextNode.alpha = 1.0
strongSelf.activityNode.alpha = 1.0
strongSelf.titleCredibilityIconView.alpha = 1.0
strongSelf.titleTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.button.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:))))
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func layoutSubviews() {
super.layoutSubviews()
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
}
}
public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, hasEmbeddedTitleContent: Bool) {
self.theme = theme
self.hasEmbeddedTitleContent = hasEmbeddedTitleContent
self.strings = strings
let titleContent = self.titleContent
self.titleCredibilityIcon = .none
self.titleContent = titleContent
let _ = self.updateStatus()
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
}
}
public func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, clearBounds)
self.button.frame = clearBounds
self.contentContainer.frame = clearBounds
var leftIconWidth: CGFloat = 0.0
var rightIconWidth: CGFloat = 0.0
var credibilityIconWidth: CGFloat = 0.0
if let image = self.titleLeftIconNode.image {
if self.titleLeftIconNode.supernode == nil {
self.titleTextNode.addSubnode(self.titleLeftIconNode)
}
leftIconWidth = image.size.width + 6.0
} else if self.titleLeftIconNode.supernode != nil {
self.titleLeftIconNode.removeFromSupernode()
}
let titleCredibilityContent: EmojiStatusComponent.Content
switch self.titleCredibilityIcon {
case .none:
titleCredibilityContent = .none
case .premium:
titleCredibilityContent = .premium(color: self.theme.list.itemAccentColor)
case .verified:
titleCredibilityContent = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor, sizeType: .large)
case .fake:
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased())
case .scam:
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased())
case let .emojiStatus(emojiStatus):
titleCredibilityContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2))
}
let titleCredibilitySize = self.titleCredibilityIconView.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: self.context,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer,
content: titleCredibilityContent,
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: CGSize(width: 20.0, height: 20.0)
)
if self.titleCredibilityIcon != .none {
self.titleTextNode.view.addSubview(self.titleCredibilityIconView)
credibilityIconWidth = titleCredibilitySize.width + 3.0
} else {
if self.titleCredibilityIconView.superview != nil {
self.titleCredibilityIconView.removeFromSuperview()
}
}
if let image = self.titleRightIconNode.image {
if self.titleRightIconNode.supernode == nil {
self.titleTextNode.addSubnode(self.titleRightIconNode)
}
rightIconWidth = image.size.width + 3.0
} else if self.titleRightIconNode.supernode != nil {
self.titleRightIconNode.removeFromSupernode()
}
var titleTransition = transition
if self.titleContainerView.bounds.width.isZero {
titleTransition = .immediate
}
let titleSideInset: CGFloat = 3.0
var titleFrame: CGRect
if size.height > 40.0 {
var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), animated: titleTransition.isAnimated)
titleSize.width += credibilityIconWidth
let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .center)
let titleInfoSpacing: CGFloat = 0.0
if activitySize.height.isZero {
titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
if titleFrame.size.width < size.width {
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
}
titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame)
titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size))
} else {
let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing
titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
if titleFrame.size.width < size.width {
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
}
titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth)
titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame)
titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size))
var activityFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize)
if activitySize.width < size.width {
activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0)
}
self.activityNode.frame = activityFrame
}
if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)
}
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.width - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
if let image = self.titleRightIconNode.image {
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0 + UIScreenPixel, y: 6.0), size: image.size)
}
} else {
let titleSize = self.titleTextNode.updateLayout(size: CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height), animated: titleTransition.isAnimated)
let activitySize = self.activityNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height), alignment: .center)
let titleInfoSpacing: CGFloat = 8.0
let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing
titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
titleTransition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame)
titleTransition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size))
self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize)
if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size)
}
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
if let image = self.titleRightIconNode.image {
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size)
}
}
self.pointerInteraction = PointerInteraction(view: self, style: .rectangle(CGSize(width: titleFrame.width + 16.0, height: 40.0)))
}
@objc private func buttonPressed() {
self.pressed?()
}
@objc private func longPressGesture(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
self.longPressed?()
default:
break
}
}
public func animateLayoutTransition() {
UIView.transition(with: self, duration: 0.25, options: [.transitionCrossDissolve], animations: {
}, completion: nil)
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.isUserInteractionEnabled {
return nil
}
if self.button.frame.contains(point) {
return self.button.view
}
return super.hitTest(point, with: event)
}
public final class SnapshotState {
fileprivate let snapshotView: UIView
fileprivate init(snapshotView: UIView) {
self.snapshotView = snapshotView
}
}
public func prepareSnapshotState() -> SnapshotState {
let snapshotView = self.snapshotView(afterScreenUpdates: false)!
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)
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
snapshotView?.removeFromSuperview()
})
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
}
}

View File

@ -48,7 +48,7 @@ public final class EmojiStatusComponent: Component {
case verified(fillColor: UIColor, foregroundColor: UIColor, sizeType: SizeType)
case text(color: UIColor, string: String)
case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode)
case topic(title: String)
case topic(title: String, colorIndex: Int)
}
public let context: AccountContext
@ -223,7 +223,7 @@ public final class EmojiStatusComponent: Component {
} else {
iconImage = nil
}
case let .topic(title):
case let .topic(title, colorIndex):
func generateTopicIcon(backgroundColors: [UIColor], strokeColors: [UIColor]) -> UIImage? {
return generateImage(CGSize(width: 44.0, height: 44.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
@ -279,7 +279,18 @@ public final class EmojiStatusComponent: Component {
})
}
if let image = generateTopicIcon(backgroundColors: [UIColor(rgb: 0x6FB9F0), UIColor(rgb: 0x0261E4)], strokeColors: [UIColor(rgb: 0x026CB5), UIColor(rgb: 0x064BB7)]) {
let topicColors: [([UInt32], [UInt32])] = [
([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
let clippedIndex = colorIndex % topicColors.count
if let image = generateTopicIcon(backgroundColors: topicColors[clippedIndex].0.map(UIColor.init(rgb:)), strokeColors: topicColors[clippedIndex].1.map(UIColor.init(rgb:))) {
iconImage = image
} else {
iconImage = nil

View File

@ -106,7 +106,7 @@ private final class TitleFieldComponent: Component {
let titleCredibilityContent: EmojiStatusComponent.Content
if component.fileId == 0 {
titleCredibilityContent = .topic(title: String(component.text.prefix(1)))
titleCredibilityContent = .topic(title: String(component.text.prefix(1)), colorIndex: 0)
} else {
titleCredibilityContent = .animation(content: .customEmoji(fileId: component.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: component.placeholderColor, themeColor: component.accentColor, loopMode: .count(2))
}

View File

@ -13,6 +13,8 @@ import UniversalMediaPlayer
import GalleryUI
import HierarchyTrackingLayer
import AccountContext
import ComponentFlow
import EmojiStatusComponent
private let normalFont = avatarPlaceholderFont(size: 16.0)
private let smallFont = avatarPlaceholderFont(size: 12.0)
@ -26,6 +28,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
let avatarNode: AvatarNode
private var videoNode: UniversalVideoNode?
private let statusView: ComponentView<Empty>
private var videoContent: NativeVideoContent?
private let playbackStartDisposable = MetaDisposable()
private var cachedDataDisposable = MetaDisposable()
@ -54,6 +58,7 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
override init() {
self.containerNode = ContextControllerSourceNode()
self.avatarNode = AvatarNode(font: normalFont)
self.statusView = ComponentView()
super.init()
@ -81,6 +86,31 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
self.view.isOpaque = false
}
public func setStatus(context: AccountContext, content: EmojiStatusComponent.Content) {
let statusSize = self.statusView.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: content,
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: CGSize(width: 32.0, height: 32.0)
)
if let statusComponentView = self.statusView.view {
if statusComponentView.superview == nil {
self.containerNode.view.addSubview(statusComponentView)
}
statusComponentView.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - statusSize.width) / 2.0), y: floor((self.containerNode.bounds.height - statusSize.height) / 2.0)), size: statusSize)
}
self.avatarNode.isHidden = true
}
public func setPeer(context: AccountContext, theme: PresentationTheme, peer: EnginePeer?, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, clipStyle: AvatarNodeClipStyle = .round, synchronousLoad: Bool = false, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), storeUnrounded: Bool = false) {
self.context = context
self.avatarNode.setPeer(context: context, theme: theme, peer: peer, authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: displayDimensions, storeUnrounded: storeUnrounded)

View File

@ -453,7 +453,14 @@ final class ChatBotInfoItemNode: ListViewItemNode {
case let .url(url, concealed):
self.item?.controllerInteraction.openUrl(url, concealed, nil, nil)
case let .peerMention(peerId, _):
self.item?.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil)
if let item = self.item {
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
if let peer = peer {
self?.item?.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
}
})
}
case let .textMention(name):
self.item?.controllerInteraction.openPeerMention(name)
case let .botCommand(command):

View File

@ -242,12 +242,14 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
botPeer = message.author
}
var peerId: PeerId?
var peer: Peer?
if samePeer {
peerId = message.id.peerId
peer = message.peers[message.id.peerId]
} else {
peer = botPeer
}
if let botPeer = botPeer, let addressName = botPeer.addressName {
self.controllerInteraction.openPeer(peerId, .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: "@\(addressName) \(query)")), subject: nil, peekData: nil), nil, false, nil)
if let peer = peer, let botPeer = botPeer, let addressName = botPeer.addressName {
self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: "@\(addressName) \(query)")), subject: nil, peekData: nil), nil, false)
}
}
case .payment:
@ -259,7 +261,13 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
case let .setupPoll(isQuiz):
self.controllerInteraction.openPollCreation(isQuiz)
case let .openUserProfile(peerId):
self.controllerInteraction.openPeer(peerId, .info, nil, false, nil)
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
self.controllerInteraction.openPeer(peer, .info, nil, false)
})
case let .openWebView(url, simple):
self.controllerInteraction.openWebView(markupButton.title, url, simple, false)
}

View File

@ -81,6 +81,8 @@ import ImageTransparency
import StickerPackPreviewUI
import TextNodeWithEntities
import EntityKeyboard
import ChatTitleView
import EmojiStatusComponent
#if DEBUG
import os.signpost
@ -258,6 +260,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var rightNavigationButton: ChatNavigationButton?
private var chatInfoNavigationButton: ChatNavigationButton?
private var moreBarButton: MoreHeaderButton
private var moreInfoNavigationButton: ChatNavigationButton?
private var peerView: PeerView?
private var historyStateDisposable: Disposable?
@ -590,6 +595,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
}
self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor)
self.moreBarButton.isUserInteractionEnabled = true
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource, groupCallPanelSource: groupCallPanelSource)
self.automaticallyControlPresentationContextLayout = false
@ -831,7 +840,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}, openUrl: { url in
self?.openUrl(url, concealed: false, skipConcealedAlert: isLocation, message: nil)
}, openPeer: { peer, navigation in
self?.openPeer(peerId: peer.id, navigation: navigation, fromMessage: nil)
self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil)
}, callPeer: { peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId, isVideo)
}, enqueueMessage: { message in
@ -892,9 +901,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
strongSelf.controllerInteraction?.openPeerMention(mention)
}
}, openPeer: { [weak self] peerId in
}, openPeer: { [weak self] peer in
if let strongSelf = self {
strongSelf.controllerInteraction?.openPeer(peerId, .default, nil, false, nil)
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false)
}
}, openHashtag: { [weak self] peerName, hashtag in
if let strongSelf = self {
@ -963,8 +972,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
})))
}, openPeer: { [weak self] id, navigation, fromMessage, isReaction, _ in
self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: isReaction ? fromMessage?.id : nil)
}, openPeer: { [weak self] peer, navigation, fromMessage, isReaction in
self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: isReaction ? fromMessage?.id : nil)
}, openPeerMention: { [weak self] name in
self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
@ -1406,13 +1415,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
availableReactions: availableReactions,
animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
animationRenderer: strongSelf.controllerInteraction!.presentationContext.animationRenderer,
message: EngineMessage(message), reaction: value, readStats: nil, back: nil, openPeer: { id in
message: EngineMessage(message), reaction: value, readStats: nil, back: nil, openPeer: { peer in
dismissController?({
guard let strongSelf = self else {
return
}
strongSelf.openPeer(peerId: id, navigation: .default, fromMessage: MessageReference(message), fromReactionMessageId: message.id)
strongSelf.openPeer(peer: peer, navigation: .default, fromMessage: MessageReference(message), fromReactionMessageId: message.id)
})
})))
@ -2318,9 +2327,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
if let botStart = strongSelf.botStart, case let .automatic(returnToPeerId, scheduled) = botStart.behavior {
strongSelf.openPeer(peerId: returnToPeerId, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: scheduled ? .scheduledMessages : nil, peekData: nil), fromMessage: nil)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: returnToPeerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: scheduled ? .scheduledMessages : nil, peekData: nil), fromMessage: nil)
}
})
} else {
strongSelf.openPeer(peerId: peerId, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil)
if let peerId = peerId {
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil)
}
})
}
}
}, openUrl: { [weak self] url, concealed, _, message in
if let strongSelf = self {
@ -2661,7 +2682,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.openPeer(peerId: peerId, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
}
})
}
}))
if !mention.isEmpty {
@ -3578,14 +3604,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
self?.openPeer(peerId: peer.id, navigation: .info, fromMessage: nil)
self?.openPeer(peer: peer, navigation: .info, fromMessage: nil)
}))
]
items.append(.action(ContextMenuActionItem(text: isChannel ? strongSelf.presentationData.strings.Conversation_ContextMenuOpenChannel : strongSelf.presentationData.strings.Conversation_ContextMenuSendMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isChannel ? "Chat/Context Menu/Channels" : "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
self?.openPeer(peerId: peer.id, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
self?.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
})))
if !isChannel && canSendMessagesToChat(strongSelf.presentationInterfaceState) {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuMention, icon: { theme in
@ -4114,6 +4140,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction)
self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo(expandAvatar: true), buttonItem: chatInfoButtonItem)
self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor)))
self.moreInfoNavigationButton = ChatNavigationButton(action: .toggleInfoPanel, buttonItem: UIBarButtonItem(customDisplayNode: self.moreBarButton)!)
self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in
guard let self = self else {
return
}
guard case let .peer(peerId) = self.chatLocation else {
return
}
ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture)
}
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
self.navigationItem.titleView = self.chatTitleView
self.chatTitleView?.pressed = { [weak self] in
self?.navigationButtonAction(.openChatInfo(expandAvatar: false))
@ -4338,7 +4377,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case .pinnedMessages = presentationInterfaceState.subject {
strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
} else {
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages)
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages)
let imageOverride: AvatarNodeImageOverride?
if strongSelf.context.account.peerId == peer.id {
imageOverride = .savedMessagesIcon
@ -4369,9 +4408,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.hasScheduledMessages = hasScheduledMessages
var upgradedToPeerId: PeerId?
var movedToForumTopics = false
if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference {
upgradedToPeerId = migrationReference.peerId
}
if let previous = strongSelf.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, !channel.flags.contains(.isForum), let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.flags.contains(.isForum) {
movedToForumTopics = true
}
var shouldDismiss = false
if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.membership != .Removed, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, updatedGroup.membership == .Removed {
@ -4661,6 +4704,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
navigationController.setViewControllers(viewControllers, animated: false)
}
}
} else if movedToForumTopics {
if let navigationController = strongSelf.effectiveNavigationController {
let chatListController = strongSelf.context.sharedContext.makeChatListController(context: strongSelf.context, location: .forum(peerId: peerView.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
navigationController.replaceController(strongSelf, with: chatListController, animated: true)
}
} else if shouldDismiss {
strongSelf.dismiss()
}
@ -4685,13 +4733,65 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let peerView = context.account.viewTracker.peerView(peerId)
let messageAndTopic = messagePromise.get()
|> mapToSignal { message -> Signal<(message: Message?, threadInfo: EngineMessageHistoryThread.Info?), NoError> in
guard let message = message else {
return .single((nil, nil))
}
let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: Int64(message.id.id))
return context.account.postbox.combinedView(keys: [viewKey])
|> map { views -> (message: Message?, threadInfo: EngineMessageHistoryThread.Info?) in
guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else {
return (message, nil)
}
return (message, view.info?.get(MessageHistoryThreadData.self)?.info)
}
}
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
if peerId.namespace == Namespaces.Peer.CloudChannel {
let recentOnlineSignal: Signal<Int32?, NoError> = peerView
|> map { view -> Bool? in
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
if case .broadcast = peer.info {
return nil
} else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 {
return true
} else {
return false
}
} else {
return false
}
}
|> distinctUntilChanged
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
if let isLarge = isLarge {
if isLarge {
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init)
} else {
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init)
}
} else {
return .single(nil)
}
}
onlineMemberCount = recentOnlineSignal
}
self.titleDisposable.set(nil)
self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(),
peerView,
messagePromise.get()
messageAndTopic,
onlineMemberCount
)
|> deliverOnMainQueue).start(next: { [weak self] peerView, message in
|> deliverOnMainQueue).start(next: { [weak self] peerView, messageAndTopic, onlineMemberCount in
if let strongSelf = self {
let message = messageAndTopic.message
var count = 0
if let message = message {
for attribute in message.attributes {
@ -4702,7 +4802,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
strongSelf.chatTitleView?.titleContent = .replyThread(type: replyThreadType, count: count)
if let threadInfo = messageAndTopic.threadInfo, let message = message {
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false)
let avatarContent: EmojiStatusComponent.Content
if let fileId = threadInfo.icon {
avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .count(2))
} else {
avatarContent = .topic(title: String(threadInfo.title.prefix(1)), colorIndex: Int(message.id.id))
}
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setStatus(context: strongSelf.context, content: avatarContent)
} else {
strongSelf.chatTitleView?.titleContent = .replyThread(type: replyThreadType, count: count)
}
let firstTime = strongSelf.peerView == nil
strongSelf.peerView = peerView
@ -5866,7 +5978,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation {
pinnedMessageId = replyThreadMessageId.effectiveTopId
if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
pinnedMessageId = nil
} else {
pinnedMessageId = replyThreadMessageId.effectiveTopId
}
}
var pinnedMessage: ChatPinnedMessage?
@ -6012,12 +6128,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
|> distinctUntilChanged
let isForum = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> map { peer -> Bool in
if case let .channel(channel) = peer {
return channel.flags.contains(.isForum)
} else {
return false
}
}
|> distinctUntilChanged
self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages,
hasPendingMessages,
isTopReplyThreadMessageShown,
topPinnedMessage,
customEmojiAvailable
).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, customEmojiAvailable in
customEmojiAvailable,
isForum
).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, customEmojiAvailable, isForum in
if let strongSelf = self {
let (cachedData, messages) = cachedDataAndMessages
@ -6073,14 +6200,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var pinnedMessage: ChatPinnedMessage?
switch strongSelf.chatLocation {
case let .replyThread(replyThreadMessage):
if isTopReplyThreadMessageShown {
if isForum {
pinnedMessageId = nil
} else {
pinnedMessageId = replyThreadMessage.effectiveTopId
}
if let pinnedMessageId = pinnedMessageId {
if let message = messages?[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id)
if isTopReplyThreadMessageShown {
pinnedMessageId = nil
} else {
pinnedMessageId = replyThreadMessage.effectiveTopId
}
if let pinnedMessageId = pinnedMessageId {
if let message = messages?[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id)
}
}
}
case .peer:
@ -7703,7 +7834,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else {
return
}
strongSelf.openPeer(peerId: peerId, navigation: .default, fromMessage: nil)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.openPeer(peer: peer, navigation: .default, fromMessage: nil)
}
})
}, openPeerInfo: { [weak self] in
self?.navigationButtonAction(.openChatInfo(expandAvatar: false))
}, togglePeerNotifications: { [weak self] in
@ -7765,7 +7901,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
isScheduled = true
}
strongSelf.openPeer(peerId: peerId, navigation: .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .automatic(returnToPeerId: currentPeerId, scheduled: isScheduled))), fromMessage: nil)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.openPeer(peer: peer, navigation: .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .automatic(returnToPeerId: currentPeerId, scheduled: isScheduled))), fromMessage: nil)
}
})
}
}, beginMediaRecording: { [weak self] isVideo in
guard let strongSelf = self else {
@ -10554,7 +10695,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.leftNavigationButton = nil
}
if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton) {
if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton, moreInfoNavigationButton: self.moreInfoNavigationButton) {
if self.rightNavigationButton != button {
var animated = transition.isAnimated
if let currentButton = self.rightNavigationButton?.action, currentButton == button.action {
@ -10680,6 +10821,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
@objc private func moreButtonPressed() {
self.moreBarButton.play()
self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
}
func beginClearHistory(type: InteractiveHistoryClearingType) {
guard case let .peer(peerId) = self.chatLocation else {
return
@ -12799,7 +12945,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case let .mention(peerId, mention):
switch action {
case .tap:
strongSelf.controllerInteraction?.openPeer(peerId, .default, nil, false, nil)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false)
}
})
case .longTap:
strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
}
@ -12887,7 +13038,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case let .mention(peerId, mention):
switch action {
case .tap:
strongSelf.controllerInteraction?.openPeer(peerId, .default, nil, false, nil)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false)
}
})
case .longTap:
strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
}
@ -12996,7 +13152,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case let .mention(peerId, mention):
switch action {
case .tap:
strongSelf.controllerInteraction?.openPeer(peerId, .default, nil, false, nil)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false)
}
})
case .longTap:
strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
}
@ -14980,9 +15141,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
private func openPeer(peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false) {
private func openPeer(peer: EnginePeer?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false) {
let _ = self.presentVoiceMessageDiscardAlert(action: {
if case let .peer(currentPeerId) = self.chatLocation, peerId == currentPeerId {
if case let .peer(currentPeerId) = self.chatLocation, peer?.id == currentPeerId {
switch navigation {
case .info:
self.navigationButtonAction(.openChatInfo(expandAvatar: expandAvatar))
@ -15006,7 +15167,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
break
}
} else {
if let peerId = peerId {
if let peer = peer {
do {
var chatPeerId: PeerId?
if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramGroup {
@ -15019,9 +15180,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .info, .default:
let peerSignal: Signal<Peer?, NoError>
if let messageId = fromMessage?.id {
peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peerId, messageId: messageId)
peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peer.id, messageId: messageId)
} else {
peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init)
peerSignal = self.context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init)
}
self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, let peer = peer {
@ -15046,22 +15207,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
case let .chat(textInputState, subject, peekData):
if let textInputState = textInputState {
let _ = (ChatInterfaceState.update(engine: self.context.engine, peerId: peerId, threadId: nil, { currentState in
let _ = (ChatInterfaceState.update(engine: self.context.engine, peerId: peer.id, threadId: nil, { currentState in
return currentState.withUpdatedComposeInputState(textInputState)
})
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: subject, updateTextInputState: textInputState, peekData: peekData))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, updateTextInputState: textInputState, peekData: peekData))
}
})
} else {
self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peerId), subject: subject))
self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peer.id), subject: subject))
}
case let .withBotStartPayload(botStart):
self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peerId), botStart: botStart))
self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peer.id), botStart: botStart))
case let .withAttachBot(attachBotStart):
if let navigationController = self.effectiveNavigationController {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart))
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: peer.id), attachBotStart: attachBotStart))
}
}
}
@ -15175,7 +15336,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
navigation = .chat(textInputState: nil, subject: nil, peekData: nil)
}
}
strongSelf.openResolved(result: .peer(peer.id, navigation), sourceMessageId: sourceMessageId)
strongSelf.openResolved(result: .peer(peer, navigation), sourceMessageId: sourceMessageId)
} else {
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
@ -15505,17 +15666,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch navigation {
case let .chat(_, subject, peekData):
if case .peer(peerId) = strongSelf.chatLocation {
if case .peer(peerId.id) = strongSelf.chatLocation {
if let subject = subject, case let .message(messageSubject, _, timecode) = subject {
if case let .id(messageId) = messageSubject {
strongSelf.navigateToMessage(from: sourceMessageId, to: .id(messageId, timecode))
}
}
} else if let navigationController = strongSelf.effectiveNavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: subject, keepStack: .always, peekData: peekData))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId.id), subject: subject, keepStack: .always, peekData: peekData))
}
case .info:
strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId)
strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId.id)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil {
@ -15525,16 +15686,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}))
case let .withBotStartPayload(startPayload):
if case .peer(peerId) = strongSelf.chatLocation {
if case .peer(peerId.id) = strongSelf.chatLocation {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
$0.updatedBotStartPayload(startPayload.payload)
})
} else if let navigationController = strongSelf.effectiveNavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: startPayload, keepStack: .always))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId.id), botStart: startPayload, keepStack: .always))
}
case let .withAttachBot(attachBotStart):
if let navigationController = strongSelf.effectiveNavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId.id), attachBotStart: attachBotStart))
}
default:
break

View File

@ -61,7 +61,7 @@ struct UnreadMessageRangeKey: Hashable {
public final class ChatControllerInteraction {
let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool, Peer?) -> Void
let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool) -> Void
let openPeerMention: (String) -> Void
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void
let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void
@ -167,7 +167,7 @@ public final class ChatControllerInteraction {
init(
openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool,
openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool, Peer?) -> Void,
openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool) -> Void,
openPeerMention: @escaping (String) -> Void,
openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void,
openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void,

View File

@ -20,6 +20,7 @@ import SparseItemGrid
import ChatPresentationInterfaceState
import ChatInputPanelContainer
import PremiumUI
import ChatTitleView
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
let itemNode: OverlayMediaItemNode

View File

@ -238,9 +238,11 @@ func chatHistoryEntriesForView(
entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id], isPlaying: false, isCentered: false)), at: 0)
}
let replyCount = view.entries.isEmpty ? 0 : 1
entries.insert(.ReplyCountEntry(messages[0].index, replyThreadMessage.isChannelPost, replyCount, presentationData), at: 1)
if !replyThreadMessage.isForumPost {
let replyCount = view.entries.isEmpty ? 0 : 1
entries.insert(.ReplyCountEntry(messages[0].index, replyThreadMessage.isChannelPost, replyCount, presentationData), at: 1)
}
}
break loop
default:

View File

@ -1599,7 +1599,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: hasReadReports, stats: readStats, action: { c, f, stats, customReactionEmojiPacks, firstCustomEmojiReaction in
if reactionCount == 0, let stats = stats, stats.peers.count == 1 {
c.dismiss(completion: {
controllerInteraction.openPeer(stats.peers[0].id, .default, nil, false, nil)
controllerInteraction.openPeer(stats.peers[0], .default, nil, false)
})
} else if (stats != nil && !stats!.peers.isEmpty) || reactionCount != 0 {
var tip: ContextController.Tip?
@ -1641,9 +1641,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
back: { [weak c] in
c?.popItems()
},
openPeer: { [weak c] id in
openPeer: { [weak c] peer in
c?.dismiss(completion: {
controllerInteraction.openPeer(id, .default, MessageReference(message), true, nil)
controllerInteraction.openPeer(peer, .default, MessageReference(message), true)
})
}
)), tip: tip)))

View File

@ -76,7 +76,7 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
return nil
}
func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?, chatInfoNavigationButton: ChatNavigationButton?) -> ChatNavigationButton? {
func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?, chatInfoNavigationButton: ChatNavigationButton?, moreInfoNavigationButton: ChatNavigationButton?) -> ChatNavigationButton? {
if let _ = presentationInterfaceState.interfaceState.selectionState {
if case .forwardedMessages = presentationInterfaceState.subject {
return nil
@ -90,6 +90,13 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch
}
}
if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum), let moreInfoNavigationButton = moreInfoNavigationButton {
if case .replyThread = presentationInterfaceState.chatLocation {
} else {
return moreInfoNavigationButton
}
}
var hasMessages = false
if let chatHistoryState = presentationInterfaceState.chatHistoryState {
if case .loaded(false) = chatHistoryState {
@ -106,7 +113,8 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch
}
if case .replyThread = presentationInterfaceState.chatLocation {
if hasMessages {
if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
} else if hasMessages {
if case .search = currentButton?.action {
return currentButton
} else {

View File

@ -1993,7 +1993,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let peer = forwardInfo.source ?? forwardInfo.author {
item.controllerInteraction.openPeer(peer.id, peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil)
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
}

View File

@ -3295,7 +3295,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
})
} else {
return .optionalAction({
item.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, item.message.peers[peerId])
if let peer = item.message.peers[peerId] {
item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
}
})
}
}
@ -3327,7 +3329,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let peer = forwardInfo.source ?? forwardInfo.author {
item.controllerInteraction.openPeer(peer.id, peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil)
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
}
@ -3365,8 +3367,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.item?.controllerInteraction.openUrl(url, concealed, nil, self.item?.content.firstMessage)
})
case let .peerMention(peerId, _):
return .action({
self.item?.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil)
return .action({ [weak self] in
if let item = self?.item {
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let self = self, let item = self.item, let peer = peer {
item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
}
})
}
})
case let .textMention(name):
return .action({

View File

@ -635,11 +635,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
if case .ended = recognizer.state {
if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, id, _, _, _) = self.messageReference?.content {
self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame)
} else {
if let channel = self.peer as? TelegramChannel, case .broadcast = channel.info {
self.controllerInteraction.openPeer(self.peerId, .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, false, nil)
} else if let peer = self.peer {
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, false)
} else {
self.controllerInteraction.openPeer(self.peerId, .info, self.messageReference, false, nil)
self.controllerInteraction.openPeer(EnginePeer(peer), .info, self.messageReference, false)
}
}
}

View File

@ -928,7 +928,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
}
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let peer = forwardInfo.source ?? forwardInfo.author {
item.controllerInteraction.openPeer(peer.id, peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil)
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
}

View File

@ -9,6 +9,7 @@ import LocalizedPeerData
import ContextUI
import ChatListUI
import TelegramPresentationData
import SwiftSignalKit
struct ChatMessageItemWidthFill {
var compactInset: CGFloat
@ -851,7 +852,12 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
case .setupPoll:
break
case let .openUserProfile(peerId):
item.controllerInteraction.openPeer(peerId, .info, nil, false, nil)
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let peer = peer {
item.controllerInteraction.openPeer(peer, .info, nil, false)
}
})
case let .openWebView(url, simple):
item.controllerInteraction.openWebView(button.title, url, simple, false)
}

View File

@ -76,7 +76,12 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
}
navigationData = .chat(textInputState: nil, subject: subject, peekData: nil)
}
item.controllerInteraction.openPeer(id, navigationData, nil, false, nil)
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: id))
|> deliverOnMainQueue).start(next: { peer in
if let peer = peer {
item.controllerInteraction.openPeer(peer, navigationData, nil, false)
}
})
case let .join(_, joinHash):
item.controllerInteraction.openJoinLink(joinHash)
}

View File

@ -851,7 +851,12 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
case .setupPoll:
break
case let .openUserProfile(peerId):
controllerInteraction.openPeer(peerId, .info, nil, false, nil)
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let peer = peer {
controllerInteraction.openPeer(peer, .info, nil, false)
}
})
case let .openWebView(url, simple):
controllerInteraction.openWebView(button.title, url, simple, false)
}

View File

@ -221,7 +221,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openUrl: { url in
self?.openUrl(url)
}, openPeer: { peer, navigation in
self?.openPeer(peerId: peer.id, peer: peer)
self?.openPeer(peer: EnginePeer(peer))
}, callPeer: { peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId, isVideo)
}, enqueueMessage: { _ in
@ -248,9 +248,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, gallerySource: gallerySource))
}
return false
}, openPeer: { [weak self] peerId, _, message, _, peer in
if let peerId = peerId, peerId != context.account.peerId {
self?.openPeer(peerId: peerId, peer: peer)
}, openPeer: { [weak self] peer, _, message, _ in
if peer.id != context.account.peerId {
self?.openPeer(peer: peer)
}
}, openPeerMention: { [weak self] name in
self?.openPeerMention(name)
@ -376,7 +376,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.openPeer(peerId: peerId, peer: nil)
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.openPeer(peer: peer)
}
})
}
}))
if !mention.isEmpty {
@ -762,13 +767,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.eventLogContext.setFilter(self.filter)
}
private func openPeer(peerId: PeerId, peer: Peer?, peekData: ChatPeekTimeout? = nil) {
let peerSignal: Signal<Peer?, NoError>
if let peer = peer {
peerSignal = .single(peer)
} else {
peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init)
}
private func openPeer(peer: EnginePeer, peekData: ChatPeekTimeout? = nil) {
let peerSignal: Signal<Peer?, NoError> = .single(peer._asPeer())
self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, let peer = peer {
if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() {
@ -882,9 +882,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}
case .urlAuth:
break
case let .peer(peerId, _):
if let peerId = peerId {
strongSelf.openPeer(peerId: peerId, peer: nil)
case let .peer(peer, _):
if let peer = peer {
strongSelf.openPeer(peer: EnginePeer(peer))
}
case .inaccessiblePeer:
strongSelf.controllerInteraction.presentController(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
@ -892,9 +892,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break
case .groupBotStart:
break
case let .channelMessage(peerId, messageId, timecode):
case let .channelMessage(peer, messageId, timecode):
if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .message(id: .id(messageId), highlight: true, timecode: timecode)))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: .message(id: .id(messageId), highlight: true, timecode: timecode)))
}
case let .replyThreadMessage(replyThreadMessage, messageId):
if let navigationController = strongSelf.getNavigationController() {
@ -931,17 +931,17 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
case let .instantView(webpage, anchor):
strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourcePeerType: .channel, anchor: anchor))
case let .join(link):
strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peerId, peekData in
strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peer, peekData in
if let strongSelf = self {
strongSelf.openPeer(peerId: peerId, peer: nil, peekData: peekData)
strongSelf.openPeer(peer: peer, peekData: peekData)
}
}, parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil)
case let .localization(identifier):
strongSelf.presentController(LanguageLinkPreviewController(context: strongSelf.context, identifier: identifier), .window(.root), nil)
case .proxy, .confirmationCode, .cancelAccountReset, .share:
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), forceExternal: false, openPeer: { peerId, _ in
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), forceExternal: false, openPeer: { peer, _ in
if let strongSelf = self {
strongSelf.openPeer(peerId: peerId, peer: nil)
strongSelf.openPeer(peer: peer)
}
}, sendFile: nil,
sendSticker: nil,

View File

@ -1,843 +1 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import SwiftSignalKit
import LegacyComponents
import TelegramPresentationData
import TelegramUIPreferences
import ActivityIndicator
import TelegramStringFormatting
import PeerPresenceStatusManager
import ChatTitleActivityNode
import LocalizedPeerData
import PhoneNumberFormat
import ChatTitleActivityNode
import AnimatedCountLabelNode
import AccountContext
import ComponentFlow
import EmojiStatusComponent
import AnimationCache
import MultiAnimationRenderer
private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers])
private let subtitleFont = Font.regular(13.0)
enum ChatTitleContent {
enum ReplyThreadType {
case comments
case replies
}
case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool)
case replyThread(type: ReplyThreadType, count: Int)
case custom(String, String?, Bool)
}
private enum ChatTitleIcon {
case none
case lock
case mute
}
private enum ChatTitleCredibilityIcon: Equatable {
case none
case fake
case scam
case verified
case premium
case emojiStatus(PeerEmojiStatus)
}
final class ChatTitleView: UIView, NavigationBarTitleView {
private let context: AccountContext
private var theme: PresentationTheme
private var hasEmbeddedTitleContent: Bool = false
private var strings: PresentationStrings
private var dateTimeFormat: PresentationDateTimeFormat
private var nameDisplayOrder: PresentationPersonNameOrder
private let animationCache: AnimationCache
private let animationRenderer: MultiAnimationRenderer
private let contentContainer: ASDisplayNode
let titleContainerView: PortalSourceView
let titleTextNode: ImmediateAnimatedCountLabelNode
let titleLeftIconNode: ASImageNode
let titleRightIconNode: ASImageNode
let titleCredibilityIconView: ComponentHostView<Empty>
let activityNode: ChatTitleActivityNode
private let button: HighlightTrackingButtonNode
private var validLayout: (CGSize, CGRect)?
private var titleLeftIcon: ChatTitleIcon = .none
private var titleRightIcon: ChatTitleIcon = .none
private var titleCredibilityIcon: ChatTitleCredibilityIcon = .none
//private var networkStatusNode: ChatTitleNetworkStatusNode?
private var presenceManager: PeerPresenceStatusManager?
private var pointerInteraction: PointerInteraction?
var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? {
didSet {
let _ = self.updateStatus()
}
}
private func updateNetworkStatusNode(networkState: AccountNetworkState, layout: ContainerViewLayout?) {
self.setNeedsLayout()
}
var networkState: AccountNetworkState = .online(proxy: nil) {
didSet {
if self.networkState != oldValue {
updateNetworkStatusNode(networkState: self.networkState, layout: self.layout)
let _ = self.updateStatus()
}
}
}
var layout: ContainerViewLayout? {
didSet {
if self.layout != oldValue {
updateNetworkStatusNode(networkState: self.networkState, layout: self.layout)
}
}
}
var pressed: (() -> Void)?
var longPressed: (() -> Void)?
var titleContent: ChatTitleContent? {
didSet {
if let titleContent = self.titleContent {
let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme
var segments: [AnimatedCountLabelNode.Segment] = []
var titleLeftIcon: ChatTitleIcon = .none
var titleRightIcon: ChatTitleIcon = .none
var titleCredibilityIcon: ChatTitleCredibilityIcon = .none
var isEnabled = true
switch titleContent {
case let .peer(peerView, _, isScheduledMessages):
if peerView.peerId.isReplies {
let typeText: String = self.strings.DialogList_Replies
segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
isEnabled = false
} else if isScheduledMessages {
if peerView.peerId == self.context.account.peerId {
segments = [.text(0, NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
} else {
segments = [.text(0, NSAttributedString(string: self.strings.ScheduledMessages_Title, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
}
isEnabled = false
} else {
if let peer = peerViewMainPeer(peerView) {
if peerView.peerId == self.context.account.peerId {
segments = [.text(0, NSAttributedString(string: self.strings.Conversation_SavedMessages, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
} else {
if !peerView.peerIsContact, let user = peer as? TelegramUser, !user.flags.contains(.isSupport), user.botInfo == nil, let phone = user.phone, !phone.isEmpty {
segments = [.text(0, NSAttributedString(string: formatPhoneNumber(phone), font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
} else {
segments = [.text(0, NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
}
}
if peer.id != self.context.account.peerId {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
if peer.isFake {
titleCredibilityIcon = .fake
} else if peer.isScam {
titleCredibilityIcon = .scam
} else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
titleCredibilityIcon = .emojiStatus(emojiStatus)
} else if peer.isVerified {
titleCredibilityIcon = .verified
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
titleCredibilityIcon = .premium
}
}
}
if peerView.peerId.namespace == Namespaces.Peer.SecretChat {
titleLeftIcon = .lock
}
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
if titleCredibilityIcon != .verified {
titleRightIcon = .mute
}
}
}
}
case let .replyThread(type, count):
let textFont = titleFont
let textColor = titleTheme.rootController.navigationBar.primaryTextColor
if count > 0 {
var commentsPart: String
switch type {
case .comments:
commentsPart = self.strings.Conversation_TitleComments(Int32(count))
case .replies:
commentsPart = self.strings.Conversation_TitleReplies(Int32(count))
}
if commentsPart.contains("[") && commentsPart.contains("]") {
if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") {
commentsPart.removeSubrange(startIndex ... endIndex)
}
} else {
commentsPart = commentsPart.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,."))
}
let rawTextAndRanges: PresentationStrings.FormattedString
switch type {
case .comments:
rawTextAndRanges = self.strings.Conversation_TitleCommentsFormat("\(count)", commentsPart)
case .replies:
rawTextAndRanges = self.strings.Conversation_TitleRepliesFormat("\(count)", commentsPart)
}
let rawText = rawTextAndRanges.string
var textIndex = 0
var latestIndex = 0
for indexAndRange in rawTextAndRanges.ranges {
let index = indexAndRange.index
let range = indexAndRange.range
var lowerSegmentIndex = range.lowerBound
if index != 0 {
lowerSegmentIndex = min(lowerSegmentIndex, latestIndex)
} else {
if latestIndex < range.lowerBound {
let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex) ..< rawText.index(rawText.startIndex, offsetBy: range.lowerBound)])
segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor)))
textIndex += 1
}
}
latestIndex = range.upperBound
let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: lowerSegmentIndex) ..< rawText.index(rawText.startIndex, offsetBy: min(rawText.count, range.upperBound))])
if index == 0 {
segments.append(.number(count, NSAttributedString(string: part, font: textFont, textColor: textColor)))
} else {
segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor)))
textIndex += 1
}
}
if latestIndex < rawText.count {
let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex)...])
segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor)))
textIndex += 1
}
} else {
switch type {
case .comments:
segments = [.text(0, NSAttributedString(string: strings.Conversation_TitleCommentsEmpty, font: textFont, textColor: textColor))]
case .replies:
segments = [.text(0, NSAttributedString(string: strings.Conversation_TitleRepliesEmpty, font: textFont, textColor: textColor))]
}
}
isEnabled = false
case let .custom(text, _, enabled):
segments = [.text(0, NSAttributedString(string: text, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
isEnabled = enabled
}
var updated = false
if self.titleTextNode.segments != segments {
self.titleTextNode.segments = segments
updated = true
}
if titleLeftIcon != self.titleLeftIcon {
self.titleLeftIcon = titleLeftIcon
switch titleLeftIcon {
case .lock:
self.titleLeftIconNode.image = PresentationResourcesChat.chatTitleLockIcon(titleTheme)
default:
self.titleLeftIconNode.image = nil
}
updated = true
}
if titleCredibilityIcon != self.titleCredibilityIcon {
self.titleCredibilityIcon = titleCredibilityIcon
/*switch titleCredibilityIcon {
case .none:
self.titleCredibilityIconNode.image = nil
case .fake:
self.titleCredibilityIconNode.image = PresentationResourcesChatList.fakeIcon(titleTheme, strings: self.strings, type: .regular)
case .scam:
self.titleCredibilityIconNode.image = PresentationResourcesChatList.scamIcon(titleTheme, strings: self.strings, type: .regular)
case .verified:
self.titleCredibilityIconNode.image = PresentationResourcesChatList.verifiedIcon(titleTheme)
case .premium:
self.titleCredibilityIconNode.image = PresentationResourcesChatList.premiumIcon(titleTheme)
}*/
updated = true
}
if titleRightIcon != self.titleRightIcon {
self.titleRightIcon = titleRightIcon
switch titleRightIcon {
case .mute:
self.titleRightIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(titleTheme)
default:
self.titleRightIconNode.image = nil
}
updated = true
}
self.isUserInteractionEnabled = isEnabled
self.button.isUserInteractionEnabled = isEnabled
if !self.updateStatus() {
if updated {
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
}
}
}
}
private func updateStatus() -> Bool {
var inputActivitiesAllowed = true
if let titleContent = self.titleContent {
switch titleContent {
case let .peer(peerView, _, isScheduledMessages):
if let peer = peerViewMainPeer(peerView) {
if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies {
inputActivitiesAllowed = false
}
}
case .replyThread:
inputActivitiesAllowed = true
default:
inputActivitiesAllowed = false
}
}
let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme
var state = ChatTitleActivityNodeState.none
switch self.networkState {
case .waitingForNetwork, .connecting, .updating:
var infoText: String
switch self.networkState {
case .waitingForNetwork:
infoText = self.strings.ChatState_WaitingForNetwork
case .connecting:
infoText = self.strings.ChatState_Connecting
case .updating:
infoText = self.strings.ChatState_Updating
case .online:
infoText = ""
}
state = .info(NSAttributedString(string: infoText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor), .generic)
case .online:
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed {
var stringValue = ""
var mergedActivity = inputActivities[0].1
for (_, activity) in inputActivities {
if activity != mergedActivity {
mergedActivity = .typingText
break
}
}
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat {
switch mergedActivity {
case .typingText:
stringValue = strings.Conversation_typing
case .uploadingFile:
stringValue = strings.Activity_UploadingDocument
case .recordingVoice:
stringValue = strings.Activity_RecordingAudio
case .uploadingPhoto:
stringValue = strings.Activity_UploadingPhoto
case .uploadingVideo:
stringValue = strings.Activity_UploadingVideo
case .playingGame:
stringValue = strings.Activity_PlayingGame
case .recordingInstantVideo:
stringValue = strings.Activity_RecordingVideoMessage
case .uploadingInstantVideo:
stringValue = strings.Activity_UploadingVideoMessage
case .choosingSticker:
stringValue = strings.Activity_ChoosingSticker
case let .seeingEmojiInteraction(emoticon):
stringValue = strings.Activity_EnjoyingAnimations(emoticon).string
case .speakingInGroupCall, .interactingWithEmoji:
stringValue = ""
}
} else {
if inputActivities.count > 1 {
let peerTitle = EnginePeer(inputActivities[0].0).compactDisplayTitle
if inputActivities.count == 2 {
let secondPeerTitle = EnginePeer(inputActivities[1].0).compactDisplayTitle
stringValue = strings.Chat_MultipleTypingPair(peerTitle, secondPeerTitle).string
} else {
stringValue = strings.Chat_MultipleTypingMore(peerTitle, String(inputActivities.count - 1)).string
}
} else if let (peer, _) = inputActivities.first {
stringValue = EnginePeer(peer).compactDisplayTitle
}
}
let color = titleTheme.rootController.navigationBar.accentTextColor
let string = NSAttributedString(string: stringValue, font: subtitleFont, textColor: color)
switch mergedActivity {
case .typingText:
state = .typingText(string, color)
case .recordingVoice:
state = .recordingVoice(string, color)
case .recordingInstantVideo:
state = .recordingVideo(string, color)
case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo:
state = .uploading(string, color)
case .playingGame:
state = .playingGame(string, color)
case .speakingInGroupCall, .interactingWithEmoji:
state = .typingText(string, color)
case .choosingSticker:
state = .choosingSticker(string, color)
case .seeingEmojiInteraction:
state = .choosingSticker(string, color)
}
} else {
if let titleContent = self.titleContent {
switch titleContent {
case let .peer(peerView, onlineMemberCount, isScheduledMessages):
if let peer = peerViewMainPeer(peerView) {
let servicePeer = isServicePeer(peer)
if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies {
let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let user = peer as? TelegramUser {
if user.isDeleted {
state = .none
} else if servicePeer {
let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if user.flags.contains(.isSupport) {
let statusText = self.strings.Bot_GenericSupportStatus
let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let _ = user.botInfo {
let statusText = self.strings.Bot_GenericBotStatus
let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let peer = peerViewMainPeer(peerView) {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let userPresence: TelegramUserPresence
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence {
userPresence = presence
self.presenceManager?.reset(presence: EnginePeer.Presence(presence))
} else {
userPresence = TelegramUserPresence(status: .none, lastActivity: 0)
}
let (string, activity) = stringAndActivityForUserPresence(strings: self.strings, dateTimeFormat: self.dateTimeFormat, presence: EnginePeer.Presence(userPresence), relativeTo: Int32(timestamp))
let attributedString = NSAttributedString(string: string, font: subtitleFont, textColor: activity ? titleTheme.rootController.navigationBar.accentTextColor : titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(attributedString, activity ? .online : .lastSeenTime)
} else {
let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
} else if let group = peer as? TelegramGroup {
var onlineCount = 0
if let cachedGroupData = peerView.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
for participant in participants.participants {
if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence {
let relativeStatus = relativeUserPresenceStatus(EnginePeer.Presence(presence), relativeTo: Int32(timestamp))
switch relativeStatus {
case .online:
onlineCount += 1
default:
break
}
}
}
}
if onlineCount > 1 {
let string = NSMutableAttributedString()
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
state = .info(string, .generic)
} else {
let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
} else if let channel = peer as? TelegramChannel {
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
if memberCount == 0 {
let string: NSAttributedString
if case .group = channel.info {
string = NSAttributedString(string: strings.Group_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
} else {
string = NSAttributedString(string: strings.Channel_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
}
state = .info(string, .generic)
} else {
if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
let string = NSMutableAttributedString()
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
state = .info(string, .generic)
} else {
let membersString: String
if case .group = channel.info {
membersString = strings.Conversation_StatusMembers(memberCount)
} else {
membersString = strings.Conversation_StatusSubscribers(memberCount)
}
let string = NSAttributedString(string: membersString, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
}
} else {
switch channel.info {
case .group:
let string = NSAttributedString(string: strings.Group_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
case .broadcast:
let string = NSAttributedString(string: strings.Channel_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
}
}
}
case let .custom(_, subtitle?, _):
let string = NSAttributedString(string: subtitle, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
default:
break
}
var accessibilityText = ""
for segment in self.titleTextNode.segments {
switch segment {
case let .number(_, string):
accessibilityText.append(string.string)
case let .text(_, string):
accessibilityText.append(string.string)
}
}
self.accessibilityLabel = accessibilityText
self.accessibilityValue = state.string
} else {
self.accessibilityLabel = nil
}
}
}
if self.activityNode.transitionToState(state, animation: .slide) {
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.3, curve: .spring))
}
return true
} else {
return false
}
}
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) {
self.context = context
self.theme = theme
self.strings = strings
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.animationCache = animationCache
self.animationRenderer = animationRenderer
self.contentContainer = ASDisplayNode()
self.titleContainerView = PortalSourceView()
self.titleTextNode = ImmediateAnimatedCountLabelNode()
self.titleLeftIconNode = ASImageNode()
self.titleLeftIconNode.isLayerBacked = true
self.titleLeftIconNode.displayWithoutProcessing = true
self.titleLeftIconNode.displaysAsynchronously = false
self.titleRightIconNode = ASImageNode()
self.titleRightIconNode.isLayerBacked = true
self.titleRightIconNode.displayWithoutProcessing = true
self.titleRightIconNode.displaysAsynchronously = false
self.titleCredibilityIconView = ComponentHostView()
self.titleCredibilityIconView.isUserInteractionEnabled = false
self.activityNode = ChatTitleActivityNode()
self.button = HighlightTrackingButtonNode()
super.init(frame: CGRect())
self.isAccessibilityElement = true
self.accessibilityTraits = .header
self.addSubnode(self.contentContainer)
self.titleContainerView.addSubnode(self.titleTextNode)
self.contentContainer.view.addSubview(self.titleContainerView)
self.contentContainer.addSubnode(self.activityNode)
self.addSubnode(self.button)
self.presenceManager = PeerPresenceStatusManager(update: { [weak self] in
let _ = self?.updateStatus()
})
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: [.touchUpInside])
self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.titleTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.activityNode.layer.removeAnimation(forKey: "opacity")
strongSelf.titleCredibilityIconView.layer.removeAnimation(forKey: "opacity")
strongSelf.titleTextNode.alpha = 0.4
strongSelf.activityNode.alpha = 0.4
strongSelf.titleCredibilityIconView.alpha = 0.4
} else {
strongSelf.titleTextNode.alpha = 1.0
strongSelf.activityNode.alpha = 1.0
strongSelf.titleCredibilityIconView.alpha = 1.0
strongSelf.titleTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.button.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:))))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
}
}
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, hasEmbeddedTitleContent: Bool) {
self.theme = theme
self.hasEmbeddedTitleContent = hasEmbeddedTitleContent
self.strings = strings
let titleContent = self.titleContent
self.titleCredibilityIcon = .none
self.titleContent = titleContent
let _ = self.updateStatus()
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
}
}
func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, clearBounds)
self.button.frame = clearBounds
self.contentContainer.frame = clearBounds
var leftIconWidth: CGFloat = 0.0
var rightIconWidth: CGFloat = 0.0
var credibilityIconWidth: CGFloat = 0.0
if let image = self.titleLeftIconNode.image {
if self.titleLeftIconNode.supernode == nil {
self.titleTextNode.addSubnode(self.titleLeftIconNode)
}
leftIconWidth = image.size.width + 6.0
} else if self.titleLeftIconNode.supernode != nil {
self.titleLeftIconNode.removeFromSupernode()
}
let titleCredibilityContent: EmojiStatusComponent.Content
switch self.titleCredibilityIcon {
case .none:
titleCredibilityContent = .none
case .premium:
titleCredibilityContent = .premium(color: self.theme.list.itemAccentColor)
case .verified:
titleCredibilityContent = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor, sizeType: .large)
case .fake:
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased())
case .scam:
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased())
case let .emojiStatus(emojiStatus):
titleCredibilityContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2))
}
let titleCredibilitySize = self.titleCredibilityIconView.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: self.context,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer,
content: titleCredibilityContent,
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: CGSize(width: 20.0, height: 20.0)
)
if self.titleCredibilityIcon != .none {
self.titleTextNode.view.addSubview(self.titleCredibilityIconView)
credibilityIconWidth = titleCredibilitySize.width + 3.0
} else {
if self.titleCredibilityIconView.superview != nil {
self.titleCredibilityIconView.removeFromSuperview()
}
}
if let image = self.titleRightIconNode.image {
if self.titleRightIconNode.supernode == nil {
self.titleTextNode.addSubnode(self.titleRightIconNode)
}
rightIconWidth = image.size.width + 3.0
} else if self.titleRightIconNode.supernode != nil {
self.titleRightIconNode.removeFromSupernode()
}
let titleSideInset: CGFloat = 3.0
var titleFrame: CGRect
if size.height > 40.0 {
var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), animated: transition.isAnimated)
titleSize.width += credibilityIconWidth
let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .center)
let titleInfoSpacing: CGFloat = 0.0
if activitySize.height.isZero {
titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
if titleFrame.size.width < size.width {
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
}
transition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame)
transition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size))
} else {
let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing
titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
if titleFrame.size.width < size.width {
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
}
titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth)
transition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame)
transition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size))
var activityFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize)
if activitySize.width < size.width {
activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0)
}
self.activityNode.frame = activityFrame
}
if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)
}
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.width - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
if let image = self.titleRightIconNode.image {
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0 + UIScreenPixel, y: 6.0), size: image.size)
}
} else {
let titleSize = self.titleTextNode.updateLayout(size: CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height), animated: transition.isAnimated)
let activitySize = self.activityNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height), alignment: .center)
let titleInfoSpacing: CGFloat = 8.0
let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing
titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
transition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame)
transition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size))
self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize)
if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size)
}
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
if let image = self.titleRightIconNode.image {
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size)
}
}
self.pointerInteraction = PointerInteraction(view: self, style: .rectangle(CGSize(width: titleFrame.width + 16.0, height: 40.0)))
}
@objc private func buttonPressed() {
self.pressed?()
}
@objc private func longPressGesture(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
self.longPressed?()
default:
break
}
}
func animateLayoutTransition() {
UIView.transition(with: self, duration: 0.25, options: [.transitionCrossDissolve], animations: {
}, completion: nil)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.isUserInteractionEnabled {
return nil
}
if self.button.frame.contains(point) {
return self.button.view
}
return super.hitTest(point, with: event)
}
final class SnapshotState {
fileprivate let snapshotView: UIView
fileprivate init(snapshotView: UIView) {
self.snapshotView = snapshotView
}
}
func prepareSnapshotState() -> SnapshotState {
let snapshotView = self.snapshotView(afterScreenUpdates: false)!
return SnapshotState(
snapshotView: snapshotView
)
}
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)
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
snapshotView?.removeFromSuperview()
})
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
}
}

View File

@ -109,7 +109,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
var selectStickerImpl: ((FileMediaReference, UIView, CGRect) -> Bool)?
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in

View File

@ -45,7 +45,7 @@ private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatContr
}
}
func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)? = nil, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)? = nil, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
if case let .chat(_, maybeUpdatedPresentationData) = urlContext {
updatedPresentationData = maybeUpdatedPresentationData
@ -60,16 +60,16 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
requestMessageActionUrlAuth?(.url(url))
dismissInput()
break
case let .peer(peerId, navigation):
if let peerId = peerId {
openPeer(peerId, defaultNavigationForPeerId(peerId, navigation: navigation))
case let .peer(peer, navigation):
if let peer = peer {
openPeer(EnginePeer(peer), defaultNavigationForPeerId(peer.id, navigation: navigation))
} else {
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
case .inaccessiblePeer:
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
case let .botStart(peerId, payload):
openPeer(peerId, .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)))
case let .botStart(peer, payload):
openPeer(EnginePeer(peer), .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)))
case let .groupBotStart(botPeerId, payload, adminRights):
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages], hasContactSelector: false, title: presentationData.strings.Bot_AddToChat_Title))
controller.peerSelected = { [weak controller] peer in
@ -155,8 +155,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}
dismissInput()
navigationController?.pushViewController(controller)
case let .channelMessage(peerId, messageId, timecode):
openPeer(peerId, .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil))
case let .channelMessage(peer, messageId, timecode):
openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil))
case let .replyThreadMessage(replyThreadMessage, messageId):
if let navigationController = navigationController {
let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in
@ -196,8 +196,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
navigationController?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .channel, anchor: anchor))
case let .join(link):
dismissInput()
present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId, peekData in
openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: peekData))
present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
}, parentNavigationController: navigationController), nil)
case let .localization(identifier):
dismissInput()

View File

@ -190,30 +190,27 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
if case let .externalUrl(value) = resolved {
context.sharedContext.applicationBindings.openUrl(value)
} else {
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peerId, navigation in
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in
switch navigation {
case .info:
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { peer in
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
context.sharedContext.applicationBindings.dismissNativeController()
navigationController?.pushViewController(infoController)
}
})
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
context.sharedContext.applicationBindings.dismissNativeController()
navigationController?.pushViewController(infoController)
}
case let .chat(_, subject, peekData):
context.sharedContext.applicationBindings.dismissNativeController()
if let navigationController = navigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), subject: subject, peekData: peekData))
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), subject: subject, peekData: peekData))
}
case let .withBotStartPayload(payload):
context.sharedContext.applicationBindings.dismissNativeController()
if let navigationController = navigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), botStart: payload))
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), botStart: payload))
}
case let .withAttachBot(attachBotStart):
context.sharedContext.applicationBindings.dismissNativeController()
if let navigationController = navigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart))
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), attachBotStart: attachBotStart))
}
default:
break

View File

@ -67,7 +67,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
} else {
return false
}
}, openPeer: { _, _, _, _, _ in
}, openPeer: { _, _, _, _ in
}, openPeerMention: { _ in
}, openMessageContextMenu: { _, _, _, _, _, _ in
}, openMessageReactionContextMenu: { _, _, _, _ in

View File

@ -186,7 +186,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
}
}
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in
self?.chatControllerInteraction.openPeer(peer.id, .default, nil, false, nil)
self?.chatControllerInteraction.openPeer(EnginePeer(peer), .default, nil, false)
}, openPeerContextAction: { [weak self] peer, node, gesture in
self?.openPeerContextAction(peer, node, gesture)
})

View File

@ -821,6 +821,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
}
var availablePanes = availablePanes
if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
availablePanes?.removeAll()
}
if let membersData = membersData, case .longList = membersData {
if availablePanes != nil {
availablePanes?.insert(.members, at: 0)

View File

@ -27,6 +27,7 @@ import EmojiStatusComponent
import AnimationCache
import MultiAnimationRenderer
import ComponentDisplayAdapters
import ChatTitleView
enum PeerInfoHeaderButtonKey: Hashable {
case message
@ -407,7 +408,17 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
self.containerNode.isGestureEnabled = false
}
self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true)
self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true)
let avatarCornerRadius: CGFloat
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
avatarCornerRadius = floor(avatarSize * 0.25)
} else {
avatarCornerRadius = avatarSize / 2.0
}
self.avatarNode.layer.cornerRadius = avatarCornerRadius
self.avatarNode.layer.masksToBounds = true
self.isFirstAvatarLoading = false
self.containerNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
@ -708,9 +719,18 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
overrideImage = item == nil && canEdit ? .editAvatarIcon(forceNone: true) : nil
}
self.avatarNode.font = avatarPlaceholderFont(size: floor(avatarSize * 16.0 / 37.0))
self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
let avatarCornerRadius: CGFloat
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
avatarCornerRadius = floor(avatarSize * 0.25)
} else {
avatarCornerRadius = avatarSize / 2.0
}
self.avatarNode.layer.cornerRadius = avatarCornerRadius
self.avatarNode.layer.masksToBounds = true
if let item = item {
let representations: [ImageRepresentationWithReference]
let videoRepresentations: [VideoRepresentationWithReference]
@ -2785,6 +2805,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
avatarScale = 1.0 * (1.0 - titleCollapseFraction) + avatarMinScale * titleCollapseFraction
avatarOffset = apparentTitleLockOffset + 0.0 * (1.0 - titleCollapseFraction) + 10.0 * titleCollapseFraction
}
let avatarCornerRadius: CGFloat
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
avatarCornerRadius = floor(avatarSize * 0.25)
} else {
avatarCornerRadius = avatarSize / 2.0
}
if self.isAvatarExpanded {
self.avatarListNode.listContainerNode.isHidden = false
@ -2795,9 +2822,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 0.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 0.0)
}
} else if self.avatarListNode.listContainerNode.cornerRadius != avatarSize / 2.0 {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: avatarSize / 2.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: avatarSize / 2.0, completion: { [weak self] _ in
} else if self.avatarListNode.listContainerNode.cornerRadius != avatarCornerRadius {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: avatarCornerRadius)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: avatarCornerRadius, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}

View File

@ -76,6 +76,7 @@ import EntityKeyboard
import AvatarNode
import ComponentFlow
import EmojiStatusComponent
import ChatTitleView
protocol PeerInfoScreenItem: AnyObject {
var id: AnyHashable { get }
@ -502,6 +503,7 @@ private final class PeerInfoInteraction {
let openQrCode: () -> Void
let editingOpenReactionsSetup: () -> Void
let dismissInput: () -> Void
let toggleForumTopics: (Bool) -> Void
init(
openUsername: @escaping (String) -> Void,
@ -545,7 +547,8 @@ private final class PeerInfoInteraction {
openAddMember: @escaping () -> Void,
openQrCode: @escaping () -> Void,
editingOpenReactionsSetup: @escaping () -> Void,
dismissInput: @escaping () -> Void
dismissInput: @escaping () -> Void,
toggleForumTopics: @escaping (Bool) -> Void
) {
self.openUsername = openUsername
self.openPhone = openPhone
@ -589,6 +592,7 @@ private final class PeerInfoInteraction {
self.openQrCode = openQrCode
self.editingOpenReactionsSetup = editingOpenReactionsSetup
self.dismissInput = dismissInput
self.toggleForumTopics = toggleForumTopics
}
}
@ -1246,6 +1250,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
case notifications
case groupLocation
case peerPublicSettings
case peerDataSettings
case peerSettings
case peerAdditionalSettings
case peerActions
@ -1419,6 +1424,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
let ItemLocationSetup = 113
let ItemDeleteGroup = 114
let ItemReactions = 115
let ItemTopics = 116
let isCreator = channel.flags.contains(.isCreator)
let isPublic = channel.username != nil
@ -1474,7 +1480,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
invitesText = ""
}
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
interaction.editingOpenInviteLinksSetup()
}))
}
@ -1487,7 +1493,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
} else {
peerTitle = EnginePeer(linkedDiscussionPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
}
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemLinkedChannel, label: .text(peerTitle), text: presentationData.strings.Group_LinkedChannel, icon: UIImage(bundleImageName: "Chat/Info/GroupLinkedChannelIcon"), action: {
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemLinkedChannel, label: .text(peerTitle), text: presentationData.strings.Group_LinkedChannel, icon: UIImage(bundleImageName: "Chat/Info/GroupLinkedChannelIcon"), action: {
interaction.editingOpenDiscussionGroupSetup()
}))
}
@ -1506,12 +1512,12 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
} else {
label = ""
}
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: {
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: {
interaction.editingOpenReactionsSetup()
}))
}
if !isPublic, case .known(nil) = cachedData.linkedDiscussionPeerId {
if !isPublic, case .known(nil) = cachedData.linkedDiscussionPeerId, !channel.flags.contains(.isForum){
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(cachedData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistoryShort, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: {
interaction.editingOpenPreHistorySetup()
}))
@ -1531,18 +1537,25 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
} else {
label = ""
}
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: {
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: {
interaction.editingOpenReactionsSetup()
}))
}
}
if cachedData.flags.contains(.canSetStickerSet) && canEditPeerInfo(context: context, peer: channel) {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/Menu/Stickers"), action: {
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/Menu/Stickers"), action: {
interaction.editingOpenStickerPackSetup()
}))
}
if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) {
//TODO:localize
items[.peerDataSettings]!.append(PeerInfoScreenSwitchItem(id: ItemTopics, text: "Topics", value: channel.flags.contains(.isForum), icon: UIImage(bundleImageName: "Settings/Menu/ChatListFilters"), toggled: { value in
interaction.toggleForumTopics(value)
}))
}
var canViewAdminsAndBanned = false
if let _ = channel.adminRights {
canViewAdminsAndBanned = true
@ -1957,6 +1970,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
},
dismissInput: { [weak self] in
self?.view.endEditing(true)
},
toggleForumTopics: { [weak self] value in
guard let strongSelf = self else {
return
}
let _ = strongSelf.context.engine.peers.setChannelForumMode(id: strongSelf.peerId, isForum: value).start()
}
)
@ -1965,10 +1984,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return false
}
return strongSelf.openMessage(id: message.id)
}, openPeer: { [weak self] id, navigation, _, _, _ in
if let id = id {
self?.openPeer(peerId: id, navigation: navigation)
}
}, openPeer: { [weak self] peer, navigation, _, _ in
self?.openPeer(peerId: peer.id, navigation: navigation)
}, openPeerMention: { _ in
}, openMessageContextMenu: { [weak self] message, _, node, frame, anyRecognizer, _ in
guard let strongSelf = self, let node = node as? ContextExtractedContentContainingNode else {
@ -2495,7 +2512,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let items: [ContextMenuItem] = [
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_LinkDialogOpen, icon: { _ in nil }, action: { _, f in
f(.dismissWithoutContent)
self?.chatInterfaceInteraction.openPeer(peer.id, .default, nil, false, nil)
self?.chatInterfaceInteraction.openPeer(EnginePeer(peer), .default, nil, false)
}))
]
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
@ -3493,9 +3510,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if let strongSelf = self {
strongSelf.openPeerMention(mention)
}
}, openPeer: { [weak self] peerId in
}, openPeer: { [weak self] peer in
if let strongSelf = self {
strongSelf.openPeer(peerId: peerId, navigation: .default)
strongSelf.openPeer(peerId: peer.id, navigation: .default)
}
}, openHashtag: { [weak self] peerName, hashtag in
if let strongSelf = self {
@ -3581,27 +3598,23 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
guard let navigationController = self.controller?.navigationController as? NavigationController else {
return
}
self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: self.peerId, updatedPresentationData: self.controller?.updatedPresentationData), navigationController: navigationController, forceExternal: false, openPeer: { [weak self] peerId, navigation in
self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: self.peerId, updatedPresentationData: self.controller?.updatedPresentationData), navigationController: navigationController, forceExternal: false, openPeer: { [weak self] peer, navigation in
guard let strongSelf = self else {
return
}
switch navigation {
case let .chat(_, subject, peekData):
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: subject, keepStack: .always, peekData: peekData))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, keepStack: .always, peekData: peekData))
case .info:
strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil {
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
strongSelf.controller?.push(infoController)
}
if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil {
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
strongSelf.controller?.push(infoController)
}
}))
}
case let .withBotStartPayload(startPayload):
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: startPayload))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), botStart: startPayload))
case let .withAttachBot(attachBotStart):
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), attachBotStart: attachBotStart))
default:
break
}
@ -3628,8 +3641,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let result: ResolvedUrl = external ? .externalUrl(url) : tempResolved
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { peerId, navigation in
self?.openPeer(peerId: peerId, navigation: navigation)
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { peer, navigation in
self?.openPeer(peerId: peer.id, navigation: navigation)
}, sendFile: nil,
sendSticker: nil,
requestMessageActionUrlAuth: nil,
@ -3735,7 +3748,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
navigation = .chat(textInputState: nil, subject: nil, peekData: nil)
}
}
strongSelf.openResolved(.peer(peer.id, navigation))
strongSelf.openResolved(.peer(peer, navigation))
} else {
strongSelf.controller?.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
@ -6746,7 +6759,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})
if let selectedAccount = selectedAccount {
let accountContext = self.context.sharedContext.makeTempAccountContext(account: selectedAccount)
let chatListController = accountContext.sharedContext.makeChatListController(context: accountContext, groupId: .root, controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
let chatListController = accountContext.sharedContext.makeChatListController(context: accountContext, location: .chatList(groupId: EngineChatList.Group(.root)), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
let contextController = ContextController(account: accountContext.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node)), items: accountContextMenuItems(context: accountContext, logout: { [weak self] in
self?.logoutAccount(id: id)

View File

@ -1210,7 +1210,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return resolveUrlImpl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth)
}
public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, forceExternal: forceExternal, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, requestMessageActionUrlAuth: requestMessageActionUrlAuth, joinVoiceChat: joinVoiceChat, present: present, dismissInput: dismissInput, contentContext: contentContext)
}
@ -1270,8 +1270,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return createGroupControllerImpl(context: context, peerIds: peerIds, initialTitle: initialTitle, mode: mode, completion: completion)
}
public func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController {
return ChatListControllerImpl(context: context, location: .chatList(groupId: EngineChatList.Group(groupId)), controlsHistoryPreload: controlsHistoryPreload, hideNetworkActivityStatus: hideNetworkActivityStatus, previewing: previewing, enableDebugActions: enableDebugActions)
public func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController {
return ChatListControllerImpl(context: context, location: location, controlsHistoryPreload: controlsHistoryPreload, hideNetworkActivityStatus: hideNetworkActivityStatus, previewing: previewing, enableDebugActions: enableDebugActions)
}
public func makePeerSelectionController(_ params: PeerSelectionControllerParams) -> PeerSelectionController {
@ -1286,7 +1286,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
let controllerInteraction: ChatControllerInteraction
controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, tapMessage: { message in

View File

@ -96,7 +96,7 @@ public final class TelegramRootController: NavigationController {
public func addRootControllers(showCallsTab: Bool) {
let tabBarController = TabBarControllerImpl(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme))
tabBarController.navigationPresentation = .master
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, groupId: .root, controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl {
chatListController.tabBarItem.badgeValue = sharedContext.switchingData.chatListBadge
}

View File

@ -22,16 +22,19 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
controller.present(controllerToPresent, in: .window(.root))
}
let openResolvedPeerImpl: (PeerId?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peerId, navigation in
context.sharedContext.openResolvedUrl(.peer(peerId, navigation), context: context, urlContext: .generic, navigationController: (controller?.navigationController as? NavigationController), forceExternal: false, openPeer: { (peerId, navigation) in
let openResolvedPeerImpl: (EnginePeer?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peer, navigation in
guard let peer = peer else {
return
}
context.sharedContext.openResolvedUrl(.peer(peer._asPeer(), navigation), context: context, urlContext: .generic, navigationController: (controller?.navigationController as? NavigationController), forceExternal: false, openPeer: { (peer, navigation) in
switch navigation {
case let .chat(_, subject, peekData):
if let navigationController = controller?.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), subject: subject, keepStack: .always, peekData: peekData))
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), subject: subject, keepStack: .always, peekData: peekData))
}
case .info:
let peerSignal: Signal<Peer?, NoError>
peerSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init)
peerSignal = context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init)
navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in
if let controller = controller, let peer = peer {
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
@ -55,11 +58,11 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
switch result {
case let .externalUrl(url):
context.sharedContext.applicationBindings.openUrl(url)
case let .peer(peerId, navigation):
openResolvedPeerImpl(peerId, navigation)
case let .channelMessage(peerId, messageId, timecode):
case let .peer(peer, navigation):
openResolvedPeerImpl(peer.flatMap(EnginePeer.init), navigation)
case let .channelMessage(peer, messageId, timecode):
if let navigationController = controller.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), subject: .message(id: .id(messageId), highlight: true, timecode: timecode)))
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), subject: .message(id: .id(messageId), highlight: true, timecode: timecode)))
}
case let .replyThreadMessage(replyThreadMessage, messageId):
if let navigationController = controller.navigationController as? NavigationController {
@ -73,8 +76,8 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
case let .instantView(webpage, anchor):
(controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .group, anchor: anchor))
case let .join(link):
controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId, peekData in
openResolvedPeerImpl(peerId, .chat(textInputState: nil, subject: nil, peekData: peekData))
controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
}, parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root))
default:
break
@ -85,7 +88,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
let openPeerMentionImpl: (String) -> Void = { mention in
navigateDisposable.set((context.engine.peers.resolvePeerByName(name: mention, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peer in
openResolvedPeerImpl(peer?.id, .default)
openResolvedPeerImpl(peer, .default)
}))
}

View File

@ -477,13 +477,13 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|> take(1)
|> map { botPeer -> ResolvedUrl? in
if let botPeer = botPeer?._asPeer() {
return .peer(peer.id, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: startAttach, justInstalled: false)))
return .peer(peer, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: startAttach, justInstalled: false)))
} else {
return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))
return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
}
}
} else {
return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)))
return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)))
}
} else {
return .single(.peer(nil, .info))
@ -500,7 +500,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
if let parameter = parameter {
switch parameter {
case let .botStart(payload):
return .single(.botStart(peerId: peer.id, payload: payload))
return .single(.botStart(peer: peer, payload: payload))
case let .groupBotStart(payload, adminRights):
return .single(.groupBotStart(peerId: peer.id, payload: payload, adminRights: adminRights))
case let .attachBotStart(name, payload):
@ -511,13 +511,13 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
}
|> mapToSignal { botPeer -> Signal<ResolvedUrl?, NoError> in
if let botPeer = botPeer {
return .single(.peer(peer.id, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: payload, justInstalled: false))))
return .single(.peer(peer, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: payload, justInstalled: false))))
} else {
return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)))
return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)))
}
}
case let .channelMessage(id, timecode):
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))
return .single(.channelMessage(peer: peer, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))
case let .replyThread(id, replyId):
let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)
return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil)
@ -527,7 +527,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
}
|> map { result -> ResolvedUrl? in
guard let result = result else {
return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId, timecode: nil)
return .channelMessage(peer: peer, messageId: replyThreadMessageId, timecode: nil)
}
return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
}
@ -535,7 +535,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return .single(.joinVoiceChat(peer.id, invite))
}
} else {
return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)))
return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)))
}
} else {
return .single(.peer(nil, .info))
@ -545,7 +545,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> in
if let peer = peer {
return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)))
return .single(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))
} else {
return .single(.inaccessiblePeer)
}
@ -571,12 +571,12 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
}
|> map { result -> ResolvedUrl? in
guard let result = result else {
return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId, timecode: timecode)
return .channelMessage(peer: foundPeer._asPeer(), messageId: replyThreadMessageId, timecode: timecode)
}
return .replyThreadMessage(replyThreadMessage: result, messageId: messageId)
}
} else {
return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil)))
return .single(.peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil)))
}
} else {
return .single(.inaccessiblePeer)