Merge commit '37e752c78f1fc0e83eb20a2063145939112be167'

# Conflicts:
#	submodules/ChatListUI/Sources/Node/ChatListItem.swift
#	submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift
#	submodules/TelegramCore/Sources/ForumChannels.swift
This commit is contained in:
Ali 2022-11-16 01:53:19 +04:00
commit 0afe69c627
62 changed files with 1532 additions and 526 deletions

View File

@ -8282,3 +8282,20 @@ Sorry for the inconvenience.";
"OwnershipTransfer.EnterPasswordText" = "Please enter your 2-Step Verification password to confirm the action."; "OwnershipTransfer.EnterPasswordText" = "Please enter your 2-Step Verification password to confirm the action.";
"Navigation.AllChats" = "All Chats"; "Navigation.AllChats" = "All Chats";
"Group.Management.AntiSpam" = "Aggressive Anti-Spam";
"Group.Management.AntiSpamInfo" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report false positives in Recent Actions.";
"Group.Management.AntiSpamMagic" = "magic";
"Group.AdminLog.AntiSpamTitle" = "Telegram Anti-Spam";
"Group.AdminLog.AntiSpamText" = "You can manage anti-spam settings in Group Info > [Administrators]().";
"ChatList.ThreadHideAction" = "Hide";
"ChatList.ThreadUnhideAction" = "Unhide";
"Notification.ForumTopicHidden" = "Topic hidden";
"Notification.ForumTopicUnhidden" = "Topic unhidden";
"Notification.ForumTopicHiddenAuthor" = "%1$@ hidden topic";
"Notification.ForumTopicUnhiddenAuthor" = "%1$@ unhidden topic";
"Notification.OverviewTopicHidden" = "%1$@ hidden %2$@ %3$@";
"Notification.OverviewTopicUnhidden" = "%1$@ unhidden %2$@ %3$@";

View File

@ -693,13 +693,6 @@ public enum ChatListSearchFilter: Equatable {
} }
} }
#if ENABLE_WALLET
public enum OpenWalletContext {
case generic
case send(address: String, amount: Int64?, comment: String?)
}
#endif
public let defaultContactLabel: String = "_$!<Mobile>!$_" public let defaultContactLabel: String = "_$!<Mobile>!$_"
public enum CreateGroupMode { public enum CreateGroupMode {
@ -839,73 +832,6 @@ public enum PremiumIntroSource {
case fasterDownload case fasterDownload
} }
#if ENABLE_WALLET
private final class TonInstanceData {
var config: String?
var blockchainName: String?
var instance: TonInstance?
}
private final class TonNetworkProxyImpl: TonNetworkProxy {
private let network: Network
init(network: Network) {
self.network = network
}
func request(data: Data, timeout timeoutValue: Double, completion: @escaping (TonNetworkProxyResult) -> Void) -> Disposable {
return (walletProxyRequest(network: self.network, data: data)
|> timeout(timeoutValue, queue: .concurrentDefaultQueue(), alternate: .fail(.generic(500, "Local Timeout")))).start(next: { data in
completion(.reponse(data))
}, error: { error in
switch error {
case let .generic(_, text):
completion(.error(text))
}
})
}
}
public final class StoredTonContext {
private let basePath: String
private let postbox: Postbox
private let network: Network
public let keychain: TonKeychain
private let currentInstance = Atomic<TonInstanceData>(value: TonInstanceData())
public init(basePath: String, postbox: Postbox, network: Network, keychain: TonKeychain) {
self.basePath = basePath
self.postbox = postbox
self.network = network
self.keychain = keychain
}
public func context(config: String, blockchainName: String, enableProxy: Bool) -> TonContext {
return self.currentInstance.with { data -> TonContext in
if let instance = data.instance, data.config == config, data.blockchainName == blockchainName {
return TonContext(instance: instance, keychain: self.keychain)
} else {
data.config = config
let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: blockchainName, proxy: enableProxy ? TonNetworkProxyImpl(network: self.network) : nil)
data.instance = instance
return TonContext(instance: instance, keychain: self.keychain)
}
}
}
}
public final class TonContext {
public let instance: TonInstance
public let keychain: TonKeychain
fileprivate init(instance: TonInstance, keychain: TonKeychain) {
self.instance = instance
self.keychain = keychain
}
}
#endif
public protocol ComposeController: ViewController { public protocol ComposeController: ViewController {
} }
@ -979,3 +905,23 @@ public struct PremiumConfiguration {
} }
} }
} }
public struct AntiSpamBotConfiguration {
public static var defaultValue: AntiSpamBotConfiguration {
return AntiSpamBotConfiguration(antiSpamBotId: nil)
}
public let antiSpamBotId: EnginePeer.Id?
fileprivate init(antiSpamBotId: EnginePeer.Id?) {
self.antiSpamBotId = antiSpamBotId
}
public static func with(appConfiguration: AppConfiguration) -> AntiSpamBotConfiguration {
if let data = appConfiguration.data, let string = data["telegram_antispam_user_id"] as? String, let value = Int64(string) {
return AntiSpamBotConfiguration(antiSpamBotId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(value)))
} else {
return .defaultValue
}
}
}

View File

@ -490,7 +490,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
} }
} }
func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: Int64, isPinned: Bool?, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> { func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: Int64, isPinned: Bool?, isClosed: Bool?, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
let strings = presentationData.strings let strings = presentationData.strings
@ -512,24 +512,27 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
if let isPinned = isPinned, channel.hasPermission(.manageTopics) { if let isClosed = isClosed, isClosed {
items.append(.action(ContextMenuActionItem(text: isPinned ? presentationData.strings.ChatList_Context_Unpin : presentationData.strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in } else {
f(.default) if let isPinned = isPinned, channel.hasPermission(.manageTopics) {
items.append(.action(ContextMenuActionItem(text: isPinned ? presentationData.strings.ChatList_Context_Unpin : presentationData.strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId) f(.default)
|> deliverOnMainQueue).start(error: { error in
switch error { let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId)
case let .limitReached(count): |> deliverOnMainQueue).start(error: { error in
if let chatListController = chatListController { switch error {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } case let .limitReached(count):
let text = presentationData.strings.ChatList_MaxThreadPinsFinalText(Int32(count)) if let chatListController = chatListController {
chatListController.present(textAlertController(context: context, title: presentationData.strings.Premium_LimitReached, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true), in: .window(.root)) let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let text = presentationData.strings.ChatList_MaxThreadPinsFinalText(Int32(count))
chatListController.present(textAlertController(context: context, title: presentationData.strings.Premium_LimitReached, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true), in: .window(.root))
}
default:
break
} }
default: })
break })))
} }
})
})))
} }
var isUnread = false var isUnread = false

View File

@ -1377,6 +1377,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
strongSelf.setPeerThreadPinned(peerId: peerId, threadId: threadId, isPinned: isPinned) strongSelf.setPeerThreadPinned(peerId: peerId, threadId: threadId, isPinned: isPinned)
} }
self.chatListDisplayNode.containerNode.setPeerThreadHidden = { [weak self] peerId, threadId, isHidden in
guard let strongSelf = self else {
return
}
strongSelf.setPeerThreadHidden(peerId: peerId, threadId: threadId, isHidden: isHidden)
}
self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, threadId, animated, activateInput, promoInfo in self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, threadId, animated, activateInput, promoInfo in
if let strongSelf = self { if let strongSelf = self {
@ -1674,7 +1680,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
chatListController.navigationPresentation = .master chatListController.navigationPresentation = .master
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController) strongSelf.presentInGlobalOverlay(contextController)
case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): case let .peer(_, peer, threadInfo, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
switch item.index { switch item.index {
case .chatList: case .chatList:
if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) { if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) {
@ -1686,7 +1692,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
chatController.canReadHistory.set(false) chatController.canReadHistory.set(false)
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController) strongSelf.presentInGlobalOverlay(contextController)
} else { } else {
let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
@ -1722,7 +1728,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
chatController.canReadHistory.set(false) chatController.canReadHistory.set(false)
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, isClosed: threadInfo?.isClosed, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController) strongSelf.presentInGlobalOverlay(contextController)
} }
} }
@ -3456,7 +3462,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.chatListDisplayNode.containerNode.updateState { state in strongSelf.chatListDisplayNode.containerNode.updateState { state in
var state = state var state = state
if updatedValue { if updatedValue {
state.archiveShouldBeTemporaryRevealed = false state.hiddenItemShouldBeTemporaryRevealed = false
} }
state.peerIdWithRevealedOptions = nil state.peerIdWithRevealedOptions = nil
return state return state
@ -3920,6 +3926,35 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.actionDisposables.add(self.context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId).start()) self.actionDisposables.add(self.context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId).start())
} }
private func setPeerThreadHidden(peerId: EnginePeer.Id, threadId: Int64, isHidden: Bool) {
self.actionDisposables.add((self.context.engine.peers.setForumChannelTopicHidden(id: peerId, threadId: threadId, isHidden: isHidden)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
strongSelf.chatListDisplayNode.containerNode.updateState { state in
var state = state
state.hiddenItemShouldBeTemporaryRevealed = false
return state
}
if isHidden {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .hidArchive(title: "General hidden", text: "Pull down to see the general topic.", undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
guard let strongSelf = self else {
return false
}
if value == .undo {
strongSelf.setPeerThreadHidden(peerId: peerId, threadId: threadId, isHidden: false)
return true
}
return false
}), in: .current)
} else {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .revealedArchive(title: "General unhidden", text: "Swipe left on the general topic to hide it.", undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false
}), in: .current)
}
}
}))
}
public func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool = false, deleteGloballyIfPossible: Bool = false, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void) { public func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool = false, deleteGloballyIfPossible: Bool = false, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void) {
guard let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else { guard let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else {
completion(false) completion(false)

View File

@ -183,7 +183,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
let timestamp1: Int32 = 100000 let timestamp1: Int32 = 100000
let peers: [EnginePeer.Id: EnginePeer] = [:] let peers: [EnginePeer.Id: EnginePeer] = [:]
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }) }, present: { _ in }, openForumThread: { _, _ in })
interaction.isInlineMode = isInlineMode interaction.isInlineMode = isInlineMode
@ -532,6 +532,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
previousItemNode.listNode.deletePeerThread = nil previousItemNode.listNode.deletePeerThread = nil
previousItemNode.listNode.setPeerThreadStopped = nil previousItemNode.listNode.setPeerThreadStopped = nil
previousItemNode.listNode.setPeerThreadPinned = nil previousItemNode.listNode.setPeerThreadPinned = nil
previousItemNode.listNode.setPeerThreadHidden = nil
previousItemNode.listNode.peerSelected = nil previousItemNode.listNode.peerSelected = nil
previousItemNode.listNode.groupSelected = nil previousItemNode.listNode.groupSelected = nil
previousItemNode.listNode.updatePeerGrouping = nil previousItemNode.listNode.updatePeerGrouping = nil
@ -576,6 +577,9 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
itemNode.listNode.setPeerThreadPinned = { [weak self] peerId, threadId, isPinned in itemNode.listNode.setPeerThreadPinned = { [weak self] peerId, threadId, isPinned in
self?.setPeerThreadPinned?(peerId, threadId, isPinned) self?.setPeerThreadPinned?(peerId, threadId, isPinned)
} }
itemNode.listNode.setPeerThreadHidden = { [weak self] peerId, threadId, isHidden in
self?.setPeerThreadHidden?(peerId, threadId, isHidden)
}
itemNode.listNode.peerSelected = { [weak self] peerId, threadId, animated, activateInput, promoInfo in itemNode.listNode.peerSelected = { [weak self] peerId, threadId, animated, activateInput, promoInfo in
self?.peerSelected?(peerId, threadId, animated, activateInput, promoInfo) self?.peerSelected?(peerId, threadId, animated, activateInput, promoInfo)
} }
@ -637,6 +641,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)? var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)?
var setPeerThreadStopped: ((EnginePeer.Id, Int64, Bool) -> Void)? var setPeerThreadStopped: ((EnginePeer.Id, Int64, Bool) -> Void)?
var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)? var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)?
var setPeerThreadHidden: ((EnginePeer.Id, Int64, Bool) -> Void)?
var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)?
var groupSelected: ((EngineChatList.Group) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)?
var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)?

View File

@ -923,7 +923,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}) })
}))) })))
} else { } else {
if !isPremium, let size = downloadResource?.size, size >= 300 * 1024 * 1024 { if !isPremium, let size = downloadResource?.size, size >= 150 * 1024 * 1024 {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_IncreaseSpeed, textColor: .primary, icon: { theme in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_IncreaseSpeed, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { _, f in

View File

@ -739,7 +739,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
index = .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)) index = .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index))
case .forum: case .forum:
if let threadId = message.threadId, let threadInfo = threadInfo { if let threadId = message.threadId, let threadInfo = threadInfo {
chatThreadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadInfo, isOwnedByMe: false, isClosed: false) chatThreadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadInfo, isOwnedByMe: false, isClosed: false, isHidden: false)
index = .forum(pinnedIndex: .none, timestamp: message.index.timestamp, threadId: threadId, namespace: message.index.id.namespace, id: message.index.id.id) index = .forum(pinnedIndex: .none, timestamp: message.index.timestamp, threadId: threadId, namespace: message.index.id.namespace, id: message.index.id.id)
} else { } else {
index = .chatList( EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)) index = .chatList( EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index))
@ -1522,7 +1522,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
for thread in allAndFoundThreads { for thread in allAndFoundThreads {
if let peer = thread.renderedPeer.peer, let threadData = thread.threadData, case let .forum(_, _, id, _, _) = thread.index { if let peer = thread.renderedPeer.peer, let threadData = thread.threadData, case let .forum(_, _, id, _, _) = thread.index {
entries.append(.topic(peer, ChatListItemContent.ThreadInfo(id: id, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed), index, presentationData.theme, presentationData.strings, .none)) entries.append(.topic(peer, ChatListItemContent.ThreadInfo(id: id, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden), index, presentationData.theme, presentationData.strings, .none))
index += 1 index += 1
} }
} }
@ -1842,6 +1842,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, deletePeerThread: { _, _ in }, deletePeerThread: { _, _ in
}, setPeerThreadStopped: { _, _, _ in }, setPeerThreadStopped: { _, _, _ in
}, setPeerThreadPinned: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in
}, setPeerThreadHidden: { _, _, _ in
}, updatePeerGrouping: { _, _ in }, updatePeerGrouping: { _, _ in
}, togglePeerMarkedUnread: { _, _ in }, togglePeerMarkedUnread: { _, _ in
}, toggleArchivedFolderHiddenByDefault: { }, toggleArchivedFolderHiddenByDefault: {
@ -3067,7 +3068,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
var peers: [EnginePeer.Id: EnginePeer] = [:] var peers: [EnginePeer.Id: EnginePeer] = [:]
peers[peer1.id] = peer1 peers[peer1.id] = peer1
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }) }, present: { _ in }, openForumThread: { _, _ in })

View File

