Folder improvements

This commit is contained in:
Ali 2020-03-09 13:39:06 +04:00
parent d6243fb78b
commit 05ffb47c1c
18 changed files with 237 additions and 155 deletions

View File

@ -18,7 +18,7 @@ func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chat
var items: [ContextMenuItem] = []
if !transaction.getUnreadChatListPeerIds(groupId: groupId, filterPredicate: nil).isEmpty {
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { [weak chatListController] _, f in
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in
let _ = (context.account.postbox.transaction { transaction in
markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: nil)
}
@ -40,7 +40,7 @@ func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chat
}
enum ChatContextMenuSource {
case chatList
case chatList(isFilter: Bool)
case search(ChatListSearchContextActionSource)
}
@ -146,20 +146,22 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
})))
}
let isPinned = index.pinningIndex != nil
items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
let _ = (toggleItemPinned(postbox: context.account.postbox, groupId: group, itemId: .peer(peerId))
|> deliverOnMainQueue).start(next: { result in
switch result {
case .done:
break
case let .limitExceeded(maxCount):
break
//strongSelf.presentAlert?(strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").0)
}
f(.default)
})
})))
if case .chatList(false) = source {
let isPinned = index.pinningIndex != nil
items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
let _ = (toggleItemPinned(postbox: context.account.postbox, groupId: group, itemId: .peer(peerId))
|> deliverOnMainQueue).start(next: { result in
switch result {
case .done:
break
case let .limitExceeded(maxCount):
break
//strongSelf.presentAlert?(strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").0)
}
f(.default)
})
})))
}
if !isSavedMessages, let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings {
var isMuted = false

View File

@ -294,6 +294,28 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
let title = !state.selectedPeerIds.isEmpty ? strongSelf.presentationData.strings.ChatList_SelectedChats(Int32(state.selectedPeerIds.count)) : defaultTitle
strongSelf.titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false)
} else if isReorderingTabs {
if strongSelf.groupId == .root {
strongSelf.navigationItem.rightBarButtonItem = nil
}
let leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed))
strongSelf.navigationItem.leftBarButtonItem = leftBarButtonItem
let (_, connectsViaProxy) = proxy
switch networkState {
case .waitingForNetwork:
strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false)
case let .connecting(proxy):
var text = strongSelf.presentationData.strings.State_Connecting
if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 {
text = strongSelf.presentationData.strings.State_ConnectingToProxy
}
strongSelf.titleView.title = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false)
case .updating:
strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false)
case .online:
strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false)
}
} else {
var isRoot = false
if case .root = strongSelf.groupId {
@ -908,7 +930,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
case let .peer(peer):
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peer.peerId, source: .chatList, chatListController: strongSelf), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peer.peerId, source: .chatList(isFilter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil), chatListController: strongSelf), reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
}
@ -1148,7 +1170,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
} else {
text = "Hold to organize your chats with folders."
}
parentController.present(TooltipScreen(text: text, location: CGPoint(x: absoluteFrame.midX - 14.0, y: absoluteFrame.minY - 8.0)), in: .current)
parentController.present(TooltipScreen(text: text, location: CGPoint(x: absoluteFrame.midX - 14.0, y: absoluteFrame.minY - 8.0), shouldDismissOnTouch: { point in
guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else {
return true
}
if parentController.isPointInsideContentArea(point: point) {
return false
}
return true
}), in: .current)
}
})
}
@ -2450,6 +2480,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = true
let ignoreContentTouches: Bool = true
private let controller: ChatListController
private let sourceNode: ContextExtractedContentContainingNode
@ -2470,6 +2501,7 @@ private final class ChatListTabBarContextExtractedContentSource: ContextExtracte
private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false
let ignoreContentTouches: Bool = true
private let controller: ChatListController
private let sourceNode: ContextExtractedContentContainingNode

View File

