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.EmptyChatListNewMessage" = "New Message";
"ChatList.EmptyChatListEditFilter" = "Edit Filter";
"ChatList.EmptyChatListEditFilter" = "Edit Folder";
"Stats.Overview" = "OVERVIEW";
"Stats.Followers" = "Followers";

View File

@ -524,7 +524,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
var items: [ContextMenuItem] = []
//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)
}, action: { c, f in
c.dismiss(completion: {
@ -2376,44 +2376,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
items.append(.separator)
for preset in presetList {
enum ChatListFilterType {
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
}
let filterType = chatListFilterType(preset)
var badge = ""
for item in filterItems {
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 let context: AccountContext
private var presentationData: PresentationData
private let becameEmpty: (ChatListFilter?) -> Void
private let emptyAction: (ChatListFilter?) -> Void
var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
private var floatingHeaderOffset: CGFloat?
private var emptyNode: ChatListEmptyNode?
var emptyShimmerEffectNode: ChatListShimmerNode?
let listNode: ChatListNode
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) {
self.context = context
self.presentationData = presentationData
self.becameEmpty = becameEmpty
self.emptyAction = emptyAction
@ -74,23 +297,35 @@ private final class ChatListContainerItemNode: ASDisplayNode {
guard let strongSelf = self else {
return
}
var needsShimmerNode = false
switch isEmptyState {
case let .empty(isLoading):
if let currentNode = strongSelf.emptyNode {
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)
if isLoading {
needsShimmerNode = true
if let emptyNode = strongSelf.emptyNode {
strongSelf.emptyNode = nil
transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
emptyNode?.removeFromSupernode()
})
}
} else {
if let currentNode = strongSelf.emptyNode {
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 {
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) {
@ -178,16 +451,16 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private func applyItemNodeAsCurrent(id: ChatListFilterTabEntryId, itemNode: ChatListContainerItemNode) {
if let previousItemNode = self.currentItemNodeValue {
previousItemNode.listNode.activateSearch = nil
previousItemNode.listNode.presentAlert = nil
previousItemNode.listNode.present = nil
previousItemNode.listNode.toggleArchivedFolderHiddenByDefault = nil
previousItemNode.listNode.deletePeerChat = nil
previousItemNode.listNode.presentAlert = nil
previousItemNode.listNode.present = nil
previousItemNode.listNode.toggleArchivedFolderHiddenByDefault = nil
previousItemNode.listNode.deletePeerChat = nil
previousItemNode.listNode.peerSelected = nil
previousItemNode.listNode.groupSelected = nil
previousItemNode.listNode.updatePeerGrouping = nil
previousItemNode.listNode.contentOffsetChanged = nil
previousItemNode.listNode.contentScrollingEnded = nil
previousItemNode.listNode.activateChatPreview = nil
previousItemNode.listNode.groupSelected = nil
previousItemNode.listNode.updatePeerGrouping = nil
previousItemNode.listNode.contentOffsetChanged = nil
previousItemNode.listNode.contentScrollingEnded = nil
previousItemNode.listNode.activateChatPreview = nil
previousItemNode.listNode.addedVisibleChatsWithPeerIds = nil
previousItemNode.accessibilityElementsHidden = true
@ -219,7 +492,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
itemNode.listNode.updatePeerGrouping = { [weak self] peerId, group in
self?.updatePeerGrouping?(peerId, group)
}
itemNode.listNode.contentOffsetChanged = { [weak self] offset in
itemNode.contentOffsetChanged = { [weak self] offset in
self?.contentOffsetChanged?(offset)
}
itemNode.listNode.contentScrollingEnded = { [weak self] listView in

View File

@ -314,6 +314,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.updateState { current in
var state = current
state.name = value
state.changedName = true
return state
}
}, action: {}, cleared: {
@ -389,6 +390,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
private struct ChatListFilterPresetControllerState: Equatable {
var name: String
var changedName: Bool
var includeCategories: ChatListFilterPeerCategories
var excludeMuted: Bool
var excludeRead: Bool
@ -431,8 +433,8 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
entries.append(.screenHeader)
}
entries.append(.nameHeader("FILTER NAME"))
entries.append(.name(placeholder: "Filter Name", value: state.name))
entries.append(.nameHeader("FOLDER NAME"))
entries.append(.name(placeholder: "Folder Name", value: state.name))
entries.append(.includePeersHeader("INCLUDED CHATS"))
entries.append(.addIncludePeer(title: "Add Chats"))
@ -683,6 +685,49 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
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 {
let initialName: String
if let currentPreset = currentPreset {
@ -690,11 +735,35 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
} else {
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 statePromise = ValuePromise(initialState, ignoreRepeated: true)
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

View File

@ -1364,20 +1364,37 @@ public final class ChatListNode: ListView {
}
var isEmpty = false
var isLoading = false
if transition.chatListView.filteredEntries.isEmpty {
isEmpty = true
} else {
if transition.chatListView.filteredEntries.count <= 2 {
isEmpty = true
loop: for entry in transition.chatListView.filteredEntries {
loop1: for entry in transition.chatListView.filteredEntries {
switch entry {
case .GroupReferenceEntry, .HeaderEntry:
case .GroupReferenceEntry, .HeaderEntry, .HoleEntry:
break
default:
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 {
isEmptyState = .empty(isLoading: true)
} else if isEmpty {
isEmptyState = .empty(isLoading: false)
isEmptyState = .empty(isLoading: isLoading)
} else {
var containsChats = false
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))
case let .HoleEntry(hole):
if hole.index.timestamp == Int32.max - 1 {
return ([], true)
return ([.HeaderEntry], true)
}
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] {
return ([], true)
return ([.HeaderEntry], true)
} else if result.count == 1, case .HoleEntry = result[0] {
return ([], true)
return ([.HeaderEntry], true)
}
return (result, false)
}

View File

@ -89,7 +89,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
tags.append(.nonContact)
}
if filter.data.categories.contains(.groups) {
tags.append(.smallGroup)
tags.append(.group)
}
if filter.data.categories.contains(.bots) {
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)
}
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
/*let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
leftSwipe.direction = .left
self.containerNode.view.addGestureRecognizer(leftSwipe)
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
rightSwipe.direction = .right
self.containerNode.view.addGestureRecognizer(rightSwipe)
self.containerNode.view.addGestureRecognizer(rightSwipe)*/
}
@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] = []
let _ = self.orderedEntries.mutableScan { entry in
let entryPeer: Peer
@ -333,16 +333,19 @@ private final class ChatListViewSpaceState {
if wasIncluded != isIncluded {
if isIncluded {
for peer in peers {
let tableEntry: ChatListIntermediateEntry?
tableEntry = postbox.chatListTable.getEntry(peerId: peer.id, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable)
let tableEntry = postbox.chatListTable.getEntry(groupId: groupId, peerId: peer.id, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable)
if let entry = tableEntry {
switch entry {
case let .message(index, messageIndex):
if self.add(entry: .IntermediateMessageEntry(index: index, messageIndex: messageIndex)) {
hasUpdates = true
if pinned.include == (entry.index.pinningIndex != nil) {
if self.orderedEntries.indicesForPeerId(peer.id) == nil {
switch entry {
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 {
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?
if !sampledHoleIndices.isEmpty {

View File

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

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
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
presentNotificationSettings(context, present, .includeChannels)
}),

View File

@ -707,7 +707,7 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
}
if enableFilters {
//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)

View File

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