@ -34,12 +34,14 @@ public enum ChatListItemContent {
public var info: EngineMessageHistoryThread.Info public var info: EngineMessageHistoryThread.Info
public var isOwnedByMe: Bool public var isOwnedByMe: Bool
public var isClosed: Bool public var isClosed: Bool
public var isHidden: Bool
public init(id: Int64, info: EngineMessageHistoryThread.Info, isOwnedByMe: Bool, isClosed: Bool) { public init(id: Int64, info: EngineMessageHistoryThread.Info, isOwnedByMe: Bool, isClosed: Bool, isHidden: Bool) {
self.id = id self.id = id
self.info = info self.info = info
self.isOwnedByMe = isOwnedByMe self.isOwnedByMe = isOwnedByMe
self.isClosed = isClosed self.isClosed = isClosed
self.isHidden = isHidden
} }
} }
@ -333,7 +335,7 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr
return options return options
} }
private func forumGeneralRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isMuted: Bool?, isEditing: Bool, canHide: Bool, hiddenByDefault: Bool) -> [ItemListRevealOption] { private func forumGeneralRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isMuted: Bool?, isClosed: Bool, isEditing: Bool, canOpenClose: Bool, canHide: Bool, hiddenByDefault: Bool) -> [ItemListRevealOption] {
var options: [ItemListRevealOption] = [] var options: [ItemListRevealOption] = []
if !isEditing { if !isEditing {
if let isMuted = isMuted { if let isMuted = isMuted {
@ -344,12 +346,21 @@ private func forumGeneralRevealOptions(strings: PresentationStrings, theme: Pres
} }
} }
} }
if canOpenClose && !hiddenByDefault {
if !isEditing {
if !isClosed {
// options.append(ItemListRevealOption(key: RevealOptionKey.close.rawValue, title: strings.ChatList_CloseAction, icon: closeIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
} else {
options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
}
}
}
if canHide { if canHide {
if !isEditing { if !isEditing {
if hiddenByDefault { if hiddenByDefault {
options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_UnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_ThreadUnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
} else { } else {
options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_HideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor)) options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_ThreadHideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor))
} }
} }
} }
@ -558,10 +569,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.view.addSubview(self.titleTopicIconView) self.view.addSubview(self.titleTopicIconView)
} }
static func asyncLayout(_ currentNode: TopicItemNode?) -> (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode) { static func asyncLayout(_ currentNode: TopicItemNode?) -> (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ threadId: Int64, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode) {
let makeTopicTitleLayout = TextNode.asyncLayout(currentNode?.topicTitleNode) let makeTopicTitleLayout = TextNode.asyncLayout(currentNode?.topicTitleNode)
return { constrainedWidth, context, theme, title, iconId, iconColor in return { constrainedWidth, context, theme, threadId, title, iconId, iconColor in
let remainingWidth = max(1.0, constrainedWidth - (18.0 + 2.0)) let remainingWidth = max(1.0, constrainedWidth - (18.0 + 2.0))
let topicTitleArguments = TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: remainingWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)) let topicTitleArguments = TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: remainingWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))
@ -579,7 +590,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
let titleTopicIconContent: EmojiStatusComponent.Content let titleTopicIconContent: EmojiStatusComponent.Content
if let fileId = iconId, fileId != 0 { if threadId == 1 {
titleTopicIconContent = .image(image: PresentationResourcesChatList.generalTopicSmallIcon(theme))
} else if let fileId = iconId, fileId != 0 {
titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(2)) titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(2))
} else { } else {
titleTopicIconContent = .topic(title: String(title.string.prefix(1)), color: iconColor, size: CGSize(width: 18.0, height: 18.0)) titleTopicIconContent = .topic(title: String(title.string.prefix(1)), color: iconColor, size: CGSize(width: 18.0, height: 18.0))
@ -654,7 +667,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topics: [(id: Int64, title: NSAttributedString, iconId: Int64?, iconColor: Int32)]) -> (CGSize, () -> CGRect?) { func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topics: [(id: Int64, title: NSAttributedString, iconId: Int64?, iconColor: Int32)]) -> (CGSize, () -> CGRect?) {
let makeAuthorLayout = TextNode.asyncLayout(self.authorNode) let makeAuthorLayout = TextNode.asyncLayout(self.authorNode)
var makeExistingTopicLayouts: [Int64: (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode)] = [:] var makeExistingTopicLayouts: [Int64: (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ threadId: Int64, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode)] = [:]
for (topicId, topicNode) in self.topicNodes { for (topicId, topicNode) in self.topicNodes {
makeExistingTopicLayouts[topicId] = TopicItemNode.asyncLayout(topicNode) makeExistingTopicLayouts[topicId] = TopicItemNode.asyncLayout(topicNode)
} }
@ -686,7 +699,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
let makeTopicLayout = makeExistingTopicLayouts[topic.id] ?? TopicItemNode.asyncLayout(nil) let makeTopicLayout = makeExistingTopicLayouts[topic.id] ?? TopicItemNode.asyncLayout(nil)
let (topicSize, topicApply) = makeTopicLayout(remainingWidth, context, theme, topic.title, topic.iconId, topic.iconColor) let (topicSize, topicApply) = makeTopicLayout(remainingWidth, context, theme, topic.id, topic.title, topic.iconId, topic.iconColor)
topicsSizeAndApply.append((topic.id, topicSize, topicApply)) topicsSizeAndApply.append((topic.id, topicSize, topicApply))
remainingWidth -= topicSize.width + 4.0 remainingWidth -= topicSize.width + 4.0
@ -2016,7 +2029,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if case let .chatList(index) = item.index, index.messageIndex.id.peerId == item.context.account.peerId { if case let .chatList(index) = item.index, index.messageIndex.id.peerId == item.context.account.peerId {
isAccountPeer = true isAccountPeer = true
} }
if !isPeerGroup && !isAccountPeer { if !isPeerGroup && !isAccountPeer && threadInfo == nil {
if displayAsMessage { if displayAsMessage {
switch item.content { switch item.content {
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
@ -2223,22 +2236,22 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if item.enableContextActions { if item.enableContextActions {
if case .forum = item.chatListLocation { if case .forum = item.chatListLocation {
if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer { if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer {
var canOpenClose = false
if channel.flags.contains(.isCreator) {
canOpenClose = true
} else if channel.hasPermission(.manageTopics) {
canOpenClose = true
} else if let threadInfo = threadInfo, threadInfo.isOwnedByMe {
canOpenClose = true
}
let canDelete = channel.hasPermission(.deleteAllMessages)
var isClosed = false
if let threadInfo {
isClosed = threadInfo.isClosed
}
if let threadInfo, threadInfo.id == 1 { if let threadInfo, threadInfo.id == 1 {
peerRevealOptions = forumGeneralRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isEditing: item.editing, canHide: channel.flags.contains(.isCreator) || channel.hasPermission(.pinMessages), hiddenByDefault: false) peerRevealOptions = forumGeneralRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canHide: channel.flags.contains(.isCreator) || channel.hasPermission(.pinMessages), hiddenByDefault: threadInfo.isHidden)
} else { } else {
var canOpenClose = false
if channel.flags.contains(.isCreator) {
canOpenClose = true
} else if channel.hasPermission(.manageTopics) {
canOpenClose = true
} else if let threadInfo = threadInfo, threadInfo.isOwnedByMe {
canOpenClose = true
}
let canDelete = channel.hasPermission(.deleteAllMessages)
var isClosed = false
if let threadInfo {
isClosed = threadInfo.isClosed
}
peerRevealOptions = forumThreadRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canDelete: canDelete) peerRevealOptions = forumThreadRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canDelete: canDelete)
} }
peerLeftRevealOptions = [] peerLeftRevealOptions = []
@ -2312,6 +2325,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.cachedChatListText = chatListText strongSelf.cachedChatListText = chatListText
strongSelf.cachedChatListSearchResult = chatListSearchResult strongSelf.cachedChatListSearchResult = chatListSearchResult
strongSelf.onlineIsVoiceChat = onlineIsVoiceChat strongSelf.onlineIsVoiceChat = onlineIsVoiceChat
strongSelf.clipsToBounds = true
if case .groupReference = item.content { if case .groupReference = item.content {
strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0)
@ -2480,7 +2495,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
let avatarIconContent: EmojiStatusComponent.Content let avatarIconContent: EmojiStatusComponent.Content
if let fileId = threadInfo.info.icon, fileId != 0 { if threadInfo.id == 1 {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(item.presentationData.theme))
} else if let fileId = threadInfo.info.icon, fileId != 0 {
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
} else { } else {
avatarIconContent = .topic(title: String(threadInfo.info.title.prefix(1)), color: threadInfo.info.iconColor, size: CGSize(width: 32.0, height: 32.0)) avatarIconContent = .topic(title: String(threadInfo.info.title.prefix(1)), color: threadInfo.info.iconColor, size: CGSize(width: 32.0, height: 32.0))
@ -3419,6 +3436,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
item.interaction.setPeerThreadPinned(peerId, threadId, true) item.interaction.setPeerThreadPinned(peerId, threadId, true)
case RevealOptionKey.unpin.rawValue: case RevealOptionKey.unpin.rawValue:
item.interaction.setPeerThreadPinned(peerId, threadId, false) item.interaction.setPeerThreadPinned(peerId, threadId, false)
case RevealOptionKey.hide.rawValue:
item.interaction.setPeerThreadHidden(peerId, threadId, true)
case RevealOptionKey.unhide.rawValue:
item.interaction.setPeerThreadHidden(peerId, threadId, false)
default: default:
break break
} }

View File

@ -82,6 +82,7 @@ public final class ChatListNodeInteraction {
let deletePeerThread: (EnginePeer.Id, Int64) -> Void let deletePeerThread: (EnginePeer.Id, Int64) -> Void
let setPeerThreadStopped: (EnginePeer.Id, Int64, Bool) -> Void let setPeerThreadStopped: (EnginePeer.Id, Int64, Bool) -> Void
let setPeerThreadPinned: (EnginePeer.Id, Int64, Bool) -> Void let setPeerThreadPinned: (EnginePeer.Id, Int64, Bool) -> Void
let setPeerThreadHidden: (EnginePeer.Id, Int64, Bool) -> Void
let updatePeerGrouping: (EnginePeer.Id, Bool) -> Void let updatePeerGrouping: (EnginePeer.Id, Bool) -> Void
let togglePeerMarkedUnread: (EnginePeer.Id, Bool) -> Void let togglePeerMarkedUnread: (EnginePeer.Id, Bool) -> Void
let toggleArchivedFolderHiddenByDefault: () -> Void let toggleArchivedFolderHiddenByDefault: () -> Void
@ -121,6 +122,7 @@ public final class ChatListNodeInteraction {
deletePeerThread: @escaping (EnginePeer.Id, Int64) -> Void, deletePeerThread: @escaping (EnginePeer.Id, Int64) -> Void,
setPeerThreadStopped: @escaping (EnginePeer.Id, Int64, Bool) -> Void, setPeerThreadStopped: @escaping (EnginePeer.Id, Int64, Bool) -> Void,
setPeerThreadPinned: @escaping (EnginePeer.Id, Int64, Bool) -> Void, setPeerThreadPinned: @escaping (EnginePeer.Id, Int64, Bool) -> Void,
setPeerThreadHidden: @escaping (EnginePeer.Id, Int64, Bool) -> Void,
updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void, updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void,
togglePeerMarkedUnread: @escaping (EnginePeer.Id, Bool) -> Void, togglePeerMarkedUnread: @escaping (EnginePeer.Id, Bool) -> Void,
toggleArchivedFolderHiddenByDefault: @escaping () -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void,
@ -147,6 +149,7 @@ public final class ChatListNodeInteraction {
self.deletePeerThread = deletePeerThread self.deletePeerThread = deletePeerThread
self.setPeerThreadStopped = setPeerThreadStopped self.setPeerThreadStopped = setPeerThreadStopped
self.setPeerThreadPinned = setPeerThreadPinned self.setPeerThreadPinned = setPeerThreadPinned
self.setPeerThreadHidden = setPeerThreadHidden
self.updatePeerGrouping = updatePeerGrouping self.updatePeerGrouping = updatePeerGrouping
self.togglePeerMarkedUnread = togglePeerMarkedUnread self.togglePeerMarkedUnread = togglePeerMarkedUnread
self.toggleArchivedFolderHiddenByDefault = toggleArchivedFolderHiddenByDefault self.toggleArchivedFolderHiddenByDefault = toggleArchivedFolderHiddenByDefault
@ -208,7 +211,7 @@ public struct ChatListNodeState: Equatable {
public var peerInputActivities: ChatListNodePeerInputActivities? public var peerInputActivities: ChatListNodePeerInputActivities?
public var pendingRemovalItemIds: Set<ItemId> public var pendingRemovalItemIds: Set<ItemId>
public var pendingClearHistoryPeerIds: Set<ItemId> public var pendingClearHistoryPeerIds: Set<ItemId>
public var archiveShouldBeTemporaryRevealed: Bool public var hiddenItemShouldBeTemporaryRevealed: Bool
public var selectedAdditionalCategoryIds: Set<Int> public var selectedAdditionalCategoryIds: Set<Int>
public var hiddenPsaPeerId: EnginePeer.Id? public var hiddenPsaPeerId: EnginePeer.Id?
public var foundPeers: [(EnginePeer, EnginePeer?)] public var foundPeers: [(EnginePeer, EnginePeer?)]
@ -226,7 +229,7 @@ public struct ChatListNodeState: Equatable {
peerInputActivities: ChatListNodePeerInputActivities?, peerInputActivities: ChatListNodePeerInputActivities?,
pendingRemovalItemIds: Set<ItemId>, pendingRemovalItemIds: Set<ItemId>,
pendingClearHistoryPeerIds: Set<ItemId>, pendingClearHistoryPeerIds: Set<ItemId>,
archiveShouldBeTemporaryRevealed: Bool, hiddenItemShouldBeTemporaryRevealed: Bool,
hiddenPsaPeerId: EnginePeer.Id?, hiddenPsaPeerId: EnginePeer.Id?,
selectedThreadIds: Set<Int64> selectedThreadIds: Set<Int64>
) { ) {
@ -240,7 +243,7 @@ public struct ChatListNodeState: Equatable {
self.peerInputActivities = peerInputActivities self.peerInputActivities = peerInputActivities
self.pendingRemovalItemIds = pendingRemovalItemIds self.pendingRemovalItemIds = pendingRemovalItemIds
self.pendingClearHistoryPeerIds = pendingClearHistoryPeerIds self.pendingClearHistoryPeerIds = pendingClearHistoryPeerIds
self.archiveShouldBeTemporaryRevealed = archiveShouldBeTemporaryRevealed self.hiddenItemShouldBeTemporaryRevealed = hiddenItemShouldBeTemporaryRevealed
self.hiddenPsaPeerId = hiddenPsaPeerId self.hiddenPsaPeerId = hiddenPsaPeerId
self.selectedThreadIds = selectedThreadIds self.selectedThreadIds = selectedThreadIds
} }
@ -276,7 +279,7 @@ public struct ChatListNodeState: Equatable {
if lhs.pendingClearHistoryPeerIds != rhs.pendingClearHistoryPeerIds { if lhs.pendingClearHistoryPeerIds != rhs.pendingClearHistoryPeerIds {
return false return false
} }
if lhs.archiveShouldBeTemporaryRevealed != rhs.archiveShouldBeTemporaryRevealed { if lhs.hiddenItemShouldBeTemporaryRevealed != rhs.hiddenItemShouldBeTemporaryRevealed {
return false return false
} }
if lhs.hiddenPsaPeerId != rhs.hiddenPsaPeerId { if lhs.hiddenPsaPeerId != rhs.hiddenPsaPeerId {
@ -312,7 +315,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction.additionalCategorySelected(id) nodeInteraction.additionalCategorySelected(id)
} }
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems): case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems, revealed):
switch mode { switch mode {
case .chatList: case .chatList:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
@ -344,7 +347,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
selected: selected, selected: selected,
header: nil, header: nil,
enableContextActions: true, enableContextActions: true,
hiddenOffset: false, hiddenOffset: threadInfo?.isHidden == true && !revealed,
interaction: nodeInteraction interaction: nodeInteraction
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
case let .peers(filter, isSelecting, _, filters): case let .peers(filter, isSelecting, _, filters):
@ -534,7 +537,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
return entries.map { entry -> ListViewUpdateItem in return entries.map { entry -> ListViewUpdateItem in
switch entry.entry { switch entry.entry {
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems): case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems, revealed):
switch mode { switch mode {
case .chatList: case .chatList:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
@ -566,7 +569,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
selected: selected, selected: selected,
header: nil, header: nil,
enableContextActions: true, enableContextActions: true,
hiddenOffset: false, hiddenOffset: threadInfo?.isHidden == true && !revealed,
interaction: nodeInteraction interaction: nodeInteraction
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
case let .peers(filter, isSelecting, _, filters): case let .peers(filter, isSelecting, _, filters):
@ -790,6 +793,7 @@ public final class ChatListNode: ListView {
public var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)? public var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)?
public var setPeerThreadStopped: ((EnginePeer.Id, Int64, Bool) -> Void)? public var setPeerThreadStopped: ((EnginePeer.Id, Int64, Bool) -> Void)?
public var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)? public var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)?
public var setPeerThreadHidden: ((EnginePeer.Id, Int64, Bool) -> Void)?
public var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? public var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)?
public var presentAlert: ((String) -> Void)? public var presentAlert: ((String) -> Void)?
public var present: ((ViewController) -> Void)? public var present: ((ViewController) -> Void)?
@ -901,7 +905,7 @@ public final class ChatListNode: ListView {
isSelecting = true isSelecting = true
} }
self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), archiveShouldBeTemporaryRevealed: false, hiddenPsaPeerId: nil, selectedThreadIds: Set()) self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, hiddenPsaPeerId: nil, selectedThreadIds: Set())
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
self.theme = theme self.theme = theme
@ -1111,6 +1115,8 @@ public final class ChatListNode: ListView {
self?.setPeerThreadStopped?(peerId, threadId, isStopped) self?.setPeerThreadStopped?(peerId, threadId, isStopped)
}, setPeerThreadPinned: { [weak self] peerId, threadId, isPinned in }, setPeerThreadPinned: { [weak self] peerId, threadId, isPinned in
self?.setPeerThreadPinned?(peerId, threadId, isPinned) self?.setPeerThreadPinned?(peerId, threadId, isPinned)
}, setPeerThreadHidden: { [weak self] peerId, threadId, isHidden in
self?.setPeerThreadHidden?(peerId, threadId, isHidden)
}, updatePeerGrouping: { [weak self] peerId, group in }, updatePeerGrouping: { [weak self] peerId, group in
self?.updatePeerGrouping?(peerId, group) self?.updatePeerGrouping?(peerId, group)
}, togglePeerMarkedUnread: { [weak self, weak context] peerId, animated in }, togglePeerMarkedUnread: { [weak self, weak context] peerId, animated in
@ -1242,7 +1248,7 @@ public final class ChatListNode: ListView {
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location) let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location)
let entries = rawEntries.filter { entry in let entries = rawEntries.filter { entry in
switch entry { switch entry {
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
switch mode { switch mode {
case .chatList: case .chatList:
return true return true
@ -1414,9 +1420,13 @@ public final class ChatListNode: ListView {
var didIncludeRemovingPeerId = false var didIncludeRemovingPeerId = false
var didIncludeHiddenByDefaultArchive = false var didIncludeHiddenByDefaultArchive = false
var didIncludeHiddenThread = false
if let previous = previousView { if let previous = previousView {
for entry in previous.filteredEntries { for entry in previous.filteredEntries {
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { if case let .PeerEntry(index, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
if let threadInfo, threadInfo.isHidden {
didIncludeHiddenThread = true
}
if case let .chatList(chatListIndex) = index { if case let .chatList(chatListIndex) = index {
if chatListIndex.pinningIndex != nil { if chatListIndex.pinningIndex != nil {
previousPinnedChats.append(chatListIndex.messageIndex.id.peerId) previousPinnedChats.append(chatListIndex.messageIndex.id.peerId)
@ -1440,8 +1450,13 @@ public final class ChatListNode: ListView {
var doesIncludeRemovingPeerId = false var doesIncludeRemovingPeerId = false
var doesIncludeArchive = false var doesIncludeArchive = false
var doesIncludeHiddenByDefaultArchive = false var doesIncludeHiddenByDefaultArchive = false
var doesIncludeHiddenThread = false
for entry in processedView.filteredEntries { for entry in processedView.filteredEntries {
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { if case let .PeerEntry(index, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
if let threadInfo, threadInfo.isHidden {
doesIncludeHiddenThread = true
}
if case let .chatList(index) = index, index.pinningIndex != nil { if case let .chatList(index) = index, index.pinningIndex != nil {
updatedPinnedChats.append(index.messageIndex.id.peerId) updatedPinnedChats.append(index.messageIndex.id.peerId)
} else if case let .forum(pinnedIndex, _, threadId, _, _) = index { } else if case let .forum(pinnedIndex, _, threadId, _, _) = index {
@ -1474,12 +1489,18 @@ public final class ChatListNode: ListView {
if doesIncludeRemovingPeerId != didIncludeRemovingPeerId { if doesIncludeRemovingPeerId != didIncludeRemovingPeerId {
disableAnimations = false disableAnimations = false
} }
if hideArchivedFolderByDefault && previousState.archiveShouldBeTemporaryRevealed != state.archiveShouldBeTemporaryRevealed && doesIncludeArchive { if hideArchivedFolderByDefault && previousState.hiddenItemShouldBeTemporaryRevealed != state.hiddenItemShouldBeTemporaryRevealed && doesIncludeArchive {
disableAnimations = false disableAnimations = false
} }
if didIncludeHiddenByDefaultArchive != doesIncludeHiddenByDefaultArchive { if didIncludeHiddenByDefaultArchive != doesIncludeHiddenByDefaultArchive {
disableAnimations = false disableAnimations = false
} }
if previousState.hiddenItemShouldBeTemporaryRevealed != state.hiddenItemShouldBeTemporaryRevealed && doesIncludeHiddenThread {
disableAnimations = false
}
if didIncludeHiddenThread != doesIncludeHiddenThread {
disableAnimations = false
}
} }
if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault { if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault {
@ -1534,7 +1555,7 @@ public final class ChatListNode: ListView {
strongSelf.enqueueHistoryPreloadUpdate() strongSelf.enqueueHistoryPreloadUpdate()
} }
var archiveVisible = false var isHiddenItemVisible = false
if let range = range.visibleRange { if let range = range.visibleRange {
let entryCount = chatListView.filteredEntries.count let entryCount = chatListView.filteredEntries.count
for i in range.firstIndex ..< range.lastIndex { for i in range.firstIndex ..< range.lastIndex {
@ -1543,19 +1564,22 @@ public final class ChatListNode: ListView {
continue continue
} }
switch chatListView.filteredEntries[entryCount - i - 1] { switch chatListView.filteredEntries[entryCount - i - 1] {
case .PeerEntry: case let .PeerEntry(_, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _):
if let threadInfo, threadInfo.isHidden {
isHiddenItemVisible = true
}
break break
case .GroupReferenceEntry: case .GroupReferenceEntry:
archiveVisible = true isHiddenItemVisible = true
default: default:
break break
} }
} }
} }
if !archiveVisible && strongSelf.currentState.archiveShouldBeTemporaryRevealed { if !isHiddenItemVisible && strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed {
strongSelf.updateState { state in strongSelf.updateState { state in
var state = state var state = state
state.archiveShouldBeTemporaryRevealed = false state.hiddenItemShouldBeTemporaryRevealed = false
return state return state
} }
} }
@ -1747,7 +1771,7 @@ public final class ChatListNode: ListView {
var referenceId: EngineChatList.PinnedItem.Id? var referenceId: EngineChatList.PinnedItem.Id?
var beforeAll = false var beforeAll = false
switch toEntry { switch toEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
if promoInfo != nil { if promoInfo != nil {
beforeAll = true beforeAll = true
} else { } else {
@ -1774,7 +1798,7 @@ public final class ChatListNode: ListView {
var itemId: EngineChatList.PinnedItem.Id? var itemId: EngineChatList.PinnedItem.Id?
switch fromEntry { switch fromEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if case let .chatList(index) = index { if case let .chatList(index) = index {
itemId = .peer(index.messageIndex.id.peerId) itemId = .peer(index.messageIndex.id.peerId)
} }
@ -1820,7 +1844,7 @@ public final class ChatListNode: ListView {
var referenceId: Int64? var referenceId: Int64?
var beforeAll = false var beforeAll = false
switch toEntry { switch toEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
if promoInfo != nil { if promoInfo != nil {
beforeAll = true beforeAll = true
} else { } else {
@ -1840,7 +1864,7 @@ public final class ChatListNode: ListView {
var itemId: Int64? var itemId: Int64?
switch fromEntry { switch fromEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if case let .forum(_, _, threadId, _, _) = index { if case let .forum(_, _, threadId, _, _) = index {
itemId = threadId itemId = threadId
} }
@ -1921,10 +1945,10 @@ public final class ChatListNode: ListView {
case let .known(value): case let .known(value):
revealHiddenItems = value <= 54.0 revealHiddenItems = value <= 54.0
} }
if !revealHiddenItems && strongSelf.currentState.archiveShouldBeTemporaryRevealed { if !revealHiddenItems && strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed {
strongSelf.updateState { state in strongSelf.updateState { state in
var state = state var state = state
state.archiveShouldBeTemporaryRevealed = false state.hiddenItemShouldBeTemporaryRevealed = false
return state return state
} }
} }
@ -1961,25 +1985,30 @@ public final class ChatListNode: ListView {
} }
strongSelf.scrolledAtTopValue = atTop strongSelf.scrolledAtTopValue = atTop
strongSelf.contentOffsetChanged?(offset) strongSelf.contentOffsetChanged?(offset)
if revealHiddenItems && !strongSelf.currentState.archiveShouldBeTemporaryRevealed { if revealHiddenItems && !strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed {
var isHiddenArchiveVisible = false var isHiddenItemVisible = false
strongSelf.forEachItemNode({ itemNode in strongSelf.forEachItemNode({ itemNode in
if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item {
if case let .peer(_, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let threadInfo {
if threadInfo.isHidden {
isHiddenItemVisible = true
}
}
if case let .groupReference(_, _, _, _, hiddenByDefault) = item.content { if case let .groupReference(_, _, _, _, hiddenByDefault) = item.content {
if hiddenByDefault { if hiddenByDefault {
isHiddenArchiveVisible = true isHiddenItemVisible = true
} }
} }
} }
}) })
if isHiddenArchiveVisible { if isHiddenItemVisible {
if strongSelf.hapticFeedback == nil { if strongSelf.hapticFeedback == nil {
strongSelf.hapticFeedback = HapticFeedback() strongSelf.hapticFeedback = HapticFeedback()
} }
strongSelf.hapticFeedback?.impact(.medium) strongSelf.hapticFeedback?.impact(.medium)
strongSelf.updateState { state in strongSelf.updateState { state in
var state = state var state = state
state.archiveShouldBeTemporaryRevealed = true state.hiddenItemShouldBeTemporaryRevealed = true
return state return state
} }
} }
@ -2104,7 +2133,7 @@ public final class ChatListNode: ListView {
if !transition.chatListView.originalList.hasLater { if !transition.chatListView.originalList.hasLater {
for entry in filteredEntries.reversed() { for entry in filteredEntries.reversed() {
switch entry { switch entry {
case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
if promoInfo == nil { if promoInfo == nil {
var hasUnread = false var hasUnread = false
if let combinedReadState = combinedReadState { if let combinedReadState = combinedReadState {
@ -2436,7 +2465,7 @@ public final class ChatListNode: ListView {
continue continue
} }
switch chatListView.filteredEntries[entryCount - i - 1] { switch chatListView.filteredEntries[entryCount - i - 1] {
case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peer.peerId) { if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peer.peerId) {
current = (index, peer.peer!, entryCount - i - 1) current = (index, peer.peer!, entryCount - i - 1)
break outer break outer
@ -2483,10 +2512,10 @@ public final class ChatListNode: ListView {
case .previous(unread: false), .next(unread: false): case .previous(unread: false), .next(unread: false):
var target: (EngineChatList.Item.Index, EnginePeer)? = nil var target: (EngineChatList.Item.Index, EnginePeer)? = nil
if let current = current, entryCount > 1 { if let current = current, entryCount > 1 {
if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] {
next = (index, peer.peer!) next = (index, peer.peer!)
} }
if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] {
previous = (index, peer.peer!) previous = (index, peer.peer!)
} }
if case .previous = option { if case .previous = option {
@ -2495,7 +2524,7 @@ public final class ChatListNode: ListView {
target = next target = next
} }
} else if entryCount > 0 { } else if entryCount > 0 {
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] {
target = (index, peer.peer!) target = (index, peer.peer!)
} }
} }
@ -2573,7 +2602,7 @@ public final class ChatListNode: ListView {
continue continue
} }
switch chatListView.filteredEntries[entryCount - i - 1] { switch chatListView.filteredEntries[entryCount - i - 1] {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return index return index
default: default:
break break

View File

@ -67,7 +67,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
hasFailedMessages: Bool, hasFailedMessages: Bool,
isContact: Bool, isContact: Bool,
forumTopicData: EngineChatList.ForumTopicData?, forumTopicData: EngineChatList.ForumTopicData?,
topForumTopicItems: [EngineChatList.ForumTopicData] topForumTopicItems: [EngineChatList.ForumTopicData],
revealed: Bool
) )
case HoleEntry(EngineMessage.Index, theme: PresentationTheme) case HoleEntry(EngineMessage.Index, theme: PresentationTheme)
case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool) case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool)
@ -78,7 +79,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
switch self { switch self {
case .HeaderEntry: case .HeaderEntry:
return .index(.chatList(.absoluteUpperBound)) return .index(.chatList(.absoluteUpperBound))
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return .index(index) return .index(index)
case let .HoleEntry(holeIndex, _): case let .HoleEntry(holeIndex, _):
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex))) return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
@ -95,7 +96,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
switch self { switch self {
case .HeaderEntry: case .HeaderEntry:
return .Header return .Header
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
switch index { switch index {
case let .chatList(index): case let .chatList(index):
return .PeerId(index.messageIndex.id.peerId.toInt64()) return .PeerId(index.messageIndex.id.peerId.toInt64())
@ -125,9 +126,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact, lhsForumThreadTitle, lhsTopForumTopicItems): case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact, lhsForumThreadTitle, lhsTopForumTopicItems, lhsRevealed):
switch rhs { switch rhs {
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact, rhsForumThreadTitle, rhsTopForumTopicItems): case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact, rhsForumThreadTitle, rhsTopForumTopicItems, rhsRevealed):
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -228,6 +229,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
if lhsTopForumTopicItems != rhsTopForumTopicItems { if lhsTopForumTopicItems != rhsTopForumTopicItems {
return false return false
} }
if lhsRevealed != rhsRevealed {
return false
}
return true return true
default: default:
return false return false
@ -394,10 +398,32 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
var threadInfo: ChatListItemContent.ThreadInfo? var threadInfo: ChatListItemContent.ThreadInfo?
if let threadData = entry.threadData, let threadId = threadId { if let threadData = entry.threadData, let threadId = threadId {
threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed) threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden)
} }
result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: threadInfo, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumTopicData: entry.forumTopicData, topForumTopicItems: entry.topForumTopicItems)) result.append(.PeerEntry(
index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset),
presentationData: state.presentationData,
messages: updatedMessages,
readState: updatedCombinedReadState,
isRemovedFromTotalUnreadCount: entry.isMuted,
draftState: draftState,
peer: entry.renderedPeer,
threadInfo: threadInfo,
presence: entry.presence,
hasUnseenMentions: entry.hasUnseenMentions,
hasUnseenReactions: entry.hasUnseenReactions,
editing: state.editing,
hasActiveRevealControls: hasActiveRevealControls,
selected: isSelected,
inputActivities: inputActivities,
promoInfo: nil,
hasFailedMessages: entry.hasFailed,
isContact: entry.isContact,
forumTopicData: entry.forumTopicData,
topForumTopicItems: entry.topForumTopicItems,
revealed: threadId == 1 && state.hiddenItemShouldBeTemporaryRevealed
))
} }
if !view.hasLater { if !view.hasLater {
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
@ -432,7 +458,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
hasFailedMessages: false, hasFailedMessages: false,
isContact: false, isContact: false,
forumTopicData: nil, forumTopicData: nil,
topForumTopicItems: [] topForumTopicItems: [],
revealed: false
)) ))
if foundPinningIndex != 0 { if foundPinningIndex != 0 {
foundPinningIndex -= 1 foundPinningIndex -= 1
@ -440,7 +467,29 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
} }
} }
result.append(.PeerEntry(index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false, forumTopicData: nil, topForumTopicItems: [])) result.append(.PeerEntry(
index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor),
presentationData: state.presentationData,
messages: [],
readState: nil,
isRemovedFromTotalUnreadCount: false,
draftState: nil,
peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]),
threadInfo: nil,
presence: nil,
hasUnseenMentions: false,
hasUnseenReactions: false,
editing: state.editing,
hasActiveRevealControls: false,
selected: state.selectedPeerIds.contains(savedMessagesPeer.id),
inputActivities: nil,
promoInfo: nil,
hasFailedMessages: false,
isContact: false,
forumTopicData: nil,
topForumTopicItems: [],
revealed: false
))
} else { } else {
if !filteredAdditionalItemEntries.isEmpty { if !filteredAdditionalItemEntries.isEmpty {
for item in filteredAdditionalItemEntries.reversed() { for item in filteredAdditionalItemEntries.reversed() {
@ -476,7 +525,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
isRemovedFromTotalUnreadCount: item.item.isMuted, isRemovedFromTotalUnreadCount: item.item.isMuted,
draftState: draftState, draftState: draftState,
peer: item.item.renderedPeer, peer: item.item.renderedPeer,
threadInfo: item.item.threadData.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0.info, isOwnedByMe: $0.isOwnedByMe, isClosed: $0.isClosed) }, threadInfo: item.item.threadData.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0.info, isOwnedByMe: $0.isOwnedByMe, isClosed: $0.isClosed, isHidden: $0.isHidden) },
presence: item.item.presence, presence: item.item.presence,
hasUnseenMentions: item.item.hasUnseenMentions, hasUnseenMentions: item.item.hasUnseenMentions,
hasUnseenReactions: item.item.hasUnseenReactions, hasUnseenReactions: item.item.hasUnseenReactions,
@ -488,7 +537,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
hasFailedMessages: item.item.hasFailed, hasFailedMessages: item.item.hasFailed,
isContact: item.item.isContact, isContact: item.item.isContact,
forumTopicData: item.item.forumTopicData, forumTopicData: item.item.forumTopicData,
topForumTopicItems: item.item.topForumTopicItems topForumTopicItems: item.item.topForumTopicItems,
revealed: threadId == 1 && state.hiddenItemShouldBeTemporaryRevealed
)) ))
if pinningIndex != 0 { if pinningIndex != 0 {
pinningIndex -= 1 pinningIndex -= 1
@ -508,7 +558,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
message: groupReference.topMessage, message: groupReference.topMessage,
editing: state.editing, editing: state.editing,
unreadCount: groupReference.unreadCount, unreadCount: groupReference.unreadCount,
revealed: state.archiveShouldBeTemporaryRevealed, revealed: state.hiddenItemShouldBeTemporaryRevealed,
hiddenByDefault: hideArchivedFolderByDefault hiddenByDefault: hideArchivedFolderByDefault
)) ))
if pinningIndex != 0 { if pinningIndex != 0 {

View File

@ -810,7 +810,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame.offsetBy(dx: 0.0, dy: additionalVisibleOffsetY), beginWithCurrentState: true) transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame.offsetBy(dx: 0.0, dy: additionalVisibleOffsetY), beginWithCurrentState: true)
if let contentNode = contentNode { if let contentNode = contentNode {
contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size), beginWithCurrentState: true) var contentFrame = CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size)
if case let .extracted(extracted) = self.source, extracted.centerVertically, contentFrame.midX > layout.size.width / 2.0 {
contentFrame.origin.x = layout.size.width - contentFrame.maxX
}
contentTransition.updateFrame(node: contentNode, frame: contentFrame, beginWithCurrentState: true)
} }
let contentHeight: CGFloat let contentHeight: CGFloat
@ -863,20 +867,38 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
let animationInContentDistance: CGFloat let animationInContentYDistance: CGFloat
let currentContentScreenFrame: CGRect let currentContentScreenFrame: CGRect
if let contentNode = contentNode { if let contentNode = contentNode {
if let animateClippingFromContentAreaInScreenSpace = contentNode.animateClippingFromContentAreaInScreenSpace { if let animateClippingFromContentAreaInScreenSpace = contentNode.animateClippingFromContentAreaInScreenSpace {
self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(x: 0.0, y: animateClippingFromContentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: animateClippingFromContentAreaInScreenSpace.height)), to: CGRect(origin: CGPoint(), size: layout.size), duration: 0.2) self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(x: 0.0, y: animateClippingFromContentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: animateClippingFromContentAreaInScreenSpace.height)), to: CGRect(origin: CGPoint(), size: layout.size), duration: 0.2)
self.clippingNode.layer.animateBoundsOriginYAdditive(from: animateClippingFromContentAreaInScreenSpace.minY, to: 0.0, duration: 0.2) self.clippingNode.layer.animateBoundsOriginYAdditive(from: animateClippingFromContentAreaInScreenSpace.minY, to: 0.0, duration: 0.2)
} }
currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view)
let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view) let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view)
animationInContentDistance = currentContentLocalFrame.maxY - currentContentScreenFrame.maxY animationInContentYDistance = currentContentLocalFrame.maxY - currentContentScreenFrame.maxY
var animationInContentXDistance: CGFloat = 0.0
let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX
let contentWidth = contentNode.containingItem.view.bounds.size.width
if case let .extracted(extracted) = self.source, extracted.centerVertically, contentX + contentWidth > layout.size.width / 2.0 {
let fixedContentX = layout.size.width - (contentX + contentWidth)
animationInContentXDistance = fixedContentX - contentX
contentNode.layer.animateSpring(
from: -animationInContentXDistance as NSNumber, to: 0.0 as NSNumber,
keyPath: "position.x",
duration: duration,
delay: 0.0,
initialVelocity: 0.0,
damping: springDamping,
additive: true
)
}
contentNode.layer.animateSpring( contentNode.layer.animateSpring(
from: -animationInContentDistance as NSNumber, to: 0.0 as NSNumber, from: -animationInContentYDistance as NSNumber, to: 0.0 as NSNumber,
keyPath: "position.y", keyPath: "position.y",
duration: duration, duration: duration,
delay: 0.0, delay: 0.0,
@ -885,7 +907,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
additive: true additive: true
) )
} else { } else {
animationInContentDistance = 0.0 animationInContentYDistance = 0.0
currentContentScreenFrame = contentRect currentContentScreenFrame = contentRect
} }
@ -926,7 +948,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
actionsVerticalTransitionDirection = 1.0 actionsVerticalTransitionDirection = 1.0
} }
} }
let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing let actionsPositionDeltaYDistance = -animationInContentYDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
self.actionsStackNode.layer.animateSpring( self.actionsStackNode.layer.animateSpring(
from: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)), from: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)),
to: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: CGPoint()),
@ -939,7 +961,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
) )
if let reactionContextNode = self.reactionContextNode { if let reactionContextNode = self.reactionContextNode {
let reactionsPositionDeltaYDistance = -animationInContentDistance let reactionsPositionDeltaYDistance = -animationInContentYDistance
reactionContextNode.layer.animateSpring( reactionContextNode.layer.animateSpring(
from: NSValue(cgPoint: CGPoint(x: 0.0, y: reactionsPositionDeltaYDistance)), from: NSValue(cgPoint: CGPoint(x: 0.0, y: reactionsPositionDeltaYDistance)),
to: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: CGPoint()),
@ -1043,13 +1065,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view) let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view)
let animationInContentDistance: CGFloat let animationInContentYDistance: CGFloat
switch result { switch result {
case .default, .custom: case .default, .custom:
animationInContentDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY animationInContentYDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY
case .dismissWithoutContent: case .dismissWithoutContent:
animationInContentDistance = 0.0 animationInContentYDistance = 0.0
if let contentNode = contentNode { if let contentNode = contentNode {
contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false) contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
} }
@ -1075,10 +1097,28 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if let contentNode = contentNode { if let contentNode = contentNode {
contentNode.containingItem.willUpdateIsExtractedToContextPreview?(false, transition) contentNode.containingItem.willUpdateIsExtractedToContextPreview?(false, transition)
contentNode.offsetContainerNode.position = contentNode.offsetContainerNode.position.offsetBy(dx: 0.0, dy: -animationInContentDistance) var animationInContentXDistance: CGFloat = 0.0
let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX
let contentWidth = contentNode.containingItem.view.bounds.size.width
if case let .extracted(extracted) = self.source, extracted.centerVertically, contentX + contentWidth > layout.size.width / 2.0 {
let fixedContentX = layout.size.width - (contentX + contentWidth)
animationInContentXDistance = contentX - fixedContentX
contentNode.offsetContainerNode.layer.animate(
from: -animationInContentXDistance as NSNumber,
to: 0.0 as NSNumber,
keyPath: "position.x",
timingFunction: timingFunction,
duration: duration,
delay: 0.0,
additive: true
)
}
contentNode.offsetContainerNode.position = contentNode.offsetContainerNode.position.offsetBy(dx: animationInContentXDistance, dy: -animationInContentYDistance)
let reactionContextNodeIsAnimatingOut = self.reactionContextNodeIsAnimatingOut let reactionContextNodeIsAnimatingOut = self.reactionContextNodeIsAnimatingOut
contentNode.offsetContainerNode.layer.animate( contentNode.offsetContainerNode.layer.animate(
from: animationInContentDistance as NSNumber, from: animationInContentYDistance as NSNumber,
to: 0.0 as NSNumber, to: 0.0 as NSNumber,
keyPath: "position.y", keyPath: "position.y",
timingFunction: timingFunction, timingFunction: timingFunction,
@ -1132,7 +1172,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
} }
let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing let actionsPositionDeltaYDistance = -animationInContentYDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
self.actionsStackNode.layer.animate( self.actionsStackNode.layer.animate(
from: NSValue(cgPoint: CGPoint()), from: NSValue(cgPoint: CGPoint()),
to: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)), to: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)),

View File

@ -81,6 +81,7 @@ public final class HashtagSearchController: TelegramBaseController {
}, deletePeerThread: { _, _ in }, deletePeerThread: { _, _ in
}, setPeerThreadStopped: { _, _, _ in }, setPeerThreadStopped: { _, _, _ in
}, setPeerThreadPinned: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in
}, setPeerThreadHidden: { _, _, _ in
}, updatePeerGrouping: { _, _ in }, updatePeerGrouping: { _, _ in
}, togglePeerMarkedUnread: { _, _ in }, togglePeerMarkedUnread: { _, _ in
}, toggleArchivedFolderHiddenByDefault: { }, toggleArchivedFolderHiddenByDefault: {

View File

@ -13,6 +13,7 @@ public enum ItemListSwitchItemNodeType {
public class ItemListSwitchItem: ListViewItem, ItemListItem { public class ItemListSwitchItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let icon: UIImage?
let title: String let title: String
let value: Bool let value: Bool
let type: ItemListSwitchItemNodeType let type: ItemListSwitchItemNodeType
@ -27,8 +28,9 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
let activatedWhileDisabled: () -> Void let activatedWhileDisabled: () -> Void
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) { public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.icon = icon
self.title = title self.title = title
self.value = value self.value = value
self.type = type self.type = type
@ -120,6 +122,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode private let maskNode: ASImageNode
private let iconNode: ASImageNode
private let titleNode: TextNode private let titleNode: TextNode
private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl
private let switchGestureNode: ASDisplayNode private let switchGestureNode: ASDisplayNode
@ -147,6 +150,10 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true self.bottomStripeNode.isLayerBacked = true
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displaysAsynchronously = false
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
switch type { switch type {
@ -206,11 +213,15 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
if currentItem?.presentationData.theme !== item.presentationData.theme { if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme updatedTheme = item.presentationData.theme
} }
var updateIcon = false
if currentItem?.icon != item.icon {
updateIcon = true
}
switch item.style { switch item.style {
case .plain: case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
@ -224,6 +235,11 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
insets = itemListNeighborsGroupedInsets(neighbors, params) insets = itemListNeighborsGroupedInsets(neighbors, params)
} }
var leftInset = 16.0 + params.leftInset
if let _ = item.icon {
leftInset += 43.0
}
if item.disableLeadingInset { if item.disableLeadingInset {
insets.top = 0.0 insets.top = 0.0
insets.bottom = 0.0 insets.bottom = 0.0
@ -260,6 +276,20 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
} }
strongSelf.activateArea.accessibilityTraits = accessibilityTraits strongSelf.activateArea.accessibilityTraits = accessibilityTraits
if let icon = item.icon {
if strongSelf.iconNode.supernode == nil {
strongSelf.addSubnode(strongSelf.iconNode)
}
if updateIcon {
strongSelf.iconNode.image = icon
}
let iconY = floor((layout.contentSize.height - icon.size.height) / 2.0)
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - icon.size.width) / 2.0), y: iconY), size: icon.size)
} else if strongSelf.iconNode.supernode != nil {
strongSelf.iconNode.image = nil
strongSelf.iconNode.removeFromSupernode()
}
let transition: ContainedViewLayoutTransition let transition: ContainedViewLayoutTransition
if animated { if animated {
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
@ -301,8 +331,6 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
let _ = titleApply() let _ = titleApply()
let leftInset = 16.0 + params.leftInset
switch item.style { switch item.style {
case .plain: case .plain:
if strongSelf.backgroundNode.supernode != nil { if strongSelf.backgroundNode.supernode != nil {
@ -345,7 +373,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
switch neighbors.bottom { switch neighbors.bottom {
case .sameSection(false): case .sameSection(false):
bottomStripeInset = 16.0 + params.leftInset bottomStripeInset = leftInset
strongSelf.bottomStripeNode.isHidden = false strongSelf.bottomStripeNode.isHidden = false
default: default:
bottomStripeInset = 0.0 bottomStripeInset = 0.0

View File

@ -1356,13 +1356,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
if let undoOverlayController = strongSelf.undoOverlayController { if let undoOverlayController = strongSelf.undoOverlayController {
undoOverlayController.content = .image(image: image ?? UIImage(), text: text) undoOverlayController.content = .image(image: image ?? UIImage(), title: nil, text: text, undo: true)
} else { } else {
var elevatedLayout = true var elevatedLayout = true
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass { if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
elevatedLayout = false elevatedLayout = false
} }
let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), text: text), elevatedLayout: elevatedLayout, action: { [weak self] action in let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), title: nil, text: text, undo: true), elevatedLayout: elevatedLayout, action: { [weak self] action in
guard let strongSelf = self else { guard let strongSelf = self else {
return true return true
} }

View File

@ -24,14 +24,16 @@ private final class ChannelAdminsControllerArguments {
let removeAdmin: (PeerId) -> Void let removeAdmin: (PeerId) -> Void
let addAdmin: () -> Void let addAdmin: () -> Void
let openAdmin: (ChannelParticipant) -> Void let openAdmin: (ChannelParticipant) -> Void
let updateAntiSpamEnabled: (Bool) -> Void
init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removeAdmin: @escaping (PeerId) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void) { init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removeAdmin: @escaping (PeerId) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void, updateAntiSpamEnabled: @escaping (Bool) -> Void) {
self.context = context self.context = context
self.openRecentActions = openRecentActions self.openRecentActions = openRecentActions
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.removeAdmin = removeAdmin self.removeAdmin = removeAdmin
self.addAdmin = addAdmin self.addAdmin = addAdmin
self.openAdmin = openAdmin self.openAdmin = openAdmin
self.updateAntiSpamEnabled = updateAntiSpamEnabled
} }
} }
@ -47,6 +49,8 @@ private enum ChannelAdminsEntryStableId: Hashable {
private enum ChannelAdminsEntry: ItemListNodeEntry { private enum ChannelAdminsEntry: ItemListNodeEntry {
case recentActions(PresentationTheme, String) case recentActions(PresentationTheme, String)
case antiSpam(PresentationTheme, String, Bool)
case antiSpamInfo(PresentationTheme, String)
case adminsHeader(PresentationTheme, String) case adminsHeader(PresentationTheme, String)
case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Bool, Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool, Bool) case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Bool, Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool, Bool)
@ -55,7 +59,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .recentActions: case .recentActions, .antiSpam, .antiSpamInfo:
return ChannelAdminsSection.administration.rawValue return ChannelAdminsSection.administration.rawValue
case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo: case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo:
return ChannelAdminsSection.admins.rawValue return ChannelAdminsSection.admins.rawValue
@ -66,6 +70,10 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
switch self { switch self {
case .recentActions: case .recentActions:
return .index(0) return .index(0)
case .antiSpam:
return .index(1)
case .antiSpamInfo:
return .index(2)
case .adminsHeader: case .adminsHeader:
return .index(3) return .index(3)
case .addAdmin: case .addAdmin:
@ -85,6 +93,18 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .antiSpam(lhsTheme, lhsText, lhsValue):
if case let .antiSpam(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .antiSpamInfo(lhsTheme, lhsText):
if case let .antiSpamInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .adminsHeader(lhsTheme, lhsText): case let .adminsHeader(lhsTheme, lhsText):
if case let .adminsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .adminsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -146,16 +166,30 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
switch lhs { switch lhs {
case .recentActions: case .recentActions:
return true return true
case .adminsHeader: case .antiSpam:
switch rhs { switch rhs {
case .recentActions: case .recentActions:
return false return false
default: default:
return true return true
} }
case .antiSpamInfo:
switch rhs {
case .recentActions, .antiSpam:
return false
default:
return true
}
case .adminsHeader:
switch rhs {
case .recentActions, .antiSpam, .antiSpamInfo:
return false
default:
return true
}
case let .adminPeerItem(_, _, _, _, _, index, _, _, _, _): case let .adminPeerItem(_, _, _, _, _, index, _, _, _, _):
switch rhs { switch rhs {
case .recentActions, .adminsHeader, .addAdmin: case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin:
return false return false
case let .adminPeerItem(_, _, _, _, _, rhsIndex, _, _, _, _): case let .adminPeerItem(_, _, _, _, _, rhsIndex, _, _, _, _):
return index < rhsIndex return index < rhsIndex
@ -164,7 +198,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
} }
case .addAdmin: case .addAdmin:
switch rhs { switch rhs {
case .recentActions, .adminsHeader, .addAdmin: case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin:
return false return false
default: default:
return true return true
@ -178,9 +212,15 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
let arguments = arguments as! ChannelAdminsControllerArguments let arguments = arguments as! ChannelAdminsControllerArguments
switch self { switch self {
case let .recentActions(_, text): case let .recentActions(_, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openRecentActions() arguments.openRecentActions()
}) })
case let .antiSpam(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/AntiSpam")?.precomposed(), title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateAntiSpamEnabled(value)
})
case let .antiSpamInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .adminsHeader(_, title): case let .adminsHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, _, participant, editing, enabled, hasAction): case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, _, participant, editing, enabled, hasAction):
@ -298,20 +338,22 @@ private struct ChannelAdminsControllerState: Equatable {
} }
} }
private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelAdminsEntry] { private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamEnabled: Bool) -> [ChannelAdminsEntry] {
if participants == nil || participants?.count == nil { if participants == nil || participants?.count == nil {
return [] return []
} }
var entries: [ChannelAdminsEntry] = [] var entries: [ChannelAdminsEntry] = []
if let peer = view.peers[view.peerId] as? TelegramChannel { if let peer = view.peers[view.peerId] as? TelegramChannel {
var isGroup = false var isGroup = false
if case .group = peer.info { if case .group = peer.info {
isGroup = true isGroup = true
entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog)) }
} else { entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog))
entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog))
if isGroup && peer.hasPermission(.deleteAllMessages) {
entries.append(.antiSpam(presentationData.theme, presentationData.strings.Group_Management_AntiSpam, antiSpamEnabled))
entries.append(.antiSpamInfo(presentationData.theme, presentationData.strings.Group_Management_AntiSpamInfo))
} }
if let participants = participants { if let participants = participants {
@ -492,8 +534,31 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
let upgradeDisposable = MetaDisposable() let upgradeDisposable = MetaDisposable()
actionsDisposable.add(upgradeDisposable) actionsDisposable.add(upgradeDisposable)
let updateAntiSpamDisposable = MetaDisposable()
actionsDisposable.add(updateAntiSpamDisposable)
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil) let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
let antiSpamBotConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let resolveAntiSpamPeerDisposable = MetaDisposable()
if let antiSpamBotId = antiSpamBotConfiguration.antiSpamBotId {
resolveAntiSpamPeerDisposable.set(
(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: antiSpamBotId))
|> mapToSignal { peer -> Signal<Never, NoError> in
if let _ = peer {
return .never()
} else {
return context.engine.peers.updatedRemotePeer(peer: .user(id: antiSpamBotId.id._internalGetInt64Value(), accessHash: 0))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .never()
}
}
}).start()
)
}
var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)? var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)?
let currentPeerId = ValuePromise<PeerId>(initialPeerId) let currentPeerId = ValuePromise<PeerId>(initialPeerId)
@ -626,6 +691,12 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
pushControllerImpl?(channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in pushControllerImpl?(channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
}, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership))
}) })
}, updateAntiSpamEnabled: { value in
let _ = (currentPeerId.get()
|> take(1)
|> deliverOnMainQueue).start(next: { peerId in
updateAntiSpamDisposable.set(context.engine.peers.toggleAntiSpamProtection(peerId: peerId, enabled: value).start())
})
}) })
let membersAndLoadMoreControlValue = Atomic<(Disposable, PeerChannelMemberCategoryControl?)?>(value: nil) let membersAndLoadMoreControlValue = Atomic<(Disposable, PeerChannelMemberCategoryControl?)?>(value: nil)
@ -700,9 +771,19 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
var previousPeers: [RenderedChannelParticipant]? var previousPeers: [RenderedChannelParticipant]?
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let signal = combineLatest(queue: .mainQueue(), presentationData, statePromise.get(), peerView.get(), adminsPromise.get() |> deliverOnMainQueue) let signal = combineLatest(
queue: .mainQueue(),
presentationData,
statePromise.get(),
peerView.get(),
adminsPromise.get(),
currentPeerId.get()
|> mapToSignal { peerId -> Signal<Bool, NoError> in
return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AntiSpamEnabled(id: peerId))
}
)
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, admins -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, view, admins, antiSpamEnabled -> (ItemListControllerState, (ItemListNodeState, Any)) in
let peerId = view.peerId let peerId = view.peerId
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
@ -776,7 +857,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, view: view, state: state, participants: admins), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, view: view, state: state, participants: admins, antiSpamEnabled: antiSpamEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {

View File

@ -109,7 +109,7 @@ private enum LogoutOptionsEntry: ItemListNodeEntry, Equatable {
} }
} }
private func logoutOptionsEntries(presentationData: PresentationData, canAddAccounts: Bool, hasPasscode: Bool, hasWallets: Bool) -> [LogoutOptionsEntry] { private func logoutOptionsEntries(presentationData: PresentationData, canAddAccounts: Bool, hasPasscode: Bool) -> [LogoutOptionsEntry] {
var entries: [LogoutOptionsEntry] = [] var entries: [LogoutOptionsEntry] = []
entries.append(.alternativeHeader(presentationData.theme, presentationData.strings.LogoutOptions_AlternativeOptionsSection)) entries.append(.alternativeHeader(presentationData.theme, presentationData.strings.LogoutOptions_AlternativeOptionsSection))
if canAddAccounts { if canAddAccounts {
@ -257,19 +257,12 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
]) ])
presentControllerImpl?(alertController, nil) presentControllerImpl?(alertController, nil)
}) })
#if ENABLE_WALLET
let hasWallets = context.hasWallets
#else
let hasWallets: Signal<Bool, NoError> = .single(false)
#endif
let signal = combineLatest(queue: .mainQueue(), let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData, context.sharedContext.presentationData,
context.sharedContext.accountManager.accessChallengeData(), context.sharedContext.accountManager.accessChallengeData()
hasWallets
) )
|> map { presentationData, accessChallengeData, hasWallets -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, accessChallengeData -> (ItemListControllerState, (ItemListNodeState, Any)) in
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?() dismissImpl?()
}) })
@ -283,7 +276,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.LogoutOptions_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.LogoutOptions_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: logoutOptionsEntries(presentationData: presentationData, canAddAccounts: canAddAccounts, hasPasscode: hasPasscode, hasWallets: hasWallets), style: .blocks) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: logoutOptionsEntries(presentationData: presentationData, canAddAccounts: canAddAccounts, hasPasscode: hasPasscode), style: .blocks)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -343,9 +343,9 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, UIScrollViewDe
self.secretChatsSwitchNode.isOn = session.flags.contains(.acceptsSecretChats) self.secretChatsSwitchNode.isOn = session.flags.contains(.acceptsSecretChats)
self.incomingCallsSwitchNode.isOn = session.flags.contains(.acceptsIncomingCalls) self.incomingCallsSwitchNode.isOn = session.flags.contains(.acceptsIncomingCalls)
if !session.flags.contains(.passwordPending) { if !session.flags.contains(.passwordPending) && session.apiId != 22 {
hasIncomingCalls = true hasIncomingCalls = true
if ![2040, 2496].contains(session.apiId) { if ![2040, 2496].contains(session.apiId) {
hasSecretChats = true hasSecretChats = true
} }
} }

