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.";
"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 enum CreateGroupMode {
@ -839,73 +832,6 @@ public enum PremiumIntroSource {
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 {
}
@ -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 strings = presentationData.strings
@ -512,24 +512,27 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
var items: [ContextMenuItem] = []
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
f(.default)
let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId)
|> deliverOnMainQueue).start(error: { error in
switch error {
case let .limitReached(count):
if let chatListController = chatListController {
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))
if let isClosed = isClosed, isClosed {
} else {
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
f(.default)
let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId)
|> deliverOnMainQueue).start(error: { error in
switch error {
case let .limitReached(count):
if let chatListController = chatListController {
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

View File

@ -1377,6 +1377,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
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
if let strongSelf = self {
@ -1674,7 +1680,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
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)
strongSelf.presentInGlobalOverlay(contextController)
case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
case let .peer(_, peer, threadInfo, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
switch item.index {
case .chatList:
if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) {
@ -1686,7 +1692,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
chatController.canReadHistory.set(false)
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)
} else {
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)
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)
}
}
@ -3456,7 +3462,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.chatListDisplayNode.containerNode.updateState { state in
var state = state
if updatedValue {
state.archiveShouldBeTemporaryRevealed = false
state.hiddenItemShouldBeTemporaryRevealed = false
}
state.peerIdWithRevealedOptions = nil
return state
@ -3920,6 +3926,35 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
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) {
guard let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else {
completion(false)

View File

@ -183,7 +183,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
let timestamp1: Int32 = 100000
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
}, 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()
}, present: { _ in }, openForumThread: { _, _ in })
interaction.isInlineMode = isInlineMode
@ -532,6 +532,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
previousItemNode.listNode.deletePeerThread = nil
previousItemNode.listNode.setPeerThreadStopped = nil
previousItemNode.listNode.setPeerThreadPinned = nil
previousItemNode.listNode.setPeerThreadHidden = nil
previousItemNode.listNode.peerSelected = nil
previousItemNode.listNode.groupSelected = nil
previousItemNode.listNode.updatePeerGrouping = nil
@ -576,6 +577,9 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
itemNode.listNode.setPeerThreadPinned = { [weak self] peerId, threadId, isPinned in
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
self?.peerSelected?(peerId, threadId, animated, activateInput, promoInfo)
}
@ -637,6 +641,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)?
var setPeerThreadStopped: ((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 groupSelected: ((EngineChatList.Group) -> Void)?
var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)?

View File

@ -923,7 +923,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})
})))
} 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
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.contextMenu.primaryColor)
}, 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))
case .forum:
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)
} else {
index = .chatList( EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index))
@ -1522,7 +1522,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
for thread in allAndFoundThreads {
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
}
}
@ -1842,6 +1842,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, deletePeerThread: { _, _ in
}, setPeerThreadStopped: { _, _, _ in
}, setPeerThreadPinned: { _, _, _ in
}, setPeerThreadHidden: { _, _, _ in
}, updatePeerGrouping: { _, _ in
}, togglePeerMarkedUnread: { _, _ in
}, toggleArchivedFolderHiddenByDefault: {
@ -3067,7 +3068,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
var peers: [EnginePeer.Id: EnginePeer] = [:]
peers[peer1.id] = peer1
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()
}, present: { _ in }, openForumThread: { _, _ in })

View File

