mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Folder improvements
This commit is contained in:
parent
e10f30d3eb
commit
0b59cd2864
@ -91,6 +91,7 @@ swift_library(
|
||||
"//submodules/InviteLinksUI",
|
||||
"//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/QrCodeUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -3217,7 +3217,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
let text = strongSelf.presentationData.strings.ChatList_DeletedThreads(Int32(threadIds.count))
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: text), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
@ -3301,7 +3301,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count))
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: text), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
@ -3646,7 +3646,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return true
|
||||
})
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: strongSelf.presentationData.strings.Undo_ChatCleared), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: strongSelf.presentationData.strings.Undo_ChatCleared, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
@ -3868,7 +3868,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
let statusText = self.presentationData.strings.Undo_DeletedTopic
|
||||
|
||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||
guard let self else {
|
||||
return false
|
||||
}
|
||||
@ -4156,7 +4156,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return true
|
||||
})
|
||||
|
||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
|
@ -15,6 +15,10 @@ import AvatarNode
|
||||
import ChatListFilterSettingsHeaderItem
|
||||
import PremiumUI
|
||||
import InviteLinksUI
|
||||
import QrCodeUI
|
||||
import ContextUI
|
||||
import AsyncDisplayKit
|
||||
import UndoUI
|
||||
|
||||
private enum FilterSection: Int32, Hashable {
|
||||
case include
|
||||
@ -37,6 +41,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
let createLink: () -> Void
|
||||
let openLink: (ExportedChatFolderLink) -> Void
|
||||
let removeLink: (ExportedChatFolderLink) -> Void
|
||||
let linkContextAction: (ExportedChatFolderLink?, ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
@ -53,7 +58,8 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
expandSection: @escaping (FilterSection) -> Void,
|
||||
createLink: @escaping () -> Void,
|
||||
openLink: @escaping (ExportedChatFolderLink) -> Void,
|
||||
removeLink: @escaping (ExportedChatFolderLink) -> Void
|
||||
removeLink: @escaping (ExportedChatFolderLink) -> Void,
|
||||
linkContextAction: @escaping (ExportedChatFolderLink?, ASDisplayNode, ContextGesture?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
@ -70,6 +76,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
self.createLink = createLink
|
||||
self.openLink = openLink
|
||||
self.removeLink = removeLink
|
||||
self.linkContextAction = linkContextAction
|
||||
}
|
||||
}
|
||||
|
||||
@ -537,7 +544,9 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
arguments.openLink(invite)
|
||||
}, removeAction: { invite in
|
||||
arguments.removeLink(invite)
|
||||
}, contextAction: nil)
|
||||
}, contextAction: { link, node, gesture in
|
||||
arguments.linkContextAction(link, node, gesture)
|
||||
})
|
||||
case let .inviteLinkInfo(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
}
|
||||
@ -624,7 +633,8 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
||||
if let currentPreset, let data = currentPreset.data, data.isShared {
|
||||
} else {
|
||||
entries.append(.excludePeersHeader(presentationData.strings.ChatListFolder_ExcludedSectionHeader))
|
||||
entries.append(.addExcludePeer(title: presentationData.strings.ChatListFolder_AddChats))
|
||||
//TODO:localize
|
||||
entries.append(.addExcludePeer(title: "Add Chats to Exclude"))
|
||||
|
||||
var excludeCategoryIndex = 0
|
||||
for category in ChatListFilterExcludeCategory.allCases {
|
||||
@ -1085,6 +1095,8 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
var focusOnNameImpl: (() -> Void)?
|
||||
var clearFocusImpl: (() -> Void)?
|
||||
var applyImpl: ((Bool, @escaping () -> Void) -> Void)?
|
||||
var getControllerImpl: (() -> ViewController?)?
|
||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||
|
||||
let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil)
|
||||
if let currentPreset {
|
||||
@ -1375,9 +1387,10 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
|
||||
if let updatedLink {
|
||||
if let index = links.firstIndex(where: { $0.link == link.link }) {
|
||||
links.remove(at: index)
|
||||
}
|
||||
links[index] = updatedLink
|
||||
} else {
|
||||
links.insert(updatedLink, at: 0)
|
||||
}
|
||||
sharedLinks.set(.single(links))
|
||||
} else {
|
||||
if let index = links.firstIndex(where: { $0.link == link.link }) {
|
||||
@ -1387,6 +1400,8 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
}
|
||||
})
|
||||
}
|
||||
}, presentController: { c in
|
||||
presentControllerImpl?(c, nil)
|
||||
}))
|
||||
})
|
||||
}
|
||||
@ -1404,6 +1419,59 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
actionsDisposable.add(context.engine.peers.deleteChatFolderLink(filterId: currentPreset.id, link: link).start())
|
||||
})
|
||||
}
|
||||
},
|
||||
linkContextAction: { invite, node, gesture in
|
||||
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?(), let invite = invite, let currentPreset else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
//dismissTooltipsImpl?()
|
||||
|
||||
UIPasteboard.general.string = invite.link
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: nil, subject: .chatFolder(slug: invite.slug)), nil)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
||||
var links = links ?? []
|
||||
if let index = links.firstIndex(where: { $0.link == invite.link }) {
|
||||
links.remove(at: index)
|
||||
}
|
||||
sharedLinks.set(.single(links))
|
||||
})
|
||||
|
||||
let _ = (context.engine.peers.editChatFolderLink(filterId: currentPreset.id, link: invite, title: nil, peerIds: nil, revoke: true)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
let _ = (context.engine.peers.deleteChatFolderLink(filterId: currentPreset.id, link: invite)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
})
|
||||
})
|
||||
})))
|
||||
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
}
|
||||
)
|
||||
|
||||
@ -1577,6 +1645,14 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
})
|
||||
})]), nil)
|
||||
}
|
||||
getControllerImpl = { [weak controller] in
|
||||
return controller
|
||||
}
|
||||
presentInGlobalOverlayImpl = { [weak controller] c in
|
||||
if let controller = controller {
|
||||
controller.presentInGlobalOverlay(c)
|
||||
}
|
||||
}
|
||||
attemptNavigationImpl = {
|
||||
let state = stateValue.with { $0 }
|
||||
if let currentPreset = currentPreset, case let .filter(currentId, currentTitle, currentEmoticon, currentData) = currentPreset {
|
||||
@ -1652,7 +1728,9 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
|
||||
|> deliverOnMainQueue).start(next: { existingLink in
|
||||
if let existingLink {
|
||||
completed()
|
||||
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peerIds, currentInvitation: existingLink, linkUpdated: linkUpdated))
|
||||
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peerIds, currentInvitation: existingLink, linkUpdated: linkUpdated, presentController: { c in
|
||||
presentController(c)
|
||||
}))
|
||||
|
||||
return
|
||||
}
|
||||
@ -1672,7 +1750,9 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
|
||||
})
|
||||
if peers.allSatisfy({ !canShareLinkToPeer(peer: $0) }) {
|
||||
completed()
|
||||
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: nil, linkUpdated: linkUpdated))
|
||||
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: nil, linkUpdated: linkUpdated, presentController: { c in
|
||||
presentController(c)
|
||||
}))
|
||||
} else {
|
||||
var enabledPeerIds: [EnginePeer.Id] = []
|
||||
for peer in peers {
|
||||
@ -1686,7 +1766,9 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
|
||||
completed()
|
||||
linkUpdated(link)
|
||||
|
||||
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: linkUpdated))
|
||||
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: linkUpdated, presentController: { c in
|
||||
presentController(c)
|
||||
}))
|
||||
}, error: { error in
|
||||
completed()
|
||||
//TODO:localize
|
||||
@ -1714,3 +1796,17 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private final class InviteLinkContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ private struct FolderInviteLinkListControllerState: Equatable {
|
||||
var isSaving: Bool = false
|
||||
}
|
||||
|
||||
public func folderInviteLinkListController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filterId: Int32, title filterTitle: String, allPeerIds: [PeerId], currentInvitation: ExportedChatFolderLink?, linkUpdated: @escaping (ExportedChatFolderLink?) -> Void) -> ViewController {
|
||||
public func folderInviteLinkListController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filterId: Int32, title filterTitle: String, allPeerIds: [PeerId], currentInvitation: ExportedChatFolderLink?, linkUpdated: @escaping (ExportedChatFolderLink?) -> Void, presentController parentPresentController: ((ViewController) -> Void)?) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
let _ = pushControllerImpl
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
@ -346,7 +346,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
|
||||
var getControllerImpl: (() -> ViewController?)?
|
||||
|
||||
var displayTooltipImpl: ((UndoOverlayContent) -> Void)?
|
||||
var displayTooltipImpl: ((UndoOverlayContent, Bool) -> Void)?
|
||||
|
||||
var didDisplayAddPeerNotice: Bool = false
|
||||
|
||||
@ -439,11 +439,6 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
}
|
||||
}
|
||||
})
|
||||
/*promptController.dismissed = { byOutsideTap in
|
||||
if byOutsideTap {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}*/
|
||||
presentControllerImpl?(promptController, nil)
|
||||
})))
|
||||
|
||||
@ -509,7 +504,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
|
||||
dismissTooltipsImpl?()
|
||||
//TODO:localize
|
||||
displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats.", timeout: 8))
|
||||
displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats.", timeout: 8), true)
|
||||
}
|
||||
} else {
|
||||
//TODO:localize
|
||||
@ -532,7 +527,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
}
|
||||
}
|
||||
dismissTooltipsImpl?()
|
||||
displayTooltipImpl?(.peers(context: context, peers: [peer], title: nil, text: text, customUndoText: nil))
|
||||
displayTooltipImpl?(.peers(context: context, peers: [peer], title: nil, text: text, customUndoText: nil), true)
|
||||
}
|
||||
}, toggleAllSelected: {
|
||||
let _ = (context.engine.data.get(
|
||||
@ -605,6 +600,9 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "An error occurred.", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
}, completed: {
|
||||
linkUpdated(ExportedChatFolderLink(title: state.title ?? "", link: currentLink.link, peerIds: Array(state.selectedPeerIds), isRevoked: false))
|
||||
//TODO:localize
|
||||
displayTooltipImpl?(.info(title: nil, text: "Link updated", timeout: 3), false)
|
||||
|
||||
dismissImpl?()
|
||||
}))
|
||||
} else {
|
||||
@ -613,7 +611,6 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
} else {
|
||||
dismissImpl?()
|
||||
}
|
||||
dismissImpl?()
|
||||
}
|
||||
|
||||
let _ = (allPeers
|
||||
@ -768,18 +765,15 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
getControllerImpl = { [weak controller] in
|
||||
return controller
|
||||
}
|
||||
displayTooltipImpl = { [weak controller] c in
|
||||
if let controller = controller {
|
||||
displayTooltipImpl = { [weak controller] c, inCurrentContext in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||
if let controller = controller, inCurrentContext {
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: c, elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
} else if !inCurrentContext {
|
||||
parentPresentController?(UndoOverlayController(presentationData: presentationData, content: c, elevatedLayout: false, action: { _ in return false }))
|
||||
}
|
||||
}
|
||||
dismissTooltipsImpl = { [weak controller] in
|
||||
controller?.window?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
})
|
||||
controller?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
|
@ -956,26 +956,26 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
|
||||
|
||||
final class InviteLinkContextExtractedContentSource: ContextExtractedContentSource {
|
||||
var keepInPlace: Bool
|
||||
let ignoreContentTouches: Bool = true
|
||||
let blurBackground: Bool
|
||||
public final class InviteLinkContextExtractedContentSource: ContextExtractedContentSource {
|
||||
public var keepInPlace: Bool
|
||||
public let ignoreContentTouches: Bool = true
|
||||
public let blurBackground: Bool
|
||||
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextExtractedContentContainingNode
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool, blurBackground: Bool) {
|
||||
public init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool, blurBackground: Bool) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
self.keepInPlace = keepInPlace
|
||||
self.blurBackground = blurBackground
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
public func takeView() -> ContextControllerTakeViewInfo? {
|
||||
return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
public func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
@ -404,6 +404,7 @@ public struct ChatListViewReadState: Equatable {
|
||||
final class MutableChatListView {
|
||||
let groupId: PeerGroupId
|
||||
let filterPredicate: ChatListFilterPredicate?
|
||||
private let aroundIndex: ChatListIndex
|
||||
private let summaryComponents: ChatListEntrySummaryComponents
|
||||
fileprivate var groupEntries: [ChatListGroupReferenceEntry]
|
||||
private var count: Int
|
||||
@ -416,11 +417,16 @@ final class MutableChatListView {
|
||||
private var additionalItems: [AdditionalChatListItem] = []
|
||||
fileprivate var additionalItemEntries: [MutableChatListAdditionalItemEntry] = []
|
||||
|
||||
private var currentHiddenPeerIds = Set<PeerId>()
|
||||
|
||||
init(postbox: PostboxImpl, currentTransaction: Transaction, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) {
|
||||
self.groupId = groupId
|
||||
self.filterPredicate = filterPredicate
|
||||
self.aroundIndex = aroundIndex
|
||||
self.summaryComponents = summaryComponents
|
||||
|
||||
self.currentHiddenPeerIds = postbox.hiddenChatIds
|
||||
|
||||
var spaces: [ChatListViewSpace] = [
|
||||
.group(groupId: self.groupId, pinned: .notPinned, predicate: filterPredicate)
|
||||
]
|
||||
@ -474,7 +480,7 @@ final class MutableChatListView {
|
||||
if let entry = postbox.chatListTable.earlierEntryInfos(groupId: groupId, index: upperBound, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: 1).first {
|
||||
switch entry {
|
||||
case let .message(index, messageIndex):
|
||||
if let messageIndex = messageIndex {
|
||||
if let messageIndex = messageIndex, !postbox.isChatHidden(peerId: messageIndex.id.peerId) {
|
||||
foundIndices.append((index, messageIndex))
|
||||
if index.pinningIndex == nil {
|
||||
unpinnedCount += 1
|
||||
@ -541,7 +547,7 @@ final class MutableChatListView {
|
||||
func refreshDueToExternalTransaction(postbox: PostboxImpl, currentTransaction: Transaction) -> Bool {
|
||||
var updated = false
|
||||
|
||||
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: .absoluteUpperBound, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
|
||||
updated = true
|
||||
|
||||
@ -559,8 +565,19 @@ final class MutableChatListView {
|
||||
func replay(postbox: PostboxImpl, currentTransaction: Transaction, operations: [PeerGroupId: [ChatListOperation]], updatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], updatedPeers: [PeerId: Peer], updatedPeerPresences: [PeerId: PeerPresence], transaction: PostboxTransaction, context: MutableChatListViewReplayContext) -> Bool {
|
||||
var hasChanges = false
|
||||
|
||||
let hiddenChatIds = postbox.hiddenChatIds
|
||||
var hasFilterChanges = false
|
||||
if hiddenChatIds != self.currentHiddenPeerIds {
|
||||
self.currentHiddenPeerIds = hiddenChatIds
|
||||
hasFilterChanges = true
|
||||
}
|
||||
|
||||
if transaction.updatedGlobalNotificationSettings && self.filterPredicate != nil {
|
||||
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: .absoluteUpperBound, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
|
||||
hasChanges = true
|
||||
} else if hasFilterChanges {
|
||||
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
|
||||
hasChanges = true
|
||||
} else {
|
||||
@ -577,6 +594,9 @@ final class MutableChatListView {
|
||||
invalidatedGroups = true
|
||||
}
|
||||
}
|
||||
if hasFilterChanges {
|
||||
invalidatedGroups = true
|
||||
}
|
||||
|
||||
if invalidatedGroups {
|
||||
self.reloadGroups(postbox: postbox)
|
||||
|
@ -71,6 +71,9 @@ private func mappedChatListFilterPredicate(postbox: PostboxImpl, currentTransact
|
||||
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId))
|
||||
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, threadId: nil, calculation: predicate.messageTagSummary)
|
||||
|
||||
if postbox.isChatHidden(peerId: peer.id) {
|
||||
return false
|
||||
}
|
||||
if predicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: messageTagSummaryResult) {
|
||||
return true
|
||||
} else {
|
||||
@ -178,6 +181,21 @@ private final class ChatListViewSpaceState {
|
||||
}
|
||||
}
|
||||
|
||||
let predicate: ((ChatListIntermediateEntry) -> Bool)?
|
||||
if let filterPredicate = filterPredicate {
|
||||
predicate = mappedChatListFilterPredicate(postbox: postbox, currentTransaction: currentTransaction, groupId: groupId, predicate: filterPredicate)
|
||||
} else if postbox.hasHiddenChatIds {
|
||||
predicate = { entry in
|
||||
if postbox.isChatHidden(peerId: entry.index.messageIndex.id.peerId) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
predicate = nil
|
||||
}
|
||||
|
||||
if case .includePinnedAsUnpinned = pinned {
|
||||
let unpinnedLowerBound: MutableChatListEntryIndex
|
||||
let unpinnedUpperBound: MutableChatListEntryIndex
|
||||
@ -186,7 +204,7 @@ private final class ChatListViewSpaceState {
|
||||
let resolvedUnpinnedAnchorIndex = min(unpinnedUpperBound, max(self.anchorIndex, unpinnedLowerBound))
|
||||
|
||||
if lowerOrAtAnchorMessages.count < self.halfLimit || higherThanAnchorMessages.count < self.halfLimit {
|
||||
let loadedMessages = postbox.chatListTable.entries(groupId: groupId, from: (ChatListIndex.pinnedLowerBound, true), to: (ChatListIndex.absoluteUpperBound, true), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit * 2, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, currentTransaction: currentTransaction, groupId: groupId, predicate: $0) }).map(mapEntry).sorted(by: { $0.entryIndex < $1.entryIndex })
|
||||
let loadedMessages = postbox.chatListTable.entries(groupId: groupId, from: (ChatListIndex.pinnedLowerBound, true), to: (ChatListIndex.absoluteUpperBound, true), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit * 2, predicate: predicate).map(mapEntry).sorted(by: { $0.entryIndex < $1.entryIndex })
|
||||
|
||||
if lowerOrAtAnchorMessages.count < self.halfLimit {
|
||||
var nextLowerIndex: MutableChatListEntryIndex
|
||||
@ -225,7 +243,7 @@ private final class ChatListViewSpaceState {
|
||||
} else {
|
||||
nextLowerIndex = resolvedAnchorIndex.successor
|
||||
}
|
||||
let loadedLowerMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextLowerIndex.index, nextLowerIndex.isMessage), to: (lowerBound.index, lowerBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - lowerOrAtAnchorMessages.count, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, currentTransaction: currentTransaction, groupId: groupId, predicate: $0) }).map(mapEntry)
|
||||
let loadedLowerMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextLowerIndex.index, nextLowerIndex.isMessage), to: (lowerBound.index, lowerBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - lowerOrAtAnchorMessages.count, predicate: predicate).map(mapEntry)
|
||||
lowerOrAtAnchorMessages.append(contentsOf: loadedLowerMessages)
|
||||
}
|
||||
if higherThanAnchorMessages.count < self.halfLimit {
|
||||
@ -235,7 +253,7 @@ private final class ChatListViewSpaceState {
|
||||
} else {
|
||||
nextHigherIndex = resolvedAnchorIndex
|
||||
}
|
||||
let loadedHigherMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextHigherIndex.index, nextHigherIndex.isMessage), to: (upperBound.index, upperBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - higherThanAnchorMessages.count, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, currentTransaction: currentTransaction, groupId: groupId, predicate: $0) }).map(mapEntry)
|
||||
let loadedHigherMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextHigherIndex.index, nextHigherIndex.isMessage), to: (upperBound.index, upperBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - higherThanAnchorMessages.count, predicate: predicate).map(mapEntry)
|
||||
higherThanAnchorMessages.append(contentsOf: loadedHigherMessages)
|
||||
}
|
||||
}
|
||||
|
@ -1236,6 +1236,16 @@ public final class Transaction {
|
||||
assert(!self.disposed)
|
||||
return self.postbox!.messageHistoryThreadPinnedTable.get(peerId: peerId)
|
||||
}
|
||||
|
||||
func addChatHidden(peerId: PeerId) -> Int {
|
||||
assert(!self.disposed)
|
||||
return self.postbox!.addChatHidden(peerId: peerId)
|
||||
}
|
||||
|
||||
func removeChatHidden(peerId: PeerId, index: Int) {
|
||||
assert(!self.disposed)
|
||||
return self.postbox!.removeChatHidden(peerId: peerId, index: index)
|
||||
}
|
||||
}
|
||||
|
||||
public enum PostboxResult {
|
||||
@ -1470,6 +1480,40 @@ final class PostboxImpl {
|
||||
private var currentUpdatedPeerThreadCombinedStates = Set<PeerId>()
|
||||
private var currentUpdatedPinnedThreads = Set<PeerId>()
|
||||
|
||||
private var currentHiddenChatIds: [PeerId: Bag<Void>] = [:]
|
||||
private var currentUpdatedHiddenPeerIds: Bool = false
|
||||
|
||||
var hiddenChatIds: Set<PeerId> {
|
||||
if self.currentHiddenChatIds.isEmpty {
|
||||
return Set()
|
||||
} else {
|
||||
var result = Set<PeerId>()
|
||||
for (peerId, bag) in self.currentHiddenChatIds {
|
||||
if !bag.isEmpty {
|
||||
result.insert(peerId)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func isChatHidden(peerId: PeerId) -> Bool {
|
||||
if let bag = self.currentHiddenChatIds[peerId], !bag.isEmpty {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var hasHiddenChatIds: Bool {
|
||||
for (_, bag) in self.currentHiddenChatIds {
|
||||
if !bag.isEmpty {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private var currentNeedsReindexUnreadCounters: Bool = false
|
||||
|
||||
private let statePipe: ValuePipe<PostboxCoding> = ValuePipe()
|
||||
@ -2117,7 +2161,7 @@ final class PostboxImpl {
|
||||
|
||||
let updatedPeerTimeoutAttributes = self.peerTimeoutPropertiesTable.hasUpdates
|
||||
|
||||
let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes, updatedMessageThreadPeerIds: updatedMessageThreadPeerIds, updatedPeerThreadCombinedStates: self.currentUpdatedPeerThreadCombinedStates, updatedPeerThreadsSummaries: Set(alteredInitialPeerThreadsSummaries.keys), updatedPinnedThreads: self.currentUpdatedPinnedThreads)
|
||||
let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes, updatedMessageThreadPeerIds: updatedMessageThreadPeerIds, updatedPeerThreadCombinedStates: self.currentUpdatedPeerThreadCombinedStates, updatedPeerThreadsSummaries: Set(alteredInitialPeerThreadsSummaries.keys), updatedPinnedThreads: self.currentUpdatedPinnedThreads, updatedHiddenPeerIds: self.currentUpdatedHiddenPeerIds)
|
||||
var updatedTransactionState: Int64?
|
||||
var updatedMasterClientId: Int64?
|
||||
if !transaction.isEmpty {
|
||||
@ -2171,6 +2215,7 @@ final class PostboxImpl {
|
||||
self.currentUpdatedPendingPeerNotificationSettings.removeAll()
|
||||
self.currentGroupIdsWithUpdatedReadStats.removeAll()
|
||||
self.currentUpdatedPinnedThreads.removeAll()
|
||||
self.currentUpdatedHiddenPeerIds = false
|
||||
self.currentNeedsReindexUnreadCounters = false
|
||||
|
||||
for table in self.tables {
|
||||
@ -3623,6 +3668,36 @@ final class PostboxImpl {
|
||||
return disposable
|
||||
}
|
||||
|
||||
public func addHiddenChatFilterAndChatIds(peerIds: [PeerId]) -> Disposable {
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
let queue = self.queue
|
||||
let _ = (self.transaction { transaction -> [PeerId: Int] in
|
||||
var peerIndices: [PeerId: Int] = [:]
|
||||
for peerId in peerIds {
|
||||
peerIndices[peerId] = transaction.addChatHidden(peerId: peerId)
|
||||
}
|
||||
return peerIndices
|
||||
}).start(next: { peerIndices in
|
||||
disposable.set(ActionDisposable { [weak self] in
|
||||
queue.async {
|
||||
guard let `self` = self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.transaction { transaction -> Void in
|
||||
for peerId in peerIds {
|
||||
if let index = peerIndices[peerId] {
|
||||
transaction.removeChatHidden(peerId: peerId, index: index)
|
||||
}
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return disposable
|
||||
}
|
||||
|
||||
fileprivate func scanMessages(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) {
|
||||
var index = MessageIndex.lowerBound(peerId: peerId, namespace: namespace)
|
||||
while true {
|
||||
@ -3812,6 +3887,25 @@ final class PostboxImpl {
|
||||
self.messageHistoryThreadPinnedTable.set(peerId: peerId, threadIds: threadIds)
|
||||
}
|
||||
|
||||
fileprivate func addChatHidden(peerId: PeerId) -> Int {
|
||||
let bag: Bag<Void>
|
||||
if let current = self.currentHiddenChatIds[peerId] {
|
||||
bag = current
|
||||
} else {
|
||||
bag = Bag()
|
||||
self.currentHiddenChatIds[peerId] = bag
|
||||
}
|
||||
self.currentUpdatedHiddenPeerIds = true
|
||||
return bag.add(Void())
|
||||
}
|
||||
|
||||
fileprivate func removeChatHidden(peerId: PeerId, index: Int) {
|
||||
if let current = self.currentHiddenChatIds[peerId] {
|
||||
current.remove(index)
|
||||
self.currentUpdatedHiddenPeerIds = true
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func reindexUnreadCounters(currentTransaction: Transaction) {
|
||||
self.groupMessageStatsTable.removeAll()
|
||||
let _ = CFAbsoluteTimeGetCurrent()
|
||||
@ -4415,6 +4509,16 @@ public class Postbox {
|
||||
return disposable
|
||||
}
|
||||
|
||||
public func addHiddenChatIds(peerIds: [PeerId]) -> Disposable {
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.addHiddenChatFilterAndChatIds(peerIds: peerIds))
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
|
||||
public func isMasterClient() -> Signal<Bool, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
@ -48,6 +48,7 @@ final class PostboxTransaction {
|
||||
let updatedPeerThreadCombinedStates: Set<PeerId>
|
||||
let updatedPeerThreadsSummaries: Set<PeerId>
|
||||
let updatedPinnedThreads: Set<PeerId>
|
||||
let updatedHiddenPeerIds: Bool
|
||||
|
||||
var isEmpty: Bool {
|
||||
if currentUpdatedState != nil {
|
||||
@ -191,10 +192,13 @@ final class PostboxTransaction {
|
||||
if !self.updatedPinnedThreads.isEmpty {
|
||||
return false
|
||||
}
|
||||
if self.updatedHiddenPeerIds {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: Set<PeerId>, currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState], currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set<PeerId>, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set<PeerId>?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set<PeerId>, replacedAdditionalChatListItems: [AdditionalChatListItem]?, updatedNoticeEntryKeys: Set<NoticeEntryKey>, updatedCacheEntryKeys: Set<ItemCacheEntryId>, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set<PeerId>, updatedFailedMessageIds: Set<MessageId>, updatedGlobalNotificationSettings: Bool, updatedPeerTimeoutAttributes: Bool, updatedMessageThreadPeerIds: Set<PeerId>, updatedPeerThreadCombinedStates: Set<PeerId>, updatedPeerThreadsSummaries: Set<PeerId>, updatedPinnedThreads: Set<PeerId>) {
|
||||
init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: Set<PeerId>, currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState], currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set<PeerId>, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set<PeerId>?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set<PeerId>, replacedAdditionalChatListItems: [AdditionalChatListItem]?, updatedNoticeEntryKeys: Set<NoticeEntryKey>, updatedCacheEntryKeys: Set<ItemCacheEntryId>, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set<PeerId>, updatedFailedMessageIds: Set<MessageId>, updatedGlobalNotificationSettings: Bool, updatedPeerTimeoutAttributes: Bool, updatedMessageThreadPeerIds: Set<PeerId>, updatedPeerThreadCombinedStates: Set<PeerId>, updatedPeerThreadsSummaries: Set<PeerId>, updatedPinnedThreads: Set<PeerId>, updatedHiddenPeerIds: Bool) {
|
||||
self.currentUpdatedState = currentUpdatedState
|
||||
self.currentPeerHoleOperations = currentPeerHoleOperations
|
||||
self.currentOperationsByPeerId = currentOperationsByPeerId
|
||||
@ -241,5 +245,6 @@ final class PostboxTransaction {
|
||||
self.updatedPeerThreadCombinedStates = updatedPeerThreadCombinedStates
|
||||
self.updatedPeerThreadsSummaries = updatedPeerThreadsSummaries
|
||||
self.updatedPinnedThreads = updatedPinnedThreads
|
||||
self.updatedHiddenPeerIds = updatedHiddenPeerIds
|
||||
}
|
||||
}
|
||||
|
@ -333,6 +333,12 @@ public final class AccountViewTracker {
|
||||
|
||||
public let chatListPreloadItems = Promise<Set<ChatHistoryPreloadItem>>([])
|
||||
|
||||
private let hiddenChatListFilterIdsValue = Atomic<[Int32: Bag<Void>]>(value: [:])
|
||||
private let hiddenChatListFilterIdsPromise = ValuePromise<Set<Int32>>(Set())
|
||||
public var hiddenChatListFilterIds: Signal<Set<Int32>, NoError> {
|
||||
return self.hiddenChatListFilterIdsPromise.get()
|
||||
}
|
||||
|
||||
var resetPeerHoleManagement: ((PeerId) -> Void)?
|
||||
|
||||
init(account: Account) {
|
||||
@ -2162,4 +2168,52 @@ public final class AccountViewTracker {
|
||||
return .never()
|
||||
}
|
||||
}
|
||||
|
||||
public func addHiddenChatListFilterIds(_ ids: [Int32]) -> Disposable {
|
||||
var indices: [Int32: Int] = [:]
|
||||
var updatedIds = Set<Int32>()
|
||||
let _ = self.hiddenChatListFilterIdsValue.modify { value in
|
||||
var value = value
|
||||
for id in ids {
|
||||
let bag: Bag<Void>
|
||||
if let current = value[id] {
|
||||
bag = current
|
||||
} else {
|
||||
bag = Bag()
|
||||
value[id] = bag
|
||||
}
|
||||
indices[id] = bag.add(Void())
|
||||
}
|
||||
for (id, bag) in value {
|
||||
if !bag.isEmpty {
|
||||
updatedIds.insert(id)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
self.hiddenChatListFilterIdsPromise.set(updatedIds)
|
||||
|
||||
return ActionDisposable { [weak self] in
|
||||
DispatchQueue.main.async {
|
||||
guard let `self` = self else {
|
||||
return
|
||||
}
|
||||
var updatedIds = Set<Int32>()
|
||||
let _ = self.hiddenChatListFilterIdsValue.modify { value in
|
||||
for id in ids {
|
||||
if let bag = value[id], let index = indices[id] {
|
||||
bag.remove(index)
|
||||
}
|
||||
}
|
||||
for (id, bag) in value {
|
||||
if !bag.isEmpty {
|
||||
updatedIds.insert(id)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
self.hiddenChatListFilterIdsPromise.set(updatedIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ extension UserLimitsConfiguration {
|
||||
self.maxAboutLength = getValue("about_length_limit", orElse: defaultValue.maxAboutLength)
|
||||
self.maxAnimatedEmojisInText = getGeneralValue("message_animated_emoji_max", orElse: defaultValue.maxAnimatedEmojisInText)
|
||||
self.maxReactionsPerMessage = getValue("reactions_user_max", orElse: 1)
|
||||
self.maxSharedFolderInviteLinks = getValue("community_invites_limit", orElse: 3)
|
||||
self.maxSharedFolderJoin = getValue("communities_joined_limit", orElse: 2)
|
||||
self.maxSharedFolderInviteLinks = getValue("chatlists_invites_limit", orElse: isPremium ? 100 : 3)
|
||||
self.maxSharedFolderJoin = getValue("chatlists_joined_limit", orElse: isPremium ? 100 : 2)
|
||||
}
|
||||
}
|
||||
|
@ -1000,11 +1000,20 @@ func _internal_updateChatListFiltersInteractively(transaction: Transaction, _ f:
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> {
|
||||
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
||||
|> map { preferences -> [ChatListFilter] in
|
||||
func _internal_updatedChatListFilters(postbox: Postbox, hiddenIds: Signal<Set<Int32>, NoError> = .single(Set())) -> Signal<[ChatListFilter], NoError> {
|
||||
return combineLatest(
|
||||
postbox.preferencesView(keys: [PreferencesKeys.chatListFilters]),
|
||||
hiddenIds
|
||||
)
|
||||
|> map { preferences, hiddenIds -> [ChatListFilter] in
|
||||
let filtersState = preferences.values[PreferencesKeys.chatListFilters]?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
|
||||
return filtersState.filters
|
||||
return filtersState.filters.filter { filter in
|
||||
if hiddenIds.contains(filter.id) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
|
@ -510,7 +510,7 @@ public extension TelegramEngine {
|
||||
}
|
||||
|
||||
public func updatedChatListFilters() -> Signal<[ChatListFilter], NoError> {
|
||||
return _internal_updatedChatListFilters(postbox: self.account.postbox)
|
||||
return _internal_updatedChatListFilters(postbox: self.account.postbox, hiddenIds: self.account.viewTracker.hiddenChatListFilterIds)
|
||||
}
|
||||
|
||||
public func updatedChatListFiltersInfo() -> Signal<(filters: [ChatListFilter], synchronized: Bool), NoError> {
|
||||
|
@ -437,7 +437,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
let text: String
|
||||
if let linkContents = component.linkContents {
|
||||
if case .remove = component.subject {
|
||||
text = "Do you want to quit the chats you joined when\nadding the folder \(linkContents.title ?? "Folder")?"
|
||||
text = "Do you also want to quit the chats included in this folder?"
|
||||
} else if allChatsAdded {
|
||||
text = "You have already added this\nfolder and its chats."
|
||||
} else if linkContents.localFilterId == nil {
|
||||
@ -763,7 +763,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
isEnabled: !self.selectedItems.isEmpty || component.linkContents?.localFilterId != nil,
|
||||
displaysProgress: self.inProgress,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||
guard let self, let component = self.component, let linkContents = component.linkContents, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -773,13 +773,78 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
|
||||
component.completion?()
|
||||
|
||||
self.joinDisposable = (component.context.engine.peers.leaveChatFolder(folderId: folderId, removePeerIds: Array(self.selectedItems))
|
||||
let disposable = DisposableSet()
|
||||
disposable.add(component.context.account.postbox.addHiddenChatIds(peerIds: Array(self.selectedItems)))
|
||||
disposable.add(component.context.account.viewTracker.addHiddenChatListFilterIds([folderId]))
|
||||
|
||||
let folderTitle = linkContents.title ?? ""
|
||||
|
||||
var additionalText: String?
|
||||
if !self.selectedItems.isEmpty {
|
||||
if self.selectedItems.count == 1 {
|
||||
additionalText = "You also left **1** chat"
|
||||
} else {
|
||||
additionalText = "You also left **\(self.selectedItems.count)** chats"
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
|
||||
var chatListController: ChatListController?
|
||||
if let navigationController = controller.navigationController as? NavigationController {
|
||||
for viewController in navigationController.viewControllers {
|
||||
if let rootController = viewController as? TabBarController {
|
||||
for c in rootController.controllers {
|
||||
if let c = c as? ChatListController {
|
||||
chatListController = c
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let c = viewController as? ChatListController {
|
||||
chatListController = c
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let context = component.context
|
||||
let selectedItems = self.selectedItems
|
||||
let undoOverlayController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .removedChat(title: "Folder \(folderTitle) deleted", text: additionalText),
|
||||
elevatedLayout: false,
|
||||
action: { value in
|
||||
if case .commit = value {
|
||||
let _ = (context.engine.peers.leaveChatFolder(folderId: folderId, removePeerIds: Array(selectedItems))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
Queue.mainQueue().after(1.0, {
|
||||
disposable.dispose()
|
||||
})
|
||||
})
|
||||
return true
|
||||
} else if case .undo = value {
|
||||
disposable.dispose()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
)
|
||||
|
||||
if let chatListController, chatListController.view.window != nil {
|
||||
chatListController.present(undoOverlayController, in: .current)
|
||||
} else {
|
||||
controller.present(undoOverlayController, in: .window(.root))
|
||||
}
|
||||
|
||||
controller.dismiss()
|
||||
|
||||
/*self.joinDisposable = (component.context.engine.peers.leaveChatFolder(folderId: folderId, removePeerIds: Array(self.selectedItems))
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let self, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
})
|
||||
})*/
|
||||
} else if allChatsAdded {
|
||||
controller.dismiss()
|
||||
} else if let _ = component.linkContents {
|
||||
|
@ -11990,7 +11990,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
statusText = self.presentationData.strings.Undo_ChatCleared
|
||||
}
|
||||
|
||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, action: { [weak self] value in
|
||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, action: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
@ -15911,7 +15911,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = range
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, action: { value in
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, action: { value in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import AccountContext
|
||||
import ComponentFlow
|
||||
|
||||
public enum UndoOverlayContent {
|
||||
case removedChat(text: String)
|
||||
case removedChat(title: String, text: String?)
|
||||
case archivedChat(peerId: Int64, title: String, text: String, undo: Bool)
|
||||
case hidArchive(title: String, text: String, undo: Bool)
|
||||
case revealedArchive(title: String, text: String, undo: Bool)
|
||||
|
@ -94,13 +94,22 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
var isUserInteractionEnabled = false
|
||||
switch content {
|
||||
case let .removedChat(text):
|
||||
case let .removedChat(title, text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = nil
|
||||
self.animatedStickerNode = nil
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||
if let text {
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
} else {
|
||||
self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||
}
|
||||
displayUndo = true
|
||||
self.originalRemainingSeconds = 5
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
|
Loading…
x
Reference in New Issue
Block a user