View File

@ -41,8 +41,6 @@ extension SettingsSearchableItemIcon {
return PresentationResourcesSettings.watch return PresentationResourcesSettings.watch
case .passport: case .passport:
return PresentationResourcesSettings.passport return PresentationResourcesSettings.passport
case .wallet:
return PresentationResourcesSettings.wallet
case .support: case .support:
return PresentationResourcesSettings.support return PresentationResourcesSettings.support
case .faq: case .faq:

View File

@ -33,7 +33,6 @@ enum SettingsSearchableItemIcon {
case appearance case appearance
case language case language
case watch case watch
case wallet
case passport case passport
case support case support
case faq case faq
@ -56,7 +55,6 @@ public enum SettingsSearchableItemId: Hashable {
case language(Int32) case language(Int32)
case watch(Int32) case watch(Int32)
case passport(Int32) case passport(Int32)
case wallet(Int32)
case support(Int32) case support(Int32)
case faq(Int32) case faq(Int32)
case chatFolders(Int32) case chatFolders(Int32)
@ -90,8 +88,6 @@ public enum SettingsSearchableItemId: Hashable {
return 11 return 11
case .passport: case .passport:
return 12 return 12
case .wallet:
return 13
case .support: case .support:
return 14 return 14
case .faq: case .faq:
@ -121,7 +117,6 @@ public enum SettingsSearchableItemId: Hashable {
let .language(id), let .language(id),
let .watch(id), let .watch(id),
let .passport(id), let .passport(id),
let .wallet(id),
let .support(id), let .support(id),
let .faq(id), let .faq(id),
let .chatFolders(id), let .chatFolders(id),
@ -164,8 +159,6 @@ public enum SettingsSearchableItemId: Hashable {
self = .watch(id) self = .watch(id)
case 12: case 12:
self = .passport(id) self = .passport(id)
case 13:
self = .wallet(id)
case 14: case 14:
self = .support(id) self = .support(id)
case 15: case 15:

View File

@ -219,7 +219,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
var items: [ChatListItem] = [] var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }) }, present: { _ in }, openForumThread: { _, _ in })

View File

@ -839,7 +839,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
var items: [ChatListItem] = [] var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, present: { _ in

View File

@ -363,7 +363,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
var items: [ChatListItem] = [] var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, present: { _ in

View File

@ -89,7 +89,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months)) return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months))
case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId): case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId):
return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId)) return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId))
case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, _): case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, hidden):
var components: [TelegramMediaActionType.ForumTopicEditComponent] = [] var components: [TelegramMediaActionType.ForumTopicEditComponent] = []
if let title = title { if let title = title {
components.append(.title(title)) components.append(.title(title))
@ -100,6 +100,9 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
if let closed = closed { if let closed = closed {
components.append(.isClosed(closed == .boolTrue)) components.append(.isClosed(closed == .boolTrue))
} }
if let hidden = hidden {
components.append(.isHidden(hidden == .boolTrue))
}
return TelegramMediaAction(action: .topicEdited(components: components)) return TelegramMediaAction(action: .topicEdited(components: components))
} }
} }