@ -849,6 +849,44 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
}
var attemptNavigationImpl: (() -> Bool)?
var applyImpl: (() -> Void)? = {
let state = stateValue.with { $0 }
let preset = 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))
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var preset = preset
if currentPreset == nil {
preset.id = max(2, settings.filters.map({ $0.id + 1 }).max() ?? 2)
}
var settings = settings
if let _ = currentPreset {
var found = false
for i in 0 ..< settings.filters.count {
if settings.filters[i].id == preset.id {
settings.filters[i] = preset
found = true
}
}
if !found {
settings.filters = settings.filters.filter { listFilter in
if listFilter.title == preset.title && listFilter.data == preset.data {
return false
}
return true
}
settings.filters.append(preset)
}
} else {
settings.filters.append(preset)
}
return settings
})
|> deliverOnMainQueue).start(next: { settings in
updated(settings.filters)
dismissImpl?()
let _ = replaceRemoteChatListFilters(account: context.account).start()
})
}
let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData,
@ -864,42 +902,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
}
})
let rightNavigationButton = ItemListNavigationButton(content: .text(currentPreset == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: {
let state = stateValue.with { $0 }
let preset = 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))
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var preset = preset
if currentPreset == nil {
preset.id = max(2, settings.filters.map({ $0.id + 1 }).max() ?? 2)
}
var settings = settings
if let _ = currentPreset {
var found = false
for i in 0 ..< settings.filters.count {
if settings.filters[i].id == preset.id {
settings.filters[i] = preset
found = true
}
}
if !found {
settings.filters = settings.filters.filter { listFilter in
if listFilter.title == preset.title && listFilter.data == preset.data {
return false
}
return true
}
settings.filters.append(preset)
}
} else {
settings.filters.append(preset)
}
return settings
})
|> deliverOnMainQueue).start(next: { settings in
updated(settings.filters)
dismissImpl?()
let _ = replaceRemoteChatListFilters(account: context.account).start()
})
applyImpl?()
})
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(currentPreset != nil ? "Folder" : "Create Folder"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
@ -935,8 +938,12 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
}
let displaySaveAlert: () -> Void = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: nil, text: "Are you sure you want to discard this folder?", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
dismissImpl?()
presentControllerImpl?(textAlertController(context: context, title: nil, text: "You have changed the filter. Apply changes?", actions: [
TextAlertAction(type: .genericAction, title: "Discard", action: {
dismissImpl?()
}),
TextAlertAction(type: .defaultAction, title: "Apply", action: {
applyImpl?()
})]), nil)
}
attemptNavigationImpl = {

View File

@ -52,6 +52,7 @@ private enum ChatListFilterPresetListEntryStableId: Hashable {
case screenHeader
case suggestedListHeader
case suggestedPreset(ChatListFilterData)
case suggestedAddCustom
case listHeader
case preset(Int32)
case addItem
@ -70,6 +71,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
case screenHeader(String)
case suggestedListHeader(String)
case suggestedPreset(index: PresetIndex, title: String, label: String, preset: ChatListFilterData)
case suggestedAddCustom(String)
case listHeader(String)
case preset(index: PresetIndex, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool)
case addItem(text: String, isEditing: Bool)
@ -79,7 +81,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
switch self {
case .screenHeader:
return ChatListFilterPresetListSection.screenHeader.rawValue
case .suggestedListHeader, .suggestedPreset:
case .suggestedListHeader, .suggestedPreset, .suggestedAddCustom:
return ChatListFilterPresetListSection.suggested.rawValue
case .listHeader, .preset, .addItem, .listFooter:
return ChatListFilterPresetListSection.list.rawValue
@ -102,6 +104,8 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
return 1002
case let .suggestedPreset(suggestedPreset):
return 1003 + suggestedPreset.index.value
case .suggestedAddCustom:
return 2000
}
}
@ -113,6 +117,8 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
return .suggestedListHeader
case let .suggestedPreset(suggestedPreset):
return .suggestedPreset(suggestedPreset.preset)
case .suggestedAddCustom:
return .suggestedAddCustom
case .listHeader:
return .listHeader
case let .preset(preset):
@ -139,6 +145,10 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, title: title, label: label, sectionId: self.section, style: .blocks, installAction: {
arguments.addSuggestedPresed(title, preset)
}, tag: nil)
case let .suggestedAddCustom(text):
return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, editing: false, action: {
arguments.addNew()
})
case let .listHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .preset(_, title, label, preset, canBeReordered, canBeDeleted, isEditing):
@ -183,6 +193,7 @@ private func filtersWithAppliedOrder(filters: [(ChatListFilter, Int)], order: [I
private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filters: [(ChatListFilter, Int)], updatedFilterOrder: [Int32]?, suggestedFilters: [ChatListFeaturedFilter], settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] {
var entries: [ChatListFilterPresetListEntry] = []
entries.append(.screenHeader("Create folders for different groups of chats and\nquickly switch between them."))
let filteredSuggestedFilters = suggestedFilters.filter { suggestedFilter in
@ -194,21 +205,26 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
return true
}
entries.append(.listHeader("FOLDERS"))
if !filters.isEmpty || suggestedFilters.isEmpty {
entries.append(.listHeader("FOLDERS"))
for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) {
entries.append(.preset(index: PresetIndex(value: entries.count), title: filter.title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing))
for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) {
entries.append(.preset(index: PresetIndex(value: entries.count), title: filter.title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing))
}
if filters.count < 10 {
entries.append(.addItem(text: "Create New Folder", isEditing: state.isEditing))
}
entries.append(.listFooter("Tap \"Edit\" to change the order or delete folders."))
}
if filters.count < 10 {
entries.append(.addItem(text: "Create New Folder", isEditing: state.isEditing))
}
entries.append(.listFooter("Tap \"Edit\" to change the order or delete folders."))
if !filteredSuggestedFilters.isEmpty {
entries.append(.suggestedListHeader("RECOMMENDED FOLDERS"))
for filter in filteredSuggestedFilters {
entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title, label: filter.description, preset: filter.data))
}
if filters.isEmpty {
entries.append(.suggestedAddCustom("Add Custom Folder"))
}
}
return entries
@ -332,7 +348,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
dismissImpl?()
})
}
let rightNavigationButton: ItemListNavigationButton
let rightNavigationButton: ItemListNavigationButton?
if state.isEditing {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
let _ = (updatedFilterOrder.get()
@ -381,7 +397,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
}
})
})
} else {
} else if !filtersWithCountsValue.isEmpty {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
updateState { state in
var state = state
@ -389,6 +405,8 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
return state
}
})
} else {
rightNavigationButton = nil
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Folders"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)

View File

@ -785,7 +785,9 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
if let previousSelectedFrame = self.previousSelectedFrame {
let previousContentOffsetX = max(0.0, min(previousContentWidth - previousScrollBounds.width, floor(previousSelectedFrame.midX - previousScrollBounds.width / 2.0)))
focusOnSelectedFilter = abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0
if abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0 {
focusOnSelectedFilter = true
}
}
if focusOnSelectedFilter && self.reorderingItem == nil {

View File

@ -6,8 +6,6 @@ import Display
import SwiftSignalKit
import TelegramPresentationData
private let titleFont = Font.regular(17.0)
class ChatListHoleItem: ListViewItem {
let theme: PresentationTheme
@ -45,9 +43,6 @@ class ChatListHoleItem: ListViewItem {
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply()
if let nodeValue = node() as? ChatListHoleItemNode {
nodeValue.updateBackgroundAndSeparatorsLayout()
}
})
}
}
@ -56,25 +51,11 @@ class ChatListHoleItem: ListViewItem {
}
}
private let separatorHeight = 1.0 / UIScreen.main.scale
class ChatListHoleItemNode: ListViewItemNode {
let separatorNode: ASDisplayNode
let labelNode: TextNode
var relativePosition: (first: Bool, last: Bool) = (false, false)
required init() {
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = UIColor(rgb: 0xc8c7cc)
self.separatorNode.isLayerBacked = true
self.labelNode = TextNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.separatorNode)
self.addSubnode(self.labelNode)
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
@ -84,45 +65,17 @@ class ChatListHoleItemNode: ListViewItemNode {
}
func asyncLayout() -> (_ item: ChatListHoleItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
let labelNodeLayout = TextNode.asyncLayout(self.labelNode)
return { item, params, first, last in
let baseWidth = params.width - params.leftInset - params.rightInset
let (labelLayout, labelApply) = labelNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "", font: titleFont, textColor: item.theme.chatList.messageTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: false)
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 68.0), insets: insets)
let separatorInset: CGFloat
if last {
separatorInset = 0.0
} else {
separatorInset = 80.0 + params.leftInset
}
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 0.0), insets: UIEdgeInsets())
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.relativePosition = (first, last)
let _ = labelApply()
strongSelf.separatorNode.backgroundColor = item.theme.chatList.itemSeparatorColor
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: floor((params.width - labelLayout.size.width) / 2.0), y: floor((layout.contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: separatorInset, y: 68.0 - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight))
strongSelf.contentSize = layout.contentSize
strongSelf.insets = layout.insets
strongSelf.updateBackgroundAndSeparatorsLayout()
}
})
}
}
func updateBackgroundAndSeparatorsLayout() {
//let size = self.bounds.size
//let insets = self.insets
}
}