@ -34,12 +34,14 @@ public enum ChatListItemContent {
public var info: EngineMessageHistoryThread.Info
public var isOwnedByMe: 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.info = info
self.isOwnedByMe = isOwnedByMe
self.isClosed = isClosed
self.isHidden = isHidden
}
}
@ -333,7 +335,7 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr
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] = []
if !isEditing {
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 !isEditing {
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 {
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)
}
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)
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 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
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))
} else {
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?) {
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 {
makeExistingTopicLayouts[topicId] = TopicItemNode.asyncLayout(topicNode)
}
@ -686,7 +699,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
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))
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 {
isAccountPeer = true
}
if !isPeerGroup && !isAccountPeer {
if !isPeerGroup && !isAccountPeer && threadInfo == nil {
if displayAsMessage {
switch item.content {
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
@ -2223,22 +2236,22 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if item.enableContextActions {
if case .forum = item.chatListLocation {
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 {
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 {
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)
}
peerLeftRevealOptions = []
@ -2312,6 +2325,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.cachedChatListText = chatListText
strongSelf.cachedChatListSearchResult = chatListSearchResult
strongSelf.onlineIsVoiceChat = onlineIsVoiceChat
strongSelf.clipsToBounds = true
if case .groupReference = item.content {
strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0)
@ -2480,7 +2495,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
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))
} else {
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)
case RevealOptionKey.unpin.rawValue:
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:
break
}

View File

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

View File

@ -67,7 +67,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
hasFailedMessages: Bool,
isContact: Bool,
forumTopicData: EngineChatList.ForumTopicData?,
topForumTopicItems: [EngineChatList.ForumTopicData]
topForumTopicItems: [EngineChatList.ForumTopicData],
revealed: Bool
)
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)
@ -78,7 +79,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
switch self {
case .HeaderEntry:
return .index(.chatList(.absoluteUpperBound))
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return .index(index)
case let .HoleEntry(holeIndex, _):
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
@ -95,7 +96,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
switch self {
case .HeaderEntry:
return .Header
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
switch index {
case let .chatList(index):
return .PeerId(index.messageIndex.id.peerId.toInt64())
@ -125,9 +126,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
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 {
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 {
return false
}
@ -228,6 +229,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
if lhsTopForumTopicItems != rhsTopForumTopicItems {
return false
}
if lhsRevealed != rhsRevealed {
return false
}
return true
default:
return false
@ -394,10 +398,32 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
var threadInfo: ChatListItemContent.ThreadInfo?
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 {
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
@ -432,7 +458,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
hasFailedMessages: false,
isContact: false,
forumTopicData: nil,
topForumTopicItems: []
topForumTopicItems: [],
revealed: false
))
if foundPinningIndex != 0 {
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 {
if !filteredAdditionalItemEntries.isEmpty {
for item in filteredAdditionalItemEntries.reversed() {
@ -476,7 +525,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
isRemovedFromTotalUnreadCount: item.item.isMuted,
draftState: draftState,
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,
hasUnseenMentions: item.item.hasUnseenMentions,
hasUnseenReactions: item.item.hasUnseenReactions,
@ -488,7 +537,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
hasFailedMessages: item.item.hasFailed,
isContact: item.item.isContact,
forumTopicData: item.item.forumTopicData,
topForumTopicItems: item.item.topForumTopicItems
topForumTopicItems: item.item.topForumTopicItems,
revealed: threadId == 1 && state.hiddenItemShouldBeTemporaryRevealed
))
if pinningIndex != 0 {
pinningIndex -= 1
@ -508,7 +558,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
message: groupReference.topMessage,
editing: state.editing,
unreadCount: groupReference.unreadCount,
revealed: state.archiveShouldBeTemporaryRevealed,
revealed: state.hiddenItemShouldBeTemporaryRevealed,
hiddenByDefault: hideArchivedFolderByDefault
))
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)
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
@ -863,20 +867,38 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
let animationInContentDistance: CGFloat
let animationInContentYDistance: CGFloat
let currentContentScreenFrame: CGRect
if let contentNode = contentNode {
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.animateBoundsOriginYAdditive(from: animateClippingFromContentAreaInScreenSpace.minY, to: 0.0, duration: 0.2)
}
currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.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(
from: -animationInContentDistance as NSNumber, to: 0.0 as NSNumber,
from: -animationInContentYDistance as NSNumber, to: 0.0 as NSNumber,
keyPath: "position.y",
duration: duration,
delay: 0.0,
@ -885,7 +907,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
additive: true
)
} else {
animationInContentDistance = 0.0
animationInContentYDistance = 0.0
currentContentScreenFrame = contentRect
}
@ -926,7 +948,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
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(
from: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)),
to: NSValue(cgPoint: CGPoint()),
@ -939,7 +961,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
)
if let reactionContextNode = self.reactionContextNode {
let reactionsPositionDeltaYDistance = -animationInContentDistance
let reactionsPositionDeltaYDistance = -animationInContentYDistance
reactionContextNode.layer.animateSpring(
from: NSValue(cgPoint: CGPoint(x: 0.0, y: reactionsPositionDeltaYDistance)),
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 animationInContentDistance: CGFloat
let animationInContentYDistance: CGFloat
switch result {
case .default, .custom:
animationInContentDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY
animationInContentYDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY
case .dismissWithoutContent:
animationInContentDistance = 0.0
animationInContentYDistance = 0.0
if let contentNode = contentNode {
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 {
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
contentNode.offsetContainerNode.layer.animate(
from: animationInContentDistance as NSNumber,
from: animationInContentYDistance as NSNumber,
to: 0.0 as NSNumber,
keyPath: "position.y",
timingFunction: timingFunction,
@ -1132,7 +1172,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
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(
from: NSValue(cgPoint: CGPoint()),
to: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)),

View File

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

View File

@ -13,6 +13,7 @@ public enum ItemListSwitchItemNodeType {
public class ItemListSwitchItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let icon: UIImage?
let title: String
let value: Bool
let type: ItemListSwitchItemNodeType
@ -27,8 +28,9 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
let activatedWhileDisabled: () -> Void
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.icon = icon
self.title = title
self.value = value
self.type = type
@ -120,6 +122,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let iconNode: ASImageNode
private let titleNode: TextNode
private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl
private let switchGestureNode: ASDisplayNode
@ -147,6 +150,10 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displaysAsynchronously = false
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
switch type {
@ -206,11 +213,15 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
var updatedTheme: PresentationTheme?
if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
}
var updateIcon = false
if currentItem?.icon != item.icon {
updateIcon = true
}
switch item.style {
case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
@ -224,6 +235,11 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
insets = itemListNeighborsGroupedInsets(neighbors, params)
}
var leftInset = 16.0 + params.leftInset
if let _ = item.icon {
leftInset += 43.0
}
if item.disableLeadingInset {
insets.top = 0.0
insets.bottom = 0.0
@ -260,6 +276,20 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
}
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
if animated {
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
@ -301,8 +331,6 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
let _ = titleApply()
let leftInset = 16.0 + params.leftInset
switch item.style {
case .plain:
if strongSelf.backgroundNode.supernode != nil {
@ -345,7 +373,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
let bottomStripeInset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = 16.0 + params.leftInset
bottomStripeInset = leftInset
strongSelf.bottomStripeNode.isHidden = false
default:
bottomStripeInset = 0.0

View File

@ -1356,13 +1356,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
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 {
var elevatedLayout = true
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
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 {
return true
}

View File

@ -24,14 +24,16 @@ private final class ChannelAdminsControllerArguments {
let removeAdmin: (PeerId) -> Void
let addAdmin: () -> 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.openRecentActions = openRecentActions
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.removeAdmin = removeAdmin
self.addAdmin = addAdmin
self.openAdmin = openAdmin
self.updateAntiSpamEnabled = updateAntiSpamEnabled
}
}
@ -47,6 +49,8 @@ private enum ChannelAdminsEntryStableId: Hashable {
private enum ChannelAdminsEntry: ItemListNodeEntry {
case recentActions(PresentationTheme, String)
case antiSpam(PresentationTheme, String, Bool)
case antiSpamInfo(PresentationTheme, String)
case adminsHeader(PresentationTheme, String)
case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Bool, Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool, Bool)
@ -55,7 +59,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .recentActions:
case .recentActions, .antiSpam, .antiSpamInfo:
return ChannelAdminsSection.administration.rawValue
case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo:
return ChannelAdminsSection.admins.rawValue
@ -66,6 +70,10 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
switch self {
case .recentActions:
return .index(0)
case .antiSpam:
return .index(1)
case .antiSpamInfo:
return .index(2)
case .adminsHeader:
return .index(3)
case .addAdmin:
@ -85,6 +93,18 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
} else {
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):
if case let .adminsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -146,16 +166,30 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
switch lhs {
case .recentActions:
return true
case .adminsHeader:
case .antiSpam:
switch rhs {
case .recentActions:
return false
default:
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, _, _, _, _):
switch rhs {
case .recentActions, .adminsHeader, .addAdmin:
case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin:
return false
case let .adminPeerItem(_, _, _, _, _, rhsIndex, _, _, _, _):
return index < rhsIndex
@ -164,7 +198,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
}
case .addAdmin:
switch rhs {
case .recentActions, .adminsHeader, .addAdmin:
case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin:
return false
default:
return true
@ -178,9 +212,15 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
let arguments = arguments as! ChannelAdminsControllerArguments
switch self {
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()
})
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):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
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 {
return []
}
var entries: [ChannelAdminsEntry] = []
if let peer = view.peers[view.peerId] as? TelegramChannel {
var isGroup = false
if case .group = peer.info {
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 {
@ -492,8 +534,31 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
let upgradeDisposable = MetaDisposable()
actionsDisposable.add(upgradeDisposable)
let updateAntiSpamDisposable = MetaDisposable()
actionsDisposable.add(updateAntiSpamDisposable)
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)?
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
}, 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)
@ -700,9 +771,19 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
var previousPeers: [RenderedChannelParticipant]?
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
|> map { presentationData, state, view, admins -> (ItemListControllerState, (ItemListNodeState, Any)) in
|> map { presentationData, state, view, admins, antiSpamEnabled -> (ItemListControllerState, (ItemListNodeState, Any)) in
let peerId = view.peerId
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 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))
} |> 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] = []
entries.append(.alternativeHeader(presentationData.theme, presentationData.strings.LogoutOptions_AlternativeOptionsSection))
if canAddAccounts {
@ -257,19 +257,12 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
])
presentControllerImpl?(alertController, nil)
})
#if ENABLE_WALLET
let hasWallets = context.hasWallets
#else
let hasWallets: Signal<Bool, NoError> = .single(false)
#endif
let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData,
context.sharedContext.accountManager.accessChallengeData(),
hasWallets
context.sharedContext.accountManager.accessChallengeData()
)
|> 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: {
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 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))
}

View File

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

View File

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

View File

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

View File

@ -219,7 +219,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
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
}, 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
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in })