View File

@ -65,6 +65,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
case maxKnownMessageId case maxKnownMessageId
case maxOutgoingReadId case maxOutgoingReadId
case isClosed case isClosed
case isHidden
case notificationSettings case notificationSettings
} }
@ -77,6 +78,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
public var maxKnownMessageId: Int32 public var maxKnownMessageId: Int32
public var maxOutgoingReadId: Int32 public var maxOutgoingReadId: Int32
public var isClosed: Bool public var isClosed: Bool
public var isHidden: Bool
public var notificationSettings: TelegramPeerNotificationSettings public var notificationSettings: TelegramPeerNotificationSettings
public init( public init(
@ -89,6 +91,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
maxKnownMessageId: Int32, maxKnownMessageId: Int32,
maxOutgoingReadId: Int32, maxOutgoingReadId: Int32,
isClosed: Bool, isClosed: Bool,
isHidden: Bool,
notificationSettings: TelegramPeerNotificationSettings notificationSettings: TelegramPeerNotificationSettings
) { ) {
self.creationDate = creationDate self.creationDate = creationDate
@ -100,6 +103,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
self.maxKnownMessageId = maxKnownMessageId self.maxKnownMessageId = maxKnownMessageId
self.maxOutgoingReadId = maxOutgoingReadId self.maxOutgoingReadId = maxOutgoingReadId
self.isClosed = isClosed self.isClosed = isClosed
self.isHidden = isHidden
self.notificationSettings = notificationSettings self.notificationSettings = notificationSettings
} }
@ -115,6 +119,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
self.maxKnownMessageId = try container.decode(Int32.self, forKey: .maxKnownMessageId) self.maxKnownMessageId = try container.decode(Int32.self, forKey: .maxKnownMessageId)
self.maxOutgoingReadId = try container.decode(Int32.self, forKey: .maxOutgoingReadId) self.maxOutgoingReadId = try container.decode(Int32.self, forKey: .maxOutgoingReadId)
self.isClosed = try container.decodeIfPresent(Bool.self, forKey: .isClosed) ?? false self.isClosed = try container.decodeIfPresent(Bool.self, forKey: .isClosed) ?? false
self.isHidden = try container.decodeIfPresent(Bool.self, forKey: .isHidden) ?? false
self.notificationSettings = try container.decode(TelegramPeerNotificationSettings.self, forKey: .notificationSettings) self.notificationSettings = try container.decode(TelegramPeerNotificationSettings.self, forKey: .notificationSettings)
} }
@ -130,6 +135,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
try container.encode(self.maxKnownMessageId, forKey: .maxKnownMessageId) try container.encode(self.maxKnownMessageId, forKey: .maxKnownMessageId)
try container.encode(self.maxOutgoingReadId, forKey: .maxOutgoingReadId) try container.encode(self.maxOutgoingReadId, forKey: .maxOutgoingReadId)
try container.encode(self.isClosed, forKey: .isClosed) try container.encode(self.isClosed, forKey: .isClosed)
try container.encode(self.isHidden, forKey: .isHidden)
try container.encode(self.notificationSettings, forKey: .notificationSettings) try container.encode(self.notificationSettings, forKey: .notificationSettings)
} }
} }
@ -285,14 +291,16 @@ func _internal_editForumChannelTopic(account: Account, peerId: PeerId, threadId:
} }
var flags: Int32 = 0 var flags: Int32 = 0
flags |= (1 << 0) flags |= (1 << 0)
flags |= (1 << 1) if threadId != 1 {
flags |= (1 << 1)
}
return account.network.request(Api.functions.channels.editForumTopic( return account.network.request(Api.functions.channels.editForumTopic(
flags: flags, flags: flags,
channel: inputChannel, channel: inputChannel,
topicId: Int32(clamping: threadId), topicId: Int32(clamping: threadId),
title: title, title: title,
iconEmojiId: iconFileId ?? 0, iconEmojiId: threadId == 1 ? nil : iconFileId ?? 0,
closed: nil, closed: nil,
hidden: nil hidden: nil
)) ))
@ -367,6 +375,55 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
} }
} }
func _internal_setForumChannelTopicHidden(account: Account, id: EnginePeer.Id, threadId: Int64, isHidden: Bool) -> Signal<Never, EditForumChannelTopicError> {
guard threadId == 1 else {
return .fail(.generic)
}
return account.postbox.transaction { transaction -> Api.InputChannel? in
return transaction.getPeer(id).flatMap(apiInputChannel)
}
|> castError(EditForumChannelTopicError.self)
|> mapToSignal { inputChannel -> Signal<Never, EditForumChannelTopicError> in
guard let inputChannel = inputChannel else {
return .fail(.generic)
}
var flags: Int32 = 0
flags |= (1 << 3)
return account.network.request(Api.functions.channels.editForumTopic(
flags: flags,
channel: inputChannel,
topicId: Int32(clamping: threadId),
title: nil,
iconEmojiId: nil,
closed: nil,
hidden: isHidden ? .boolTrue : .boolFalse
))
|> mapError { _ -> EditForumChannelTopicError in
return .generic
}
|> mapToSignal { result -> Signal<Never, EditForumChannelTopicError> in
account.stateManager.addUpdates(result)
return account.postbox.transaction { transaction -> Void in
if let initialData = transaction.getMessageHistoryThreadInfo(peerId: id, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {
var data = initialData
data.isHidden = isHidden
if data != initialData {
if let entry = StoredMessageHistoryThreadInfo(data) {
transaction.setMessageHistoryThreadInfo(peerId: id, threadId: threadId, info: entry)
}
}
}
}
|> castError(EditForumChannelTopicError.self)
|> ignoreValues
}
}
}
public enum SetForumChannelTopicPinnedError { public enum SetForumChannelTopicPinnedError {
case generic case generic
case limitReached(Int) case limitReached(Int)
@ -557,6 +614,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
isClosed: (flags & (1 << 2)) != 0, isClosed: (flags & (1 << 2)) != 0,
isHidden: (flags & (1 << 6)) != 0,
notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings)
) )