View File

@ -42,25 +42,13 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
return combineLatest(queue: context.account.postbox.queue,
context.account.postbox.combinedView(keys: keys),
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
Signal<Bool, NoError>.single(true)
)
|> map { view, sharedData -> (Int, [(ChatListFilter, Int)]) in
|> map { view, _ -> (Int, [(ChatListFilter, Int)]) in
guard let unreadCounts = view.views[unreadKey] as? UnreadMessageCountsView else {
return (0, [])
}
let inAppSettings: InAppNotificationSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings] as? InAppNotificationSettings {
inAppSettings = value
} else {
inAppSettings = .defaultSettings
}
let type: RenderedTotalUnreadCountType
switch inAppSettings.totalUnreadCountDisplayStyle {
case .filtered:
type = .filtered
}
var result: [(ChatListFilter, Int)] = []
var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int)] = [:]
@ -70,6 +58,8 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
switch entry {
case let .total(_, totalStateValue):
totalState = totalStateValue
case let .totalInGroup(groupId, totalGroupState):
break
case let .peer(peerId, state):
if let state = state, state.isUnread {
if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer {
@ -88,10 +78,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
}
}
var totalBadge = 0
if let totalState = totalState {
totalBadge = Int(totalState.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags))
}
let totalBadge = 0
for filter in filters {
var tags: [PeerSummaryCounterTags] = []

View File

@ -1363,12 +1363,16 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
if let maybeContentNode = self.contentContainerNode.contentNode {
switch maybeContentNode {
case let .extracted(contentParentNode, _):
let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view)
if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) {
if result is TextSelectionNodeView {
return result
} else if contentParentNode.contentRect.contains(contentPoint) {
return contentParentNode.contentNode.view
if case let .extracted(source) = self.source {
if !source.ignoreContentTouches {
let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view)
if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) {
if result is TextSelectionNodeView {
return result
} else if contentParentNode.contentRect.contains(contentPoint) {
return contentParentNode.contentNode.view
}
}
}
}
case let .controller(controller):
@ -1429,6 +1433,7 @@ public final class ContextControllerPutBackViewInfo {
public protocol ContextExtractedContentSource: class {
var keepInPlace: Bool { get }
var ignoreContentTouches: Bool { get }
func takeView() -> ContextControllerTakeViewInfo?
func putBack() -> ContextControllerPutBackViewInfo?

View File

@ -799,8 +799,10 @@ open class NavigationBar: ASDisplayNode {
}
private func requestLayout() {
self.requestedLayout = true
self.setNeedsLayout()
if self.transitionState == nil {
self.requestedLayout = true
self.setNeedsLayout()
}
}
override open func layout() {

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
open class ASButtonNode: ASControlNode {
public let titleNode: ImmediateTextNode
public let highlightedTitleNode: ImmediateTextNode
public let disabledTitleNode: ImmediateTextNode
public let imageNode: ASImageNode
public let disabledImageNode: ASImageNode
public let backgroundImageNode: ASImageNode
@ -48,6 +49,7 @@ open class ASButtonNode: ASControlNode {
private var calculatedTitleSize: CGSize = CGSize()
private var calculatedHighlightedTitleSize: CGSize = CGSize()
private var calculatedDisabledTitleSize: CGSize = CGSize()
override public init() {
self.titleNode = ImmediateTextNode()
@ -58,6 +60,10 @@ open class ASButtonNode: ASControlNode {
self.highlightedTitleNode.isUserInteractionEnabled = false
self.highlightedTitleNode.displaysAsynchronously = false
self.disabledTitleNode = ImmediateTextNode()
self.disabledTitleNode.isUserInteractionEnabled = false
self.disabledTitleNode.displaysAsynchronously = false
self.imageNode = ASImageNode()
self.imageNode.isUserInteractionEnabled = false
self.imageNode.displaysAsynchronously = false
@ -86,6 +92,8 @@ open class ASButtonNode: ASControlNode {
self.addSubnode(self.titleNode)
self.addSubnode(self.highlightedTitleNode)
self.highlightedTitleNode.isHidden = true
self.addSubnode(self.disabledTitleNode)
self.disabledTitleNode.isHidden = true
self.addSubnode(self.imageNode)
self.addSubnode(self.disabledImageNode)
self.disabledImageNode.isHidden = true
@ -108,6 +116,7 @@ open class ASButtonNode: ASControlNode {
self.calculatedTitleSize = normalTitleSize
let highlightedTitleSize = self.highlightedTitleNode.updateLayout(CGSize(width: widthForTitle, height: max(1.0, constrainedSize.height - verticalInsets)))
self.calculatedHighlightedTitleSize = highlightedTitleSize
self.calculatedDisabledTitleSize = self.disabledTitleNode.updateLayout(CGSize(width: widthForTitle, height: max(1.0, constrainedSize.height - verticalInsets)))
let titleSize = CGSize(width: max(normalTitleSize.width, highlightedTitleSize.width), height: max(normalTitleSize.height, highlightedTitleSize.height))
@ -161,6 +170,17 @@ open class ASButtonNode: ASControlNode {
self.setNeedsLayout()
}
self.highlightedTitleNode.attributedText = title
} else if state == .disabled {
if let attributedText = self.disabledTitleNode.attributedText {
if !attributedText.isEqual(to: title) {
self.invalidateCalculatedLayout()
self.setNeedsLayout()
}
} else {
self.invalidateCalculatedLayout()
self.setNeedsLayout()
}
self.disabledTitleNode.attributedText = title
} else {
if let attributedText = self.titleNode.attributedText {
if !attributedText.isEqual(to: title) {
@ -178,6 +198,8 @@ open class ASButtonNode: ASControlNode {
open func attributedTitle(for state: UIControl.State) -> NSAttributedString? {
if state == .highlighted || state == .selected {
return self.highlightedTitleNode.attributedText
} else if state == .disabled {
return self.disabledTitleNode.attributedText
} else {
return self.titleNode.attributedText
}
@ -255,6 +277,14 @@ open class ASButtonNode: ASControlNode {
override open var isEnabled: Bool {
didSet {
if self.isEnabled != oldValue {
if self.isEnabled || self.disabledTitleNode.attributedText == nil {
self.titleNode.isHidden = false
self.disabledTitleNode.isHidden = true
} else {
self.titleNode.isHidden = true
self.disabledTitleNode.isHidden = false
}
if self.isEnabled || self.disabledImageNode.image == nil {
self.imageNode.isHidden = false
self.disabledImageNode.isHidden = true
@ -275,6 +305,7 @@ open class ASButtonNode: ASControlNode {
let titleOrigin: CGPoint
let highlightedTitleOrigin: CGPoint
let disabledTitleOrigin: CGPoint
let imageOrigin: CGPoint
if self.laysOutHorizontally {
@ -282,14 +313,17 @@ open class ASButtonNode: ASControlNode {
case .left:
titleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0))
highlightedTitleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0))
disabledTitleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0))
imageOrigin = CGPoint(x: titleOrigin.x + self.calculatedTitleSize.width + self.contentSpacing, y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0))
case .right:
titleOrigin = CGPoint(x: contentRect.maxX - self.calculatedTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0))
highlightedTitleOrigin = CGPoint(x: contentRect.maxX - self.calculatedHighlightedTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0))
disabledTitleOrigin = CGPoint(x: contentRect.maxX - self.calculatedDisabledTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0))
imageOrigin = CGPoint(x: titleOrigin.x - self.contentSpacing - imageSize.width, y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0))
default:
titleOrigin = CGPoint(x: contentRect.minY + floor((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0))
titleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0))
highlightedTitleOrigin = CGPoint(x: floor((contentRect.width - self.calculatedHighlightedTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0))
disabledTitleOrigin = CGPoint(x: floor((contentRect.width - self.calculatedDisabledTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0))
imageOrigin = CGPoint(x: floor((contentRect.width - imageSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0))
}
} else {
@ -300,11 +334,13 @@ open class ASButtonNode: ASControlNode {
let contentY = contentRect.minY + floor((contentRect.height - contentHeight) / 2.0)
titleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedTitleSize.height)
highlightedTitleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedHighlightedTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedHighlightedTitleSize.height)
disabledTitleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedDisabledTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedDisabledTitleSize.height)
imageOrigin = CGPoint(x: floor((contentRect.width - imageSize.width) / 2.0), y: contentY)
}
self.titleNode.frame = CGRect(origin: titleOrigin, size: self.calculatedTitleSize)
self.highlightedTitleNode.frame = CGRect(origin: highlightedTitleOrigin, size: self.calculatedHighlightedTitleSize)
self.disabledTitleNode.frame = CGRect(origin: disabledTitleOrigin, size: self.calculatedDisabledTitleSize)
self.imageNode.frame = CGRect(origin: imageOrigin, size: imageSize)
self.disabledImageNode.frame = CGRect(origin: imageOrigin, size: imageSize)

View File

@ -174,6 +174,13 @@ open class TabBarController: ViewController {
}
}
public func isPointInsideContentArea(point: CGPoint) -> Bool {
if point.y < self.tabBarControllerNode.tabBarNode.frame.minY {
return true
}
return false
}
override open func loadDisplayNode() {
self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap, itemNodes in
if let strongSelf = self {

View File

@ -5,7 +5,9 @@ import Display
import SwiftSignalKit
import Postbox
private let edgeWidth: CGFloat = 44.0
private func edgeWidth(width: CGFloat) -> CGFloat {
return min(44.0, floor(width / 6.0))
}
private let leftFadeImage = generateImage(CGSize(width: 64.0, height: 1.0), opaque: false, rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
@ -153,11 +155,11 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
let size = strongSelf.bounds
var highlightedSide: Bool?
if point.x < edgeWidth && strongSelf.canGoToPreviousItem() {
if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() {
if strongSelf.items.count > 1 {
highlightedSide = false
}
} else if point.x > size.width - edgeWidth && strongSelf.canGoToNextItem() {
} else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() {
if strongSelf.items.count > 1 {
highlightedSide = true
}
@ -180,11 +182,11 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
var highlightedSide: Bool?
if let point = point {
if point.x < edgeWidth && strongSelf.canGoToPreviousItem() {
if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() {
if strongSelf.items.count > 1 {
highlightedSide = false
}
} else if point.x > size.width - edgeWidth && strongSelf.canGoToNextItem() {
} else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() {
if strongSelf.items.count > 1 {
highlightedSide = true
}
@ -233,9 +235,9 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
if case .tap = gesture {
let size = self.bounds.size
if location.x < edgeWidth && self.canGoToPreviousItem() {
if location.x < edgeWidth(width: size.width) && self.canGoToPreviousItem() {
self.goToPreviousItem()
} else if location.x > size.width - edgeWidth && self.canGoToNextItem() {
} else if location.x > size.width - edgeWidth(width: size.width) && self.canGoToNextItem() {
self.goToNextItem()
}
}

View File

@ -5,12 +5,14 @@ final class MutableBasicPeerView: MutablePostboxView {
fileprivate var peer: Peer?
fileprivate var notificationSettings: PeerNotificationSettings?
fileprivate var isContact: Bool
fileprivate var groupId: PeerGroupId?
init(postbox: Postbox, peerId: PeerId) {
self.peerId = peerId
self.peer = postbox.peerTable.get(peerId)
self.notificationSettings = postbox.peerNotificationSettingsTable.getEffective(peerId)
self.isContact = postbox.contactsTable.isContact(peerId: self.peerId)
self.groupId = postbox.chatListIndexTable.get(peerId: peerId).inclusion.groupId
}
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
@ -30,6 +32,13 @@ final class MutableBasicPeerView: MutablePostboxView {
updated = true
}
}
if transaction.currentUpdatedChatListInclusions[self.peerId] != nil {
let groupId = postbox.chatListIndexTable.get(peerId: peerId).inclusion.groupId
if self.groupId != groupId {
self.groupId = groupId
updated = true
}
}
return updated
}
@ -43,10 +52,12 @@ public final class BasicPeerView: PostboxView {
public let peer: Peer?
public let notificationSettings: PeerNotificationSettings?
public let isContact: Bool
public let groupId: PeerGroupId?
init(_ view: MutableBasicPeerView) {
self.peer = view.peer
self.notificationSettings = view.notificationSettings
self.isContact = view.isContact
self.groupId = view.groupId
}
}

View File

@ -2,16 +2,19 @@ import Foundation
public enum UnreadMessageCountsItem: Equatable {
case total(ValueBoxKey?)
case totalInGroup(PeerGroupId)
case peer(PeerId)
}
private enum MutableUnreadMessageCountsItemEntry {
case total((ValueBoxKey, PreferencesEntry?)?, ChatListTotalUnreadState)
case totalInGroup(PeerGroupId, ChatListTotalUnreadState)
case peer(PeerId, CombinedPeerReadState?)
}
public enum UnreadMessageCountsItemEntry {
case total(PreferencesEntry?, ChatListTotalUnreadState)
case totalInGroup(PeerGroupId, ChatListTotalUnreadState)
case peer(PeerId, CombinedPeerReadState?)
}
@ -23,6 +26,8 @@ final class MutableUnreadMessageCountsView: MutablePostboxView {
switch item {
case let .total(preferencesKey):
return .total(preferencesKey.flatMap({ ($0, postbox.preferencesTable.get(key: $0)) }), postbox.messageHistoryMetadataTable.getChatListTotalUnreadState())
case let .totalInGroup(groupId):
return .totalInGroup(groupId, ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:]))
case let .peer(peerId):
return .peer(peerId, postbox.readStateTable.getCombinedState(peerId))
}
@ -57,6 +62,8 @@ final class MutableUnreadMessageCountsView: MutablePostboxView {
updated = true
}
}
case let .totalInGroup(groupId, state):
break
case let .peer(peerId, _):
if transaction.alteredInitialPeerCombinedReadStates[peerId] != nil {
self.entries[i] = .peer(peerId, postbox.readStateTable.getCombinedState(peerId))
@ -82,6 +89,8 @@ public final class UnreadMessageCountsView: PostboxView {
switch entry {
case let .total(keyAndValue, state):
return .total(keyAndValue?.1, state)
case let .totalInGroup(groupId, state):
return .totalInGroup(groupId, state)
case let .peer(peerId, count):
return .peer(peerId, count)
}
@ -103,7 +112,7 @@ public final class UnreadMessageCountsView: PostboxView {
public func count(for item: UnreadMessageCountsItem) -> Int32? {
for entry in self.entries {
switch entry {
case .total:
case .total, .totalInGroup:
break
case let .peer(peerId, state):
if case .peer(peerId) = item {

View File

@ -315,6 +315,7 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
let _ = itemNode.measure(itemSize)
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: itemSize.width * CGFloat(i), y: (size.height - itemSize.height) / 2.0), size: itemSize))
let isSelected = selectedIndex == i

View File

@ -878,6 +878,7 @@ private final class SettingsControllerImpl: ItemListController, SettingsControll
private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = true
let ignoreContentTouches: Bool = true
private let controller: ViewController
private let sourceNode: ContextExtractedContentContainingNode

View File

@ -6,6 +6,7 @@ import Postbox
final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false
let ignoreContentTouches: Bool = false
private weak var chatNode: ChatControllerNode?
private let message: Message

View File

@ -8,6 +8,7 @@ import AppBundle
private final class TooltipScreenNode: ViewControllerTracingNode {
private let location: CGPoint
private let shouldDismissOnTouch: (CGPoint) -> Bool
private let requestDismiss: () -> Void
private let containerNode: ASDisplayNode
@ -16,8 +17,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private let animatedStickerNode: AnimatedStickerNode
private let textNode: ImmediateTextNode
init(text: String, location: CGPoint, requestDismiss: @escaping () -> Void) {
init(text: String, location: CGPoint, shouldDismissOnTouch: @escaping (CGPoint) -> Bool, requestDismiss: @escaping () -> Void) {
self.location = location
self.shouldDismissOnTouch = shouldDismissOnTouch
self.requestDismiss = requestDismiss
self.containerNode = ASDisplayNode()
@ -90,7 +92,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
eventIsPresses = event.type == .presses
}
if event.type == .touches || eventIsPresses {
//self.requestDismiss()
if self.shouldDismissOnTouch(point) {
self.requestDismiss()
}
return nil
}
}
@ -119,6 +123,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
public final class TooltipScreen: ViewController {
private let text: String
private let location: CGPoint
private let shouldDismissOnTouch: (CGPoint) -> Bool
private var controllerNode: TooltipScreenNode {
return self.displayNode as! TooltipScreenNode
@ -127,9 +132,10 @@ public final class TooltipScreen: ViewController {
private var validLayout: ContainerViewLayout?
private var isDismissed: Bool = false
public init(text: String, location: CGPoint) {
public init(text: String, location: CGPoint, shouldDismissOnTouch: @escaping (CGPoint) -> Bool) {
self.text = text
self.location = location
self.shouldDismissOnTouch = shouldDismissOnTouch
super.init(navigationBarPresentationData: nil)
@ -151,7 +157,7 @@ public final class TooltipScreen: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = TooltipScreenNode(text: self.text, location: self.location, requestDismiss: { [weak self] in
self.displayNode = TooltipScreenNode(text: self.text, location: self.location, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in
guard let strongSelf = self else {
return
}