mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
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:
commit
0afe69c627
@ -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$@";
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)?
|
||||
|
@ -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
|
||||
|
@ -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 })
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)),
|
||||
|
@ -81,6 +81,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
}, deletePeerThread: { _, _ in
|
||||
}, setPeerThreadStopped: { _, _, _ in
|
||||
}, setPeerThreadPinned: { _, _, _ in
|
||||
}, setPeerThreadHidden: { _, _, _ in
|
||||
}, updatePeerGrouping: { _, _ in
|
||||
}, togglePeerMarkedUnread: { _, _ in
|
||||
}, toggleArchivedFolderHiddenByDefault: {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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 })
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]>
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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))
|
||||
|
@ -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
|
||||
|
||||
|
@ -2,8 +2,6 @@ import Foundation
|
||||
import Postbox
|
||||
|
||||
public extension Peer {
|
||||
|
||||
|
||||
var debugDisplayTitle: String {
|
||||
switch self {
|
||||
case let user as TelegramUser:
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_general.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
113
submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/ic_general.pdf
vendored
Normal file
113
submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/ic_general.pdf
vendored
Normal 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
|
112
submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/AntiSpamTooltip.pdf
vendored
Normal file
112
submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/AntiSpamTooltip.pdf
vendored
Normal 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
|
12
submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AntiSpamTooltip.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
135
submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Antispam.pdf
vendored
Normal file
135
submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Antispam.pdf
vendored
Normal 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
|
12
submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Antispam.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
12
submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_general (1).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
113
submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/ic_general (1).pdf
vendored
Normal file
113
submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/ic_general (1).pdf
vendored
Normal 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
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -235,6 +235,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
}, deletePeerThread: { _, _ in
|
||||
}, setPeerThreadStopped: { _, _, _ in
|
||||
}, setPeerThreadPinned: { _, _, _ in
|
||||
}, setPeerThreadHidden: { _, _, _ in
|
||||
}, updatePeerGrouping: { _, _ in
|
||||
}, togglePeerMarkedUnread: { _, _ in
|
||||
}, toggleArchivedFolderHiddenByDefault: {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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?)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user