View File

@ -1760,6 +1760,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, state: AccountMutab
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
isClosed: (flags & (1 << 2)) != 0, isClosed: (flags & (1 << 2)) != 0,
isHidden: (flags & (1 << 6)) != 0,
notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings)
), ),
topMessageId: topMessage, topMessageId: topMessage,
@ -1859,6 +1860,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, ids: [MessageId]) -
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
isClosed: (flags & (1 << 2)) != 0, isClosed: (flags & (1 << 2)) != 0,
isHidden: (flags & (1 << 6)) != 0,
notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings)
) )
if let entry = StoredMessageHistoryThreadInfo(data) { if let entry = StoredMessageHistoryThreadInfo(data) {
@ -1982,6 +1984,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
isClosed: (flags & (1 << 2)) != 0, isClosed: (flags & (1 << 2)) != 0,
isHidden: (flags & (1 << 6)) != 0,
notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings)
), ),
topMessageId: topMessage, topMessageId: topMessage,
@ -3195,6 +3198,8 @@ func replayFinalState(
data.info = EngineMessageHistoryThread.Info(title: data.info.title, icon: fileId == 0 ? nil : fileId, iconColor: data.info.iconColor) data.info = EngineMessageHistoryThread.Info(title: data.info.title, icon: fileId == 0 ? nil : fileId, iconColor: data.info.iconColor)
case let .isClosed(isClosed): case let .isClosed(isClosed):
data.isClosed = isClosed data.isClosed = isClosed
case let .isHidden(isHidden):
data.isHidden = isHidden
} }
} }

View File

@ -18,6 +18,7 @@ public struct CachedChannelFlags: OptionSet {
public static let canViewStats = CachedChannelFlags(rawValue: 1 << 4) public static let canViewStats = CachedChannelFlags(rawValue: 1 << 4)
public static let canChangePeerGeoLocation = CachedChannelFlags(rawValue: 1 << 5) public static let canChangePeerGeoLocation = CachedChannelFlags(rawValue: 1 << 5)
public static let canDeleteHistory = CachedChannelFlags(rawValue: 1 << 6) public static let canDeleteHistory = CachedChannelFlags(rawValue: 1 << 6)
public static let antiSpamEnabled = CachedChannelFlags(rawValue: 1 << 7)
} }
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable { public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {

View File

@ -239,7 +239,6 @@ private enum PreferencesKeyValues: Int32 {
case appConfiguration = 14 case appConfiguration = 14
case searchBotsConfiguration = 15 case searchBotsConfiguration = 15
case contactsSettings = 16 case contactsSettings = 16
case walletCollection = 18
case contentSettings = 19 case contentSettings = 19
case chatListFilters = 20 case chatListFilters = 20
case peersNearby = 21 case peersNearby = 21
@ -339,13 +338,7 @@ public struct PreferencesKeys {
key.setInt32(0, value: PreferencesKeyValues.secretChatSettings.rawValue) key.setInt32(0, value: PreferencesKeyValues.secretChatSettings.rawValue)
return key return key
}() }()
public static let walletCollection: ValueBoxKey = {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: PreferencesKeyValues.walletCollection.rawValue)
return key
}()
public static let contentSettings: ValueBoxKey = { public static let contentSettings: ValueBoxKey = {
let key = ValueBoxKey(length: 4) let key = ValueBoxKey(length: 4)
key.setInt32(0, value: PreferencesKeyValues.contentSettings.rawValue) key.setInt32(0, value: PreferencesKeyValues.contentSettings.rawValue)

View File

@ -28,6 +28,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case title(String) case title(String)
case iconFileId(Int64?) case iconFileId(Int64?)
case isClosed(Bool) case isClosed(Bool)
case isHidden(Bool)
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("_t", orElse: 0) { switch decoder.decodeInt32ForKey("_t", orElse: 0) {
@ -37,6 +38,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
self = .iconFileId(decoder.decodeOptionalInt64ForKey("fileId")) self = .iconFileId(decoder.decodeOptionalInt64ForKey("fileId"))
case 2: case 2:
self = .isClosed(decoder.decodeBoolForKey("isClosed", orElse: false)) self = .isClosed(decoder.decodeBoolForKey("isClosed", orElse: false))
case 3:
self = .isHidden(decoder.decodeBoolForKey("isHidden", orElse: false))
default: default:
assertionFailure() assertionFailure()
self = .title("") self = .title("")
@ -58,6 +61,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case let .isClosed(isClosed): case let .isClosed(isClosed):
encoder.encodeInt32(2, forKey: "_t") encoder.encodeInt32(2, forKey: "_t")
encoder.encodeBool(isClosed, forKey: "isClosed") encoder.encodeBool(isClosed, forKey: "isClosed")
case let .isHidden(isHidden):
encoder.encodeInt32(3, forKey: "_t")
encoder.encodeBool(isHidden, forKey: "isHidden")
} }
} }
} }

View File

@ -806,6 +806,34 @@ public extension TelegramEngine.EngineData.Item {
} }
} }
public struct AntiSpamEnabled: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Bool
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else {
preconditionFailure()
}
if let cachedData = view.cachedPeerData as? CachedChannelData {
return cachedData.flags.contains(.antiSpamEnabled)
} else {
return false
}
}
}
public struct LegacyGroupParticipants: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public struct LegacyGroupParticipants: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = EnginePeerCachedInfoItem<[EngineLegacyGroupParticipant]> public typealias Result = EnginePeerCachedInfoItem<[EngineLegacyGroupParticipant]>

View File

@ -0,0 +1,38 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_toggleAntiSpamProtection(account: Account, peerId: PeerId, enabled: Bool) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.toggleAntiSpam(channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse)) |> `catch` { _ in .complete() } |> map { updates -> Void in
account.stateManager.addUpdates(updates)
}
} else {
return .complete()
}
} |> switchToLatest
}
func _internal_reportAntiSpamFalsePositive(account: Account, peerId: PeerId, messageId: MessageId) -> Signal<Bool, NoError> {
return account.postbox.transaction { transaction -> Signal<Bool, NoError> in
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.reportAntiSpamFalsePositive(channel: inputChannel, msgId: messageId.id))
|> map { result -> Bool in
switch result {
case .boolTrue:
return true
case .boolFalse:
return false
}
}
|> `catch` { _ -> Signal<Bool, NoError> in
return .single(false)
}
} else {
return .complete()
}
} |> switchToLatest
}

View File

