Folder improvements

This commit is contained in:
Ali 2020-03-10 03:16:32 +05:30
parent 7841759125
commit ecc32313a1
13 changed files with 442 additions and 126 deletions

View File

@ -5352,7 +5352,7 @@ Any member of this group will be able to see messages in the channel.";
"ChatList.EmptyChatFilterList" = "No chats currently\nmatch this filter."; "ChatList.EmptyChatFilterList" = "No chats currently\nmatch this filter.";
"ChatList.EmptyChatListNewMessage" = "New Message"; "ChatList.EmptyChatListNewMessage" = "New Message";
"ChatList.EmptyChatListEditFilter" = "Edit Filter"; "ChatList.EmptyChatListEditFilter" = "Edit Folder";
"Stats.Overview" = "OVERVIEW"; "Stats.Overview" = "OVERVIEW";
"Stats.Followers" = "Followers"; "Stats.Followers" = "Followers";

View File

@ -524,7 +524,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
} }
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
//TODO:localization //TODO:localization
items.append(.action(ContextMenuActionItem(text: "Edit Filter", icon: { theme in items.append(.action(ContextMenuActionItem(text: "Edit Folder", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { c, f in }, action: { c, f in
c.dismiss(completion: { c.dismiss(completion: {
@ -2376,44 +2376,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
items.append(.separator) items.append(.separator)
for preset in presetList { for preset in presetList {
enum ChatListFilterType { let filterType = chatListFilterType(preset)
case generic
case unmuted
case unread
case channels
case groups
case bots
case contacts
case nonContacts
}
let filterType: ChatListFilterType
if preset.data.includePeers.isEmpty {
if preset.data.categories == .all {
if preset.data.excludeRead {
filterType = .unread
} else if preset.data.excludeMuted {
filterType = .unmuted
} else {
filterType = .generic
}
} else {
if preset.data.categories == .channels {
filterType = .channels
} else if preset.data.categories == .groups {
filterType = .groups
} else if preset.data.categories == .bots {
filterType = .bots
} else if preset.data.categories == .contacts {
filterType = .contacts
} else if preset.data.categories == .nonContacts {
filterType = .nonContacts
} else {
filterType = .generic
}
}
} else {
filterType = .generic
}
var badge = "" var badge = ""
for item in filterItems { for item in filterItems {
if item.0.id == preset.id && item.1 != 0 { if item.0.id == preset.id && item.1 != 0 {

View File

@ -49,17 +49,240 @@ enum ChatListContainerNodeFilter: Equatable {
} }
} }
private final class ShimmerEffectNode: ASDisplayNode {
private var currentBackgroundColor: UIColor?
private var currentForegroundColor: UIColor?
private let imageNodeContainer: ASDisplayNode
private let imageNode: ASImageNode
private var absoluteLocation: (CGRect, CGSize)?
private var isCurrentlyInHierarchy = false
private var shouldBeAnimating = false
override init() {
self.imageNodeContainer = ASDisplayNode()
self.imageNodeContainer.isLayerBacked = true
self.imageNode = ASImageNode()
self.imageNode.isLayerBacked = true
self.imageNode.displaysAsynchronously = false
self.imageNode.displayWithoutProcessing = true
self.imageNode.contentMode = .scaleToFill
super.init()
self.isLayerBacked = true
self.clipsToBounds = true
self.imageNodeContainer.addSubnode(self.imageNode)
self.addSubnode(self.imageNodeContainer)
}
override func didEnterHierarchy() {
super.didEnterHierarchy()
self.isCurrentlyInHierarchy = true
self.updateAnimation()
}
override func didExitHierarchy() {
super.didExitHierarchy()
self.isCurrentlyInHierarchy = false
self.updateAnimation()
}
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
return
}
self.currentBackgroundColor = backgroundColor
self.currentForegroundColor = foregroundColor
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
context.setFillColor(backgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: CGPoint(), size: size))
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
let peakColor = foregroundColor.cgColor
var locations: [CGFloat] = [0.0, 0.5, 1.0]
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
})
}
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
return
}
let sizeUpdated = self.absoluteLocation?.1 != containerSize
let frameUpdated = self.absoluteLocation?.0 != rect
self.absoluteLocation = (rect, containerSize)
if sizeUpdated {
if self.shouldBeAnimating {
self.imageNode.layer.removeAnimation(forKey: "shimmer")
self.addImageAnimation()
}
}
if frameUpdated {
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
}
self.updateAnimation()
}
private func updateAnimation() {
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
if shouldBeAnimating != self.shouldBeAnimating {
self.shouldBeAnimating = shouldBeAnimating
if shouldBeAnimating {
self.addImageAnimation()
} else {
self.imageNode.layer.removeAnimation(forKey: "shimmer")
}
}
}
private func addImageAnimation() {
guard let containerSize = self.absoluteLocation?.1 else {
return
}
let gradientHeight: CGFloat = 250.0
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
animation.repeatCount = Float.infinity
animation.beginTime = 1.0
self.imageNode.layer.add(animation, forKey: "shimmer")
}
}
private final class ChatListShimmerNode: ASDisplayNode {
private let backgroundColorNode: ASDisplayNode
private let effectNode: ShimmerEffectNode
private let maskNode: ASImageNode
private var currentParams: (size: CGSize, presentationData: PresentationData)?
override init() {
self.backgroundColorNode = ASDisplayNode()
self.effectNode = ShimmerEffectNode()
self.maskNode = ASImageNode()
super.init()
self.isUserInteractionEnabled = false
self.addSubnode(self.backgroundColorNode)
self.addSubnode(self.effectNode)
self.addSubnode(self.maskNode)
}
func update(context: AccountContext, size: CGSize, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData {
self.currentParams = (size, presentationData)
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
let peer1 = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 1), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
let timestamp1: Int32 = 100000
let peers = SimpleDictionary<PeerId, Peer>()
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
gesture?.cancel()
}, present: { _ in })
let items = (0 ..< 2).map { _ -> ChatListItem in
return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
}
var itemNodes: [ChatListItemNode] = []
for i in 0 ..< items.count {
items[i].nodeConfiguredForParams(async: { f in f() }, params: ListViewItemLayoutParams(width: size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 100.0), synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: (i == items.count - 1) ? nil : items[i + 1], completion: { node, apply in
if let itemNode = node as? ChatListItemNode {
itemNodes.append(itemNode)
}
apply().1(ListViewItemApply(isOnScreen: true))
})
}
self.backgroundColorNode.backgroundColor = presentationData.theme.list.mediaPlaceholderColor
self.maskNode.image = generateImage(size, rotatedContext: { size, context in
context.setFillColor(presentationData.theme.chatList.backgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
var currentY: CGFloat = 0.0
let fakeLabelPlaceholderHeight: CGFloat = 8.0
func fillLabelPlaceholderRect(origin: CGPoint, width: CGFloat) {
let startPoint = origin
let diameter = fakeLabelPlaceholderHeight
context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter)))
context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter)))
}
while currentY < size.height {
let sampleIndex = 0
let itemHeight: CGFloat = itemNodes[sampleIndex].contentSize.height
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY))
let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0)
let dateFrame = itemNodes[sampleIndex].dateNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0)
context.setBlendMode(.normal)
context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor)
context.fill(itemNodes[sampleIndex].separatorNode.frame.offsetBy(dx: 0.0, dy: currentY))
currentY += itemHeight
}
})
self.effectNode.update(backgroundColor: presentationData.theme.list.mediaPlaceholderColor, foregroundColor: presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4))
self.effectNode.updateAbsoluteRect(CGRect(origin: CGPoint(), size: size), within: size)
}
transition.updateFrame(node: self.backgroundColorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
}
}
private final class ChatListContainerItemNode: ASDisplayNode { private final class ChatListContainerItemNode: ASDisplayNode {
private let context: AccountContext
private var presentationData: PresentationData private var presentationData: PresentationData
private let becameEmpty: (ChatListFilter?) -> Void private let becameEmpty: (ChatListFilter?) -> Void
private let emptyAction: (ChatListFilter?) -> Void private let emptyAction: (ChatListFilter?) -> Void
var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
private var floatingHeaderOffset: CGFloat?
private var emptyNode: ChatListEmptyNode? private var emptyNode: ChatListEmptyNode?
var emptyShimmerEffectNode: ChatListShimmerNode?
let listNode: ChatListNode let listNode: ChatListNode
private var validLayout: (CGSize, UIEdgeInsets, CGFloat)? private var validLayout: (CGSize, UIEdgeInsets, CGFloat)?
init(context: AccountContext, groupId: PeerGroupId, filter: ChatListFilter?, previewing: Bool, presentationData: PresentationData, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void) { init(context: AccountContext, groupId: PeerGroupId, filter: ChatListFilter?, previewing: Bool, presentationData: PresentationData, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void) {
self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.becameEmpty = becameEmpty self.becameEmpty = becameEmpty
self.emptyAction = emptyAction self.emptyAction = emptyAction
@ -74,23 +297,35 @@ private final class ChatListContainerItemNode: ASDisplayNode {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
var needsShimmerNode = false
switch isEmptyState { switch isEmptyState {
case let .empty(isLoading): case let .empty(isLoading):
if let currentNode = strongSelf.emptyNode { if isLoading {
currentNode.updateIsLoading(isLoading) needsShimmerNode = true
} else {
let emptyNode = ChatListEmptyNode(isFilter: filter != nil, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: { if let emptyNode = strongSelf.emptyNode {
self?.emptyAction(filter) strongSelf.emptyNode = nil
}) transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
strongSelf.emptyNode = emptyNode emptyNode?.removeFromSupernode()
strongSelf.addSubnode(emptyNode) })
if let (size, insets, _) = strongSelf.validLayout { }
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) } else {
emptyNode.frame = emptyNodeFrame if let currentNode = strongSelf.emptyNode {
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate) currentNode.updateIsLoading(isLoading)
} else {
let emptyNode = ChatListEmptyNode(isFilter: filter != nil, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: {
self?.emptyAction(filter)
})
strongSelf.emptyNode = emptyNode
strongSelf.addSubnode(emptyNode)
if let (size, insets, _) = strongSelf.validLayout {
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
emptyNode.frame = emptyNodeFrame
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate)
}
emptyNode.alpha = 0.0
transition.updateAlpha(node: emptyNode, alpha: 1.0)
} }
emptyNode.alpha = 0.0
transition.updateAlpha(node: emptyNode, alpha: 1.0)
} }
if !isLoading { if !isLoading {
strongSelf.becameEmpty(filter) strongSelf.becameEmpty(filter)
@ -103,7 +338,45 @@ private final class ChatListContainerItemNode: ASDisplayNode {
}) })
} }
} }
if needsShimmerNode {
if strongSelf.emptyShimmerEffectNode == nil {
let emptyShimmerEffectNode = ChatListShimmerNode()
strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode
strongSelf.addSubnode(emptyShimmerEffectNode)
if let (size, insets, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset {
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: .immediate)
}
}
} else if let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
strongSelf.emptyShimmerEffectNode = nil
let emptyNodeTransition = transition.isAnimated ? transition : .animated(duration: 0.3, curve: .easeInOut)
emptyNodeTransition.updateAlpha(node: emptyShimmerEffectNode, alpha: 0.0, completion: { [weak emptyShimmerEffectNode] _ in
emptyShimmerEffectNode?.removeFromSupernode()
})
}
} }
self.listNode.contentOffsetChanged = { [weak self] offset in
guard let strongSelf = self else {
return
}
strongSelf.contentOffsetChanged?(offset)
}
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
guard let strongSelf = self else {
return
}
strongSelf.floatingHeaderOffset = offset
if let (size, insets, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: transition)
}
}
}
private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) {
node.update(context: self.context, size: size, presentationData: self.presentationData, transition: .immediate)
transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size))
} }
func updatePresentationData(_ presentationData: PresentationData) { func updatePresentationData(_ presentationData: PresentationData) {
@ -178,16 +451,16 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private func applyItemNodeAsCurrent(id: ChatListFilterTabEntryId, itemNode: ChatListContainerItemNode) { private func applyItemNodeAsCurrent(id: ChatListFilterTabEntryId, itemNode: ChatListContainerItemNode) {
if let previousItemNode = self.currentItemNodeValue { if let previousItemNode = self.currentItemNodeValue {
previousItemNode.listNode.activateSearch = nil previousItemNode.listNode.activateSearch = nil
previousItemNode.listNode.presentAlert = nil previousItemNode.listNode.presentAlert = nil
previousItemNode.listNode.present = nil previousItemNode.listNode.present = nil
previousItemNode.listNode.toggleArchivedFolderHiddenByDefault = nil previousItemNode.listNode.toggleArchivedFolderHiddenByDefault = nil
previousItemNode.listNode.deletePeerChat = nil previousItemNode.listNode.deletePeerChat = nil
previousItemNode.listNode.peerSelected = nil previousItemNode.listNode.peerSelected = nil
previousItemNode.listNode.groupSelected = nil previousItemNode.listNode.groupSelected = nil
previousItemNode.listNode.updatePeerGrouping = nil previousItemNode.listNode.updatePeerGrouping = nil
previousItemNode.listNode.contentOffsetChanged = nil previousItemNode.listNode.contentOffsetChanged = nil
previousItemNode.listNode.contentScrollingEnded = nil previousItemNode.listNode.contentScrollingEnded = nil
previousItemNode.listNode.activateChatPreview = nil previousItemNode.listNode.activateChatPreview = nil
previousItemNode.listNode.addedVisibleChatsWithPeerIds = nil previousItemNode.listNode.addedVisibleChatsWithPeerIds = nil
previousItemNode.accessibilityElementsHidden = true previousItemNode.accessibilityElementsHidden = true
@ -219,7 +492,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
itemNode.listNode.updatePeerGrouping = { [weak self] peerId, group in itemNode.listNode.updatePeerGrouping = { [weak self] peerId, group in
self?.updatePeerGrouping?(peerId, group) self?.updatePeerGrouping?(peerId, group)
} }
itemNode.listNode.contentOffsetChanged = { [weak self] offset in itemNode.contentOffsetChanged = { [weak self] offset in
self?.contentOffsetChanged?(offset) self?.contentOffsetChanged?(offset)
} }
itemNode.listNode.contentScrollingEnded = { [weak self] listView in itemNode.listNode.contentScrollingEnded = { [weak self] listView in

View File

@ -314,6 +314,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.updateState { current in arguments.updateState { current in
var state = current var state = current
state.name = value state.name = value
state.changedName = true
return state return state
} }
}, action: {}, cleared: { }, action: {}, cleared: {
@ -389,6 +390,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
private struct ChatListFilterPresetControllerState: Equatable { private struct ChatListFilterPresetControllerState: Equatable {
var name: String var name: String
var changedName: Bool
var includeCategories: ChatListFilterPeerCategories var includeCategories: ChatListFilterPeerCategories
var excludeMuted: Bool var excludeMuted: Bool
var excludeRead: Bool var excludeRead: Bool
@ -431,8 +433,8 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
entries.append(.screenHeader) entries.append(.screenHeader)
} }
entries.append(.nameHeader("FILTER NAME")) entries.append(.nameHeader("FOLDER NAME"))
entries.append(.name(placeholder: "Filter Name", value: state.name)) entries.append(.name(placeholder: "Folder Name", value: state.name))
entries.append(.includePeersHeader("INCLUDED CHATS")) entries.append(.includePeersHeader("INCLUDED CHATS"))
entries.append(.addIncludePeer(title: "Add Chats")) entries.append(.addIncludePeer(title: "Add Chats"))
@ -683,6 +685,49 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
return controller return controller
} }
enum ChatListFilterType {
case generic
case unmuted
case unread
case channels
case groups
case bots
case contacts
case nonContacts
}
func chatListFilterType(_ filter: ChatListFilter) -> ChatListFilterType {
let filterType: ChatListFilterType
if filter.data.includePeers.isEmpty {
if filter.data.categories == .all {
if filter.data.excludeRead {
filterType = .unread
} else if filter.data.excludeMuted {
filterType = .unmuted
} else {
filterType = .generic
}
} else {
if filter.data.categories == .channels {
filterType = .channels
} else if filter.data.categories == .groups {
filterType = .groups
} else if filter.data.categories == .bots {
filterType = .bots
} else if filter.data.categories == .contacts {
filterType = .contacts
} else if filter.data.categories == .nonContacts {
filterType = .nonContacts
} else {
filterType = .generic
}
}
} else {
filterType = .generic
}
return filterType
}
func chatListFilterPresetController(context: AccountContext, currentPreset: ChatListFilter?, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController { func chatListFilterPresetController(context: AccountContext, currentPreset: ChatListFilter?, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController {
let initialName: String let initialName: String
if let currentPreset = currentPreset { if let currentPreset = currentPreset {
@ -690,11 +735,35 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
} else { } else {
initialName = "New Folder" initialName = "New Folder"
} }
let initialState = ChatListFilterPresetControllerState(name: initialName, includeCategories: currentPreset?.data.categories ?? [], excludeMuted: currentPreset?.data.excludeMuted ?? false, excludeRead: currentPreset?.data.excludeRead ?? false, excludeArchived: currentPreset?.data.excludeArchived ?? false, additionallyIncludePeers: currentPreset?.data.includePeers ?? [], additionallyExcludePeers: currentPreset?.data.excludePeers ?? []) let initialState = ChatListFilterPresetControllerState(name: initialName, changedName: currentPreset != nil, includeCategories: currentPreset?.data.categories ?? [], excludeMuted: currentPreset?.data.excludeMuted ?? false, excludeRead: currentPreset?.data.excludeRead ?? false, excludeArchived: currentPreset?.data.excludeArchived ?? false, additionallyIncludePeers: currentPreset?.data.includePeers ?? [], additionallyExcludePeers: currentPreset?.data.excludePeers ?? [])
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)
let statePromise = ValuePromise(initialState, ignoreRepeated: true) let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void = { f in let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) }) statePromise.set(stateValue.modify { current in
var state = f(current)
if !state.changedName {
let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers))
switch chatListFilterType(filter) {
case .generic:
state.name = initialName
case .unmuted:
state.name = "Not Muted"
case .unread:
state.name = "Unread"
case .channels:
state.name = "Channels"
case .groups:
state.name = "Groups"
case .bots:
state.name = "Bots"
case .contacts:
state.name = "Contacts"
case .nonContacts:
state.name = "Non-Contacts"
}
}
return state
})
} }
var skipStateAnimation = false var skipStateAnimation = false

View File

@ -1364,20 +1364,37 @@ public final class ChatListNode: ListView {
} }
var isEmpty = false var isEmpty = false
var isLoading = false
if transition.chatListView.filteredEntries.isEmpty { if transition.chatListView.filteredEntries.isEmpty {
isEmpty = true isEmpty = true
} else { } else {
if transition.chatListView.filteredEntries.count <= 2 { if transition.chatListView.filteredEntries.count <= 2 {
isEmpty = true isEmpty = true
loop: for entry in transition.chatListView.filteredEntries { loop1: for entry in transition.chatListView.filteredEntries {
switch entry { switch entry {
case .GroupReferenceEntry, .HeaderEntry: case .GroupReferenceEntry, .HeaderEntry, .HoleEntry:
break break
default: default:
isEmpty = false isEmpty = false
break loop break loop1
} }
} }
isLoading = true
var hasHoles = false
loop2: for entry in transition.chatListView.filteredEntries {
switch entry {
case .HoleEntry:
hasHoles = true
case .HeaderEntry:
break
default:
isLoading = false
break loop2
}
}
if !hasHoles {
isLoading = false
}
} }
} }
@ -1385,7 +1402,7 @@ public final class ChatListNode: ListView {
if transition.chatListView.isLoading { if transition.chatListView.isLoading {
isEmptyState = .empty(isLoading: true) isEmptyState = .empty(isLoading: true)
} else if isEmpty { } else if isEmpty {
isEmptyState = .empty(isLoading: false) isEmptyState = .empty(isLoading: isLoading)
} else { } else {
var containsChats = false var containsChats = false
loop: for entry in transition.chatListView.filteredEntries { loop: for entry in transition.chatListView.filteredEntries {

View File

@ -305,7 +305,7 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: updatedMessage, readState: updatedCombinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false, hasFailedMessages: hasFailed, isContact: isContact)) result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: updatedMessage, readState: updatedCombinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false, hasFailedMessages: hasFailed, isContact: isContact))
case let .HoleEntry(hole): case let .HoleEntry(hole):
if hole.index.timestamp == Int32.max - 1 { if hole.index.timestamp == Int32.max - 1 {
return ([], true) return ([.HeaderEntry], true)
} }
result.append(.HoleEntry(hole, theme: state.presentationData.theme)) result.append(.HoleEntry(hole, theme: state.presentationData.theme))
} }
@ -357,9 +357,9 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
} }
if result.count >= 1, case .HoleEntry = result[result.count - 1] { if result.count >= 1, case .HoleEntry = result[result.count - 1] {
return ([], true) return ([.HeaderEntry], true)
} else if result.count == 1, case .HoleEntry = result[0] { } else if result.count == 1, case .HoleEntry = result[0] {
return ([], true) return ([.HeaderEntry], true)
} }
return (result, false) return (result, false)
} }