View File

@ -839,7 +839,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
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
}, 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
gesture?.cancel()
}, present: { _ in

View File

@ -363,7 +363,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
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
}, 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
gesture?.cancel()
}, present: { _ in

View File

@ -89,7 +89,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months))
case let .messageActionTopicCreate(_, title, iconColor, 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] = []
if let title = title {
components.append(.title(title))
@ -100,6 +100,9 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
if let closed = closed {
components.append(.isClosed(closed == .boolTrue))
}
if let hidden = hidden {
components.append(.isHidden(hidden == .boolTrue))
}
return TelegramMediaAction(action: .topicEdited(components: components))
}
}

View File

@ -65,6 +65,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
case maxKnownMessageId
case maxOutgoingReadId
case isClosed
case isHidden
case notificationSettings
}
@ -77,6 +78,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
public var maxKnownMessageId: Int32
public var maxOutgoingReadId: Int32
public var isClosed: Bool
public var isHidden: Bool
public var notificationSettings: TelegramPeerNotificationSettings
public init(
@ -89,6 +91,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
maxKnownMessageId: Int32,
maxOutgoingReadId: Int32,
isClosed: Bool,
isHidden: Bool,
notificationSettings: TelegramPeerNotificationSettings
) {
self.creationDate = creationDate
@ -100,6 +103,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
self.maxKnownMessageId = maxKnownMessageId
self.maxOutgoingReadId = maxOutgoingReadId
self.isClosed = isClosed
self.isHidden = isHidden
self.notificationSettings = notificationSettings
}
@ -115,6 +119,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
self.maxKnownMessageId = try container.decode(Int32.self, forKey: .maxKnownMessageId)
self.maxOutgoingReadId = try container.decode(Int32.self, forKey: .maxOutgoingReadId)
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)
}
@ -130,6 +135,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
try container.encode(self.maxKnownMessageId, forKey: .maxKnownMessageId)
try container.encode(self.maxOutgoingReadId, forKey: .maxOutgoingReadId)
try container.encode(self.isClosed, forKey: .isClosed)
try container.encode(self.isHidden, forKey: .isHidden)
try container.encode(self.notificationSettings, forKey: .notificationSettings)
}
}
@ -285,14 +291,16 @@ func _internal_editForumChannelTopic(account: Account, peerId: PeerId, threadId:
}
var flags: Int32 = 0
flags |= (1 << 0)
flags |= (1 << 1)
if threadId != 1 {
flags |= (1 << 1)
}
return account.network.request(Api.functions.channels.editForumTopic(
flags: flags,
channel: inputChannel,
topicId: Int32(clamping: threadId),
title: title,
iconEmojiId: iconFileId ?? 0,
iconEmojiId: threadId == 1 ? nil : iconFileId ?? 0,
closed: 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 {
case generic
case limitReached(Int)
@ -557,6 +614,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId,
isClosed: (flags & (1 << 2)) != 0,
isHidden: (flags & (1 << 6)) != 0,
notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings)
)

View File

@ -1760,6 +1760,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, state: AccountMutab
maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId,
isClosed: (flags & (1 << 2)) != 0,
isHidden: (flags & (1 << 6)) != 0,
notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings)
),
topMessageId: topMessage,
@ -1859,6 +1860,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, ids: [MessageId]) -
maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId,
isClosed: (flags & (1 << 2)) != 0,
isHidden: (flags & (1 << 6)) != 0,
notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings)
)
if let entry = StoredMessageHistoryThreadInfo(data) {
@ -1982,6 +1984,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe
maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId,
isClosed: (flags & (1 << 2)) != 0,
isHidden: (flags & (1 << 6)) != 0,
notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings)
),
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)
case let .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 canChangePeerGeoLocation = CachedChannelFlags(rawValue: 1 << 5)
public static let canDeleteHistory = CachedChannelFlags(rawValue: 1 << 6)
public static let antiSpamEnabled = CachedChannelFlags(rawValue: 1 << 7)
}
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {

View File

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

View File

@ -28,6 +28,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case title(String)
case iconFileId(Int64?)
case isClosed(Bool)
case isHidden(Bool)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("_t", orElse: 0) {
@ -37,6 +38,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
self = .iconFileId(decoder.decodeOptionalInt64ForKey("fileId"))
case 2:
self = .isClosed(decoder.decodeBoolForKey("isClosed", orElse: false))
case 3:
self = .isHidden(decoder.decodeBoolForKey("isHidden", orElse: false))
default:
assertionFailure()
self = .title("")
@ -58,6 +61,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case let .isClosed(isClosed):
encoder.encodeInt32(2, forKey: "_t")
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 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> {
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> {
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)
}
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> {
return self.account.postbox.transaction { transaction -> Void in
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 {
channelFlags.insert(.canDeleteHistory)
}
if (flags2 & Int32(1 << 1)) != 0 {
channelFlags.insert(.antiSpamEnabled)
}
let sendAsPeerId = defaultSendAs?.peerId

View File

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

View File

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

View File

@ -212,7 +212,7 @@ public struct PresentationResourcesChat {
})?.stretchableImage(withLeftCapWidth: 1, topCapHeight: 4)
})
}
public static func chatInfoItemBackgroundImageWithoutWallpaper(_ theme: PresentationTheme) -> UIImage? {
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)
@ -344,7 +344,7 @@ public struct PresentationResourcesChat {
]
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
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())
}
})
@ -420,7 +420,7 @@ public struct PresentationResourcesChat {
return generateInputPanelButtonStrokeImage(color: theme.chat.inputButtonPanel.buttonStrokeColor, offset: 1.0)
})
}
public static func chatInputTextFieldBackgroundImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatInputTextFieldBackgroundImage.rawValue, { theme in
let diameter: CGFloat = 35.0
@ -428,7 +428,7 @@ public struct PresentationResourcesChat {
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(theme.chat.inputPanel.panelBackgroundColor.cgColor)
context.fill(CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter))
context.setBlendMode(.clear)
context.setFillColor(UIColor.clear.cgColor)
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? {
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 {
@ -1247,13 +1247,13 @@ public struct PresentationResourcesChat {
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? {
return theme.image(PresentationResourceKey.chatFreeNavigateButtonIcon.rawValue, { _ in
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? {
return theme.image(PresentationResourceKey.chatFreeShareButtonIcon.rawValue, { _ in
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)
})
}
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)
})
}
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 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
let bounds = CGRect(origin: CGPoint(), size: size)

View File

@ -682,12 +682,49 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case let .topicCreated(title, iconColor, iconFileId):
if forForumOverview {
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 {
attributedString = NSAttributedString(string: strings.Notification_ForumTopicCreated, font: titleFont, textColor: primaryTextColor)
}
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 {
case let .isClosed(isClosed):
return isClosed
@ -706,9 +743,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
maybeFileId = info.icon ?? 0
}
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 {
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 {
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: [
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 {
attributedString = NSAttributedString(string: strings.Notification_ForumTopicRenamed(title).string, font: titleFont, textColor: primaryTextColor)
@ -779,9 +816,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
title = info.title
}
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 {
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:

View File

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

View File

@ -261,23 +261,33 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
}
}
private func updateTopicInfo(topicInfo: EngineMessageHistoryThread.Info) {
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
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.iconColor] ?? generateTopicColors(topicInfo.iconColor)
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)) {
self.contents = image.cgImage
private func updateTopicInfo(topicInfo: (Int64, EngineMessageHistoryThread.Info)) {
if topicInfo.0 == 1 {
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 {
context.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
})
self.contents = image?.cgImage
} else {
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
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 accentColor: UIColor
let placeholderColor: UIColor
let isGeneral: Bool
let fileId: Int64
let iconColor: Int32
let text: String
@ -36,6 +37,7 @@ private final class TitleFieldComponent: Component {
textColor: UIColor,
accentColor: UIColor,
placeholderColor: UIColor,
isGeneral: Bool,
fileId: Int64,
iconColor: Int32,
text: String,
@ -47,6 +49,7 @@ private final class TitleFieldComponent: Component {
self.textColor = textColor
self.accentColor = accentColor
self.placeholderColor = placeholderColor
self.isGeneral = isGeneral
self.fileId = fileId
self.iconColor = iconColor
self.text = text
@ -68,6 +71,9 @@ private final class TitleFieldComponent: Component {
if lhs.placeholderColor != rhs.placeholderColor {
return false
}
if lhs.isGeneral != rhs.isGeneral {
return false
}
if lhs.fileId != rhs.fileId {
return false
}
@ -140,7 +146,10 @@ private final class TitleFieldComponent: Component {
self.state = state
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))
self.iconButton.isUserInteractionEnabled = true
} else {
@ -413,6 +422,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
private var defaultIconFilesDisposable: Disposable?
private var defaultIconFiles = Set<Int64>()
let isGeneral: Bool
var title: String
var fileId: Int64
var iconColor: Int32
@ -427,11 +437,13 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
switch mode {
case .create:
self.isGeneral = false
self.title = ""
self.fileId = 0
self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0
case let .edit(info):
case let .edit(threadId, info):
self.isGeneral = threadId == 1
self.title = info.title
self.fileId = info.icon ?? 0
self.iconColor = info.iconColor
@ -638,6 +650,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
textColor: environment.theme.list.itemPrimaryTextColor,
accentColor: environment.theme.list.itemAccentColor,
placeholderColor: environment.theme.list.disclosureArrowColor,
isGeneral: state.isGeneral,
fileId: state.fileId,
iconColor: state.iconColor,
text: state.title,
@ -658,124 +671,128 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
contentHeight += titleBackground.size.height + sectionSpacing
let iconHeader = iconHeader.update(
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)
if case let .edit(threadId, _) = context.component.mode, threadId == 1 {
let iconSelector = iconSelector.update(
component: TopicIconSelectionComponent(
theme: environment.theme,
strings: environment.strings,
deviceMetrics: environment.deviceMetrics,
emojiContent: emojiContent,
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
separatorColor: environment.theme.list.blocksBackgroundColor
} else {
let iconHeader = iconHeader.update(
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
),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: availableHeight),
transition: context.transition
)
context.add(iconSelector
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconSelector.size.height / 2.0))
.cornerRadius(10.0)
.clipsToBounds(true)
context.add(iconBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconBackground.size.height / 2.0))
)
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
}
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
let _ = (accountContext.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { views in
guard let view = views.views[viewKey] as? OrderedItemListView else {
if let emojiContent = state.emojiContent {
let availableHeight = context.availableSize.height - contentHeight - max(bottomInset, environment.inputHeight)
let iconSelector = iconSelector.update(
component: TopicIconSelectionComponent(
theme: environment.theme,
strings: environment.strings,
deviceMetrics: environment.deviceMetrics,
emojiContent: emojiContent,
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
separatorColor: environment.theme.list.blocksBackgroundColor
),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: availableHeight),
transition: context.transition
)
context.add(iconSelector
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconSelector.size.height / 2.0))
.cornerRadius(10.0)
.clipsToBounds(true)
)
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
}
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredEmojiPack.info.id == collectionId {
// if let strongSelf = self {
// strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId))
// }
let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
break
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
let _ = (accountContext.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { views in
guard let view = views.views[viewKey] as? OrderedItemListView else {
return
}
}
})
},
clearGroup: { _ in
},
pushController: { c in
},
presentController: { c in
},
presentGlobalOverlayController: { c in
},
navigationController: {
return nil
},
requestUpdate: { _ in
},
updateSearchQuery: { _, _ in
},
chatPeerId: nil,
peekBehavior: nil,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
useOpaqueTheme: true
)
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredEmojiPack.info.id == collectionId {
// if let strongSelf = self {
// strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId))
// }
let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
break
}
}
})
},
clearGroup: { _ in
},
pushController: { c in
},
presentController: { c in
},
presentGlobalOverlayController: { c in
},
navigationController: {
return nil
},
requestUpdate: { _ in
},
updateSearchQuery: { _, _ in
},
chatPeerId: nil,
peekBehavior: nil,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
useOpaqueTheme: true
)
}
}
return context.availableSize
@ -788,7 +805,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
public enum Mode: Equatable {
case create
case edit(topic: EngineMessageHistoryThread.Info)
case edit(threadId: Int64, threadInfo: EngineMessageHistoryThread.Info)
}
private let context: AccountContext
@ -835,9 +852,9 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
case .create:
title = presentationData.strings.CreateTopic_CreateTitle
doneTitle = presentationData.strings.CreateTopic_Create
case let .edit(topic):
case let .edit(_, topic):
title = presentationData.strings.CreateTopic_EditTitle
doneTitle = presentationData.strings.Common_Done
doneTitle = presentationData.strings.Common_Done
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)
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))
} else {
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
for media in message.media {
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
}
break

View File

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

View File

@ -87,6 +87,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
private var adminsState: ChannelMemberListState?
private let banDisposables = DisposableDict<PeerId>()
private weak var antiSpamTooltipController: UndoOverlayController?
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?) {
@ -774,18 +776,33 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}
private func openPeer(peer: EnginePeer, peekData: ChatPeekTimeout? = nil) {
let peerSignal: Signal<Peer?, NoError> = .single(peer._asPeer())
self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, let peer = peer {
if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() {
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)
let antiSpamBotConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
if peer.id == antiSpamBotConfiguration.antiSpamBotId {
self.dismissAllTooltips()
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
if let strongSelf = self {
if case .info = action {
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) {
@ -966,10 +983,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break
case .theme:
break
#if ENABLE_WALLET
case .wallet:
break
#endif
case .settings:
break
case .premiumOffer:
@ -1014,4 +1027,20 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.view.endEditing(true)
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 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 {
switch self {
@ -68,7 +68,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
return .index(200)
case .allAdmins:
return .index(201)
case let .adminPeerItem(_, _, _, _, _, participant, _):
case let .adminPeerItem(_, _, _, _, _, participant, _, _):
return .peer(participant.peer.id)
}
}
@ -105,8 +105,8 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
} else {
return false
}
case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsParticipant, lhsChecked):
if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsParticipant, rhsChecked) = rhs {
case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsParticipant, lhsIsAntiSpam, lhsChecked):
if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsParticipant, rhsIsAntiSpam, rhsChecked) = rhs {
if lhsTheme !== rhsTheme {
return false
}
@ -125,6 +125,9 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
if lhsParticipant != rhsParticipant {
return false
}
if lhsIsAntiSpam != rhsIsAntiSpam {
return false
}
if lhsChecked != rhsChecked {
return false
}
@ -169,9 +172,9 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
default:
return false
}
case let .adminPeerItem(_, _, _, _, lhsIndex, _, _):
case let .adminPeerItem(_, _, _, _, lhsIndex, _, _, _):
switch rhs {
case let .adminPeerItem(_, _, _, _, rhsIndex, _, _):
case let .adminPeerItem(_, _, _, _, rhsIndex, _, _, _):
return lhsIndex < rhsIndex
default:
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
arguments.toggleAllAdmins(value)
})
case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, checked):
let peerText: String
switch participant.participant {
case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, isAntiSpam, checked):
var peerText: String = ""
if isAntiSpam {
peerText = strings.Group_Management_AntiSpamMagic
} else {
switch participant.participant {
case .creator:
peerText = strings.Channel_Management_LabelOwner
peerText = strings.Channel_Management_LabelOwner.lowercased()
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: {
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
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
@ -335,7 +342,7 @@ private func channelRecentActionsFilterControllerEntries(presentationData: Prese
} else {
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
}
}
@ -353,7 +360,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update
var dismissImpl: (() -> Void)?
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
let actionsDisposable = DisposableSet()
let arguments = ChatRecentActionsFilterControllerArguments(context: context, toggleAllActions: { value in
@ -429,12 +436,25 @@ public func channelRecentActionsFilterController(context: AccountContext, update
}
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]?
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
|> 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: {
dismissImpl?()
})
@ -456,6 +476,9 @@ public func channelRecentActionsFilterController(context: AccountContext, update
var sortedAdmins: [RenderedChannelParticipant]?
if let admins = admins {
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 {
sortedAdmins = nil
}
@ -464,7 +487,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update
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 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))
}

View File

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

View File

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

View File

@ -381,7 +381,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
}
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 {
let previousItem = self.item
var item = item
@ -422,7 +422,9 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
self.iconView = iconView
}
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)
} else {
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>()
var arguments: (Peer?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)?
var arguments: (Peer?, Int64?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)?
var item: PeerInfoAvatarListItem?
var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)?
@ -945,14 +947,14 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
if let strongSelf = self {
strongSelf.item = items.first
strongSelf.itemsUpdated?(items)
if let (peer, 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)
if let (peer, threadId, threadInfo, theme, avatarSize, isExpanded) = strongSelf.arguments {
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
guard let strongSelf = self, let (_, _, _, _, isExpanded) = strongSelf.arguments, isExpanded else {
guard let strongSelf = self, let (_, _, _, _, _, isExpanded) = strongSelf.arguments, isExpanded else {
return
}
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) {
self.arguments = (peer, threadInfo, theme, avatarSize, isExpanded)
func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) {
self.arguments = (peer, threadId, threadInfo, theme, avatarSize, isExpanded)
self.pinchSourceNode.update(size: size, transition: transition)
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? {
@ -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.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 {

View File

@ -2907,7 +2907,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if case let .replyThread(message) = strongSelf.chatLocation {
let threadId = Int64(message.messageId.id)
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
let context = strongSelf.context
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)
}
#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) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
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 let interactivelySelectedFromPackId: ItemCollectionId?
public let fileId: Int64
public let topicInfo: EngineMessageHistoryThread.Info?
public let topicInfo: (Int64, EngineMessageHistoryThread.Info)?
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.fileId = fileId
self.file = file

View File

@ -39,7 +39,7 @@ public enum UndoOverlayContent {
case mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: 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 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?)

View File

@ -861,7 +861,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else {
displayUndo = false
}
case let .image(image, text):
case let .image(image, title, text, undo):
self.avatarNode = nil
self.iconNode = ASImageNode()
self.iconNode?.clipsToBounds = true
@ -872,9 +872,26 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = true
self.originalRemainingSeconds = 5
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} 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):
self.avatarNode = nil
let multiAvatarsNode = AnimatedAvatarSetNode()
@ -1084,8 +1101,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.content = content
switch content {
case let .image(image, text):
case let .image(image, title, text, _):
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)
default:
break