@ -287,6 +287,10 @@ public extension TelegramEngine {
public func toggleChannelJoinRequest(peerId: PeerId, enabled: Bool) -> Signal<Never, UpdateChannelJoinRequestError> { public func toggleChannelJoinRequest(peerId: PeerId, enabled: Bool) -> Signal<Never, UpdateChannelJoinRequestError> {
return _internal_toggleChannelJoinRequest(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, enabled: enabled) return _internal_toggleChannelJoinRequest(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, enabled: enabled)
} }
public func toggleAntiSpamProtection(peerId: PeerId, enabled: Bool) -> Signal<Void, NoError> {
return _internal_toggleAntiSpamProtection(account: self.account, peerId: peerId, enabled: enabled)
}
public func requestPeerPhotos(peerId: PeerId) -> Signal<[TelegramPeerPhoto], NoError> { public func requestPeerPhotos(peerId: PeerId) -> Signal<[TelegramPeerPhoto], NoError> {
return _internal_requestPeerPhotos(postbox: self.account.postbox, network: self.account.network, peerId: peerId) return _internal_requestPeerPhotos(postbox: self.account.postbox, network: self.account.network, peerId: peerId)
@ -847,6 +851,10 @@ public extension TelegramEngine {
return _internal_setForumChannelTopicClosed(account: self.account, id: id, threadId: threadId, isClosed: isClosed) return _internal_setForumChannelTopicClosed(account: self.account, id: id, threadId: threadId, isClosed: isClosed)
} }
public func setForumChannelTopicHidden(id: EnginePeer.Id, threadId: Int64, isHidden: Bool) -> Signal<Never, EditForumChannelTopicError> {
return _internal_setForumChannelTopicHidden(account: self.account, id: id, threadId: threadId, isHidden: isHidden)
}
public func removeForumChannelThread(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, NoError> { public func removeForumChannelThread(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in return self.account.postbox.transaction { transaction -> Void in
cloudChatAddClearHistoryOperation(transaction: transaction, peerId: id, threadId: threadId, explicitTopMessageId: nil, minTimestamp: nil, maxTimestamp: nil, type: CloudChatClearHistoryType(.forEveryone)) cloudChatAddClearHistoryOperation(transaction: transaction, peerId: id, threadId: threadId, explicitTopMessageId: nil, minTimestamp: nil, maxTimestamp: nil, type: CloudChatClearHistoryType(.forEveryone))

View File

@ -458,6 +458,9 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
if (flags2 & (1 << 0)) != 0 { if (flags2 & (1 << 0)) != 0 {
channelFlags.insert(.canDeleteHistory) channelFlags.insert(.canDeleteHistory)
} }
if (flags2 & Int32(1 << 1)) != 0 {
channelFlags.insert(.antiSpamEnabled)
}
let sendAsPeerId = defaultSendAs?.peerId let sendAsPeerId = defaultSendAs?.peerId

View File

@ -2,8 +2,6 @@ import Foundation
import Postbox import Postbox
public extension Peer { public extension Peer {
var debugDisplayTitle: String { var debugDisplayTitle: String {
switch self { switch self {
case let user as TelegramUser: case let user as TelegramUser:

View File

@ -105,6 +105,9 @@ public enum PresentationResourceKey: Int32 {
case chatListRecentStatusVoiceChatHighlightedIcon case chatListRecentStatusVoiceChatHighlightedIcon
case chatListRecentStatusVoiceChatPinnedIcon case chatListRecentStatusVoiceChatPinnedIcon
case chatListRecentStatusVoiceChatPanelIcon case chatListRecentStatusVoiceChatPanelIcon
case chatListGeneralTopicIcon
case chatListGeneralTopicSmallIcon
case chatTitleLockIcon case chatTitleLockIcon
case chatTitleMuteIcon case chatTitleMuteIcon
@ -285,6 +288,8 @@ public enum PresentationResourceKey: Int32 {
case chatKeyboardActionButtonAddToChatIcon case chatKeyboardActionButtonAddToChatIcon
case chatKeyboardActionButtonWebAppIcon case chatKeyboardActionButtonWebAppIcon
case chatGeneralThreadIcon
case uploadToneIcon case uploadToneIcon
} }

View File

@ -212,7 +212,7 @@ public struct PresentationResourcesChat {
})?.stretchableImage(withLeftCapWidth: 1, topCapHeight: 4) })?.stretchableImage(withLeftCapWidth: 1, topCapHeight: 4)
}) })
} }
public static func chatInfoItemBackgroundImageWithoutWallpaper(_ theme: PresentationTheme) -> UIImage? { public static func chatInfoItemBackgroundImageWithoutWallpaper(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatInfoItemBackgroundImageWithoutWallpaper.rawValue, { theme in return theme.image(PresentationResourceKey.chatInfoItemBackgroundImageWithoutWallpaper.rawValue, { theme in
return messageSingleBubbleLikeImage(fillColor: theme.chat.message.incoming.bubble.withoutWallpaper.fill[0], strokeColor: theme.chat.message.incoming.bubble.withoutWallpaper.stroke) return messageSingleBubbleLikeImage(fillColor: theme.chat.message.incoming.bubble.withoutWallpaper.fill[0], strokeColor: theme.chat.message.incoming.bubble.withoutWallpaper.stroke)
@ -344,7 +344,7 @@ public struct PresentationResourcesChat {
] ]
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0] var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions()) context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
} }
}) })
@ -420,7 +420,7 @@ public struct PresentationResourcesChat {
return generateInputPanelButtonStrokeImage(color: theme.chat.inputButtonPanel.buttonStrokeColor, offset: 1.0) return generateInputPanelButtonStrokeImage(color: theme.chat.inputButtonPanel.buttonStrokeColor, offset: 1.0)
}) })
} }
public static func chatInputTextFieldBackgroundImage(_ theme: PresentationTheme) -> UIImage? { public static func chatInputTextFieldBackgroundImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatInputTextFieldBackgroundImage.rawValue, { theme in return theme.image(PresentationResourceKey.chatInputTextFieldBackgroundImage.rawValue, { theme in
let diameter: CGFloat = 35.0 let diameter: CGFloat = 35.0
@ -428,7 +428,7 @@ public struct PresentationResourcesChat {
let context = UIGraphicsGetCurrentContext()! let context = UIGraphicsGetCurrentContext()!
context.setFillColor(theme.chat.inputPanel.panelBackgroundColor.cgColor) context.setFillColor(theme.chat.inputPanel.panelBackgroundColor.cgColor)
context.fill(CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)) context.fill(CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter))
context.setBlendMode(.clear) context.setBlendMode(.clear)
context.setFillColor(UIColor.clear.cgColor) context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)) context.fillEllipse(in: CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter))
@ -1053,7 +1053,7 @@ public struct PresentationResourcesChat {
}) })
}) })
} }
public static func chatBubbleFileCloudFetchMediaIcon(_ theme: PresentationTheme) -> UIImage? { public static func chatBubbleFileCloudFetchMediaIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleFileCloudFetchMediaIcon.rawValue, { theme in return theme.image(PresentationResourceKey.chatBubbleFileCloudFetchMediaIcon.rawValue, { theme in
guard let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: theme.chat.message.mediaOverlayControlColors.foregroundColor) else { guard let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: theme.chat.message.mediaOverlayControlColors.foregroundColor) else {
@ -1247,13 +1247,13 @@ public struct PresentationResourcesChat {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FreeRepliesIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FreeRepliesIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper))
}) })
} }
public static func chatFreeNavigateButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { public static func chatFreeNavigateButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? {
return theme.image(PresentationResourceKey.chatFreeNavigateButtonIcon.rawValue, { _ in return theme.image(PresentationResourceKey.chatFreeNavigateButtonIcon.rawValue, { _ in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/NavigateToMessageIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/NavigateToMessageIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper))
}) })
} }
public static func chatFreeShareButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { public static func chatFreeShareButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? {
return theme.image(PresentationResourceKey.chatFreeShareButtonIcon.rawValue, { _ in return theme.image(PresentationResourceKey.chatFreeShareButtonIcon.rawValue, { _ in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ShareIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ShareIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper))
@ -1319,4 +1319,10 @@ public struct PresentationResourcesChat {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelSectionLockIcon"), color: color) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelSectionLockIcon"), color: color)
}) })
} }
public static func chatGeneralThreadIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatGeneralThreadIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Info/GeneralIcon"), color: theme.rootController.navigationBar.controlColor)
})
}
} }

View File

@ -384,4 +384,22 @@ public struct PresentationResourcesChatList {
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/TopicArrowIcon"), color: theme.chatList.titleColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat List/TopicArrowIcon"), color: theme.chatList.titleColor)
}) })
} }
public static func generalTopicSmallIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListGeneralTopicSmallIcon.rawValue, { theme in
let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor)
return generateImage(CGSize(width: 18.0, height: 18.0), contextGenerator: { size, context in
context.clear(CGRect(origin: .zero, size: size))
if let cgImage = image?.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
})
})
}
public static func generalTopicIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListGeneralTopicIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor)
})
}
} }

View File

@ -37,20 +37,6 @@ public struct PresentationResourcesSettings {
public static let language = renderIcon(name: "Settings/Menu/Language") public static let language = renderIcon(name: "Settings/Menu/Language")
public static let deleteAccount = renderIcon(name: "Chat/Info/GroupRemovedIcon") public static let deleteAccount = renderIcon(name: "Chat/Info/GroupRemovedIcon")
public static let wallet = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(UIColor.white.cgColor)
context.fill(bounds.insetBy(dx: 5.0, dy: 5.0))
if let image = generateTintedImage(image: UIImage(bundleImageName: "Settings/Menu/Wallet"), color: UIColor(rgb: 0x1b1b1c))?.cgImage {
context.draw(image, in: bounds)
}
drawBorder(context: context, rect: bounds)
})
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)

View File

@ -682,12 +682,49 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case let .topicCreated(title, iconColor, iconFileId): case let .topicCreated(title, iconColor, iconFileId):
if forForumOverview { if forForumOverview {
let maybeFileId = iconFileId ?? 0 let maybeFileId = iconFileId ?? 0
attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicCreated(".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicCreated(".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])])
} else { } else {
attributedString = NSAttributedString(string: strings.Notification_ForumTopicCreated, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_ForumTopicCreated, font: titleFont, textColor: primaryTextColor)
} }
case let .topicEdited(components): case let .topicEdited(components):
if let isClosed = components.compactMap({ item -> Bool? in if let isHidden = components.compactMap({ item -> Bool? in
switch item {
case let .isHidden(isHidden):
return isHidden
default:
return nil
}
}).first {
if case let .user(user) = message.author {
if forForumOverview {
var title: String = ""
var iconColor: Int32 = 0
var maybeFileId: Int64 = 0
if let info = message.associatedThreadInfo {
iconColor = info.iconColor
title = info.title
maybeFileId = info.icon ?? 0
}
if isHidden {
attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicHidden(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])])
} else {
attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicUnhidden(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])])
}
} else {
if isHidden {
attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicHiddenAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id)])
} else {
attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicUnhiddenAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id)])
}
}
} else {
if isHidden {
attributedString = NSAttributedString(string: strings.Notification_ForumTopicHidden, font: titleFont, textColor: primaryTextColor)
} else {
attributedString = NSAttributedString(string: strings.Notification_ForumTopicUnhidden, font: titleFont, textColor: primaryTextColor)
}
}
} else if let isClosed = components.compactMap({ item -> Bool? in
switch item { switch item {
case let .isClosed(isClosed): case let .isClosed(isClosed):
return isClosed return isClosed
@ -706,9 +743,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
maybeFileId = info.icon ?? 0 maybeFileId = info.icon ?? 0
} }
if isClosed { if isClosed {
attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicClosed(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicClosed(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])])
} else { } else {
attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicReopened(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicReopened(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])])
} }
} else { } else {
if isClosed { if isClosed {
@ -746,7 +783,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} }
attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicRenamedIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [ attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicRenamedIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [
0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id),
1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)]) 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])
]) ])
} else { } else {
attributedString = NSAttributedString(string: strings.Notification_ForumTopicRenamed(title).string, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_ForumTopicRenamed(title).string, font: titleFont, textColor: primaryTextColor)
@ -779,9 +816,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
title = info.title title = info.title
} }
if case let .user(user) = message.author { if case let .user(user) = message.author {
attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".")._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".")._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])])
} else { } else {
attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChanged(".")._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChanged(".")._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])])
} }
} }
case .unknown: case .unknown:

View File

@ -49,6 +49,7 @@ public final class EmojiStatusComponent: Component {
case text(color: UIColor, string: String) case text(color: UIColor, string: String)
case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode) case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode)
case topic(title: String, color: Int32, size: CGSize) case topic(title: String, color: Int32, size: CGSize)
case image(image: UIImage?)
} }
public let context: AccountContext public let context: AccountContext
@ -230,6 +231,8 @@ public final class EmojiStatusComponent: Component {
} else { } else {
iconImage = nil iconImage = nil
} }
case let .image(image):
iconImage = image
case let .verified(fillColor, foregroundColor, sizeType): case let .verified(fillColor, foregroundColor, sizeType):
let imageNamePrefix: String let imageNamePrefix: String
switch sizeType { switch sizeType {

View File

@ -261,23 +261,33 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
} }
} }
private func updateTopicInfo(topicInfo: EngineMessageHistoryThread.Info) { private func updateTopicInfo(topicInfo: (Int64, EngineMessageHistoryThread.Info)) {
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) { if topicInfo.0 == 1 {
return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]) let image = generateImage(CGSize(width: 18.0, height: 18.0), contextGenerator: { size, context in
} context.clear(CGRect(origin: .zero, size: size))
if let cgImage = generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: .white)?.cgImage {
let topicColors: [Int32: ([UInt32], [UInt32])] = [ context.draw(cgImage, in: CGRect(origin: .zero, size: size))
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]), }
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]), })
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]), self.contents = image?.cgImage
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]), } else {
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]), func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506]) return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
] }
let colors = topicColors[topicInfo.iconColor] ?? generateTopicColors(topicInfo.iconColor)
let topicColors: [Int32: ([UInt32], [UInt32])] = [
if let image = generateTopicIcon(title: String(topicInfo.title.prefix(1)), backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: CGSize(width: 32.0, height: 32.0)) { 0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
self.contents = image.cgImage 0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
let colors = topicColors[topicInfo.1.iconColor] ?? generateTopicColors(topicInfo.1.iconColor)
if let image = generateTopicIcon(title: String(topicInfo.1.title.prefix(1)), backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: CGSize(width: 32.0, height: 32.0)) {
self.contents = image.cgImage
}
} }
} }

View File

@ -24,6 +24,7 @@ private final class TitleFieldComponent: Component {
let textColor: UIColor let textColor: UIColor
let accentColor: UIColor let accentColor: UIColor
let placeholderColor: UIColor let placeholderColor: UIColor
let isGeneral: Bool
let fileId: Int64 let fileId: Int64
let iconColor: Int32 let iconColor: Int32
let text: String let text: String
@ -36,6 +37,7 @@ private final class TitleFieldComponent: Component {
textColor: UIColor, textColor: UIColor,
accentColor: UIColor, accentColor: UIColor,
placeholderColor: UIColor, placeholderColor: UIColor,
isGeneral: Bool,
fileId: Int64, fileId: Int64,
iconColor: Int32, iconColor: Int32,
text: String, text: String,
@ -47,6 +49,7 @@ private final class TitleFieldComponent: Component {
self.textColor = textColor self.textColor = textColor
self.accentColor = accentColor self.accentColor = accentColor
self.placeholderColor = placeholderColor self.placeholderColor = placeholderColor
self.isGeneral = isGeneral
self.fileId = fileId self.fileId = fileId
self.iconColor = iconColor self.iconColor = iconColor
self.text = text self.text = text
@ -68,6 +71,9 @@ private final class TitleFieldComponent: Component {
if lhs.placeholderColor != rhs.placeholderColor { if lhs.placeholderColor != rhs.placeholderColor {
return false return false
} }
if lhs.isGeneral != rhs.isGeneral {
return false
}
if lhs.fileId != rhs.fileId { if lhs.fileId != rhs.fileId {
return false return false
} }
@ -140,7 +146,10 @@ private final class TitleFieldComponent: Component {
self.state = state self.state = state
let iconContent: EmojiStatusComponent.Content let iconContent: EmojiStatusComponent.Content
if component.fileId == 0 { if component.isGeneral {
iconContent = .image(image: generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: component.placeholderColor))
self.iconButton.isUserInteractionEnabled = false
} else if component.fileId == 0 {
iconContent = .topic(title: String(component.text.prefix(1)), color: component.iconColor, size: CGSize(width: 32.0, height: 32.0)) iconContent = .topic(title: String(component.text.prefix(1)), color: component.iconColor, size: CGSize(width: 32.0, height: 32.0))
self.iconButton.isUserInteractionEnabled = true self.iconButton.isUserInteractionEnabled = true
} else { } else {
@ -413,6 +422,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
private var defaultIconFilesDisposable: Disposable? private var defaultIconFilesDisposable: Disposable?
private var defaultIconFiles = Set<Int64>() private var defaultIconFiles = Set<Int64>()
let isGeneral: Bool
var title: String var title: String
var fileId: Int64 var fileId: Int64
var iconColor: Int32 var iconColor: Int32
@ -427,11 +437,13 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
switch mode { switch mode {
case .create: case .create:
self.isGeneral = false
self.title = "" self.title = ""
self.fileId = 0 self.fileId = 0
self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0 self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0
case let .edit(info): case let .edit(threadId, info):
self.isGeneral = threadId == 1
self.title = info.title self.title = info.title
self.fileId = info.icon ?? 0 self.fileId = info.icon ?? 0
self.iconColor = info.iconColor self.iconColor = info.iconColor
@ -638,6 +650,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
textColor: environment.theme.list.itemPrimaryTextColor, textColor: environment.theme.list.itemPrimaryTextColor,
accentColor: environment.theme.list.itemAccentColor, accentColor: environment.theme.list.itemAccentColor,
placeholderColor: environment.theme.list.disclosureArrowColor, placeholderColor: environment.theme.list.disclosureArrowColor,
isGeneral: state.isGeneral,
fileId: state.fileId, fileId: state.fileId,
iconColor: state.iconColor, iconColor: state.iconColor,
text: state.title, text: state.title,
@ -658,124 +671,128 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
contentHeight += titleBackground.size.height + sectionSpacing contentHeight += titleBackground.size.height + sectionSpacing
let iconHeader = iconHeader.update( if case let .edit(threadId, _) = context.component.mode, threadId == 1 {
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.CreateTopic_SelectTopicIcon,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor,
paragraphAlignment: .natural)
),
horizontalAlignment: .natural,
maximumNumberOfLines: 1
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0,
height: CGFloat.greatestFiniteMagnitude
),
transition: .immediate
)
context.add(iconHeader
.position(CGPoint(x: sideInset * 2.0 + iconHeader.size.width / 2.0, y: contentHeight + iconHeader.size.height / 2.0))
)
contentHeight += iconHeader.size.height + headerSpacing
let bottomInset = max(environment.safeInsets.bottom, 12.0)
let iconBackground = iconBackground.update(
component: RoundedRectangle(
color: environment.theme.list.itemBlocksBackgroundColor,
cornerRadius: 10.0
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0,
height: context.availableSize.height - contentHeight - bottomInset
),
transition: context.transition
)
context.add(iconBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconBackground.size.height / 2.0))
)
if let emojiContent = state.emojiContent {
let availableHeight = context.availableSize.height - contentHeight - max(bottomInset, environment.inputHeight)
let iconSelector = iconSelector.update( } else {
component: TopicIconSelectionComponent( let iconHeader = iconHeader.update(
theme: environment.theme, component: MultilineTextComponent(
strings: environment.strings, text: .plain(NSAttributedString(
deviceMetrics: environment.deviceMetrics, string: environment.strings.CreateTopic_SelectTopicIcon,
emojiContent: emojiContent, font: Font.regular(13.0),
backgroundColor: environment.theme.list.itemBlocksBackgroundColor, textColor: environment.theme.list.freeTextColor,
separatorColor: environment.theme.list.blocksBackgroundColor paragraphAlignment: .natural)
),
horizontalAlignment: .natural,
maximumNumberOfLines: 1
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0,
height: CGFloat.greatestFiniteMagnitude
),
transition: .immediate
)
context.add(iconHeader
.position(CGPoint(x: sideInset * 2.0 + iconHeader.size.width / 2.0, y: contentHeight + iconHeader.size.height / 2.0))
)
contentHeight += iconHeader.size.height + headerSpacing
let bottomInset = max(environment.safeInsets.bottom, 12.0)
let iconBackground = iconBackground.update(
component: RoundedRectangle(
color: environment.theme.list.itemBlocksBackgroundColor,
cornerRadius: 10.0
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0,
height: context.availableSize.height - contentHeight - bottomInset
), ),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: availableHeight),
transition: context.transition transition: context.transition
) )
context.add(iconSelector context.add(iconBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconSelector.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconBackground.size.height / 2.0))
.cornerRadius(10.0)
.clipsToBounds(true)
) )
let accountContext = context.component.context if let emojiContent = state.emojiContent {
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( let availableHeight = context.availableSize.height - contentHeight - max(bottomInset, environment.inputHeight)
performItemAction: { [weak state] groupId, item, _, _, _, _ in
state?.applyItem(groupId: groupId, item: item) let iconSelector = iconSelector.update(
}, component: TopicIconSelectionComponent(
deleteBackwards: { theme: environment.theme,
}, strings: environment.strings,
openStickerSettings: { deviceMetrics: environment.deviceMetrics,
}, emojiContent: emojiContent,
openFeatured: { backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
}, separatorColor: environment.theme.list.blocksBackgroundColor
addGroupAction: { groupId, isPremiumLocked in ),
guard let collectionId = groupId.base as? ItemCollectionId else { environment: {},
return availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: availableHeight),
} transition: context.transition
)
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) context.add(iconSelector
let _ = (accountContext.account.postbox.combinedView(keys: [viewKey]) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconSelector.size.height / 2.0))
|> take(1) .cornerRadius(10.0)
|> deliverOnMainQueue).start(next: { views in .clipsToBounds(true)
guard let view = views.views[viewKey] as? OrderedItemListView else { )
let accountContext = context.component.context
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak state] groupId, item, _, _, _, _ in
state?.applyItem(groupId: groupId, item: item)
},
deleteBackwards: {
},
openStickerSettings: {
},
openFeatured: {
},
addGroupAction: { groupId, isPremiumLocked in
guard let collectionId = groupId.base as? ItemCollectionId else {
return return
} }
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredEmojiPack.info.id == collectionId { let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
// if let strongSelf = self { let _ = (accountContext.account.postbox.combinedView(keys: [viewKey])
// strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId)) |> take(1)
// } |> deliverOnMainQueue).start(next: { views in
let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start() guard let view = views.views[viewKey] as? OrderedItemListView else {
return
break
} }
} for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
}) if featuredEmojiPack.info.id == collectionId {
}, // if let strongSelf = self {
clearGroup: { _ in // strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId))
}, // }
pushController: { c in let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
},
presentController: { c in break
}, }
presentGlobalOverlayController: { c in }
}, })
navigationController: { },
return nil clearGroup: { _ in
}, },
requestUpdate: { _ in pushController: { c in
}, },
updateSearchQuery: { _, _ in presentController: { c in
}, },
chatPeerId: nil, presentGlobalOverlayController: { c in
peekBehavior: nil, },
customLayout: nil, navigationController: {
externalBackground: nil, return nil
externalExpansionView: nil, },
useOpaqueTheme: true requestUpdate: { _ in
) },
updateSearchQuery: { _, _ in
},
chatPeerId: nil,
peekBehavior: nil,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
useOpaqueTheme: true
)
}
} }
return context.availableSize return context.availableSize
@ -788,7 +805,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
public enum Mode: Equatable { public enum Mode: Equatable {
case create case create
case edit(topic: EngineMessageHistoryThread.Info) case edit(threadId: Int64, threadInfo: EngineMessageHistoryThread.Info)
} }
private let context: AccountContext private let context: AccountContext
@ -835,9 +852,9 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
case .create: case .create:
title = presentationData.strings.CreateTopic_CreateTitle title = presentationData.strings.CreateTopic_CreateTitle
doneTitle = presentationData.strings.CreateTopic_Create doneTitle = presentationData.strings.CreateTopic_Create
case let .edit(topic): case let .edit(_, topic):
title = presentationData.strings.CreateTopic_EditTitle title = presentationData.strings.CreateTopic_EditTitle
doneTitle = presentationData.strings.Common_Done doneTitle = presentationData.strings.Common_Done
self.state = (topic.title, topic.icon) self.state = (topic.title, topic.icon)
} }

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_general.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,113 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.799988 1.991699 cm
0.000000 0.000000 0.000000 scn
4.025391 7.171875 m
3.005859 2.056641 l
2.953125 1.880859 2.935547 1.634766 2.935547 1.458984 c
2.935547 0.544922 3.533203 0.000000 4.482422 0.000000 c
5.484375 0.000000 6.082031 0.509766 6.292969 1.582031 c
7.417969 7.171875 l
12.515625 7.171875 l
11.478516 2.056641 l
11.425781 1.880859 11.408203 1.634766 11.408203 1.458984 c
11.408203 0.544922 12.005859 0.000000 12.955078 0.000000 c
13.957031 0.000000 14.554688 0.544922 14.765625 1.617188 c
15.908203 7.171875 l
18.931641 7.171875 l
20.074219 7.171875 20.812500 7.927734 20.812500 9.035156 c
20.812500 9.914062 20.232422 10.511719 19.300781 10.511719 c
16.558594 10.511719 l
17.648438 15.855469 l
20.566406 15.855469 l
21.708984 15.855469 22.447266 16.593750 22.447266 17.701172 c
22.447266 18.580078 21.867188 19.160156 20.935547 19.160156 c
18.316406 19.160156 l
19.300781 24.011719 l
19.353516 24.187500 19.371094 24.433594 19.371094 24.609375 c
19.371094 25.523438 18.773438 26.068359 17.824219 26.068359 c
16.822266 26.068359 16.224609 25.523438 16.013672 24.451172 c
14.923828 19.160156 l
9.861328 19.160156 l
10.828125 24.011719 l
10.863281 24.187500 10.898438 24.433594 10.898438 24.609375 c
10.898438 25.523438 10.300781 26.068359 9.351562 26.068359 c
8.349609 26.068359 7.751953 25.523438 7.541016 24.468750 c
6.433594 19.160156 l
3.498047 19.160156 l
2.355469 19.160156 1.599609 18.421875 1.599609 17.314453 c
1.599609 16.453125 2.197266 15.837891 3.128906 15.837891 c
5.783203 15.837891 l
4.710938 10.494141 l
1.898438 10.494141 l
0.738281 10.494141 0.000000 9.755859 0.000000 8.648438 c
0.000000 7.787109 0.597656 7.171875 1.529297 7.171875 c
4.025391 7.171875 l
h
7.875000 10.283203 m
9.052734 16.066406 l
14.466797 16.066406 l
13.271484 10.283203 l
7.875000 10.283203 l
h
f
n
Q
endstream
endobj
3 0 obj
1868
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001958 00000 n
0000001981 00000 n
0000002154 00000 n
0000002228 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2287
%%EOF

