From 6b8adfc5b0ff2f74b94a7da6fb73f76be3c27e44 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sun, 21 Apr 2019 23:53:32 +0400 Subject: [PATCH] ChatListNode: improved archive handling ItemListEditableNode: improved swipe actions --- TelegramUI/AvatarNode.swift | 17 ++-- TelegramUI/ChatController.swift | 9 +++ TelegramUI/ChatListController.swift | 10 +-- TelegramUI/ChatListControllerNode.swift | 80 ++++++++++--------- TelegramUI/ChatListItem.swift | 54 +++++++------ TelegramUI/ChatListNode.swift | 59 +++++++++++--- TelegramUI/DebugController.swift | 20 ++++- .../DefaultDarkAccentPresentationTheme.swift | 3 +- TelegramUI/DefaultDarkPresentationTheme.swift | 3 +- TelegramUI/DefaultPresentationTheme.swift | 6 +- TelegramUI/ExperimentalUISettings.swift | 8 +- TelegramUI/ItemListEditableItem.swift | 18 ++++- TelegramUI/ItemListRevealOptionsNode.swift | 72 ++++++++++------- .../NavigationBarSearchContentNode.swift | 9 ++- TelegramUI/PresentationTheme.swift | 4 +- 15 files changed, 236 insertions(+), 136 deletions(-) diff --git a/TelegramUI/AvatarNode.swift b/TelegramUI/AvatarNode.swift index e6121c378e..387c4ab782 100644 --- a/TelegramUI/AvatarNode.swift +++ b/TelegramUI/AvatarNode.swift @@ -316,15 +316,10 @@ public final class AvatarNode: ASDisplayNode { let colorIndex: Int if let parameters = parameters as? AvatarNodeParameters { - if case .archivedChatsIcon = parameters.icon { - let path = UIBezierPath(roundedRect: bounds, cornerRadius: floor(bounds.width / 3.8)) - path.addClip() - } else { - context.beginPath() - context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height: - bounds.size.height)) - context.clip() - } + context.beginPath() + context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height: + bounds.size.height)) + context.clip() if let explicitColorIndex = parameters.explicitColorIndex { colorIndex = explicitColorIndex @@ -349,8 +344,8 @@ public final class AvatarNode: ASDisplayNode { colorsArray = savedMessagesColors } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme { colorsArray = [theme.list.blocksBackgroundColor.cgColor, theme.list.blocksBackgroundColor.cgColor] - } else if case .archivedChatsIcon = parameters.icon { - let color = UIColor(rgb: 0x4ac058) + } else if case .archivedChatsIcon = parameters.icon, let theme = parameters.theme { + let color = theme.chatList.neutralAvatarColor colorsArray = [color.cgColor, color.cgColor] } else { colorsArray = grayscaleColors diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index a250867bb1..e796c4d7bd 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -1981,6 +1981,15 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal return chatLocationInfoReady }) + if self.context.sharedContext.immediateExperimentalUISettings.crashOnLongQueries { + let _ = (self.ready.get() + |> filter({ $0 }) + |> take(1) + |> timeout(0.8, queue: .concurrentDefaultQueue(), alternate: Signal { _ in + preconditionFailure() + })).start() + } + self.chatDisplayNode.historyNode.contentPositionChanged = { [weak self] offset in if let strongSelf = self { let offsetAlpha: CGFloat diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 282cf83d68..e1e391dccd 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -13,10 +13,10 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa let scrollToItem: ListViewScrollToItem let targetProgress: CGFloat if searchNode.expansionProgress < 0.6 { - scrollToItem = ListViewScrollToItem(index: 1, position: .top(-navigationBarSearchContentHeight), animated: true, curve: .Default(duration: 0.3), directionHint: .Up) + scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: true, curve: .Default(duration: 0.3), directionHint: .Up) targetProgress = 0.0 } else { - scrollToItem = ListViewScrollToItem(index: 1, position: .top(0.0), animated: true, curve: .Default(duration: 0.3), directionHint: .Up) + scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: 0.3), directionHint: .Up) targetProgress = 1.0 } searchNode.updateExpansionProgress(targetProgress, animated: true) @@ -35,7 +35,7 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa } }) - if let sortItemNode = sortItemNode { + if false, let sortItemNode = sortItemNode { let itemFrame = sortItemNode.apparentFrame if itemFrame.contains(CGPoint(x: 0.0, y: listNode.insets.top)) { var scrollToItem: ListViewScrollToItem? @@ -744,13 +744,13 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie } }*/ - /*self.chatListDisplayNode.chatListNode.contentScrollingEnded = { [weak self] listView in + self.chatListDisplayNode.chatListNode.contentScrollingEnded = { [weak self] listView in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { return fixListNodeScrolling(listView, searchNode: searchContentNode) } else { return false } - }*/ + } self.chatListDisplayNode.toolbarActionSelected = { [weak self] left in self?.toolbarActionSelected(left: left) diff --git a/TelegramUI/ChatListControllerNode.swift b/TelegramUI/ChatListControllerNode.swift index 177bdb5800..6b9e5cbab3 100644 --- a/TelegramUI/ChatListControllerNode.swift +++ b/TelegramUI/ChatListControllerNode.swift @@ -133,6 +133,47 @@ final class ChatListControllerNode: ASDisplayNode { insets.left += layout.safeInsets.left insets.right += layout.safeInsets.right + if let toolbar = self.toolbar { + var tabBarHeight: CGFloat + var options: ContainerViewLayoutInsetOptions = [] + if layout.metrics.widthClass == .regular { + options.insert(.input) + } + let bottomInset: CGFloat = layout.insets(options: options).bottom + if !layout.safeInsets.left.isZero { + tabBarHeight = 34.0 + bottomInset + insets.bottom += 34.0 + } else { + tabBarHeight = 49.0 + bottomInset + insets.bottom += 49.0 + } + + let tabBarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) + + if let toolbarNode = self.toolbarNode { + transition.updateFrame(node: toolbarNode, frame: tabBarFrame) + toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: transition) + } else { + let toolbarNode = ToolbarNode(theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme), displaySeparator: true, left: { [weak self] in + self?.toolbarActionSelected?(true) + }, right: { [weak self] in + self?.toolbarActionSelected?(false) + }) + toolbarNode.frame = tabBarFrame + toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate) + self.addSubnode(toolbarNode) + self.toolbarNode = toolbarNode + if transition.isAnimated { + toolbarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + } else if let toolbarNode = self.toolbarNode { + self.toolbarNode = nil + transition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { [weak toolbarNode] _ in + toolbarNode?.removeFromSupernode() + }) + } + self.chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) self.chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) @@ -177,45 +218,6 @@ final class ChatListControllerNode: ASDisplayNode { if let searchDisplayController = self.searchDisplayController { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } - - if let toolbar = self.toolbar { - var tabBarHeight: CGFloat - var options: ContainerViewLayoutInsetOptions = [] - if layout.metrics.widthClass == .regular { - options.insert(.input) - } - let bottomInset: CGFloat = layout.insets(options: options).bottom - if !layout.safeInsets.left.isZero { - tabBarHeight = 34.0 + bottomInset - } else { - tabBarHeight = 49.0 + bottomInset - } - - let tabBarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) - - if let toolbarNode = self.toolbarNode { - transition.updateFrame(node: toolbarNode, frame: tabBarFrame) - toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: transition) - } else { - let toolbarNode = ToolbarNode(theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme), displaySeparator: true, left: { [weak self] in - self?.toolbarActionSelected?(true) - }, right: { [weak self] in - self?.toolbarActionSelected?(false) - }) - toolbarNode.frame = tabBarFrame - toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate) - self.addSubnode(toolbarNode) - self.toolbarNode = toolbarNode - if transition.isAnimated { - toolbarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - } else if let toolbarNode = self.toolbarNode { - self.toolbarNode = nil - transition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { [weak toolbarNode] _ in - toolbarNode?.removeFromSupernode() - }) - } } func activateSearch(placeholderNode: SearchBarPlaceholderNode) { diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index 029c6b70da..304e0471d4 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -35,6 +35,10 @@ class ChatListItem: ListViewItem { let selectable: Bool = true + var approximateHeight: CGFloat { + return self.hiddenOffset ? 0.0 : 44.0 + } + let header: ListViewItemHeader? init(presentationData: ChatListPresentationData, account: Account, peerGroupId: PeerGroupId?, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { @@ -837,7 +841,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var layoutOffset: CGFloat = 0.0 if item.hiddenOffset { - layoutOffset = -itemHeight + //layoutOffset = -itemHeight } let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: params.width - leftInset - params.rightInset - 10.0 - 1.0 - editingOffset, height: itemHeight - 12.0 - 9.0)) @@ -918,11 +922,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) - let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: max(0.0, itemHeight + layoutOffset)), insets: insets) + var heightOffset: CGFloat = 0.0 + if item.hiddenOffset { + heightOffset = -itemHeight + } + let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: max(0.0, itemHeight + heightOffset)), insets: insets) return (layout, { [weak self] synchronousLoads, animated in if let strongSelf = self { strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params) + if true || !animated { + strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) + } if let _ = updatedTheme { strongSelf.separatorNode.backgroundColor = item.presentationData.theme.chatList.itemSeparatorColor @@ -1049,21 +1060,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let previousBadgeFrame = strongSelf.badgeNode.frame let badgeFrame = CGRect(x: contentRect.maxX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height - 2.0, width: badgeLayout.width, height: badgeLayout.height) - if !previousBadgeFrame.width.isZero && !badgeFrame.width.isZero && badgeFrame != previousBadgeFrame { - if animateContent { - strongSelf.badgeNode.frame = badgeFrame - strongSelf.badgeNode.layer.animateFrame(from: previousBadgeFrame, to: badgeFrame, duration: 0.15, timingFunction: kCAMediaTimingFunctionEaseInEaseOut) - } else { - transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame) - } - } else { - strongSelf.badgeNode.frame = badgeFrame - } + transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame) } if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil { - let previousBadgeFrame = strongSelf.mentionBadgeNode.frame - let mentionBadgeOffset: CGFloat if badgeLayout.width.isZero { mentionBadgeOffset = contentRect.maxX - mentionBadgeLayout.width @@ -1072,13 +1072,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let badgeFrame = CGRect(x: mentionBadgeOffset, y: contentRect.maxY - mentionBadgeLayout.height - 2.0, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height) - strongSelf.mentionBadgeNode.position = badgeFrame.center - strongSelf.mentionBadgeNode.bounds = CGRect(origin: CGPoint(), size: badgeFrame.size) - if animateContent && !previousBadgeFrame.width.isZero && !badgeFrame.width.isZero && badgeFrame != previousBadgeFrame { - strongSelf.mentionBadgeNode.layer.animatePosition(from: previousBadgeFrame.center, to: badgeFrame.center, duration: 0.15, timingFunction: kCAMediaTimingFunctionEaseInEaseOut) - strongSelf.mentionBadgeNode.layer.animateBounds(from: CGRect(origin: CGPoint(), size: previousBadgeFrame.size), to: CGRect(origin: CGPoint(), size: badgeFrame.size), duration: 0.15, timingFunction: kCAMediaTimingFunctionEaseInEaseOut) - } + transition.updateFrame(node: strongSelf.mentionBadgeNode, frame: badgeFrame) } if let currentPinnedIconImage = currentPinnedIconImage { @@ -1219,7 +1214,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let separatorInset: CGFloat - if (!nextIsPinned && item.index.pinningIndex != nil) || last || groupHiddenByDefault { + if (!nextIsPinned && item.index.pinningIndex != nil) || last { separatorInset = 0.0 } else { separatorInset = editingOffset + leftInset + rawContentRect.origin.x @@ -1227,7 +1222,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.contentSize)) + transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: itemHeight))) if item.selected { strongSelf.backgroundNode.backgroundColor = theme.itemSelectedBackgroundColor } else if item.index.pinningIndex != nil { @@ -1291,7 +1286,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var layoutOffset: CGFloat = 0.0 if item.hiddenOffset { - layoutOffset = -itemHeight + //layoutOffset = -itemHeight } if let reorderControlNode = self.reorderControlNode { @@ -1356,7 +1351,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: self.badgeNode, frame: updatedBadgeFrame) var mentionBadgeFrame = self.mentionBadgeNode.frame - mentionBadgeFrame.origin.x = updatedBadgeFrame.minX - 6.0 - mentionBadgeFrame.width + if updatedBadgeFrame.width.isZero { + mentionBadgeFrame.origin.x = updatedBadgeFrame.minX - mentionBadgeFrame.width + } else { + mentionBadgeFrame.origin.x = updatedBadgeFrame.minX - 6.0 - mentionBadgeFrame.width + } transition.updateFrame(node: self.mentionBadgeNode, frame: mentionBadgeFrame) let pinnedIconSize = self.pinnedIconNode.bounds.size @@ -1431,6 +1430,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { close = false case RevealOptionKey.hide.rawValue, RevealOptionKey.unhide.rawValue: item.interaction.toggleArchivedFolderHiddenByDefault() + close = false default: break } @@ -1458,4 +1458,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self?.updateIsHighlighted(transition: .immediate) }) } + + override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { + super.animateFrameTransition(progress, currentValue) + + self.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, currentValue - itemHeight, 0.0) + } } diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index feef942a7e..5d97c24636 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -367,6 +367,8 @@ final class ChatListNode: ListView { let _ = self.currentRemovingPeerId.swap(peerId) } + private var hapticFeedback: HapticFeedback? + init(context: AccountContext, groupId: PeerGroupId?, controlsHistoryPreload: Bool, mode: ChatListNodeMode, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) { self.context = context self.controlsHistoryPreload = controlsHistoryPreload @@ -380,6 +382,7 @@ final class ChatListNode: ListView { super.init() self.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor + self.verticalScrollIndicatorFollowsOverscroll = true let nodeInteraction = ChatListNodeInteraction(activateSearch: { [weak self] in if let strongSelf = self, let activateSearch = strongSelf.activateSearch { @@ -468,12 +471,17 @@ final class ChatListNode: ListView { }) return updatedValue } - |> deliverOnMainQueue).start(next: { updatedValue in + |> deliverOnMainQueue).start(next: { value in guard let strongSelf = self else { return } - if !updatedValue { - + strongSelf.updateState { state in + var state = state + if !value { + state.archiveShouldBeTemporaryRevealed = false + } + state.peerIdWithRevealedOptions = nil + return state } }) }) @@ -488,6 +496,7 @@ final class ChatListNode: ListView { let previousState = Atomic(value: self.currentState) let previousView = Atomic(value: nil) + let previousHideArchivedFolderByDefault = Atomic(value: nil) let currentRemovingPeerId = self.currentRemovingPeerId let savedMessagesPeer: Signal @@ -509,6 +518,8 @@ final class ChatListNode: ListView { let chatListNodeViewTransition = combineLatest(hideArchivedFolderByDefault, savedMessagesPeer, chatListViewUpdate, self.statePromise.get()) |> mapToQueue { (hideArchivedFolderByDefault, savedMessagesPeer, update, state) -> Signal in + let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault) + let (rawEntries, isLoading) = chatListNodeEntriesForView(update.view, state: state, savedMessagesPeer: savedMessagesPeer, hideArchivedFolderByDefault: hideArchivedFolderByDefault, mode: mode) let entries = rawEntries.filter { entry in switch entry { @@ -644,7 +655,7 @@ final class ChatListNode: ListView { if doesIncludeRemovingPeerId != didIncludeRemovingPeerId { disableAnimations = false } - if previousState.archiveShouldBeTemporaryRevealed != state.archiveShouldBeTemporaryRevealed && doesIncludeArchive { + if hideArchivedFolderByDefault && previousState.archiveShouldBeTemporaryRevealed != state.archiveShouldBeTemporaryRevealed && doesIncludeArchive { disableAnimations = false } if didIncludeHiddenByDefaultArchive != doesIncludeHiddenByDefaultArchive { @@ -652,6 +663,10 @@ final class ChatListNode: ListView { } } + if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault { + disableAnimations = false + } + var searchMode = false if case .peers = mode { searchMode = true @@ -689,6 +704,7 @@ final class ChatListNode: ListView { var rawUnreadCount: Int32 = 0 var filteredUnreadCount: Int32 = 0 + var archiveVisible = false if let range = range.visibleRange { let entryCount = chatListView.filteredEntries.count for i in range.firstIndex ..< range.lastIndex { @@ -705,6 +721,8 @@ final class ChatListNode: ListView { filteredUnreadCount += count } } + case .GroupReferenceEntry: + archiveVisible = true default: break } @@ -714,6 +732,13 @@ final class ChatListNode: ListView { visibleUnreadCountsValue.raw = rawUnreadCount visibleUnreadCountsValue.filtered = filteredUnreadCount strongSelf.visibleUnreadCountsValue = visibleUnreadCountsValue + if !archiveVisible && strongSelf.currentState.archiveShouldBeTemporaryRevealed { + strongSelf.updateState { state in + var state = state + state.archiveShouldBeTemporaryRevealed = false + return state + } + } } } @@ -925,7 +950,7 @@ final class ChatListNode: ListView { case .none, .unknown: revealHiddenItems = false case let .known(value): - revealHiddenItems = value <= 76.0 + revealHiddenItems = value <= 54.0 } if !revealHiddenItems && strongSelf.currentState.archiveShouldBeTemporaryRevealed { strongSelf.updateState { state in @@ -974,16 +999,30 @@ final class ChatListNode: ListView { case let .known(value): atTop = value <= 0.0 if startedScrollingAtUpperBound && strongSelf.isTracking { - revealHiddenItems = value <= -32.0 + revealHiddenItems = value <= -76.0 } } strongSelf.scrolledAtTopValue = atTop strongSelf.contentOffsetChanged?(offset) if revealHiddenItems && !strongSelf.currentState.archiveShouldBeTemporaryRevealed { - strongSelf.updateState { state in - var state = state - state.archiveShouldBeTemporaryRevealed = true - return state + var isArchiveVisible = false + strongSelf.forEachItemNode({ itemNode in + if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { + if case .groupReference = item.content { + isArchiveVisible = true + } + } + }) + if isArchiveVisible { + if strongSelf.hapticFeedback == nil { + strongSelf.hapticFeedback = HapticFeedback() + } + strongSelf.hapticFeedback?.impact(.medium) + strongSelf.updateState { state in + var state = state + state.archiveShouldBeTemporaryRevealed = true + return state + } } } } diff --git a/TelegramUI/DebugController.swift b/TelegramUI/DebugController.swift index 7df9e36afc..d5ddb43f8c 100644 --- a/TelegramUI/DebugController.swift +++ b/TelegramUI/DebugController.swift @@ -40,6 +40,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case enableRaiseToSpeak(PresentationTheme, Bool) case keepChatNavigationStack(PresentationTheme, Bool) case skipReadHistory(PresentationTheme, Bool) + case crashOnSlowQueries(PresentationTheme, Bool) case clearTips(PresentationTheme) case reimport(PresentationTheme) case resetData(PresentationTheme) @@ -54,7 +55,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logs.rawValue case .logToFile, .logToConsole, .redactSensitiveData: return DebugControllerSection.logging.rawValue - case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory: + case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue case .clearTips, .reimport, .resetData, .animatedStickers: return DebugControllerSection.experiments.rawValue @@ -85,12 +86,14 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 8 case .skipReadHistory: return 9 - case .clearTips: + case .crashOnSlowQueries: return 10 - case .reimport: + case .clearTips: return 11 - case .resetData: + case .reimport: return 12 + case .resetData: + return 13 case .animatedStickers: return 14 case .versionInfo: @@ -275,6 +278,14 @@ private enum DebugControllerEntry: ItemListNodeEntry { return settings }).start() }) + case let .crashOnSlowQueries(theme, value): + return ItemListSwitchItem(theme: theme, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in + let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in + var settings = settings + settings.crashOnLongQueries = value + return settings + }).start() + }) case let .clearTips(theme): return ItemListActionItem(theme: theme, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in @@ -346,6 +357,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS #if DEBUG entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory)) #endif + entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries)) entries.append(.clearTips(presentationData.theme)) if hasLegacyAppData { entries.append(.reimport(presentationData.theme)) diff --git a/TelegramUI/DefaultDarkAccentPresentationTheme.swift b/TelegramUI/DefaultDarkAccentPresentationTheme.swift index ffe5794289..cea71a76f2 100644 --- a/TelegramUI/DefaultDarkAccentPresentationTheme.swift +++ b/TelegramUI/DefaultDarkAccentPresentationTheme.swift @@ -141,7 +141,8 @@ private let chatList = PresentationThemeChatList( searchBarKeyboardColor: .dark, verifiedIconFillColor: accentColor, verifiedIconForegroundColor: .white, - secretIconColor: secretColor + secretIconColor: secretColor, + neutralAvatarColor: UIColor(rgb: 0xDBF5FF, alpha: 0.4) ) private let bubble = PresentationThemeChatBubble( diff --git a/TelegramUI/DefaultDarkPresentationTheme.swift b/TelegramUI/DefaultDarkPresentationTheme.swift index 092fbc0582..1e39a31af9 100644 --- a/TelegramUI/DefaultDarkPresentationTheme.swift +++ b/TelegramUI/DefaultDarkPresentationTheme.swift @@ -141,7 +141,8 @@ private let chatList = PresentationThemeChatList( searchBarKeyboardColor: .dark, verifiedIconFillColor: accentColor, verifiedIconForegroundColor: .white, - secretIconColor: secretColor + secretIconColor: secretColor, + neutralAvatarColor: UIColor(rgb: 0x666666) ) private let bubble = PresentationThemeChatBubble( diff --git a/TelegramUI/DefaultPresentationTheme.swift b/TelegramUI/DefaultPresentationTheme.swift index 1a3441f8a0..a89bcd5502 100644 --- a/TelegramUI/DefaultPresentationTheme.swift +++ b/TelegramUI/DefaultPresentationTheme.swift @@ -141,7 +141,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun searchBarKeyboardColor: .light, verifiedIconFillColor: accentColor, verifiedIconForegroundColor: .white, - secretIconColor: secretColor + secretIconColor: secretColor, + neutralAvatarColor: UIColor(rgb: 0xb6b6ba) ) let chatListDay = PresentationThemeChatList( @@ -172,7 +173,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun searchBarKeyboardColor: .light, verifiedIconFillColor: accentColor, verifiedIconForegroundColor: .white, - secretIconColor: secretColor + secretIconColor: secretColor, + neutralAvatarColor: UIColor(rgb: 0xb6b6ba) ) let bubble = PresentationThemeChatBubble( diff --git a/TelegramUI/ExperimentalUISettings.swift b/TelegramUI/ExperimentalUISettings.swift index e2a1c7a79a..be6d184ce2 100644 --- a/TelegramUI/ExperimentalUISettings.swift +++ b/TelegramUI/ExperimentalUISettings.swift @@ -5,24 +5,28 @@ import SwiftSignalKit public struct ExperimentalUISettings: Equatable, PreferencesEntry { public var keepChatNavigationStack: Bool public var skipReadHistory: Bool + public var crashOnLongQueries: Bool public static var defaultSettings: ExperimentalUISettings { - return ExperimentalUISettings(keepChatNavigationStack: false, skipReadHistory: false) + return ExperimentalUISettings(keepChatNavigationStack: false, skipReadHistory: false, crashOnLongQueries: false) } - public init(keepChatNavigationStack: Bool, skipReadHistory: Bool) { + public init(keepChatNavigationStack: Bool, skipReadHistory: Bool, crashOnLongQueries: Bool) { self.keepChatNavigationStack = keepChatNavigationStack self.skipReadHistory = skipReadHistory + self.crashOnLongQueries = crashOnLongQueries } public init(decoder: PostboxDecoder) { self.keepChatNavigationStack = decoder.decodeInt32ForKey("keepChatNavigationStack", orElse: 0) != 0 self.skipReadHistory = decoder.decodeInt32ForKey("skipReadHistory", orElse: 0) != 0 + self.crashOnLongQueries = decoder.decodeInt32ForKey("crashOnLongQueries", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.keepChatNavigationStack ? 1 : 0, forKey: "keepChatNavigationStack") encoder.encodeInt32(self.skipReadHistory ? 1 : 0, forKey: "skipReadHistory") + encoder.encodeInt32(self.crashOnLongQueries ? 1 : 0, forKey: "crashOnLongQueries") } public func isEqual(to: PreferencesEntry) -> Bool { diff --git a/TelegramUI/ItemListEditableItem.swift b/TelegramUI/ItemListEditableItem.swift index 894f2470e8..41727847b3 100644 --- a/TelegramUI/ItemListEditableItem.swift +++ b/TelegramUI/ItemListEditableItem.swift @@ -269,9 +269,21 @@ class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerDelega reveal = false } } - self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring)) - if !reveal { - self.revealOptionsInteractivelyClosed() + + var selectedOption: ItemListRevealOption? + if reveal && rightRevealNode.isDisplayingExtendedAction() { + reveal = false + selectedOption = self.revealOptions.right.last + } else { + self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring)) + } + + if let selectedOption = selectedOption { + self.revealOptionSelected(selectedOption, animated: true) + } else { + if !reveal { + self.revealOptionsInteractivelyClosed() + } } } default: diff --git a/TelegramUI/ItemListRevealOptionsNode.swift b/TelegramUI/ItemListRevealOptionsNode.swift index 455ca677e5..0cfa4293a8 100644 --- a/TelegramUI/ItemListRevealOptionsNode.swift +++ b/TelegramUI/ItemListRevealOptionsNode.swift @@ -128,6 +128,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode { private let animationNode: ItemListRevealAnimationNode? private var animationNodeOffset: CGFloat = 0.0 var alignment: ItemListRevealOptionAlignment? + var isExpanded: Bool = false init(title: String, icon: ItemListRevealOptionIcon, color: UIColor, textColor: UIColor) { self.backgroundNode = ASDisplayNode() @@ -178,11 +179,11 @@ private final class ItemListRevealOptionNode: ASDisplayNode { } } - func updateLayout(isFirst: Bool, isLeft: Bool, baseSize: CGSize, alignment: ItemListRevealOptionAlignment, extendedWidth: CGFloat, sideInset: CGFloat, transition: ContainedViewLayoutTransition, revealFactor: CGFloat) { + func updateLayout(isFirst: Bool, isLeft: Bool, baseSize: CGSize, alignment: ItemListRevealOptionAlignment, isExpanded: Bool, extendedWidth: CGFloat, sideInset: CGFloat, transition: ContainedViewLayoutTransition, additive: Bool, revealFactor: CGFloat) { self.highlightNode.frame = CGRect(origin: CGPoint(), size: baseSize) var animateAdditive = false - if transition.isAnimated, self.alignment != alignment { + if additive && transition.isAnimated && self.isExpanded != isExpanded { animateAdditive = true } @@ -192,15 +193,23 @@ private final class ItemListRevealOptionNode: ASDisplayNode { } else { backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: extendedWidth, height: baseSize.height)) } + let deltaX: CGFloat if animateAdditive { let previousFrame = self.backgroundNode.frame self.backgroundNode.frame = backgroundFrame - transition.animatePositionAdditive(node: self.backgroundNode, offset: CGPoint(x: previousFrame.width - backgroundFrame.width, y: 0.0)) + if isLeft { + deltaX = previousFrame.width - backgroundFrame.width + } else { + deltaX = -(previousFrame.width - backgroundFrame.width) + } + transition.animatePositionAdditive(node: self.backgroundNode, offset: CGPoint(x: deltaX, y: 0.0)) } else { + deltaX = 0.0 transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) } self.alignment = alignment + self.isExpanded = isExpanded let titleSize = self.titleNode.calculatedSize var contentRect = CGRect(origin: CGPoint(), size: baseSize) switch alignment { @@ -215,13 +224,13 @@ private final class ItemListRevealOptionNode: ASDisplayNode { let titleIconSpacing: CGFloat = 11.0 let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - imageSize.width + sideInset) / 2.0), y: contentRect.midY - imageSize.height / 2.0 + iconOffset), size: imageSize) if animateAdditive { - transition.animatePositionAdditive(node: animationNode, offset: CGPoint(x: animationNode.frame.minX - iconFrame.minX, y: 0.0)) + transition.animatePositionAdditive(node: animationNode, offset: CGPoint(x: deltaX, y: 0.0)) } animationNode.frame = iconFrame let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize) if animateAdditive { - transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: self.titleNode.frame.minX - titleFrame.minX, y: 0.0)) + transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: deltaX, y: 0.0)) } self.titleNode.frame = titleFrame @@ -235,13 +244,13 @@ private final class ItemListRevealOptionNode: ASDisplayNode { let titleIconSpacing: CGFloat = 11.0 let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - imageSize.width + sideInset) / 2.0), y: contentRect.midY - imageSize.height / 2.0 + iconOffset), size: imageSize) if animateAdditive { - transition.animatePositionAdditive(node: iconNode, offset: CGPoint(x: iconNode.frame.minX - iconFrame.minX, y: 0.0)) + transition.animatePositionAdditive(node: iconNode, offset: CGPoint(x: deltaX, y: 0.0)) } iconNode.frame = iconFrame let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize) if animateAdditive { - transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: self.titleNode.frame.minX - titleFrame.minX, y: 0.0)) + transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: deltaX, y: 0.0)) } self.titleNode.frame = titleFrame } else { @@ -345,33 +354,30 @@ final class ItemListRevealOptionsNode: ASDisplayNode { let lastNodeWidth = size.width - basicNodeWidth * CGFloat(self.optionNodes.count - 1) let revealFactor = self.revealOffset / size.width let boundaryRevealFactor: CGFloat = 1.0 + basicNodeWidth / size.width * 0.7 - var leftOffset: CGFloat = 0.0 + var leftOffset: CGFloat if self.isLeft { + leftOffset = size.width + max(0.0, abs(revealFactor) - 1.0) * size.width + } else { leftOffset = 0.0 } - for i in 0 ..< self.optionNodes.count { + var i = self.isLeft ? (self.optionNodes.count - 1) : 0 + while i >= 0 && i < self.optionNodes.count { let node = self.optionNodes[i] let nodeWidth = i == (self.optionNodes.count - 1) ? lastNodeWidth : basicNodeWidth var extendedWidth = nodeWidth - let defaultAlignment: ItemListRevealOptionAlignment = isLeft ? .left : .right - var alignment = defaultAlignment + let defaultAlignment: ItemListRevealOptionAlignment = isLeft ? .right : .left var nodeTransition = transition - extendedWidth = nodeWidth * max(1.0, abs(revealFactor)) + extendedWidth = floorToScreenPixels(nodeWidth * max(1.0, abs(revealFactor))) var isExpanded = false if (isLeft && i == 0) || (!isLeft && i == self.optionNodes.count - 1) { - if isLeft && abs(revealFactor) > boundaryRevealFactor { + if abs(revealFactor) > boundaryRevealFactor { extendedWidth = size.width * max(1.0, abs(revealFactor)) isExpanded = true - if isLeft { - alignment = .right - } else { - alignment = .left - } } } - if let nodeAlignment = node.alignment, alignment != nodeAlignment { + if let _ = node.alignment, node.isExpanded != isExpanded { nodeTransition = transition.isAnimated ? transition : .animated(duration: 0.2, curve: .spring) - if alignment != defaultAlignment || !transition.isAnimated { + if !transition.isAnimated { self.tapticAction() } } @@ -382,13 +388,25 @@ final class ItemListRevealOptionsNode: ASDisplayNode { } var nodeLeftOffset = leftOffset + if self.isLeft { + nodeLeftOffset -= extendedWidth + } else { + nodeLeftOffset *= abs(revealFactor) + } if isExpanded { nodeLeftOffset = 0.0 } - transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(nodeLeftOffset * abs(revealFactor)), y: 0.0), size: CGSize(width: extendedWidth, height: size.height))) - node.updateLayout(isFirst: i == 0, isLeft: self.isLeft, baseSize: CGSize(width: nodeWidth, height: size.height), alignment: alignment, extendedWidth: extendedWidth, sideInset: sideInset, transition: nodeTransition, revealFactor: revealFactor) - leftOffset += nodeWidth + transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(nodeLeftOffset), y: 0.0), size: CGSize(width: extendedWidth, height: size.height))) + node.updateLayout(isFirst: (self.isLeft && i == 0) || (!self.isLeft && i == self.optionNodes.count - 1), isLeft: self.isLeft, baseSize: CGSize(width: nodeWidth, height: size.height), alignment: defaultAlignment, isExpanded: isExpanded, extendedWidth: extendedWidth, sideInset: sideInset, transition: nodeTransition, additive: !transition.isAnimated, revealFactor: revealFactor) + + if self.isLeft { + leftOffset -= extendedWidth + i -= 1 + } else { + leftOffset += nodeWidth + i += 1 + } } } @@ -409,12 +427,6 @@ final class ItemListRevealOptionsNode: ASDisplayNode { } func isDisplayingExtendedAction() -> Bool { - let defaultAlignment: ItemListRevealOptionAlignment = self.isLeft ? .left : .right - for node in self.optionNodes { - if let alignment = node.alignment, alignment != defaultAlignment { - return true - } - } - return false + return self.optionNodes.contains(where: { $0.isExpanded }) } } diff --git a/TelegramUI/NavigationBarSearchContentNode.swift b/TelegramUI/NavigationBarSearchContentNode.swift index 8d80096bc4..c29ba1c084 100644 --- a/TelegramUI/NavigationBarSearchContentNode.swift +++ b/TelegramUI/NavigationBarSearchContentNode.swift @@ -72,7 +72,7 @@ class NavigationBarSearchContentNode: NavigationBarContentNode { } func updateExpansionProgress(_ progress: CGFloat, animated: Bool = false) { - let newProgress = max(0.0, min(1.0, progress)) + let newProgress = max(0.0, min(10.0, progress)) if abs(newProgress - self.expansionProgress) > 0.0001 { self.expansionProgress = newProgress @@ -121,13 +121,16 @@ class NavigationBarSearchContentNode: NavigationBarContentNode { let fieldHeight: CGFloat = 36.0 let fraction = fieldHeight / self.nominalHeight - let visibleProgress = max(0.0, self.expansionProgress - 1.0 + fraction) / fraction + + let visibleProgress = max(0.0, min(1.0, self.expansionProgress) - 1.0 + fraction) / fraction + + let overscrollProgress = max(0.0, max(0.0, self.expansionProgress - 1.0 + fraction) / fraction - visibleProgress) let searchBarNodeLayout = self.placeholderNode.asyncLayout() let (searchBarHeight, searchBarApply) = searchBarNodeLayout(NSAttributedString(string: self.placeholder, font: searchBarFont, textColor: self.theme?.rootController.activeNavigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93)), CGSize(width: baseWidth, height: fieldHeight), visibleProgress, self.theme?.rootController.activeNavigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93), self.theme?.rootController.activeNavigationSearchBar.inputFillColor ?? .clear, self.theme?.rootController.navigationBar.backgroundColor ?? .clear, transition) searchBarApply() - let searchBarFrame = CGRect(origin: CGPoint(x: padding + leftInset, y: 8.0), size: CGSize(width: baseWidth, height: fieldHeight)) + let searchBarFrame = CGRect(origin: CGPoint(x: padding + leftInset, y: 8.0 + overscrollProgress * fieldHeight), size: CGSize(width: baseWidth, height: fieldHeight)) transition.updateFrame(node: self.placeholderNode, frame: searchBarFrame) self.placeholderHeight = searchBarHeight diff --git a/TelegramUI/PresentationTheme.swift b/TelegramUI/PresentationTheme.swift index 82483e8f36..a21e565509 100644 --- a/TelegramUI/PresentationTheme.swift +++ b/TelegramUI/PresentationTheme.swift @@ -375,8 +375,9 @@ public final class PresentationThemeChatList { public let verifiedIconFillColor: UIColor public let verifiedIconForegroundColor: UIColor public let secretIconColor: UIColor + public let neutralAvatarColor: UIColor - init(backgroundColor: UIColor, itemSeparatorColor: UIColor, itemBackgroundColor: UIColor, pinnedItemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, itemSelectedBackgroundColor: UIColor, titleColor: UIColor, secretTitleColor: UIColor, dateTextColor: UIColor, authorNameColor: UIColor, messageTextColor: UIColor, messageDraftTextColor: UIColor, checkmarkColor: UIColor, pendingIndicatorColor: UIColor, muteIconColor: UIColor, unreadBadgeActiveBackgroundColor: UIColor, unreadBadgeActiveTextColor: UIColor, unreadBadgeInactiveBackgroundColor: UIColor, unreadBadgeInactiveTextColor: UIColor, pinnedBadgeColor: UIColor, pinnedSearchBarColor: UIColor, regularSearchBarColor: UIColor, sectionHeaderFillColor: UIColor, sectionHeaderTextColor: UIColor, searchBarKeyboardColor: PresentationThemeKeyboardColor, verifiedIconFillColor: UIColor, verifiedIconForegroundColor: UIColor, secretIconColor: UIColor) { + init(backgroundColor: UIColor, itemSeparatorColor: UIColor, itemBackgroundColor: UIColor, pinnedItemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, itemSelectedBackgroundColor: UIColor, titleColor: UIColor, secretTitleColor: UIColor, dateTextColor: UIColor, authorNameColor: UIColor, messageTextColor: UIColor, messageDraftTextColor: UIColor, checkmarkColor: UIColor, pendingIndicatorColor: UIColor, muteIconColor: UIColor, unreadBadgeActiveBackgroundColor: UIColor, unreadBadgeActiveTextColor: UIColor, unreadBadgeInactiveBackgroundColor: UIColor, unreadBadgeInactiveTextColor: UIColor, pinnedBadgeColor: UIColor, pinnedSearchBarColor: UIColor, regularSearchBarColor: UIColor, sectionHeaderFillColor: UIColor, sectionHeaderTextColor: UIColor, searchBarKeyboardColor: PresentationThemeKeyboardColor, verifiedIconFillColor: UIColor, verifiedIconForegroundColor: UIColor, secretIconColor: UIColor, neutralAvatarColor: UIColor) { self.backgroundColor = backgroundColor self.itemSeparatorColor = itemSeparatorColor self.itemBackgroundColor = itemBackgroundColor @@ -405,6 +406,7 @@ public final class PresentationThemeChatList { self.verifiedIconFillColor = verifiedIconFillColor self.verifiedIconForegroundColor = verifiedIconForegroundColor self.secretIconColor = secretIconColor + self.neutralAvatarColor = neutralAvatarColor } }