View File

@ -89,7 +89,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
tags.append(.nonContact) tags.append(.nonContact)
} }
if filter.data.categories.contains(.groups) { if filter.data.categories.contains(.groups) {
tags.append(.smallGroup) tags.append(.group)
} }
if filter.data.categories.contains(.bots) { if filter.data.categories.contains(.bots) {
tags.append(.bot) tags.append(.bot)

View File

@ -156,12 +156,12 @@ private final class TabBarItemNode: ASDisplayNode {
transition.updateAlpha(node: strongSelf.contextTextImageNode, alpha: isExtracted ? 1.0 : 0.0) transition.updateAlpha(node: strongSelf.contextTextImageNode, alpha: isExtracted ? 1.0 : 0.0)
} }
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:))) /*let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
leftSwipe.direction = .left leftSwipe.direction = .left
self.containerNode.view.addGestureRecognizer(leftSwipe) self.containerNode.view.addGestureRecognizer(leftSwipe)
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:))) let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
rightSwipe.direction = .right rightSwipe.direction = .right
self.containerNode.view.addGestureRecognizer(rightSwipe) self.containerNode.view.addGestureRecognizer(rightSwipe)*/
} }
@objc private func swipeGesture(_ gesture: UISwipeGestureRecognizer) { @objc private func swipeGesture(_ gesture: UISwipeGestureRecognizer) {

View File

@ -275,7 +275,7 @@ private final class ChatListViewSpaceState {
} }
} }
if !transaction.currentUpdatedPeerNotificationSettings.isEmpty, let filterPredicate = self.filterPredicate, case let .group(groupId, _) = self.space { if !transaction.currentUpdatedPeerNotificationSettings.isEmpty, let filterPredicate = self.filterPredicate, case let .group(groupId, pinned) = self.space {
var removeEntryIndices: [MutableChatListEntryIndex] = [] var removeEntryIndices: [MutableChatListEntryIndex] = []
let _ = self.orderedEntries.mutableScan { entry in let _ = self.orderedEntries.mutableScan { entry in
let entryPeer: Peer let entryPeer: Peer
@ -333,16 +333,19 @@ private final class ChatListViewSpaceState {
if wasIncluded != isIncluded { if wasIncluded != isIncluded {
if isIncluded { if isIncluded {
for peer in peers { for peer in peers {
let tableEntry: ChatListIntermediateEntry? let tableEntry = postbox.chatListTable.getEntry(groupId: groupId, peerId: peer.id, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable)
tableEntry = postbox.chatListTable.getEntry(peerId: peer.id, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable)
if let entry = tableEntry { if let entry = tableEntry {
switch entry { if pinned.include == (entry.index.pinningIndex != nil) {
case let .message(index, messageIndex): if self.orderedEntries.indicesForPeerId(peer.id) == nil {
if self.add(entry: .IntermediateMessageEntry(index: index, messageIndex: messageIndex)) { switch entry {
hasUpdates = true case let .message(index, messageIndex):
if self.add(entry: .IntermediateMessageEntry(index: index, messageIndex: messageIndex)) {
hasUpdates = true
}
default:
break
}
} }
default:
break
} }
} }
} }
@ -980,7 +983,27 @@ struct ChatListViewState {
for i in 0 ..< allIndicesSorted.count { for i in 0 ..< allIndicesSorted.count {
assert(allIndicesSorted[i] == allIndices[i]) assert(allIndicesSorted[i] == allIndices[i])
} }
assert(Set(allIndices).count == allIndices.count)
if Set(allIndices).count != allIndices.count {
var seenIndices = Set<MutableChatListEntryIndex>()
var updatedResult: [(ChatListViewSpace, MutableChatListEntry)] = []
for item in result {
if !seenIndices.contains(item.1.entryIndex) {
seenIndices.insert(item.1.entryIndex)
updatedResult.append(item)
}
}
result = updatedResult
let allIndices = result.map { $0.1.entryIndex }
let allIndicesSorted = allIndices.sorted()
for i in 0 ..< allIndicesSorted.count {
assert(allIndicesSorted[i] == allIndices[i])
}
assert(Set(allIndices).count == allIndices.count)
assert(false)
}
var sampledHoleIndex: Int? var sampledHoleIndex: Int?
if !sampledHoleIndices.isEmpty { if !sampledHoleIndices.isEmpty {

View File

@ -27,27 +27,21 @@ private struct CounterTagSettings: OptionSet {
init(summaryTags: PeerSummaryCounterTags) { init(summaryTags: PeerSummaryCounterTags) {
var result = CounterTagSettings() var result = CounterTagSettings()
if summaryTags.contains(.contact) { if summaryTags.contains(.contact) {
result.insert(.regularChatsAndPrivateGroups) result.insert(.regularChatsAndGroups)
} }
if summaryTags.contains(.channel) { if summaryTags.contains(.channel) {
result.insert(.channels) result.insert(.channels)
} }
if summaryTags.contains(.largeGroup) {
result.insert(.publicGroups)
}
self = result self = result
} }
func toSumaryTags() -> PeerSummaryCounterTags { func toSumaryTags() -> PeerSummaryCounterTags {
var result = PeerSummaryCounterTags() var result = PeerSummaryCounterTags()
if self.contains(.regularChatsAndPrivateGroups) { if self.contains(.regularChatsAndGroups) {
result.insert(.contact) result.insert(.contact)
result.insert(.nonContact) result.insert(.nonContact)
result.insert(.bot) result.insert(.bot)
result.insert(.smallGroup) result.insert(.group)
}
if self.contains(.publicGroups) {
result.insert(.largeGroup)
} }
if self.contains(.channels) { if self.contains(.channels) {
result.insert(.channel) result.insert(.channel)
@ -55,9 +49,8 @@ private struct CounterTagSettings: OptionSet {
return result return result
} }
static let regularChatsAndPrivateGroups = CounterTagSettings(rawValue: 1 << 0) static let regularChatsAndGroups = CounterTagSettings(rawValue: 1 << 0)
static let publicGroups = CounterTagSettings(rawValue: 1 << 1) static let channels = CounterTagSettings(rawValue: 1 << 1)
static let channels = CounterTagSettings(rawValue: 1 << 2)
} }
private final class NotificationsAndSoundsArguments { private final class NotificationsAndSoundsArguments {
@ -154,7 +147,6 @@ public enum NotificationsAndSoundsEntryTag: ItemListItemTag {
case inAppVibrate case inAppVibrate
case inAppPreviews case inAppPreviews
case displayNamesOnLockscreen case displayNamesOnLockscreen
case includePublicGroups
case includeChannels case includeChannels
case unreadCountCategory case unreadCountCategory
case joinedNotifications case joinedNotifications
@ -208,7 +200,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
case displayNamesOnLockscreenInfo(PresentationTheme, String) case displayNamesOnLockscreenInfo(PresentationTheme, String)
case badgeHeader(PresentationTheme, String) case badgeHeader(PresentationTheme, String)
case includePublicGroups(PresentationTheme, String, Bool)
case includeChannels(PresentationTheme, String, Bool) case includeChannels(PresentationTheme, String, Bool)
case unreadCountCategory(PresentationTheme, String, Bool) case unreadCountCategory(PresentationTheme, String, Bool)
case unreadCountCategoryInfo(PresentationTheme, String) case unreadCountCategoryInfo(PresentationTheme, String)
@ -235,7 +226,7 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
return NotificationsAndSoundsSection.inApp.rawValue return NotificationsAndSoundsSection.inApp.rawValue
case .displayNamesOnLockscreen, .displayNamesOnLockscreenInfo: case .displayNamesOnLockscreen, .displayNamesOnLockscreenInfo:
return NotificationsAndSoundsSection.displayNamesOnLockscreen.rawValue return NotificationsAndSoundsSection.displayNamesOnLockscreen.rawValue
case .badgeHeader, .includePublicGroups, .includeChannels, .unreadCountCategory, .unreadCountCategoryInfo: case .badgeHeader, .includeChannels, .unreadCountCategory, .unreadCountCategoryInfo:
return NotificationsAndSoundsSection.badge.rawValue return NotificationsAndSoundsSection.badge.rawValue
case .joinedNotifications, .joinedNotificationsInfo: case .joinedNotifications, .joinedNotificationsInfo:
return NotificationsAndSoundsSection.joinedNotifications.rawValue return NotificationsAndSoundsSection.joinedNotifications.rawValue
@ -306,8 +297,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
return 28 return 28
case .badgeHeader: case .badgeHeader:
return 29 return 29
case .includePublicGroups:
return 31
case .includeChannels: case .includeChannels:
return 32 return 32
case .unreadCountCategory: case .unreadCountCategory:
@ -349,8 +338,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
return NotificationsAndSoundsEntryTag.inAppPreviews return NotificationsAndSoundsEntryTag.inAppPreviews
case .displayNamesOnLockscreen: case .displayNamesOnLockscreen:
return NotificationsAndSoundsEntryTag.displayNamesOnLockscreen return NotificationsAndSoundsEntryTag.displayNamesOnLockscreen
case .includePublicGroups:
return NotificationsAndSoundsEntryTag.includePublicGroups
case .includeChannels: case .includeChannels:
return NotificationsAndSoundsEntryTag.includeChannels return NotificationsAndSoundsEntryTag.includeChannels
case .unreadCountCategory: case .unreadCountCategory:
@ -546,12 +533,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .includePublicGroups(lhsTheme, lhsText, lhsValue):
if case let .includePublicGroups(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .includeChannels(lhsTheme, lhsText, lhsValue): case let .includeChannels(lhsTheme, lhsText, lhsValue):
if case let .includeChannels(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .includeChannels(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true return true
@ -719,10 +700,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
}) })
case let .badgeHeader(theme, text): case let .badgeHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .includePublicGroups(theme, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateIncludeTag(.publicGroups, updatedValue)
}, tag: self.tag)
case let .includeChannels(theme, text, value): case let .includeChannels(theme, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateIncludeTag(.channels, updatedValue) arguments.updateIncludeTag(.channels, updatedValue)
@ -825,7 +802,6 @@ private func notificationsAndSoundsEntries(authorizationStatus: AccessType, warn
let counterTagSettings = CounterTagSettings(summaryTags: inAppSettings.totalUnreadCountIncludeTags) let counterTagSettings = CounterTagSettings(summaryTags: inAppSettings.totalUnreadCountIncludeTags)
entries.append(.includePublicGroups(presentationData.theme, presentationData.strings.Notifications_Badge_IncludePublicGroups, counterTagSettings.contains(.publicGroups)))
entries.append(.includeChannels(presentationData.theme, presentationData.strings.Notifications_Badge_IncludeChannels, counterTagSettings.contains(.channels))) entries.append(.includeChannels(presentationData.theme, presentationData.strings.Notifications_Badge_IncludeChannels, counterTagSettings.contains(.channels)))
entries.append(.unreadCountCategory(presentationData.theme, presentationData.strings.Notifications_Badge_CountUnreadMessages, inAppSettings.totalUnreadCountDisplayCategory == .messages)) entries.append(.unreadCountCategory(presentationData.theme, presentationData.strings.Notifications_Badge_CountUnreadMessages, inAppSettings.totalUnreadCountDisplayCategory == .messages))
entries.append(.unreadCountCategoryInfo(presentationData.theme, inAppSettings.totalUnreadCountDisplayCategory == .chats ? presentationData.strings.Notifications_Badge_CountUnreadMessages_InfoOff : presentationData.strings.Notifications_Badge_CountUnreadMessages_InfoOn)) entries.append(.unreadCountCategoryInfo(presentationData.theme, inAppSettings.totalUnreadCountDisplayCategory == .chats ? presentationData.strings.Notifications_Badge_CountUnreadMessages_InfoOff : presentationData.strings.Notifications_Badge_CountUnreadMessages_InfoOn))

View File

@ -409,9 +409,6 @@ private func notificationSearchableItems(context: AccountContext, settings: Glob
SettingsSearchableItem(id: .notifications(16), title: strings.Notifications_DisplayNamesOnLockScreen, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, _, present in SettingsSearchableItem(id: .notifications(16), title: strings.Notifications_DisplayNamesOnLockScreen, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, _, present in
presentNotificationSettings(context, present, .displayNamesOnLockscreen) presentNotificationSettings(context, present, .displayNamesOnLockscreen)
}), }),
SettingsSearchableItem(id: .notifications(18), title: strings.Notifications_Badge_IncludePublicGroups, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedPublicGroups), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge], present: { context, _, present in
presentNotificationSettings(context, present, .includePublicGroups)
}),
SettingsSearchableItem(id: .notifications(19), title: strings.Notifications_Badge_IncludeChannels, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChannels), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge], present: { context, _, present in SettingsSearchableItem(id: .notifications(19), title: strings.Notifications_Badge_IncludeChannels, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChannels), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge], present: { context, _, present in
presentNotificationSettings(context, present, .includeChannels) presentNotificationSettings(context, present, .includeChannels)
}), }),

View File

@ -707,7 +707,7 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
} }
if enableFilters { if enableFilters {
//TODO:localize //TODO:localize
entries.append(.filters(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/ChatListFilters")?.precomposed(), "Chat Filters", "")) entries.append(.filters(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/ChatListFilters")?.precomposed(), "Chat Folders", ""))
} }
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed) let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)

View File

@ -68,11 +68,9 @@ public struct InAppNotificationSettings: PreferencesEntry, Equatable {
resultTags.insert(.contact) resultTags.insert(.contact)
resultTags.insert(.nonContact) resultTags.insert(.nonContact)
resultTags.insert(.bot) resultTags.insert(.bot)
resultTags.insert(.smallGroup) resultTags.insert(.group)
resultTags.insert(.largeGroup)
} else if legacyTag == .publicGroups { } else if legacyTag == .publicGroups {
resultTags.insert(.smallGroup) resultTags.insert(.group)
resultTags.insert(.largeGroup)
} else if legacyTag == .channels { } else if legacyTag == .channels {
resultTags.insert(.channel) resultTags.insert(.channel)
} }