View File

@ -0,0 +1,112 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 4.199997 1.907497 cm
1.000000 1.000000 1.000000 scn
0.000000 12.592503 m
0.000000 18.498425 l
0.000000 19.400753 0.000000 19.851917 0.142046 20.246223 c
0.267605 20.594761 0.472146 20.909500 0.739650 21.165794 c
1.042280 21.455738 1.454996 21.639168 2.280427 22.006027 c
7.680857 24.406218 l
8.829472 24.916712 9.403779 25.171961 10.000750 25.272770 c
10.529839 25.362116 11.070163 25.362116 11.599251 25.272770 c
12.196222 25.171961 12.770529 24.916714 13.919143 24.406218 c
19.319572 22.006027 l
20.145004 21.639168 20.557720 21.455738 20.860350 21.165794 c
21.127855 20.909500 21.332396 20.594761 21.457954 20.246223 c
21.600000 19.851917 21.600000 19.400753 21.600000 18.498423 c
21.600000 12.592503 l
21.600000 4.965950 14.783872 1.126137 11.981524 -0.130262 c
11.981509 -0.130268 l
11.662810 -0.273155 11.503456 -0.344597 11.202699 -0.395788 c
10.999416 -0.430387 10.600584 -0.430387 10.397303 -0.395788 c
10.096541 -0.344597 9.937186 -0.273151 9.618476 -0.130262 c
6.816126 1.126139 0.000000 4.965952 0.000000 12.592503 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 7.199997 8.199517 cm
0.274510 0.294118 0.298039 scn
0.989925 6.863103 m
4.855387 8.547224 7.432969 9.657499 8.722668 10.193930 c
12.405018 11.725546 13.170178 11.991605 13.668900 12.000390 c
13.778588 12.002322 14.023846 11.975138 14.182714 11.846229 c
14.316857 11.737380 14.353765 11.590341 14.371427 11.487141 c
14.389089 11.383940 14.411081 11.148846 14.393599 10.965151 c
14.194051 8.868484 13.330610 3.780426 12.891341 1.432123 c
12.705469 0.438469 12.339483 0.105302 11.985166 0.072697 c
11.215152 0.001839 10.630439 0.581574 9.884643 1.070453 c
8.717619 1.835451 8.058326 2.311666 6.925527 3.058163 c
5.616383 3.920870 6.465046 4.395029 7.211124 5.169937 c
7.406376 5.372734 10.799074 8.458654 10.864739 8.738596 c
10.872952 8.773607 10.880574 8.904113 10.803043 8.973024 c
10.725513 9.041937 10.611083 9.018372 10.528507 8.999630 c
10.411459 8.973064 8.547124 7.740808 4.935502 5.302861 c
4.406317 4.939481 3.926997 4.762432 3.497544 4.771710 c
3.024105 4.781938 2.113399 5.039400 1.436383 5.259471 c
0.605995 5.529397 -0.053981 5.672108 0.003489 6.130527 c
0.033422 6.369299 0.362234 6.613492 0.989925 6.863103 c
h
f*
n
Q
endstream
endobj
3 0 obj
2275
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002365 00000 n
0000002388 00000 n
0000002561 00000 n
0000002635 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2694
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "AntiSpamTooltip.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,135 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.203922 0.780392 0.349020 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 6.000000 4.506248 cm
1.000000 1.000000 1.000000 scn
0.000000 10.493752 m
0.000000 15.415352 l
0.000000 16.167294 0.000000 16.543264 0.118372 16.871851 c
0.223004 17.162300 0.393455 17.424583 0.616375 17.638161 c
0.868567 17.879782 1.212497 18.032639 1.900359 18.338356 c
6.400713 20.338512 l
7.357893 20.763926 7.836482 20.976633 8.333958 21.060640 c
8.774865 21.135096 9.225135 21.135096 9.666042 21.060640 c
10.163518 20.976633 10.642108 20.763926 11.599288 20.338512 c
16.099644 18.338354 l
16.787502 18.032639 17.131433 17.879782 17.383625 17.638161 c
17.606544 17.424583 17.776997 17.162300 17.881628 16.871851 c
18.000000 16.543264 18.000000 16.167294 18.000000 15.415352 c
18.000000 10.493752 l
18.000000 4.138292 12.319893 0.938446 9.984602 -0.108553 c
9.719011 -0.227627 9.586216 -0.287165 9.335582 -0.329823 c
9.166181 -0.358656 8.833819 -0.358656 8.664418 -0.329823 c
8.413784 -0.287165 8.280988 -0.227627 8.015397 -0.108553 c
5.680106 0.938448 0.000000 4.138292 0.000000 10.493752 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 8.500000 9.749598 cm
0.203922 0.780392 0.349020 scn
0.824937 5.719253 m
4.046156 7.122686 6.194141 8.047915 7.268889 8.494941 c
10.337516 9.771288 10.975148 9.993003 11.390750 10.000324 c
11.482157 10.001935 11.686539 9.979281 11.818928 9.871857 c
11.930715 9.781149 11.961472 9.658617 11.976190 9.572617 c
11.990908 9.486616 12.009235 9.290705 11.994666 9.137626 c
11.828376 7.390403 11.108842 3.150355 10.742785 1.193437 c
10.587892 0.365391 10.282903 0.087751 9.987638 0.060581 c
9.345960 0.001533 8.858699 0.484646 8.237203 0.892044 c
7.264683 1.529543 6.715271 1.926389 5.771273 2.548470 c
4.680319 3.267392 5.387539 3.662524 6.009270 4.308280 c
6.171980 4.477278 8.999228 7.048879 9.053950 7.282163 c
9.060794 7.311339 9.067145 7.420094 9.002536 7.477520 c
8.937927 7.534947 8.842569 7.515309 8.773756 7.499691 c
8.676216 7.477553 7.122603 6.450673 4.112918 4.419051 c
3.671931 4.116235 3.272498 3.968693 2.914620 3.976425 c
2.520088 3.984949 1.761166 4.199500 1.196986 4.382892 c
0.504996 4.607831 -0.044984 4.726757 0.002907 5.108772 c
0.027852 5.307749 0.301862 5.511243 0.824937 5.719253 c
h
f*
n
Q
endstream
endobj
3 0 obj
3134
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003224 00000 n
0000003247 00000 n
0000003420 00000 n
0000003494 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3553
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Antispam.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_general (1).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,113 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 12.666870 6.638916 cm
0.000000 0.000000 0.000000 scn
13.417969 23.906250 m
10.019531 6.855469 l
9.843750 6.269531 9.785156 5.449219 9.785156 4.863281 c
9.785156 1.816406 11.777344 0.000000 14.941406 0.000000 c
18.281250 0.000000 20.273438 1.699219 20.976562 5.273438 c
24.726562 23.906250 l
41.718750 23.906250 l
38.261719 6.855469 l
38.085938 6.269531 38.027344 5.449219 38.027344 4.863281 c
38.027344 1.816406 40.019531 0.000000 43.183594 0.000000 c
46.523438 0.000000 48.515625 1.816406 49.218750 5.390625 c
53.027344 23.906250 l
63.105469 23.906250 l
66.914062 23.906250 69.375000 26.425781 69.375000 30.117188 c
69.375000 33.046875 67.441406 35.039062 64.335938 35.039062 c
55.195312 35.039062 l
58.828125 52.851562 l
68.554688 52.851562 l
72.363281 52.851562 74.824219 55.312500 74.824219 59.003906 c
74.824219 61.933594 72.890625 63.867188 69.785156 63.867188 c
61.054688 63.867188 l
64.335938 80.039062 l
64.511719 80.625000 64.570312 81.445312 64.570312 82.031250 c
64.570312 85.078125 62.578125 86.894531 59.414062 86.894531 c
56.074219 86.894531 54.082031 85.078125 53.378906 81.503906 c
49.746094 63.867188 l
32.871094 63.867188 l
36.093750 80.039062 l
36.210938 80.625000 36.328125 81.445312 36.328125 82.031250 c
36.328125 85.078125 34.335938 86.894531 31.171875 86.894531 c
27.832031 86.894531 25.839844 85.078125 25.136719 81.562500 c
21.445312 63.867188 l
11.660156 63.867188 l
7.851562 63.867188 5.332031 61.406250 5.332031 57.714844 c
5.332031 54.843750 7.324219 52.792969 10.429688 52.792969 c
19.277344 52.792969 l
15.703125 34.980469 l
6.328125 34.980469 l
2.460938 34.980469 0.000000 32.519531 0.000000 28.828125 c
0.000000 25.957031 1.992188 23.906250 5.097656 23.906250 c
13.417969 23.906250 l
h
26.250000 34.277344 m
30.175781 53.554688 l
48.222656 53.554688 l
44.238281 34.277344 l
26.250000 34.277344 l
h
f
n
Q
endstream
endobj
3 0 obj
1906
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 100.000000 100.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001996 00000 n
0000002019 00000 n
0000002194 00000 n
0000002268 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2327
%%EOF

View File

@ -4949,7 +4949,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount) strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount)
let avatarContent: EmojiStatusComponent.Content let avatarContent: EmojiStatusComponent.Content
if let fileId = threadInfo.icon { if strongSelf.chatLocation.threadId == 1 {
avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme))
} else if let fileId = threadInfo.icon {
avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1)) avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1))
} else { } else {
avatarContent = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: 32.0, height: 32.0)) avatarContent = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: 32.0, height: 32.0))

View File

@ -960,7 +960,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
var isLargeFile = false var isLargeFile = false
for media in message.media { for media in message.media {
if let file = media as? TelegramMediaFile { if let file = media as? TelegramMediaFile {
if let size = file.size, size >= 300 * 1024 * 1024 { if let size = file.size, size >= 150 * 1024 * 1024 {
isLargeFile = true isLargeFile = true
} }
break break

View File

@ -335,9 +335,15 @@ class ChatMessageThreadInfoNode: ASDisplayNode {
textColor = UIColor(rgb: colors.1.first ?? 0x000000) textColor = UIColor(rgb: colors.1.first ?? 0x000000)
arrowIcon = PresentationResourcesChat.chatBubbleArrowImage(color: textColor.withAlphaComponent(0.3)) arrowIcon = PresentationResourcesChat.chatBubbleArrowImage(color: textColor.withAlphaComponent(0.3))
} else { } else {
backgroundColor = (incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor) if incoming {
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor backgroundColor = arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor
arrowIcon = incoming ? PresentationResourcesChat.chatBubbleArrowIncomingImage(arguments.presentationData.theme.theme) : PresentationResourcesChat.chatBubbleArrowOutgoingImage(arguments.presentationData.theme.theme) textColor = arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor
arrowIcon = PresentationResourcesChat.chatBubbleArrowIncomingImage(arguments.presentationData.theme.theme)
} else {
backgroundColor = arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
textColor = arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
arrowIcon = PresentationResourcesChat.chatBubbleArrowOutgoingImage(arguments.presentationData.theme.theme)
}
} }
case .standalone: case .standalone:
textColor = .white textColor = .white

View File

@ -87,6 +87,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
private var adminsState: ChannelMemberListState? private var adminsState: ChannelMemberListState?
private let banDisposables = DisposableDict<PeerId>() private let banDisposables = DisposableDict<PeerId>()
private weak var antiSpamTooltipController: UndoOverlayController?
private weak var controller: ChatRecentActionsController? private weak var controller: ChatRecentActionsController?
init(context: AccountContext, controller: ChatRecentActionsController, peer: Peer, presentationData: PresentationData, interaction: ChatRecentActionsInteraction, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, PresentationContextType, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?) { init(context: AccountContext, controller: ChatRecentActionsController, peer: Peer, presentationData: PresentationData, interaction: ChatRecentActionsInteraction, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, PresentationContextType, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?) {
@ -774,18 +776,33 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
} }
private func openPeer(peer: EnginePeer, peekData: ChatPeekTimeout? = nil) { private func openPeer(peer: EnginePeer, peekData: ChatPeekTimeout? = nil) {
let peerSignal: Signal<Peer?, NoError> = .single(peer._asPeer()) let antiSpamBotConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in if peer.id == antiSpamBotConfiguration.antiSpamBotId {
if let strongSelf = self, let peer = peer { self.dismissAllTooltips()
if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), peekData: peekData, animated: true)) self.presentController(UndoOverlayController(presentationData: self.presentationData, content: .image(image: UIImage(bundleImageName: "Chat/AntiSpamTooltipIcon")!, title: self.presentationData.strings.Group_AdminLog_AntiSpamTitle, text: self.presentationData.strings.Group_AdminLog_AntiSpamText, undo: false), elevatedLayout: true, action: { [weak self] action in
} else { if let strongSelf = self {
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { if case .info = action {
strongSelf.pushController(infoController) let _ = strongSelf.getNavigationController()?.popViewController(animated: true)
return true
} }
} }
} return false
})) }), .window(.root), nil)
} else {
let peerSignal: Signal<Peer?, NoError> = .single(peer._asPeer())
self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, let peer = peer {
if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), peekData: peekData, animated: true))
} else {
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
strongSelf.pushController(infoController)
}
}
}
}))
}
} }
private func openPeerMention(_ name: String) { private func openPeerMention(_ name: String) {
@ -966,10 +983,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break break
case .theme: case .theme:
break break
#if ENABLE_WALLET
case .wallet:
break
#endif
case .settings: case .settings:
break break
case .premiumOffer: case .premiumOffer:
@ -1014,4 +1027,20 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.view.endEditing(true) self.view.endEditing(true)
self.present(controller, in: .window(.root))*/ self.present(controller, in: .window(.root))*/
} }
private func dismissAllTooltips() {
self.antiSpamTooltipController?.dismiss()
self.controller?.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
})
self.controller?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
return true
})
}
} }

