mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 08:49:47 +00:00
[Temp]
This commit is contained in:
parent
f7db73db40
commit
109fa6b537
@ -1785,8 +1785,10 @@ ios_application(
|
||||
#"//submodules/TelegramApi",
|
||||
#"//submodules/TelegramCore",
|
||||
#"//submodules/FFMpegBinding",
|
||||
"//submodules/Display",
|
||||
#"//submodules/Display",
|
||||
#"//third-party/webrtc",
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/ObjCRuntimeUtils",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@ -95,6 +95,7 @@ _IGNORE_OBJC_LIBRARY_ATTRS = [
|
||||
"toolchains",
|
||||
"transitive_configs",
|
||||
"visibility",
|
||||
"package_metadata",
|
||||
]
|
||||
|
||||
_IGNORE_OBJC_LIBRARY_EMPTY_ATTRS = [
|
||||
@ -106,6 +107,8 @@ _IGNORE_OBJC_LIBRARY_EMPTY_ATTRS = [
|
||||
"restricted_to",
|
||||
"textual_hdrs",
|
||||
"sdk_includes",
|
||||
"conlyopts",
|
||||
"cxxopts",
|
||||
]
|
||||
|
||||
_OBJC_LIBRARY_ATTRS = {
|
||||
@ -154,6 +157,8 @@ _IGNORE_SWIFT_LIBRARY_ATTRS = [
|
||||
"toolchains",
|
||||
"transitive_configs",
|
||||
"visibility",
|
||||
"library_evolution",
|
||||
"package_metadata",
|
||||
]
|
||||
|
||||
_IGNORE_SWIFT_LIBRARY_EMPTY_ATTRS = [
|
||||
@ -332,9 +337,9 @@ def _collect_spm_modules_impl(target, ctx):
|
||||
|
||||
# Extract the path from the label
|
||||
# Example: @//path/ModuleName:ModuleSubname -> path/ModuleName
|
||||
if not str(ctx.label).startswith("@//"):
|
||||
if not str(ctx.label).startswith("@@//"):
|
||||
fail("Invalid label: {}".format(ctx.label))
|
||||
module_path = str(ctx.label).split(":")[0].split("@//")[1]
|
||||
module_path = str(ctx.label).split(":")[0].split("@@//")[1]
|
||||
|
||||
if module_type == "objc_library":
|
||||
module_info = {
|
||||
|
||||
@ -51,6 +51,11 @@ class EncryptionV2
|
||||
key = keyIv[0..31]
|
||||
iv = keyIv[32..43]
|
||||
auth_data = keyIv[44..-1]
|
||||
|
||||
puts "key: #{key.inspect}"
|
||||
puts "iv: #{iv.inspect}"
|
||||
puts "auth_data: #{auth_data.inspect}"
|
||||
|
||||
cipher.key = key
|
||||
cipher.iv = iv
|
||||
cipher.auth_data = auth_data
|
||||
|
||||
@ -1073,6 +1073,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
selectedMessages: Signal<Set<MessageId>?, NoError>,
|
||||
mode: ChatHistoryListMode
|
||||
) -> ChatHistoryListNode
|
||||
func subscribeChatListData(context: AccountContext, location: ChatListControllerLocation) -> Signal<EngineChatList, NoError>
|
||||
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: ((UIView?, CGPoint?) -> Void)?, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, accountPeer: Peer?, isCentered: Bool, isPreview: Bool, isStandalone: Bool) -> ListViewItem
|
||||
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
|
||||
func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: Peer, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
|
||||
|
||||
@ -6,12 +6,12 @@ import Display
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
|
||||
enum ChatListNodeLocation: Equatable {
|
||||
public enum ChatListNodeLocation: Equatable {
|
||||
case initial(count: Int, filter: ChatListFilter?)
|
||||
case navigation(index: EngineChatList.Item.Index, filter: ChatListFilter?)
|
||||
case scroll(index: EngineChatList.Item.Index, sourceIndex: EngineChatList.Item.Index, scrollPosition: ListViewScrollPosition, animated: Bool, filter: ChatListFilter?)
|
||||
|
||||
var filter: ChatListFilter? {
|
||||
public var filter: ChatListFilter? {
|
||||
switch self {
|
||||
case let .initial(_, filter):
|
||||
return filter
|
||||
@ -23,10 +23,16 @@ enum ChatListNodeLocation: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListNodeViewUpdate {
|
||||
let list: EngineChatList
|
||||
let type: ViewUpdateType
|
||||
let scrollPosition: ChatListNodeViewScrollPosition?
|
||||
public struct ChatListNodeViewUpdate {
|
||||
public let list: EngineChatList
|
||||
public let type: ViewUpdateType
|
||||
public let scrollPosition: ChatListNodeViewScrollPosition?
|
||||
|
||||
public init(list: EngineChatList, type: ViewUpdateType, scrollPosition: ChatListNodeViewScrollPosition?) {
|
||||
self.list = list
|
||||
self.type = type
|
||||
self.scrollPosition = scrollPosition
|
||||
}
|
||||
}
|
||||
|
||||
public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: EnginePeer.Id) -> ChatListFilterPredicate {
|
||||
@ -113,7 +119,7 @@ public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: E
|
||||
})
|
||||
}
|
||||
|
||||
func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account, shouldLoadCanMessagePeer: Bool) -> Signal<ChatListNodeViewUpdate, NoError> {
|
||||
public func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account, shouldLoadCanMessagePeer: Bool) -> Signal<ChatListNodeViewUpdate, NoError> {
|
||||
let accountPeerId = account.peerId
|
||||
|
||||
switch chatListLocation {
|
||||
|
||||
@ -47,7 +47,7 @@ struct ChatListNodeViewTransition {
|
||||
let animateCrossfade: Bool
|
||||
}
|
||||
|
||||
enum ChatListNodeViewScrollPosition {
|
||||
public enum ChatListNodeViewScrollPosition {
|
||||
case index(index: ChatListIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool)
|
||||
}
|
||||
|
||||
|
||||
@ -2376,16 +2376,24 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
||||
} else {
|
||||
let updateItem = updateIndicesAndItems[0]
|
||||
if let previousNode = previousNodes[updateItem.index] {
|
||||
let threadId = pthread_self()
|
||||
var tailRecurse = false
|
||||
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimationIsAnimated: animated, updateAnimationIsCrossfade: crossfade, customAnimationTransition: customAnimationTransition, completion: { _, layout, apply in
|
||||
state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, isAnimated: animated, apply: apply, operations: &operations)
|
||||
|
||||
updateIndicesAndItems.remove(at: 0)
|
||||
self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, crossfade: crossfade, customAnimationTransition: customAnimationTransition, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
||||
if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse {
|
||||
tailRecurse = true
|
||||
} else {
|
||||
self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, crossfade: crossfade, customAnimationTransition: customAnimationTransition, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
||||
}
|
||||
})
|
||||
break
|
||||
if !tailRecurse {
|
||||
tailRecurse = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
updateIndicesAndItems.remove(at: 0)
|
||||
//self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,10 +424,10 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
|
||||
}
|
||||
|
||||
open func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: 0.0, transition: transition)
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: 0.0, additionalCutout: nil, transition: transition)
|
||||
}
|
||||
|
||||
public func applyNavigationBarLayout(_ layout: ContainerViewLayout, navigationLayout: NavigationLayout, additionalBackgroundHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
public func applyNavigationBarLayout(_ layout: ContainerViewLayout, navigationLayout: NavigationLayout, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, transition: ContainedViewLayoutTransition) {
|
||||
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
|
||||
|
||||
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: navigationLayout.navigationFrame.maxY))
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#import "RuntimeUtils.h"
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
@ -977,7 +977,7 @@ public class ReplaceBoostScreen: ViewController {
|
||||
|
||||
layout.statusBarHeight = nil
|
||||
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, transition: transition)
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, additionalCutout: nil, transition: transition)
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
@ -394,13 +394,14 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
private var suspendNavigationBarLayout: Bool = false
|
||||
private var suspendedNavigationBarLayout: ContainerViewLayout?
|
||||
private var additionalNavigationBarBackgroundHeight: CGFloat = 0.0
|
||||
private var additionalNavigationBarCutout: CGSize?
|
||||
|
||||
override open func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
if self.suspendNavigationBarLayout {
|
||||
self.suspendedNavigationBarLayout = layout
|
||||
return
|
||||
}
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: self.additionalNavigationBarCutout, transition: transition)
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
@ -969,7 +970,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
self.suspendNavigationBarLayout = false
|
||||
if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout {
|
||||
self.suspendedNavigationBarLayout = suspendedNavigationBarLayout
|
||||
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
||||
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalNavigationBarCutout: self.additionalNavigationBarCutout, transition: transition)
|
||||
}
|
||||
|
||||
self.accessoryPanelContainerHeight = additionalHeight
|
||||
|
||||
@ -71,6 +71,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
case empty
|
||||
case tag(MemoryBuffer)
|
||||
case search(query: String, includeSavedPeers: Bool)
|
||||
case monoforumChats(query: String)
|
||||
}
|
||||
|
||||
public let context: AccountContext
|
||||
@ -85,6 +86,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?
|
||||
public let getSearchResult: () -> Signal<SearchMessagesResult?, NoError>?
|
||||
public let getSavedPeers: (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>?
|
||||
public let getChats: (String) -> Signal<EngineChatList?, NoError>?
|
||||
public let loadMoreSearchResults: () -> Void
|
||||
|
||||
public init(
|
||||
@ -100,6 +102,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?,
|
||||
getSearchResult: @escaping () -> Signal<SearchMessagesResult?, NoError>?,
|
||||
getSavedPeers: @escaping (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>?,
|
||||
getChats: @escaping (String) -> Signal<EngineChatList?, NoError>?,
|
||||
loadMoreSearchResults: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
@ -114,6 +117,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
self.loadTagMessages = loadTagMessages
|
||||
self.getSearchResult = getSearchResult
|
||||
self.getSavedPeers = getSavedPeers
|
||||
self.getChats = getChats
|
||||
self.loadMoreSearchResults = loadMoreSearchResults
|
||||
}
|
||||
|
||||
@ -146,10 +150,12 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
enum Id: Hashable {
|
||||
case peer(EnginePeer.Id)
|
||||
case message(EngineMessage.Id)
|
||||
case chat(EngineChatList.Item.Id)
|
||||
}
|
||||
|
||||
case peer(EnginePeer)
|
||||
case message(EngineMessage)
|
||||
case chat(EngineChatList.Item)
|
||||
|
||||
var id: Id {
|
||||
switch self {
|
||||
@ -157,6 +163,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
return .peer(peer.id)
|
||||
case let .message(message):
|
||||
return .message(message.id)
|
||||
case let .chat(chat):
|
||||
return .chat(chat.id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,6 +182,12 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .chat(chat):
|
||||
if case .chat(chat) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,14 +202,27 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
return lhsPeer.id < rhsPeer.id
|
||||
case .message:
|
||||
return true
|
||||
case .chat:
|
||||
return true
|
||||
}
|
||||
case let .message(lhsMessage):
|
||||
switch rhs {
|
||||
case .peer:
|
||||
return false
|
||||
case .chat:
|
||||
return false
|
||||
case let .message(rhsMessage):
|
||||
return lhsMessage.index > rhsMessage.index
|
||||
}
|
||||
case let .chat(lhsChat):
|
||||
switch rhs {
|
||||
case let .chat(rhsChat):
|
||||
return lhsChat.index > rhsChat.index
|
||||
case .peer:
|
||||
return false
|
||||
case .message:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -418,6 +445,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
}
|
||||
case .search:
|
||||
break
|
||||
case .monoforumChats:
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let (currentIndex, disposable) = self.searchContents {
|
||||
@ -431,6 +460,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
self.searchContents = (loadAroundIndex, disposable)
|
||||
|
||||
component.loadMoreSearchResults()
|
||||
case .monoforumChats:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -581,6 +612,127 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
}))
|
||||
}
|
||||
}
|
||||
case let .monoforumChats(query):
|
||||
let _ = query
|
||||
|
||||
if previousComponent?.contents != component.contents {
|
||||
self.tagContents?.disposable?.dispose()
|
||||
self.tagContents = nil
|
||||
|
||||
self.searchContents?.disposable?.dispose()
|
||||
self.searchContents = nil
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
self.searchContents = (nil, disposable)
|
||||
|
||||
let savedPeers: Signal<EngineChatList?, NoError>
|
||||
if let savedPeersSignal = component.getChats(query) {
|
||||
savedPeers = savedPeersSignal
|
||||
} else {
|
||||
savedPeers = .single(nil)
|
||||
}
|
||||
|
||||
disposable.set((savedPeers
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] chatList in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let messages: [EngineMessage] = [] /*result?.messages.map { entry in
|
||||
return EngineMessage(entry)
|
||||
} ?? []*/
|
||||
|
||||
var entries: [Entry] = []
|
||||
if let chatList {
|
||||
for item in chatList.items {
|
||||
entries.append(.chat(item))
|
||||
}
|
||||
}
|
||||
for message in messages {
|
||||
entries.append(.message(message))
|
||||
}
|
||||
entries.sort()
|
||||
|
||||
let contentsId = self.nextContentsId
|
||||
self.nextContentsId += 1
|
||||
self.contentsState = ContentsState(
|
||||
id: contentsId,
|
||||
contentId: .search(query),
|
||||
entries: entries,
|
||||
messages: messages,
|
||||
hasEarlier: false, //!(result?.completed ?? true),
|
||||
hasLater: false
|
||||
)
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
|
||||
if !self.didSetReady {
|
||||
self.didSetReady = true
|
||||
self.isReadyPromise.set(.single(true))
|
||||
}
|
||||
}))
|
||||
|
||||
/*if !query.isEmpty, let savedPeersSignal = component.getSavedPeers(query) {
|
||||
savedPeers = savedPeersSignal
|
||||
} else {
|
||||
savedPeers = .single([])
|
||||
}*/
|
||||
|
||||
/*if let historySignal = component.getSearchResult() {
|
||||
disposable.set((savedPeers
|
||||
|> mapToSignal { savedPeers -> Signal<([(EnginePeer, MessageIndex?)], SearchMessagesResult?), NoError> in
|
||||
if savedPeers.isEmpty {
|
||||
return historySignal
|
||||
|> map { result in
|
||||
return ([], result)
|
||||
}
|
||||
} else {
|
||||
return (.single(nil) |> then(historySignal))
|
||||
|> map { result in
|
||||
return (savedPeers, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] savedPeers, result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let messages: [EngineMessage] = result?.messages.map { entry in
|
||||
return EngineMessage(entry)
|
||||
} ?? []
|
||||
|
||||
var entries: [Entry] = []
|
||||
for (peer, _) in savedPeers {
|
||||
entries.append(.peer(peer))
|
||||
}
|
||||
for message in messages {
|
||||
entries.append(.message(message))
|
||||
}
|
||||
entries.sort()
|
||||
|
||||
let contentsId = self.nextContentsId
|
||||
self.nextContentsId += 1
|
||||
self.contentsState = ContentsState(
|
||||
id: contentsId,
|
||||
contentId: .search(query),
|
||||
entries: entries,
|
||||
messages: messages,
|
||||
hasEarlier: !(result?.completed ?? true),
|
||||
hasLater: false
|
||||
)
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
|
||||
if !self.didSetReady {
|
||||
self.didSetReady = true
|
||||
self.isReadyPromise.set(.single(true))
|
||||
}
|
||||
}))
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
if let contentsState = self.contentsState, self.contentsState != self.appliedContentsState {
|
||||
@ -607,13 +759,17 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
},
|
||||
additionalCategorySelected: { _ in
|
||||
},
|
||||
messageSelected: { [weak self] _, _, message, _ in
|
||||
guard let self else {
|
||||
messageSelected: { [weak self] peer, _, message, _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.listNode.clearHighlightAnimated(true)
|
||||
|
||||
self.component?.messageSelected(message)
|
||||
if case .monoforumChats = component.contents {
|
||||
component.peerSelected(peer)
|
||||
} else {
|
||||
component.messageSelected(message)
|
||||
}
|
||||
},
|
||||
groupSelected: { _ in
|
||||
},
|
||||
@ -845,6 +1001,45 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
hiddenOffset: false,
|
||||
interaction: chatListNodeInteraction
|
||||
)
|
||||
case let .chat(item):
|
||||
return ChatListItem(
|
||||
presentationData: chatListPresentationData,
|
||||
context: component.context,
|
||||
chatListLocation: component.peerId.flatMap { peerId in .savedMessagesChats(peerId: peerId) } ?? .chatList(groupId: .root),
|
||||
filterData: nil,
|
||||
index: item.index,
|
||||
content: .peer(ChatListItemContent.PeerData(
|
||||
messages: item.messages,
|
||||
peer: item.renderedPeer,
|
||||
threadInfo: nil,
|
||||
combinedReadState: nil,
|
||||
isRemovedFromTotalUnreadCount: false,
|
||||
presence: nil,
|
||||
hasUnseenMentions: false,
|
||||
hasUnseenReactions: false,
|
||||
draftState: nil,
|
||||
mediaDraftContentType: nil,
|
||||
inputActivities: nil,
|
||||
promoInfo: nil,
|
||||
ignoreUnreadBadge: false,
|
||||
displayAsMessage: component.peerId != component.context.account.peerId && !component.showEmptyResults,
|
||||
hasFailedMessages: false,
|
||||
forumTopicData: nil,
|
||||
topForumTopicItems: [],
|
||||
autoremoveTimeout: nil,
|
||||
storyState: nil,
|
||||
requiresPremiumForMessaging: false,
|
||||
displayAsTopicList: false,
|
||||
tags: []
|
||||
)),
|
||||
editing: false,
|
||||
hasActiveRevealControls: false,
|
||||
selected: false,
|
||||
header: nil,
|
||||
enableContextActions: false,
|
||||
hiddenOffset: false,
|
||||
interaction: chatListNodeInteraction
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ public final class ChatSideTopicsPanel: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let peerId: EnginePeer.Id
|
||||
let isMonoforum: Bool
|
||||
let topicId: Int64?
|
||||
let togglePanel: () -> Void
|
||||
let updateTopicId: (Int64?) -> Void
|
||||
@ -46,6 +47,7 @@ public final class ChatSideTopicsPanel: Component {
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
peerId: EnginePeer.Id,
|
||||
isMonoforum: Bool,
|
||||
topicId: Int64?,
|
||||
togglePanel: @escaping () -> Void,
|
||||
updateTopicId: @escaping (Int64?) -> Void
|
||||
@ -54,6 +56,7 @@ public final class ChatSideTopicsPanel: Component {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.peerId = peerId
|
||||
self.isMonoforum = isMonoforum
|
||||
self.topicId = topicId
|
||||
self.togglePanel = togglePanel
|
||||
self.updateTopicId = updateTopicId
|
||||
@ -72,6 +75,9 @@ public final class ChatSideTopicsPanel: Component {
|
||||
if lhs.peerId != rhs.peerId {
|
||||
return false
|
||||
}
|
||||
if lhs.isMonoforum != rhs.isMonoforum {
|
||||
return false
|
||||
}
|
||||
if lhs.topicId != rhs.topicId {
|
||||
return false
|
||||
}
|
||||
@ -111,7 +117,7 @@ public final class ChatSideTopicsPanel: Component {
|
||||
|
||||
private let containerButton: HighlightTrackingButton
|
||||
|
||||
private let icon = ComponentView<Empty>()
|
||||
private var icon: ComponentView<Empty>?
|
||||
private var avatarNode: AvatarNode?
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
@ -179,34 +185,78 @@ public final class ChatSideTopicsPanel: Component {
|
||||
|
||||
func update(context: AccountContext, item: Item, isSelected: Bool, theme: PresentationTheme, width: CGFloat, transition: ComponentTransition) -> CGSize {
|
||||
let spacing: CGFloat = 3.0
|
||||
let iconSize = CGSize(width: 30.0, height: 30.0)
|
||||
|
||||
let avatarIconContent: EmojiStatusComponent.Content
|
||||
if case let .forum(topicId) = item.item.id, topicId != 1, let threadData = item.item.threadData {
|
||||
if let fileId = threadData.info.icon, fileId != 0 {
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0))
|
||||
var avatarIconContent: EmojiStatusComponent.Content?
|
||||
if case let .forum(topicId) = item.item.id {
|
||||
if topicId != 1, let threadData = item.item.threadData {
|
||||
if let fileId = threadData.info.icon, fileId != 0 {
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0))
|
||||
} else {
|
||||
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
|
||||
}
|
||||
} else {
|
||||
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: CGSize(width: 18.0, height: 18.0))
|
||||
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme))
|
||||
}
|
||||
} else {
|
||||
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme))
|
||||
}
|
||||
|
||||
let avatarIconComponent = EmojiStatusComponent(
|
||||
context: context,
|
||||
animationCache: context.animationCache,
|
||||
animationRenderer: context.animationRenderer,
|
||||
content: avatarIconContent,
|
||||
isVisibleForAnimations: false,
|
||||
action: nil
|
||||
)
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(avatarIconComponent),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 30.0, height: 30.0)
|
||||
)
|
||||
if let avatarIconContent {
|
||||
let avatarIconComponent = EmojiStatusComponent(
|
||||
context: context,
|
||||
animationCache: context.animationCache,
|
||||
animationRenderer: context.animationRenderer,
|
||||
content: avatarIconContent,
|
||||
isVisibleForAnimations: false,
|
||||
action: nil
|
||||
)
|
||||
let icon: ComponentView<Empty>
|
||||
if let current = self.icon {
|
||||
icon = current
|
||||
} else {
|
||||
icon = ComponentView()
|
||||
self.icon = icon
|
||||
}
|
||||
let _ = icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(avatarIconComponent),
|
||||
environment: {},
|
||||
containerSize: iconSize
|
||||
)
|
||||
} else if let icon = self.icon {
|
||||
self.icon = nil
|
||||
icon.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
let titleText: String
|
||||
if case let .forum(topicId) = item.item.id {
|
||||
let _ = topicId
|
||||
if let threadData = item.item.threadData {
|
||||
titleText = threadData.info.title
|
||||
} else {
|
||||
//TODO:localize
|
||||
titleText = "General"
|
||||
}
|
||||
} else {
|
||||
titleText = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " "
|
||||
}
|
||||
|
||||
if let avatarIconContent, let icon = self.icon {
|
||||
let avatarIconComponent = EmojiStatusComponent(
|
||||
context: context,
|
||||
animationCache: context.animationCache,
|
||||
animationRenderer: context.animationRenderer,
|
||||
content: avatarIconContent,
|
||||
isVisibleForAnimations: false,
|
||||
action: nil
|
||||
)
|
||||
let _ = icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(avatarIconComponent),
|
||||
environment: {},
|
||||
containerSize: iconSize
|
||||
)
|
||||
}
|
||||
|
||||
let titleText: String = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " "
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
@ -224,39 +274,38 @@ public final class ChatSideTopicsPanel: Component {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) * 0.5), y: 0.0), size: iconSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) * 0.5), y: iconFrame.maxY + spacing), size: titleSize)
|
||||
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.containerButton.addSubview(iconView)
|
||||
}
|
||||
iconView.frame = iconFrame
|
||||
|
||||
if "".isEmpty {
|
||||
iconView.isHidden = true
|
||||
|
||||
let avatarNode: AvatarNode
|
||||
if let current = self.avatarNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 11.0))
|
||||
avatarNode.isUserInteractionEnabled = false
|
||||
self.avatarNode = avatarNode
|
||||
self.containerButton.addSubview(avatarNode.view)
|
||||
}
|
||||
avatarNode.frame = iconFrame
|
||||
avatarNode.updateSize(size: iconFrame.size)
|
||||
|
||||
if let peer = item.item.renderedPeer.chatMainPeer {
|
||||
if peer.smallProfileImage != nil {
|
||||
avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
|
||||
} else {
|
||||
avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
|
||||
}
|
||||
}
|
||||
} else if let avatarNode = self.avatarNode {
|
||||
if let icon = self.icon {
|
||||
if let avatarNode = self.avatarNode {
|
||||
self.avatarNode = nil
|
||||
avatarNode.view.removeFromSuperview()
|
||||
iconView.isHidden = false
|
||||
}
|
||||
|
||||
if let iconView = icon.view {
|
||||
if iconView.superview == nil {
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.containerButton.addSubview(iconView)
|
||||
}
|
||||
iconView.frame = iconFrame
|
||||
}
|
||||
} else {
|
||||
let avatarNode: AvatarNode
|
||||
if let current = self.avatarNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 11.0))
|
||||
avatarNode.isUserInteractionEnabled = false
|
||||
self.avatarNode = avatarNode
|
||||
self.containerButton.addSubview(avatarNode.view)
|
||||
}
|
||||
avatarNode.frame = iconFrame
|
||||
avatarNode.updateSize(size: iconFrame.size)
|
||||
|
||||
if let peer = item.item.renderedPeer.chatMainPeer {
|
||||
if peer.smallProfileImage != nil {
|
||||
avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
|
||||
} else {
|
||||
avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -600,7 +649,15 @@ public final class ChatSideTopicsPanel: Component {
|
||||
|
||||
public func topicIndex(threadId: Int64?) -> Int? {
|
||||
if let threadId {
|
||||
if let value = self.items.firstIndex(where: { $0.id == .chatList(PeerId(threadId)) }) {
|
||||
if let value = self.items.firstIndex(where: { item in
|
||||
if item.id == .chatList(PeerId(threadId)) {
|
||||
return true
|
||||
} else if item.id == .forum(threadId) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
return value + 1
|
||||
} else {
|
||||
return nil
|
||||
@ -619,77 +676,7 @@ public final class ChatSideTopicsPanel: Component {
|
||||
self.state = state
|
||||
|
||||
if self.component == nil {
|
||||
let viewKey: PostboxViewKey = .savedMessagesIndex(peerId: component.peerId)
|
||||
let interfaceStateKey: PostboxViewKey = .chatInterfaceState(peerId: component.peerId)
|
||||
|
||||
let accountPeerId = component.context.account.peerId
|
||||
let threadListSignal: Signal<EngineChatList, NoError> = component.context.account.postbox.combinedView(keys: [viewKey, interfaceStateKey])
|
||||
|> map { views -> EngineChatList in
|
||||
guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
var draft: EngineChatList.Draft?
|
||||
if let interfaceStateView = views.views[interfaceStateKey] as? ChatInterfaceStateView {
|
||||
if let embeddedState = interfaceStateView.value, let _ = embeddedState.overrideChatTimestamp {
|
||||
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
|
||||
if let text = opaqueState.synchronizeableInputState?.text {
|
||||
draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? [])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var items: [EngineChatList.Item] = []
|
||||
for item in view.items {
|
||||
guard let sourcePeer = item.peer else {
|
||||
continue
|
||||
}
|
||||
|
||||
let sourceId = PeerId(item.id)
|
||||
|
||||
var messages: [EngineMessage] = []
|
||||
if let topMessage = item.topMessage {
|
||||
messages.append(EngineMessage(topMessage))
|
||||
}
|
||||
|
||||
let mappedMessageIndex = MessageIndex(id: MessageId(peerId: sourceId, namespace: item.index.id.namespace, id: item.index.id.id), timestamp: item.index.timestamp)
|
||||
|
||||
items.append(EngineChatList.Item(
|
||||
id: .chatList(sourceId),
|
||||
index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)),
|
||||
messages: messages,
|
||||
readCounters: nil,
|
||||
isMuted: false,
|
||||
draft: sourceId == accountPeerId ? draft : nil,
|
||||
threadData: nil,
|
||||
renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)),
|
||||
presence: nil,
|
||||
hasUnseenMentions: false,
|
||||
hasUnseenReactions: false,
|
||||
forumTopicData: nil,
|
||||
topForumTopicItems: [],
|
||||
hasFailed: false,
|
||||
isContact: false,
|
||||
autoremoveTimeout: nil,
|
||||
storyStats: nil,
|
||||
displayAsTopicList: false,
|
||||
isPremiumRequiredToMessage: false,
|
||||
mediaDraftContentType: nil
|
||||
))
|
||||
}
|
||||
|
||||
let list = EngineChatList(
|
||||
items: items.reversed(),
|
||||
groupItems: [],
|
||||
additionalItems: [],
|
||||
hasEarlier: false,
|
||||
hasLater: false,
|
||||
isLoading: view.isLoading
|
||||
)
|
||||
|
||||
return list
|
||||
}
|
||||
let threadListSignal: Signal<EngineChatList, NoError> = component.context.sharedContext.subscribeChatListData(context: component.context, location: component.isMonoforum ? .savedMessagesChats(peerId: component.peerId) : .forum(peerId: component.peerId))
|
||||
|
||||
self.itemsDisposable = (threadListSignal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] chatList in
|
||||
@ -864,8 +851,12 @@ public final class ChatSideTopicsPanel: Component {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let topicId = chatListItem.renderedPeer.peerId.toInt64()
|
||||
component.updateTopicId(topicId)
|
||||
if case let .forum(topicId) = item.item.id {
|
||||
component.updateTopicId(topicId)
|
||||
} else {
|
||||
let topicId = chatListItem.renderedPeer.peerId.toInt64()
|
||||
component.updateTopicId(topicId)
|
||||
}
|
||||
}, contextGesture: { gesture, sourceNode in
|
||||
})
|
||||
self.itemViews[itemId] = itemView
|
||||
@ -873,8 +864,10 @@ public final class ChatSideTopicsPanel: Component {
|
||||
}
|
||||
|
||||
var isSelected = false
|
||||
if component.topicId == item.item.renderedPeer.peerId.toInt64() {
|
||||
isSelected = true
|
||||
if case let .forum(topicId) = item.item.id {
|
||||
isSelected = component.topicId == topicId
|
||||
} else {
|
||||
isSelected = component.topicId == item.item.renderedPeer.peerId.toInt64()
|
||||
}
|
||||
let itemSize = itemView.update(context: component.context, item: item, isSelected: isSelected, theme: component.theme, width: panelWidth, transition: .immediate)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: containerInsets.left, y: contentSize.height), size: itemSize)
|
||||
|
||||
@ -2729,9 +2729,10 @@ extension ChatControllerImpl {
|
||||
}
|
||||
|
||||
mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: transition.peerType, networkType: transition.networkType, animateIn: false, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: false), updateSizeAndInsets)
|
||||
}, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, _ in
|
||||
}, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, cutout, _ in
|
||||
strongSelf.additionalNavigationBarBackgroundHeight = value
|
||||
strongSelf.additionalNavigationBarHitTestSlop = hitTestSlop
|
||||
strongSelf.additionalNavigationBarCutout = cutout
|
||||
})
|
||||
|
||||
if let mappedTransition = mappedTransition {
|
||||
|
||||
@ -7072,13 +7072,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var suspendedNavigationBarLayout: ContainerViewLayout?
|
||||
var additionalNavigationBarBackgroundHeight: CGFloat = 0.0
|
||||
var additionalNavigationBarHitTestSlop: CGFloat = 0.0
|
||||
var additionalNavigationBarCutout: CGSize?
|
||||
|
||||
override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
if self.suspendNavigationBarLayout {
|
||||
self.suspendedNavigationBarLayout = layout
|
||||
return
|
||||
}
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: self.additionalNavigationBarCutout, transition: transition)
|
||||
}
|
||||
|
||||
override public func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
|
||||
@ -7116,10 +7117,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var navigationBarTransition = transition
|
||||
self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
|
||||
self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
|
||||
}, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, extraNavigationTransition in
|
||||
}, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, cutout, extraNavigationTransition in
|
||||
navigationBarTransition = extraNavigationTransition
|
||||
self.additionalNavigationBarBackgroundHeight = value
|
||||
self.additionalNavigationBarHitTestSlop = hitTestSlop
|
||||
self.additionalNavigationBarCutout = cutout
|
||||
})
|
||||
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
@ -7138,7 +7140,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.suspendNavigationBarLayout = false
|
||||
if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout {
|
||||
self.suspendedNavigationBarLayout = suspendedNavigationBarLayout
|
||||
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: navigationBarTransition)
|
||||
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: self.additionalNavigationBarCutout, transition: navigationBarTransition)
|
||||
}
|
||||
self.navigationBar?.additionalContentNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.additionalNavigationBarHitTestSlop, right: 0.0)
|
||||
}
|
||||
|
||||
@ -351,7 +351,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate, listViewTransaction: { _, _, _, _ in
|
||||
}, updateExtraNavigationBarBackgroundHeight: { _, _, _ in
|
||||
}, updateExtraNavigationBarBackgroundHeight: { _, _, _, _ in
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1033,7 +1033,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
self.wrappingNode.update(size: layout.size, cornerRadius: layout.deviceMetrics.screenCornerRadius, transition: .immediate)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, CGFloat, ContainedViewLayoutTransition) -> Void) {
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, CGFloat, CGSize?, ContainedViewLayoutTransition) -> Void) {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if let _ = self.scheduledAnimateInAsOverlayFromNode {
|
||||
transition = .immediate
|
||||
@ -1302,6 +1302,28 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
self.titleTopicsAccessoryPanelNode = nil
|
||||
}
|
||||
|
||||
var defaultLeftPanelWidth: CGFloat = 72.0
|
||||
defaultLeftPanelWidth += layout.safeInsets.left
|
||||
let leftPanelLeftInset = defaultLeftPanelWidth - 72.0
|
||||
|
||||
var leftPanelSize: CGSize?
|
||||
var dismissedLeftPanel: (component: AnyComponentWithIdentity<ChatSidePanelEnvironment>, view: ComponentView<ChatSidePanelEnvironment>)?
|
||||
var immediatelyLayoutLeftPanelNodeAndAnimateAppearance = false
|
||||
if let leftPanelComponent = sidePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.leftPanel?.component, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) {
|
||||
if self.leftPanel?.component.id != leftPanelComponent.id {
|
||||
dismissedLeftPanel = self.leftPanel
|
||||
self.leftPanel = (leftPanelComponent, ComponentView())
|
||||
immediatelyLayoutLeftPanelNodeAndAnimateAppearance = true
|
||||
} else if let leftPanel = self.leftPanel {
|
||||
self.leftPanel = (leftPanelComponent, leftPanel.view)
|
||||
}
|
||||
|
||||
leftPanelSize = CGSize(width: defaultLeftPanelWidth, height: layout.size.height)
|
||||
} else if let leftPanel = self.leftPanel {
|
||||
dismissedLeftPanel = leftPanel
|
||||
self.leftPanel = nil
|
||||
}
|
||||
|
||||
var dismissedTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode?
|
||||
var immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = false
|
||||
var titleAccessoryPanelHeight: CGFloat?
|
||||
@ -1321,7 +1343,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
|
||||
let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
|
||||
titleAccessoryPanelHeight = layoutResult.insetHeight
|
||||
titleAccessoryPanelBackgroundHeight = layoutResult.backgroundHeight
|
||||
titleAccessoryPanelHitTestSlop = layoutResult.hitTestSlop
|
||||
@ -1489,30 +1511,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
self.feePanelNode = nil
|
||||
}
|
||||
|
||||
var defaultLeftPanelWidth: CGFloat = 72.0
|
||||
if case .landscapeRight = layout.metrics.orientation {
|
||||
defaultLeftPanelWidth += layout.safeInsets.left
|
||||
}
|
||||
let leftPanelLeftInset = defaultLeftPanelWidth - 72.0
|
||||
|
||||
var leftPanelSize: CGSize?
|
||||
var dismissedLeftPanel: (component: AnyComponentWithIdentity<ChatSidePanelEnvironment>, view: ComponentView<ChatSidePanelEnvironment>)?
|
||||
var immediatelyLayoutLeftPanelNodeAndAnimateAppearance = false
|
||||
if let leftPanelComponent = sidePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.leftPanel?.component, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) {
|
||||
if self.leftPanel?.component.id != leftPanelComponent.id {
|
||||
dismissedLeftPanel = self.leftPanel
|
||||
self.leftPanel = (leftPanelComponent, ComponentView())
|
||||
immediatelyLayoutLeftPanelNodeAndAnimateAppearance = true
|
||||
} else if let leftPanel = self.leftPanel {
|
||||
self.leftPanel = (leftPanelComponent, leftPanel.view)
|
||||
}
|
||||
|
||||
leftPanelSize = CGSize(width: defaultLeftPanelWidth, height: layout.size.height)
|
||||
} else if let leftPanel = self.leftPanel {
|
||||
dismissedLeftPanel = leftPanel
|
||||
self.leftPanel = nil
|
||||
}
|
||||
|
||||
self.controllerInteraction.isSidePanelOpen = self.leftPanel != nil
|
||||
|
||||
var inputPanelNodeBaseHeight: CGFloat = 0.0
|
||||
@ -1793,6 +1791,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
self.historyNode.isSelectionGestureEnabled = isSelectionEnabled
|
||||
|
||||
transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 200.0)))
|
||||
self.titleAccessoryPanelContainer.hitTestExcludeInsets = UIEdgeInsets(top: 0.0, left: leftPanelSize?.width ?? 0.0, bottom: 0.0, right: 0.0)
|
||||
|
||||
transition.updateFrame(node: self.inputContextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
|
||||
transition.updateFrame(node: self.inputContextOverTextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
|
||||
@ -1848,8 +1847,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
insets.top += panelHeight
|
||||
extraNavigationBarHeight += panelHeight
|
||||
}
|
||||
|
||||
var extraNavigationBarLeftCutout: CGSize?
|
||||
extraNavigationBarLeftCutout = CGSize(width: 44.0, height: 30.0)
|
||||
|
||||
updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, extraTransition)
|
||||
updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, extraNavigationBarLeftCutout, extraTransition)
|
||||
|
||||
let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom)
|
||||
|
||||
@ -2270,8 +2272,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
if immediatelyLayoutLeftPanelNodeAndAnimateAppearance {
|
||||
leftPanelView.frame = leftPanelFrame.offsetBy(dx: -leftPanelSize.width, dy: 0.0)
|
||||
if let leftPanelView = leftPanelView as? ChatSideTopicsPanel.View {
|
||||
leftPanelView.updateGlobalOffset(globalOffset: -leftPanelSize.width, transition: ComponentTransition(transition))
|
||||
|
||||
if self.titleTopicsAccessoryPanelNode != nil {
|
||||
if let leftPanelView = leftPanelView as? ChatSideTopicsPanel.View {
|
||||
leftPanelView.updateGlobalOffset(globalOffset: -leftPanelSize.width, transition: ComponentTransition(transition))
|
||||
}
|
||||
}
|
||||
}
|
||||
transition.updateFrame(view: leftPanelView, frame: leftPanelFrame)
|
||||
@ -2298,7 +2303,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
dismissedLeftPanelView?.removeFromSuperview()
|
||||
})
|
||||
if let dismissedLeftPanelView = dismissedLeftPanelView as? ChatSideTopicsPanel.View {
|
||||
dismissedLeftPanelView.updateGlobalOffset(globalOffset: -dismissedLeftPanelSize.width, transition: ComponentTransition(transition))
|
||||
if self.titleTopicsAccessoryPanelNode != nil {
|
||||
dismissedLeftPanelView.updateGlobalOffset(globalOffset: -dismissedLeftPanelSize.width, transition: ComponentTransition(transition))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2352,7 +2359,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode, let titleTopicsAccessoryPanelFrame, (immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance || !titleTopicsAccessoryPanelNode.frame.equalTo(titleTopicsAccessoryPanelFrame)) {
|
||||
if immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance {
|
||||
titleTopicsAccessoryPanelNode.frame = titleTopicsAccessoryPanelFrame.offsetBy(dx: 0.0, dy: -titleTopicsAccessoryPanelFrame.height)
|
||||
titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate)
|
||||
if self.leftPanel != nil {
|
||||
titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate)
|
||||
}
|
||||
|
||||
let topPanelTransition = ComponentTransition(transition)
|
||||
/*switch topPanelTransition.animation {
|
||||
@ -2378,8 +2387,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
let previousFrame = titleAccessoryPanelNode.frame
|
||||
titleAccessoryPanelNode.frame = titleAccessoryPanelFrame
|
||||
if transition.isAnimated && previousFrame.width != titleAccessoryPanelFrame.width {
|
||||
} else {
|
||||
} else if immediatelyLayoutAccessoryPanelAndAnimateAppearance {
|
||||
transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: -titleAccessoryPanelFrame.height))
|
||||
} else if previousFrame.minY != titleAccessoryPanelFrame.minY {
|
||||
transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: previousFrame.minY - titleAccessoryPanelFrame.minY))
|
||||
}
|
||||
}
|
||||
|
||||
@ -2502,7 +2513,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
transition.updateFrame(node: dismissedTitleTopicsAccessoryPanelNode, frame: dismissedTopPanelFrame, completion: { [weak dismissedTitleTopicsAccessoryPanelNode] _ in
|
||||
dismissedTitleTopicsAccessoryPanelNode?.removeFromSupernode()
|
||||
})
|
||||
dismissedTitleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -dismissedTopPanelFrame.height, transition: ComponentTransition(transition))
|
||||
if self.leftPanel != nil {
|
||||
dismissedTitleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -dismissedTopPanelFrame.height, transition: ComponentTransition(transition))
|
||||
}
|
||||
}
|
||||
|
||||
if let dismissedTitleAccessoryPanelNode {
|
||||
@ -2802,6 +2815,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
displayInlineSearch = true
|
||||
}
|
||||
}
|
||||
if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {
|
||||
if self.chatPresentationInterfaceState.search != nil {
|
||||
displayInlineSearch = true
|
||||
}
|
||||
}
|
||||
|
||||
if displayInlineSearch {
|
||||
let peerId = self.chatPresentationInterfaceState.chatLocation.peerId
|
||||
@ -2827,6 +2845,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
} else {
|
||||
mappedContents = .empty
|
||||
}
|
||||
} else if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {
|
||||
mappedContents = .monoforumChats(query: self.chatPresentationInterfaceState.search?.query ?? "")
|
||||
} else if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation {
|
||||
mappedContents = .tag(MemoryBuffer())
|
||||
} else {
|
||||
@ -2952,30 +2972,39 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
|
||||
if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
|
||||
self.interfaceInteraction?.updateChatLocationThread(peer.id.toInt64())
|
||||
|
||||
self.controller?.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
|
||||
return current.updatedSearch(nil)
|
||||
})
|
||||
} else {
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
context: self.context,
|
||||
chatLocation: .replyThread(ChatReplyThreadMessage(
|
||||
peerId: self.context.account.peerId,
|
||||
threadId: peer.id.toInt64(),
|
||||
channelMessageId: nil,
|
||||
isChannelPost: false,
|
||||
isForumPost: false,
|
||||
isMonoforumPost: false,
|
||||
maxMessage: nil,
|
||||
maxReadIncomingMessageId: nil,
|
||||
maxReadOutgoingMessageId: nil,
|
||||
unreadCount: 0,
|
||||
initialFilledHoles: IndexSet(),
|
||||
initialAnchor: .automatic,
|
||||
isNotAvailable: false
|
||||
)),
|
||||
subject: nil,
|
||||
keepStack: .always
|
||||
))
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
context: self.context,
|
||||
chatLocation: .replyThread(ChatReplyThreadMessage(
|
||||
peerId: self.context.account.peerId,
|
||||
threadId: peer.id.toInt64(),
|
||||
channelMessageId: nil,
|
||||
isChannelPost: false,
|
||||
isForumPost: false,
|
||||
isMonoforumPost: false,
|
||||
maxMessage: nil,
|
||||
maxReadIncomingMessageId: nil,
|
||||
maxReadOutgoingMessageId: nil,
|
||||
unreadCount: 0,
|
||||
initialFilledHoles: IndexSet(),
|
||||
initialAnchor: .automatic,
|
||||
isNotAvailable: false
|
||||
)),
|
||||
subject: nil,
|
||||
keepStack: .always
|
||||
))
|
||||
},
|
||||
loadTagMessages: { tag, index in
|
||||
let input: ChatHistoryLocationInput
|
||||
@ -3048,6 +3077,89 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
return foundLocalPeers
|
||||
},
|
||||
getChats: { [weak self] query in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
guard let peerId = self.chatPresentationInterfaceState.chatLocation.peerId else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let viewKey: PostboxViewKey = .savedMessagesIndex(peerId: peerId)
|
||||
let interfaceStateKey: PostboxViewKey = .chatInterfaceState(peerId: peerId)
|
||||
|
||||
let accountPeerId = self.context.account.peerId
|
||||
let threadListSignal: Signal<EngineChatList?, NoError> = self.context.account.postbox.combinedView(keys: [viewKey, interfaceStateKey])
|
||||
|> map { views -> EngineChatList? in
|
||||
guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
var draft: EngineChatList.Draft?
|
||||
if let interfaceStateView = views.views[interfaceStateKey] as? ChatInterfaceStateView {
|
||||
if let embeddedState = interfaceStateView.value, let _ = embeddedState.overrideChatTimestamp {
|
||||
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
|
||||
if let text = opaqueState.synchronizeableInputState?.text {
|
||||
draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? [])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var items: [EngineChatList.Item] = []
|
||||
for item in view.items {
|
||||
guard let sourcePeer = item.peer else {
|
||||
continue
|
||||
}
|
||||
|
||||
let sourceId = PeerId(item.id)
|
||||
|
||||
var messages: [EngineMessage] = []
|
||||
if let topMessage = item.topMessage {
|
||||
messages.append(EngineMessage(topMessage))
|
||||
}
|
||||
|
||||
let mappedMessageIndex = MessageIndex(id: MessageId(peerId: sourceId, namespace: item.index.id.namespace, id: item.index.id.id), timestamp: item.index.timestamp)
|
||||
|
||||
items.append(EngineChatList.Item(
|
||||
id: .chatList(sourceId),
|
||||
index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)),
|
||||
messages: messages,
|
||||
readCounters: EnginePeerReadCounters(
|
||||
incomingReadId: 0, outgoingReadId: 0, count: Int32(item.unreadCount), markedUnread: false),
|
||||
isMuted: false,
|
||||
draft: sourceId == accountPeerId ? draft : nil,
|
||||
threadData: nil,
|
||||
renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)),
|
||||
presence: nil,
|
||||
hasUnseenMentions: false,
|
||||
hasUnseenReactions: false,
|
||||
forumTopicData: nil,
|
||||
topForumTopicItems: [],
|
||||
hasFailed: false,
|
||||
isContact: false,
|
||||
autoremoveTimeout: nil,
|
||||
storyStats: nil,
|
||||
displayAsTopicList: false,
|
||||
isPremiumRequiredToMessage: false,
|
||||
mediaDraftContentType: nil
|
||||
))
|
||||
}
|
||||
|
||||
let list = EngineChatList(
|
||||
items: items.reversed(),
|
||||
groupItems: [],
|
||||
additionalItems: [],
|
||||
hasEarlier: false,
|
||||
hasLater: false,
|
||||
isLoading: view.isLoading
|
||||
)
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
return threadListSignal
|
||||
},
|
||||
loadMoreSearchResults: { [weak self] in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
@ -4052,7 +4164,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
|
||||
self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
|
||||
}, updateExtraNavigationBarBackgroundHeight: { _, _, _ in
|
||||
}, updateExtraNavigationBarBackgroundHeight: { _, _, _, _ in
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,13 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class ChatControllerTitlePanelNodeContainer: ASDisplayNode {
|
||||
var hitTestExcludeInsets = UIEdgeInsets()
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if point.x < self.hitTestExcludeInsets.left {
|
||||
return nil
|
||||
}
|
||||
|
||||
for subview in self.view.subviews {
|
||||
if let result = subview.hitTest(self.view.convert(point, to: subview), with: event) {
|
||||
return result
|
||||
|
||||
@ -232,13 +232,24 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
||||
}
|
||||
|
||||
func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? {
|
||||
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {
|
||||
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil {
|
||||
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
|
||||
if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId {
|
||||
if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode {
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatTopicListTitleAccessoryPanelNode(context: context, peerId: peerId)
|
||||
let panel = ChatTopicListTitleAccessoryPanelNode(context: context, peerId: peerId, isMonoforum: true)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
}
|
||||
} else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForum, chatPresentationInterfaceState.search == nil {
|
||||
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
|
||||
if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId {
|
||||
if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode {
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatTopicListTitleAccessoryPanelNode(context: context, peerId: peerId, isMonoforum: false)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
@ -253,7 +264,7 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState
|
||||
return nil
|
||||
}
|
||||
|
||||
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {
|
||||
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil {
|
||||
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
|
||||
if case .side = topicListDisplayMode {
|
||||
return AnyComponentWithIdentity(
|
||||
@ -263,6 +274,28 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState
|
||||
theme: chatPresentationInterfaceState.theme,
|
||||
strings: chatPresentationInterfaceState.strings,
|
||||
peerId: peerId,
|
||||
isMonoforum: true,
|
||||
topicId: chatPresentationInterfaceState.chatLocation.threadId,
|
||||
togglePanel: { [weak interfaceInteraction] in
|
||||
interfaceInteraction?.toggleChatSidebarMode()
|
||||
},
|
||||
updateTopicId: { [weak interfaceInteraction] topicId in
|
||||
interfaceInteraction?.updateChatLocationThread(topicId)
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
} else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForum, chatPresentationInterfaceState.search == nil {
|
||||
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
|
||||
if case .side = topicListDisplayMode {
|
||||
return AnyComponentWithIdentity(
|
||||
id: "topics",
|
||||
component: AnyComponent(ChatSideTopicsPanel(
|
||||
context: context,
|
||||
theme: chatPresentationInterfaceState.theme,
|
||||
strings: chatPresentationInterfaceState.strings,
|
||||
peerId: peerId,
|
||||
isMonoforum: false,
|
||||
topicId: chatPresentationInterfaceState.chatLocation.threadId,
|
||||
togglePanel: { [weak interfaceInteraction] in
|
||||
interfaceInteraction?.toggleChatSidebarMode()
|
||||
|
||||
@ -517,12 +517,13 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if self.currentLayout?.0 != width || self.currentLayout?.1 != leftInset || self.currentLayout?.2 != rightInset || messageUpdated || themeUpdated || currentTranslateToLanguageUpdated {
|
||||
self.currentLayout = (width, leftInset, rightInset)
|
||||
|
||||
let messageUpdated = self.currentMessage?.message.id != interfaceState.pinnedMessage?.message.id
|
||||
let previousMessageWasNil = self.currentMessage == nil
|
||||
self.currentMessage = interfaceState.pinnedMessage
|
||||
|
||||
if let currentMessage = self.currentMessage, let currentLayout = self.currentLayout {
|
||||
self.dustNode?.update(revealed: false, animated: false)
|
||||
self.enqueueTransition(width: currentLayout.0, panelHeight: panelHeight, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread, translateToLanguage: translateToLanguage?.toLang)
|
||||
self.enqueueTransition(width: currentLayout.0, panelHeight: panelHeight, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: messageUpdated ? .immediate : transition, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread, translateToLanguage: translateToLanguage?.toLang)
|
||||
}
|
||||
}
|
||||
|
||||
@ -565,6 +566,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.textNode.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -offset), to: CGPoint(), duration: 0.2, additive: true)
|
||||
self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
} else {
|
||||
animationTransition = transition
|
||||
}
|
||||
|
||||
let makeTitleLayout = self.titleNode.asyncLayout()
|
||||
@ -575,13 +578,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
let previousMediaReference = self.previousMediaReference
|
||||
let context = self.context
|
||||
|
||||
let targetQueue: Queue
|
||||
if firstTime {
|
||||
targetQueue = Queue.mainQueue()
|
||||
} else {
|
||||
targetQueue = self.queue
|
||||
}
|
||||
|
||||
let contentLeftInset: CGFloat = leftInset + 10.0
|
||||
var textLineInset: CGFloat = 10.0
|
||||
var rightInset: CGFloat = 14.0 + rightInset
|
||||
@ -592,293 +588,288 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
rightInset += self.actionButton.bounds.width - 14.0
|
||||
}
|
||||
|
||||
targetQueue.async { [weak self] in
|
||||
var updatedMediaReference: AnyMediaReference?
|
||||
var imageDimensions: CGSize?
|
||||
|
||||
let giveaway = pinnedMessage.message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway
|
||||
|
||||
var titleStrings: [AnimatedCountLabelNode.Segment] = []
|
||||
if let _ = giveaway {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
} else {
|
||||
if pinnedMessage.totalCount == 2 {
|
||||
if pinnedMessage.index == 0 {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
} else {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
}
|
||||
} else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
var updatedMediaReference: AnyMediaReference?
|
||||
var imageDimensions: CGSize?
|
||||
|
||||
let giveaway = pinnedMessage.message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway
|
||||
|
||||
var titleStrings: [AnimatedCountLabelNode.Segment] = []
|
||||
if let _ = giveaway {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
} else {
|
||||
if pinnedMessage.totalCount == 2 {
|
||||
if pinnedMessage.index == 0 {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
} else {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
}
|
||||
} else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
} else {
|
||||
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
|
||||
}
|
||||
|
||||
if !message.containsSecretMedia {
|
||||
for media in message.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
updatedMediaReference = .message(message: MessageReference(message), media: image)
|
||||
if let representation = largestRepresentationForPhoto(image) {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
}
|
||||
break
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
updatedMediaReference = .message(message: MessageReference(message), media: file)
|
||||
if !file.isInstantVideo && !file.isSticker, let representation = largestImageRepresentation(file.previewRepresentations) {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
} else if file.isAnimated, let dimensions = file.dimensions {
|
||||
}
|
||||
|
||||
if !message.containsSecretMedia {
|
||||
for media in message.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
updatedMediaReference = .message(message: MessageReference(message), media: image)
|
||||
if let representation = largestRepresentationForPhoto(image) {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
}
|
||||
break
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
updatedMediaReference = .message(message: MessageReference(message), media: file)
|
||||
if !file.isInstantVideo && !file.isSticker, let representation = largestImageRepresentation(file.previewRepresentations) {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
} else if file.isAnimated, let dimensions = file.dimensions {
|
||||
imageDimensions = dimensions.cgSize
|
||||
}
|
||||
break
|
||||
} else if let paidContent = media as? TelegramMediaPaidContent, let firstMedia = paidContent.extendedMedia.first {
|
||||
switch firstMedia {
|
||||
case let .preview(dimensions, immediateThumbnailData, _):
|
||||
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
|
||||
if let dimensions {
|
||||
imageDimensions = dimensions.cgSize
|
||||
}
|
||||
break
|
||||
} else if let paidContent = media as? TelegramMediaPaidContent, let firstMedia = paidContent.extendedMedia.first {
|
||||
switch firstMedia {
|
||||
case let .preview(dimensions, immediateThumbnailData, _):
|
||||
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
|
||||
if let dimensions {
|
||||
updatedMediaReference = .standalone(media: thumbnailMedia)
|
||||
case let .full(fullMedia):
|
||||
updatedMediaReference = .message(message: MessageReference(message), media: fullMedia)
|
||||
if let image = fullMedia as? TelegramMediaImage {
|
||||
if let representation = largestRepresentationForPhoto(image) {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
}
|
||||
break
|
||||
} else if let file = fullMedia as? TelegramMediaFile {
|
||||
if let dimensions = file.dimensions {
|
||||
imageDimensions = dimensions.cgSize
|
||||
}
|
||||
updatedMediaReference = .standalone(media: thumbnailMedia)
|
||||
case let .full(fullMedia):
|
||||
updatedMediaReference = .message(message: MessageReference(message), media: fullMedia)
|
||||
if let image = fullMedia as? TelegramMediaImage {
|
||||
if let representation = largestRepresentationForPhoto(image) {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
}
|
||||
break
|
||||
} else if let file = fullMedia as? TelegramMediaFile {
|
||||
if let dimensions = file.dimensions {
|
||||
imageDimensions = dimensions.cgSize
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isReplyThread {
|
||||
let titleString: String
|
||||
if let author = message.effectiveAuthor {
|
||||
titleString = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||
} else {
|
||||
titleString = ""
|
||||
}
|
||||
titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
|
||||
} else {
|
||||
for media in message.media {
|
||||
if let media = media as? TelegramMediaInvoice {
|
||||
titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var applyImage: (() -> Void)?
|
||||
if let imageDimensions = imageDimensions {
|
||||
let boundingSize = CGSize(width: 35.0, height: 35.0)
|
||||
applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
|
||||
|
||||
textLineInset += 9.0 + 35.0
|
||||
}
|
||||
|
||||
var mediaUpdated = false
|
||||
if let updatedMediaReference = updatedMediaReference, let previousMediaReference = previousMediaReference {
|
||||
mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media)
|
||||
} else if (updatedMediaReference != nil) != (previousMediaReference != nil) {
|
||||
mediaUpdated = true
|
||||
}
|
||||
|
||||
let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute })
|
||||
|
||||
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
var updatedFetchMediaSignal: Signal<FetchResourceSourceType, FetchResourceError>?
|
||||
if mediaUpdated {
|
||||
if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
|
||||
if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
|
||||
if imageReference.media.representations.isEmpty {
|
||||
updateImageSignal = chatSecretPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, ignoreFullSize: true, synchronousLoad: true)
|
||||
} else {
|
||||
updateImageSignal = chatMessagePhotoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, blurred: hasSpoiler)
|
||||
}
|
||||
} else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) {
|
||||
if fileReference.media.isAnimatedSticker {
|
||||
let dimensions = fileReference.media.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: fileReference.media, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)))
|
||||
updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource))
|
||||
} else if fileReference.media.isVideo || fileReference.media.isAnimated {
|
||||
updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, blurred: hasSpoiler)
|
||||
} else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
|
||||
updateImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .peer(message.id.peerId), mediaReference: fileReference.abstract, representation: iconImageRepresentation)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateImageSignal = .single({ _ in return nil })
|
||||
}
|
||||
}
|
||||
let (titleLayout, titleApply) = makeTitleLayout(CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), .zero, titleStrings)
|
||||
|
||||
let (textString, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
|
||||
|
||||
let messageText: NSAttributedString
|
||||
let textFont = Font.regular(15.0)
|
||||
if let giveaway {
|
||||
let dateString = stringForDateWithoutYear(date: Date(timeIntervalSince1970: TimeInterval(giveaway.untilDate)), timeZone: .current, strings: strings)
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
let isFinished = currentTime >= giveaway.untilDate
|
||||
let text: String
|
||||
if isFinished {
|
||||
let winnersString = strings.Conversation_PinnedGiveaway_Finished_Winners(giveaway.quantity)
|
||||
text = strings.Conversation_PinnedGiveaway_Finished(winnersString, dateString).string
|
||||
} else {
|
||||
let winnersString = strings.Conversation_PinnedGiveaway_Ongoing_Winners(giveaway.quantity)
|
||||
text = strings.Conversation_PinnedGiveaway_Ongoing(winnersString, dateString).string
|
||||
}
|
||||
messageText = NSAttributedString(string: text, font: textFont, textColor: theme.chat.inputPanel.primaryTextColor)
|
||||
} else if isText {
|
||||
var text = message.text
|
||||
var messageEntities = message.textEntitiesAttribute?.entities ?? []
|
||||
|
||||
if let translateToLanguage = translateToLanguage, !text.isEmpty {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage {
|
||||
text = attribute.text
|
||||
messageEntities = attribute.entities
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let entities = messageEntities.filter { entity in
|
||||
switch entity.type {
|
||||
case .Spoiler, .CustomEmoji:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
let textColor = theme.chat.inputPanel.primaryTextColor
|
||||
if entities.count > 0 {
|
||||
messageText = stringWithAppliedEntities(trimToLineCount(text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||
} else {
|
||||
messageText = NSAttributedString(string: foldLineBreaks(text), font: textFont, textColor: textColor)
|
||||
}
|
||||
} else {
|
||||
messageText = NSAttributedString(string: foldLineBreaks(textString.string), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor)
|
||||
}
|
||||
|
||||
let textConstrainedSize = CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude)
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
|
||||
|
||||
let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
||||
if !textLayout.spoilers.isEmpty {
|
||||
spoilerTextLayoutAndApply = makeSpoilerTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0), displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true))
|
||||
}
|
||||
|
||||
if isReplyThread {
|
||||
let titleString: String
|
||||
if let author = message.effectiveAuthor {
|
||||
titleString = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||
} else {
|
||||
spoilerTextLayoutAndApply = nil
|
||||
titleString = ""
|
||||
}
|
||||
titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
|
||||
} else {
|
||||
for media in message.media {
|
||||
if let media = media as? TelegramMediaInvoice {
|
||||
titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var applyImage: (() -> Void)?
|
||||
if let imageDimensions = imageDimensions {
|
||||
let boundingSize = CGSize(width: 35.0, height: 35.0)
|
||||
applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
|
||||
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
let _ = titleApply(animation != nil)
|
||||
|
||||
var textArguments: TextNodeWithEntities.Arguments?
|
||||
if let cache = strongSelf.animationCache, let renderer = strongSelf.animationRenderer {
|
||||
textArguments = TextNodeWithEntities.Arguments(
|
||||
context: strongSelf.context,
|
||||
cache: cache,
|
||||
renderer: renderer,
|
||||
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||
attemptSynchronous: false
|
||||
)
|
||||
}
|
||||
let _ = textApply(textArguments)
|
||||
|
||||
strongSelf.previousMediaReference = updatedMediaReference
|
||||
|
||||
animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize(width: width, height: panelHeight)))
|
||||
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size)
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size)
|
||||
strongSelf.textNode.textNode.frame = textFrame
|
||||
|
||||
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
|
||||
let spoilerTextNode = spoilerTextApply(textArguments)
|
||||
if strongSelf.spoilerTextNode == nil {
|
||||
spoilerTextNode.textNode.alpha = 0.0
|
||||
spoilerTextNode.textNode.isUserInteractionEnabled = false
|
||||
spoilerTextNode.textNode.contentMode = .topLeft
|
||||
spoilerTextNode.textNode.contentsScale = UIScreenScale
|
||||
spoilerTextNode.textNode.displaysAsynchronously = false
|
||||
strongSelf.contentTextContainer.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textNode.textNode)
|
||||
|
||||
strongSelf.spoilerTextNode = spoilerTextNode
|
||||
}
|
||||
|
||||
strongSelf.spoilerTextNode?.textNode.frame = textFrame
|
||||
|
||||
let dustNode: InvisibleInkDustNode
|
||||
if let current = strongSelf.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
strongSelf.dustNode = dustNode
|
||||
strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
|
||||
}
|
||||
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
||||
dustNode.update(size: dustNode.frame.size, color: theme.chat.inputPanel.secondaryTextColor, textColor: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
||||
} else if let spoilerTextNode = strongSelf.spoilerTextNode {
|
||||
strongSelf.spoilerTextNode = nil
|
||||
spoilerTextNode.textNode.removeFromSupernode()
|
||||
|
||||
if let dustNode = strongSelf.dustNode {
|
||||
strongSelf.dustNode = nil
|
||||
dustNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.textNode.visibilityRect = CGRect.infinite
|
||||
strongSelf.spoilerTextNode?.visibilityRect = CGRect.infinite
|
||||
|
||||
let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight))
|
||||
animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame)
|
||||
strongSelf.lineNode.update(
|
||||
colors: AnimatedNavigationStripeNode.Colors(
|
||||
foreground: theme.chat.inputPanel.panelControlAccentColor,
|
||||
background: theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5),
|
||||
clearBackground: theme.chat.inputPanel.panelBackgroundColor
|
||||
),
|
||||
configuration: AnimatedNavigationStripeNode.Configuration(
|
||||
height: panelHeight,
|
||||
index: pinnedMessage.index,
|
||||
count: pinnedMessage.totalCount
|
||||
),
|
||||
transition: animationTransition
|
||||
)
|
||||
|
||||
strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 35.0, height: 35.0))
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0))
|
||||
|
||||
if let applyImage = applyImage {
|
||||
applyImage()
|
||||
|
||||
animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 1.0)
|
||||
animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 1.0, beginWithCurrentState: true)
|
||||
textLineInset += 9.0 + 35.0
|
||||
}
|
||||
|
||||
var mediaUpdated = false
|
||||
if let updatedMediaReference = updatedMediaReference, let previousMediaReference = previousMediaReference {
|
||||
mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media)
|
||||
} else if (updatedMediaReference != nil) != (previousMediaReference != nil) {
|
||||
mediaUpdated = true
|
||||
}
|
||||
|
||||
let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute })
|
||||
|
||||
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
var updatedFetchMediaSignal: Signal<FetchResourceSourceType, FetchResourceError>?
|
||||
if mediaUpdated {
|
||||
if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
|
||||
if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
|
||||
if imageReference.media.representations.isEmpty {
|
||||
updateImageSignal = chatSecretPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, ignoreFullSize: true, synchronousLoad: true)
|
||||
} else {
|
||||
animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 0.1)
|
||||
animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 0.0, beginWithCurrentState: true)
|
||||
updateImageSignal = chatMessagePhotoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, blurred: hasSpoiler)
|
||||
}
|
||||
|
||||
if let updateImageSignal = updateImageSignal {
|
||||
strongSelf.imageNode.setSignal(updateImageSignal)
|
||||
} else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) {
|
||||
if fileReference.media.isAnimatedSticker {
|
||||
let dimensions = fileReference.media.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: fileReference.media, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)))
|
||||
updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource))
|
||||
} else if fileReference.media.isVideo || fileReference.media.isAnimated {
|
||||
updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, blurred: hasSpoiler)
|
||||
} else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
|
||||
updateImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .peer(message.id.peerId), mediaReference: fileReference.abstract, representation: iconImageRepresentation)
|
||||
}
|
||||
if let updatedFetchMediaSignal = updatedFetchMediaSignal {
|
||||
strongSelf.fetchDisposable.set(updatedFetchMediaSignal.startStrict())
|
||||
}
|
||||
} else {
|
||||
updateImageSignal = .single({ _ in return nil })
|
||||
}
|
||||
}
|
||||
let (titleLayout, titleApply) = makeTitleLayout(CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), .zero, titleStrings)
|
||||
|
||||
let (textString, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
|
||||
|
||||
let messageText: NSAttributedString
|
||||
let textFont = Font.regular(15.0)
|
||||
if let giveaway {
|
||||
let dateString = stringForDateWithoutYear(date: Date(timeIntervalSince1970: TimeInterval(giveaway.untilDate)), timeZone: .current, strings: strings)
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
let isFinished = currentTime >= giveaway.untilDate
|
||||
let text: String
|
||||
if isFinished {
|
||||
let winnersString = strings.Conversation_PinnedGiveaway_Finished_Winners(giveaway.quantity)
|
||||
text = strings.Conversation_PinnedGiveaway_Finished(winnersString, dateString).string
|
||||
} else {
|
||||
let winnersString = strings.Conversation_PinnedGiveaway_Ongoing_Winners(giveaway.quantity)
|
||||
text = strings.Conversation_PinnedGiveaway_Ongoing(winnersString, dateString).string
|
||||
}
|
||||
messageText = NSAttributedString(string: text, font: textFont, textColor: theme.chat.inputPanel.primaryTextColor)
|
||||
} else if isText {
|
||||
var text = message.text
|
||||
var messageEntities = message.textEntitiesAttribute?.entities ?? []
|
||||
|
||||
if let translateToLanguage = translateToLanguage, !text.isEmpty {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage {
|
||||
text = attribute.text
|
||||
messageEntities = attribute.entities
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let entities = messageEntities.filter { entity in
|
||||
switch entity.type {
|
||||
case .Spoiler, .CustomEmoji:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
let textColor = theme.chat.inputPanel.primaryTextColor
|
||||
if entities.count > 0 {
|
||||
messageText = stringWithAppliedEntities(trimToLineCount(text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||
} else {
|
||||
messageText = NSAttributedString(string: foldLineBreaks(text), font: textFont, textColor: textColor)
|
||||
}
|
||||
} else {
|
||||
messageText = NSAttributedString(string: foldLineBreaks(textString.string), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor)
|
||||
}
|
||||
|
||||
let textConstrainedSize = CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude)
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
|
||||
|
||||
let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
||||
if !textLayout.spoilers.isEmpty {
|
||||
spoilerTextLayoutAndApply = makeSpoilerTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0), displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true))
|
||||
} else {
|
||||
spoilerTextLayoutAndApply = nil
|
||||
}
|
||||
|
||||
let strongSelf = self
|
||||
let _ = titleApply(animation != nil)
|
||||
|
||||
var textArguments: TextNodeWithEntities.Arguments?
|
||||
if let cache = strongSelf.animationCache, let renderer = strongSelf.animationRenderer {
|
||||
textArguments = TextNodeWithEntities.Arguments(
|
||||
context: strongSelf.context,
|
||||
cache: cache,
|
||||
renderer: renderer,
|
||||
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||
attemptSynchronous: false
|
||||
)
|
||||
}
|
||||
let _ = textApply(textArguments)
|
||||
|
||||
strongSelf.previousMediaReference = updatedMediaReference
|
||||
|
||||
animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize(width: width, height: panelHeight)))
|
||||
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size)
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size)
|
||||
strongSelf.textNode.textNode.frame = textFrame
|
||||
|
||||
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
|
||||
let spoilerTextNode = spoilerTextApply(textArguments)
|
||||
if strongSelf.spoilerTextNode == nil {
|
||||
spoilerTextNode.textNode.alpha = 0.0
|
||||
spoilerTextNode.textNode.isUserInteractionEnabled = false
|
||||
spoilerTextNode.textNode.contentMode = .topLeft
|
||||
spoilerTextNode.textNode.contentsScale = UIScreenScale
|
||||
spoilerTextNode.textNode.displaysAsynchronously = false
|
||||
strongSelf.contentTextContainer.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textNode.textNode)
|
||||
|
||||
strongSelf.spoilerTextNode = spoilerTextNode
|
||||
}
|
||||
|
||||
strongSelf.spoilerTextNode?.textNode.frame = textFrame
|
||||
|
||||
let dustNode: InvisibleInkDustNode
|
||||
if let current = strongSelf.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
strongSelf.dustNode = dustNode
|
||||
strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
|
||||
}
|
||||
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
||||
dustNode.update(size: dustNode.frame.size, color: theme.chat.inputPanel.secondaryTextColor, textColor: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
||||
} else if let spoilerTextNode = strongSelf.spoilerTextNode {
|
||||
strongSelf.spoilerTextNode = nil
|
||||
spoilerTextNode.textNode.removeFromSupernode()
|
||||
|
||||
if let dustNode = strongSelf.dustNode {
|
||||
strongSelf.dustNode = nil
|
||||
dustNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.textNode.visibilityRect = CGRect.infinite
|
||||
strongSelf.spoilerTextNode?.visibilityRect = CGRect.infinite
|
||||
|
||||
let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight))
|
||||
animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame)
|
||||
strongSelf.lineNode.update(
|
||||
colors: AnimatedNavigationStripeNode.Colors(
|
||||
foreground: theme.chat.inputPanel.panelControlAccentColor,
|
||||
background: theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5),
|
||||
clearBackground: theme.chat.inputPanel.panelBackgroundColor
|
||||
),
|
||||
configuration: AnimatedNavigationStripeNode.Configuration(
|
||||
height: panelHeight,
|
||||
index: pinnedMessage.index,
|
||||
count: pinnedMessage.totalCount
|
||||
),
|
||||
transition: animationTransition
|
||||
)
|
||||
|
||||
strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 35.0, height: 35.0))
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0))
|
||||
|
||||
if let applyImage = applyImage {
|
||||
applyImage()
|
||||
|
||||
animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 1.0)
|
||||
animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 1.0, beginWithCurrentState: true)
|
||||
} else {
|
||||
animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 0.1)
|
||||
animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 0.0, beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
if let updateImageSignal = updateImageSignal {
|
||||
strongSelf.imageNode.setSignal(updateImageSignal)
|
||||
}
|
||||
if let updatedFetchMediaSignal = updatedFetchMediaSignal {
|
||||
strongSelf.fetchDisposable.set(updatedFetchMediaSignal.startStrict())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -246,7 +246,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
|
||||
private let containerButton: HighlightTrackingButton
|
||||
|
||||
private let icon = ComponentView<Empty>()
|
||||
private var icon: ComponentView<Empty>?
|
||||
private var avatarNode: AvatarNode?
|
||||
private let title = ComponentView<Empty>()
|
||||
private var badge: ComponentView<Empty>?
|
||||
@ -319,33 +319,60 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
let spacing: CGFloat = 3.0
|
||||
let badgeSpacing: CGFloat = 4.0
|
||||
|
||||
let avatarIconContent: EmojiStatusComponent.Content
|
||||
if case let .forum(topicId) = item.item.id, topicId != 1, let threadData = item.item.threadData {
|
||||
if let fileId = threadData.info.icon, fileId != 0 {
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0))
|
||||
let iconSize = CGSize(width: 18.0, height: 18.0)
|
||||
|
||||
var avatarIconContent: EmojiStatusComponent.Content?
|
||||
if case let .forum(topicId) = item.item.id {
|
||||
if topicId != 1, let threadData = item.item.threadData {
|
||||
if let fileId = threadData.info.icon, fileId != 0 {
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0))
|
||||
} else {
|
||||
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
|
||||
}
|
||||
} else {
|
||||
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: CGSize(width: 18.0, height: 18.0))
|
||||
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme))
|
||||
}
|
||||
} else {
|
||||
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme))
|
||||
}
|
||||
|
||||
let avatarIconComponent = EmojiStatusComponent(
|
||||
context: context,
|
||||
animationCache: context.animationCache,
|
||||
animationRenderer: context.animationRenderer,
|
||||
content: avatarIconContent,
|
||||
isVisibleForAnimations: false,
|
||||
action: nil
|
||||
)
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(avatarIconComponent),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 18.0, height: 18.0)
|
||||
)
|
||||
if let avatarIconContent {
|
||||
let avatarIconComponent = EmojiStatusComponent(
|
||||
context: context,
|
||||
animationCache: context.animationCache,
|
||||
animationRenderer: context.animationRenderer,
|
||||
content: avatarIconContent,
|
||||
isVisibleForAnimations: false,
|
||||
action: nil
|
||||
)
|
||||
let icon: ComponentView<Empty>
|
||||
if let current = self.icon {
|
||||
icon = current
|
||||
} else {
|
||||
icon = ComponentView()
|
||||
self.icon = icon
|
||||
}
|
||||
let _ = icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(avatarIconComponent),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 18.0, height: 18.0)
|
||||
)
|
||||
} else if let icon = self.icon {
|
||||
self.icon = nil
|
||||
icon.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
let titleText: String = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " "
|
||||
let titleText: String
|
||||
if case let .forum(topicId) = item.item.id {
|
||||
let _ = topicId
|
||||
if let threadData = item.item.threadData {
|
||||
titleText = threadData.info.title
|
||||
} else {
|
||||
//TODO:localize
|
||||
titleText = "General"
|
||||
}
|
||||
} else {
|
||||
titleText = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " "
|
||||
}
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
@ -391,38 +418,37 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
let iconFrame = CGRect(origin: CGPoint(x: 0.0, y: 5.0 + floor((size.height - iconSize.height) * 0.5)), size: iconSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: 5.0 + floor((size.height - titleSize.height) * 0.5)), size: titleSize)
|
||||
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.containerButton.addSubview(iconView)
|
||||
if let icon = self.icon {
|
||||
if let iconView = icon.view {
|
||||
if iconView.superview == nil {
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.containerButton.addSubview(iconView)
|
||||
}
|
||||
iconView.frame = iconFrame
|
||||
}
|
||||
iconView.frame = iconFrame
|
||||
|
||||
if "".isEmpty {
|
||||
iconView.isHidden = true
|
||||
|
||||
let avatarNode: AvatarNode
|
||||
if let current = self.avatarNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 7.0))
|
||||
self.avatarNode = avatarNode
|
||||
self.containerButton.addSubview(avatarNode.view)
|
||||
}
|
||||
avatarNode.frame = iconFrame
|
||||
avatarNode.updateSize(size: iconFrame.size)
|
||||
|
||||
if let peer = item.item.renderedPeer.chatMainPeer {
|
||||
if peer.smallProfileImage != nil {
|
||||
avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
|
||||
} else {
|
||||
avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
|
||||
}
|
||||
}
|
||||
} else if let avatarNode = self.avatarNode {
|
||||
if let avatarNode = self.avatarNode {
|
||||
self.avatarNode = nil
|
||||
avatarNode.view.removeFromSuperview()
|
||||
iconView.isHidden = false
|
||||
}
|
||||
} else {
|
||||
let avatarNode: AvatarNode
|
||||
if let current = self.avatarNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 7.0))
|
||||
self.avatarNode = avatarNode
|
||||
self.containerButton.addSubview(avatarNode.view)
|
||||
}
|
||||
avatarNode.frame = iconFrame
|
||||
avatarNode.updateSize(size: iconFrame.size)
|
||||
|
||||
if let peer = item.item.renderedPeer.chatMainPeer {
|
||||
if peer.smallProfileImage != nil {
|
||||
avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
|
||||
} else {
|
||||
avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -707,6 +733,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let isMonoforum: Bool
|
||||
|
||||
private let scrollView: ScrollView
|
||||
|
||||
@ -722,8 +749,9 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
|
||||
private var appliedScrollToId: ScrollId?
|
||||
|
||||
init(context: AccountContext, peerId: EnginePeer.Id) {
|
||||
init(context: AccountContext, peerId: EnginePeer.Id, isMonoforum: Bool) {
|
||||
self.context = context
|
||||
self.isMonoforum = isMonoforum
|
||||
|
||||
self.selectedLineView = UIImageView()
|
||||
|
||||
@ -751,78 +779,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
|
||||
self.scrollView.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
let viewKey: PostboxViewKey = .savedMessagesIndex(peerId: peerId)
|
||||
let interfaceStateKey: PostboxViewKey = .chatInterfaceState(peerId: peerId)
|
||||
|
||||
let accountPeerId = context.account.peerId
|
||||
let threadListSignal: Signal<EngineChatList, NoError> = context.account.postbox.combinedView(keys: [viewKey, interfaceStateKey])
|
||||
|> map { views -> EngineChatList in
|
||||
guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
var draft: EngineChatList.Draft?
|
||||
if let interfaceStateView = views.views[interfaceStateKey] as? ChatInterfaceStateView {
|
||||
if let embeddedState = interfaceStateView.value, let _ = embeddedState.overrideChatTimestamp {
|
||||
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
|
||||
if let text = opaqueState.synchronizeableInputState?.text {
|
||||
draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? [])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var items: [EngineChatList.Item] = []
|
||||
for item in view.items {
|
||||
guard let sourcePeer = item.peer else {
|
||||
continue
|
||||
}
|
||||
|
||||
let sourceId = PeerId(item.id)
|
||||
|
||||
var messages: [EngineMessage] = []
|
||||
if let topMessage = item.topMessage {
|
||||
messages.append(EngineMessage(topMessage))
|
||||
}
|
||||
|
||||
let mappedMessageIndex = MessageIndex(id: MessageId(peerId: sourceId, namespace: item.index.id.namespace, id: item.index.id.id), timestamp: item.index.timestamp)
|
||||
|
||||
items.append(EngineChatList.Item(
|
||||
id: .chatList(sourceId),
|
||||
index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)),
|
||||
messages: messages,
|
||||
readCounters: EnginePeerReadCounters(
|
||||
incomingReadId: 0, outgoingReadId: 0, count: Int32(item.unreadCount), markedUnread: false),
|
||||
isMuted: false,
|
||||
draft: sourceId == accountPeerId ? draft : nil,
|
||||
threadData: nil,
|
||||
renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)),
|
||||
presence: nil,
|
||||
hasUnseenMentions: false,
|
||||
hasUnseenReactions: false,
|
||||
forumTopicData: nil,
|
||||
topForumTopicItems: [],
|
||||
hasFailed: false,
|
||||
isContact: false,
|
||||
autoremoveTimeout: nil,
|
||||
storyStats: nil,
|
||||
displayAsTopicList: false,
|
||||
isPremiumRequiredToMessage: false,
|
||||
mediaDraftContentType: nil
|
||||
))
|
||||
}
|
||||
|
||||
let list = EngineChatList(
|
||||
items: items.reversed(),
|
||||
groupItems: [],
|
||||
additionalItems: [],
|
||||
hasEarlier: false,
|
||||
hasLater: false,
|
||||
isLoading: view.isLoading
|
||||
)
|
||||
|
||||
return list
|
||||
}
|
||||
let threadListSignal: Signal<EngineChatList, NoError> = context.sharedContext.subscribeChatListData(context: context, location: isMonoforum ? .savedMessagesChats(peerId: peerId) : .forum(peerId: peerId))
|
||||
|
||||
self.itemsDisposable = (threadListSignal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] chatList in
|
||||
@ -1001,8 +958,12 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let topicId = chatListItem.renderedPeer.peerId.toInt64()
|
||||
self.interfaceInteraction?.updateChatLocationThread(topicId)
|
||||
if case let .forum(topicId) = chatListItem.id {
|
||||
self.interfaceInteraction?.updateChatLocationThread(topicId)
|
||||
} else {
|
||||
let topicId = chatListItem.renderedPeer.peerId.toInt64()
|
||||
self.interfaceInteraction?.updateChatLocationThread(topicId)
|
||||
}
|
||||
}, contextGesture: { gesture, sourceNode in
|
||||
})
|
||||
self.itemViews[itemId] = itemView
|
||||
@ -1010,8 +971,10 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
}
|
||||
|
||||
var isSelected = false
|
||||
if params.interfaceState.chatLocation.threadId == item.item.renderedPeer.peerId.toInt64() {
|
||||
isSelected = true
|
||||
if case let .forum(topicId) = item.item.id {
|
||||
isSelected = params.interfaceState.chatLocation.threadId == topicId
|
||||
} else {
|
||||
isSelected = params.interfaceState.chatLocation.threadId == item.item.renderedPeer.peerId.toInt64()
|
||||
}
|
||||
let itemSize = itemView.update(context: self.context, item: item, isSelected: isSelected, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize)
|
||||
@ -1100,7 +1063,15 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
||||
|
||||
public func topicIndex(threadId: Int64?) -> Int? {
|
||||
if let threadId {
|
||||
if let value = self.items.firstIndex(where: { $0.id == .chatList(PeerId(threadId)) }) {
|
||||
if let value = self.items.firstIndex(where: { item in
|
||||
if item.id == .chatList(PeerId(threadId)) {
|
||||
return true
|
||||
} else if item.id == .forum(threadId) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
return value + 1
|
||||
} else {
|
||||
return nil
|
||||
|
||||
@ -805,7 +805,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
self.suspendedNavigationBarLayout = layout
|
||||
return
|
||||
}
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: nil, transition: transition)
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
@ -818,7 +818,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
self.suspendNavigationBarLayout = false
|
||||
if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout {
|
||||
self.suspendedNavigationBarLayout = suspendedNavigationBarLayout
|
||||
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
||||
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: nil, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2238,6 +2238,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
)
|
||||
}
|
||||
|
||||
public func subscribeChatListData(context: AccountContext, location: ChatListControllerLocation) -> Signal<EngineChatList, NoError> {
|
||||
return chatListViewForLocation(chatListLocation: location, location: .initial(count: 100, filter: nil), account: context.account, shouldLoadCanMessagePeer: false)
|
||||
|> map { update -> EngineChatList in
|
||||
return update.list
|
||||
}
|
||||
}
|
||||
|
||||
public func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController? {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1151,7 +1151,7 @@ public class TranslateScreen: ViewController {
|
||||
|
||||
layout.statusBarHeight = nil
|
||||
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, transition: transition)
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, additionalCutout: nil, transition: transition)
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user