View File

@ -45,7 +45,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
case adminsTitle(PresentationTheme, String) case adminsTitle(PresentationTheme, String)
case allAdmins(PresentationTheme, String, Bool) case allAdmins(PresentationTheme, String, Bool)
case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Int32, RenderedChannelParticipant, Bool) case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Int32, RenderedChannelParticipant, Bool, Bool)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
@ -68,7 +68,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
return .index(200) return .index(200)
case .allAdmins: case .allAdmins:
return .index(201) return .index(201)
case let .adminPeerItem(_, _, _, _, _, participant, _): case let .adminPeerItem(_, _, _, _, _, participant, _, _):
return .peer(participant.peer.id) return .peer(participant.peer.id)
} }
} }
@ -105,8 +105,8 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsParticipant, lhsChecked): case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsParticipant, lhsIsAntiSpam, lhsChecked):
if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsParticipant, rhsChecked) = rhs { if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsParticipant, rhsIsAntiSpam, rhsChecked) = rhs {
if lhsTheme !== rhsTheme { if lhsTheme !== rhsTheme {
return false return false
} }
@ -125,6 +125,9 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
if lhsParticipant != rhsParticipant { if lhsParticipant != rhsParticipant {
return false return false
} }
if lhsIsAntiSpam != rhsIsAntiSpam {
return false
}
if lhsChecked != rhsChecked { if lhsChecked != rhsChecked {
return false return false
} }
@ -169,9 +172,9 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
default: default:
return false return false
} }
case let .adminPeerItem(_, _, _, _, lhsIndex, _, _): case let .adminPeerItem(_, _, _, _, lhsIndex, _, _, _):
switch rhs { switch rhs {
case let .adminPeerItem(_, _, _, _, rhsIndex, _, _): case let .adminPeerItem(_, _, _, _, rhsIndex, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
default: default:
return false return false
@ -198,13 +201,17 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleAllAdmins(value) arguments.toggleAllAdmins(value)
}) })
case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, checked): case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, isAntiSpam, checked):
let peerText: String var peerText: String = ""
switch participant.participant { if isAntiSpam {
peerText = strings.Group_Management_AntiSpamMagic
} else {
switch participant.participant {
case .creator: case .creator:
peerText = strings.Channel_Management_LabelOwner peerText = strings.Channel_Management_LabelOwner.lowercased()
case .member: case .member:
peerText = strings.ChatAdmins_AdminLabel.capitalized peerText = strings.ChatAdmins_AdminLabel.lowercased()
}
} }
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: { return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: {
arguments.toggleAdmin(participant.peer.id) arguments.toggleAdmin(participant.peer.id)
@ -247,7 +254,7 @@ private struct ChatRecentActionsFilterControllerState: Equatable {
} }
} }
private func channelRecentActionsFilterControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, peer: Peer, state: ChatRecentActionsFilterControllerState, participants: [RenderedChannelParticipant]?) -> [ChatRecentActionsFilterEntry] { private func channelRecentActionsFilterControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, peer: Peer, antiSpamBotId: PeerId?, state: ChatRecentActionsFilterControllerState, participants: [RenderedChannelParticipant]?) -> [ChatRecentActionsFilterEntry] {
var isGroup = true var isGroup = true
if let peer = peer as? TelegramChannel, case .broadcast = peer.info { if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false isGroup = false
@ -335,7 +342,7 @@ private func channelRecentActionsFilterControllerEntries(presentationData: Prese
} else { } else {
adminSelected = true adminSelected = true
} }
entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, participant, adminSelected)) entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, participant, participant.peer.id == antiSpamBotId, adminSelected))
index += 1 index += 1
} }
} }
@ -353,7 +360,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil) let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
let arguments = ChatRecentActionsFilterControllerArguments(context: context, toggleAllActions: { value in let arguments = ChatRecentActionsFilterControllerArguments(context: context, toggleAllActions: { value in
@ -429,12 +436,25 @@ public func channelRecentActionsFilterController(context: AccountContext, update
} }
actionsDisposable.add(membersDisposable) actionsDisposable.add(membersDisposable)
let antiSpamBotConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let antiSpamBotPeerPromise = Promise<RenderedChannelParticipant?>(nil)
if let antiSpamBotId = antiSpamBotConfiguration.antiSpamBotId {
antiSpamBotPeerPromise.set(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: antiSpamBotId))
|> map { peer in
if let peer = peer, case let .user(user) = peer {
return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: user)
} else {
return nil
}
})
}
var previousPeers: [RenderedChannelParticipant]? var previousPeers: [RenderedChannelParticipant]?
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let signal = combineLatest(presentationData, statePromise.get(), adminsPromise.get() |> deliverOnMainQueue) let signal = combineLatest(presentationData, statePromise.get(), adminsPromise.get(), antiSpamBotPeerPromise.get())
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, admins -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, admins, antiSpamBot -> (ItemListControllerState, (ItemListNodeState, Any)) in
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?() dismissImpl?()
}) })
@ -456,6 +476,9 @@ public func channelRecentActionsFilterController(context: AccountContext, update
var sortedAdmins: [RenderedChannelParticipant]? var sortedAdmins: [RenderedChannelParticipant]?
if let admins = admins { if let admins = admins {
sortedAdmins = admins.filter { $0.peer.id == context.account.peerId } + admins.filter({ $0.peer.id != context.account.peerId }) sortedAdmins = admins.filter { $0.peer.id == context.account.peerId } + admins.filter({ $0.peer.id != context.account.peerId })
if let antiSpamBot = antiSpamBot {
sortedAdmins?.insert(antiSpamBot, at: 0)
}
} else { } else {
sortedAdmins = nil sortedAdmins = nil
} }
@ -464,7 +487,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update
previousPeers = sortedAdmins previousPeers = sortedAdmins
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatAdmins_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatAdmins_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelRecentActionsFilterControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: peer, state: state, participants: sortedAdmins), style: .blocks, animateChanges: previous != nil && admins != nil && previous!.count >= sortedAdmins!.count) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelRecentActionsFilterControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: peer, antiSpamBotId: antiSpamBotConfiguration.antiSpamBotId, state: state, participants: sortedAdmins), style: .blocks, animateChanges: previous != nil && admins != nil && previous!.count >= sortedAdmins!.count)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -235,6 +235,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
}, deletePeerThread: { _, _ in }, deletePeerThread: { _, _ in
}, setPeerThreadStopped: { _, _, _ in }, setPeerThreadStopped: { _, _, _ in
}, setPeerThreadPinned: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in
}, setPeerThreadHidden: { _, _, _ in
}, updatePeerGrouping: { _, _ in }, updatePeerGrouping: { _, _ in
}, togglePeerMarkedUnread: { _, _ in }, togglePeerMarkedUnread: { _, _ in
}, toggleArchivedFolderHiddenByDefault: { }, toggleArchivedFolderHiddenByDefault: {

View File

@ -1215,9 +1215,7 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag
return true return true
} else if let peer = peer as? TelegramChannel { } else if let peer = peer as? TelegramChannel {
if peer.flags.contains(.isForum), let threadData = threadData { if peer.flags.contains(.isForum), let threadData = threadData {
if chatLocation.threadId == 1 { if peer.flags.contains(.isCreator) {
return false
} else if peer.flags.contains(.isCreator) {
return true return true
} else if threadData.isOwnedByMe { } else if threadData.isOwnedByMe {
return true return true

View File

@ -381,7 +381,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
} }
var removedPhotoResourceIds = Set<String>() var removedPhotoResourceIds = Set<String>()
func update(peer: Peer?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { func update(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) {
if let peer = peer { if let peer = peer {
let previousItem = self.item let previousItem = self.item
var item = item var item = item
@ -422,7 +422,9 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
self.iconView = iconView self.iconView = iconView
} }
let content: EmojiStatusComponent.Content let content: EmojiStatusComponent.Content
if let iconFileId = threadInfo.icon { if threadId == 1 {
content = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(theme))
} else if let iconFileId = threadInfo.icon {
content = .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: avatarSize, height: avatarSize), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .forever) content = .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: avatarSize, height: avatarSize), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .forever)
} else { } else {
content = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: avatarSize, height: avatarSize)) content = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: avatarSize, height: avatarSize))
@ -886,7 +888,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
let isReady = Promise<Bool>() let isReady = Promise<Bool>()
var arguments: (Peer?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)? var arguments: (Peer?, Int64?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)?
var item: PeerInfoAvatarListItem? var item: PeerInfoAvatarListItem?
var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)? var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)?
@ -945,14 +947,14 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = items.first strongSelf.item = items.first
strongSelf.itemsUpdated?(items) strongSelf.itemsUpdated?(items)
if let (peer, threadInfo, theme, avatarSize, isExpanded) = strongSelf.arguments { if let (peer, threadId, threadInfo, theme, avatarSize, isExpanded) = strongSelf.arguments {
strongSelf.avatarContainerNode.update(peer: peer, threadInfo: threadInfo, item: strongSelf.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: strongSelf.isSettings) strongSelf.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: strongSelf.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: strongSelf.isSettings)
} }
} }
} }
self.pinchSourceNode.activate = { [weak self] sourceNode in self.pinchSourceNode.activate = { [weak self] sourceNode in
guard let strongSelf = self, let (_, _, _, _, isExpanded) = strongSelf.arguments, isExpanded else { guard let strongSelf = self, let (_, _, _, _, _, isExpanded) = strongSelf.arguments, isExpanded else {
return return
} }
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: { let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
@ -971,11 +973,11 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
} }
} }
func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) {
self.arguments = (peer, threadInfo, theme, avatarSize, isExpanded) self.arguments = (peer, threadId, threadInfo, theme, avatarSize, isExpanded)
self.pinchSourceNode.update(size: size, transition: transition) self.pinchSourceNode.update(size: size, transition: transition)
self.pinchSourceNode.frame = CGRect(origin: CGPoint(), size: size) self.pinchSourceNode.frame = CGRect(origin: CGPoint(), size: size)
self.avatarContainerNode.update(peer: peer, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings) self.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings)
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -2953,7 +2955,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
}) })
} }
self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition) self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, threadId: self.forumTopicThreadId, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition)
self.editingContentNode.avatarNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) self.editingContentNode.avatarNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing)
self.avatarOverlayNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) self.avatarOverlayNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing)
if additive { if additive {

View File

@ -2907,7 +2907,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if case let .replyThread(message) = strongSelf.chatLocation { if case let .replyThread(message) = strongSelf.chatLocation {
let threadId = Int64(message.messageId.id) let threadId = Int64(message.messageId.id)
if let threadInfo = strongSelf.data?.threadData?.info { if let threadInfo = strongSelf.data?.threadData?.info {
let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(topic: threadInfo)) let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(threadId: threadId, threadInfo: threadInfo))
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
let context = strongSelf.context let context = strongSelf.context
controller.completion = { [weak controller] title, fileId in controller.completion = { [weak controller] title, fileId in

View File

@ -1377,68 +1377,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return ChatMessageDateHeader(timestamp: timestamp, scheduled: false, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), controllerInteraction: nil, context: context) return ChatMessageDateHeader(timestamp: timestamp, scheduled: false, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), controllerInteraction: nil, context: context)
} }
#if ENABLE_WALLET
public func openWallet(context: AccountContext, walletContext: OpenWalletContext, present: @escaping (ViewController) -> Void) {
guard let storedContext = context.tonContext else {
return
}
let _ = (combineLatest(queue: .mainQueue(),
WalletStorageInterfaceImpl(postbox: context.account.postbox).getWalletRecords(),
storedContext.keychain.encryptionPublicKey(),
context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
)
|> deliverOnMainQueue).start(next: { wallets, currentPublicKey, preferences in
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
let walletConfiguration = WalletConfiguration.with(appConfiguration: appConfiguration)
guard let config = walletConfiguration.config, let blockchainName = walletConfiguration.blockchainName else {
return
}
let tonContext = storedContext.context(config: config, blockchainName: blockchainName, enableProxy: !walletConfiguration.disableProxy)
if wallets.isEmpty {
if case .send = walletContext {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = textAlertController(context: context, title: presentationData.strings.Conversation_WalletRequiredTitle, text: presentationData.strings.Conversation_WalletRequiredText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Conversation_WalletRequiredNotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Conversation_WalletRequiredSetup, action: { [weak self] in
self?.openWallet(context: context, walletContext: .generic, present: present)
})])
present(controller)
} else {
if let _ = currentPublicKey {
present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .intro, walletCreatedPreloadState: nil))
} else {
present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .secureStorageNotAvailable, walletCreatedPreloadState: nil))
}
}
} else {
let walletInfo = wallets[0].info
let exportCompleted = wallets[0].exportCompleted
if let currentPublicKey = currentPublicKey {
if currentPublicKey == walletInfo.encryptedSecret.publicKey {
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance)
|> deliverOnMainQueue).start(next: { address in
switch walletContext {
case .generic:
if exportCompleted {
present(WalletInfoScreen(context: WalletContextImpl(context: context, tonContext: tonContext), walletInfo: walletInfo, address: address, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild))
} else {
present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .created(walletInfo, nil), walletCreatedPreloadState: nil))
}
case let .send(address, amount, comment):
present(walletSendScreen(context: WalletContextImpl(context: context, tonContext: tonContext), randomId: Int64.random(in: Int64.min ... Int64.max), walletInfo: walletInfo, address: address, amount: amount, comment: comment))
}
})
} else {
present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .secureStorageReset(.changed), walletCreatedPreloadState: nil))
}
} else {
present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .secureStorageReset(.notAvailable), walletCreatedPreloadState: nil))
}
}
})
}
#endif
public func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) { public func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = legacyWallpaperPicker(context: context, presentationData: presentationData).start(next: { generator in let _ = legacyWallpaperPicker(context: context, presentationData: presentationData).start(next: { generator in

View File

@ -195,10 +195,10 @@ public final class ChatTextInputTextUrlAttribute: NSObject {
public final class ChatTextInputTextCustomEmojiAttribute: NSObject { public final class ChatTextInputTextCustomEmojiAttribute: NSObject {
public let interactivelySelectedFromPackId: ItemCollectionId? public let interactivelySelectedFromPackId: ItemCollectionId?
public let fileId: Int64 public let fileId: Int64
public let topicInfo: EngineMessageHistoryThread.Info? public let topicInfo: (Int64, EngineMessageHistoryThread.Info)?
public let file: TelegramMediaFile? public let file: TelegramMediaFile?
public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, topicInfo: EngineMessageHistoryThread.Info? = nil) { public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, topicInfo: (Int64, EngineMessageHistoryThread.Info)? = nil) {
self.interactivelySelectedFromPackId = interactivelySelectedFromPackId self.interactivelySelectedFromPackId = interactivelySelectedFromPackId
self.fileId = fileId self.fileId = fileId
self.file = file self.file = file

View File

@ -39,7 +39,7 @@ public enum UndoOverlayContent {
case mediaSaved(text: String) case mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: String) case paymentSent(currencyValue: String, itemTitle: String)
case inviteRequestSent(title: String, text: String) case inviteRequestSent(title: String, text: String)
case image(image: UIImage, text: String) case image(image: UIImage, title: String?, text: String, undo: Bool)
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?) case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)
case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?) case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?)
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?) case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)

View File

@ -861,7 +861,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else { } else {
displayUndo = false displayUndo = false
} }
case let .image(image, text): case let .image(image, title, text, undo):
self.avatarNode = nil self.avatarNode = nil
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode?.clipsToBounds = true self.iconNode?.clipsToBounds = true
@ -872,9 +872,26 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.iconCheckNode = nil self.iconCheckNode = nil
self.animationNode = nil self.animationNode = nil
self.animatedStickerNode = nil self.animatedStickerNode = nil
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) if let title = title {
displayUndo = true self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.originalRemainingSeconds = 5 } else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
displayUndo = undo
self.originalRemainingSeconds = undo ? 5 : 3
if text.contains("](") {
isUserInteractionEnabled = true
}
case let .peers(context, peers, title, text, customUndoText): case let .peers(context, peers, title, text, customUndoText):
self.avatarNode = nil self.avatarNode = nil
let multiAvatarsNode = AnimatedAvatarSetNode() let multiAvatarsNode = AnimatedAvatarSetNode()
@ -1084,8 +1101,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.content = content self.content = content
switch content { switch content {
case let .image(image, text): case let .image(image, title, text, _):
self.iconNode?.image = image self.iconNode?.image = image
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
default: default:
break break