ChatListNode: improved archive handling

ItemListEditableNode: improved swipe actions
This commit is contained in:
Peter 2019-04-21 23:53:32 +04:00
parent 61c1d8d350
commit 6b8adfc5b0
15 changed files with 236 additions and 136 deletions

View File

@ -316,15 +316,10 @@ public final class AvatarNode: ASDisplayNode {
let colorIndex: Int let colorIndex: Int
if let parameters = parameters as? AvatarNodeParameters { if let parameters = parameters as? AvatarNodeParameters {
if case .archivedChatsIcon = parameters.icon { context.beginPath()
let path = UIBezierPath(roundedRect: bounds, cornerRadius: floor(bounds.width / 3.8)) context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height:
path.addClip() bounds.size.height))
} else { 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 { if let explicitColorIndex = parameters.explicitColorIndex {
colorIndex = explicitColorIndex colorIndex = explicitColorIndex
@ -349,8 +344,8 @@ public final class AvatarNode: ASDisplayNode {
colorsArray = savedMessagesColors colorsArray = savedMessagesColors
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme { } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme {
colorsArray = [theme.list.blocksBackgroundColor.cgColor, theme.list.blocksBackgroundColor.cgColor] colorsArray = [theme.list.blocksBackgroundColor.cgColor, theme.list.blocksBackgroundColor.cgColor]
} else if case .archivedChatsIcon = parameters.icon { } else if case .archivedChatsIcon = parameters.icon, let theme = parameters.theme {
let color = UIColor(rgb: 0x4ac058) let color = theme.chatList.neutralAvatarColor
colorsArray = [color.cgColor, color.cgColor] colorsArray = [color.cgColor, color.cgColor]
} else { } else {
colorsArray = grayscaleColors colorsArray = grayscaleColors

View File

@ -1981,6 +1981,15 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
return chatLocationInfoReady 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 self.chatDisplayNode.historyNode.contentPositionChanged = { [weak self] offset in
if let strongSelf = self { if let strongSelf = self {
let offsetAlpha: CGFloat let offsetAlpha: CGFloat

View File

@ -13,10 +13,10 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa
let scrollToItem: ListViewScrollToItem let scrollToItem: ListViewScrollToItem
let targetProgress: CGFloat let targetProgress: CGFloat
if searchNode.expansionProgress < 0.6 { 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 targetProgress = 0.0
} else { } 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 targetProgress = 1.0
} }
searchNode.updateExpansionProgress(targetProgress, animated: true) 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 let itemFrame = sortItemNode.apparentFrame
if itemFrame.contains(CGPoint(x: 0.0, y: listNode.insets.top)) { if itemFrame.contains(CGPoint(x: 0.0, y: listNode.insets.top)) {
var scrollToItem: ListViewScrollToItem? 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 { if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
return fixListNodeScrolling(listView, searchNode: searchContentNode) return fixListNodeScrolling(listView, searchNode: searchContentNode)
} else { } else {
return false return false
} }
}*/ }
self.chatListDisplayNode.toolbarActionSelected = { [weak self] left in self.chatListDisplayNode.toolbarActionSelected = { [weak self] left in
self?.toolbarActionSelected(left: left) self?.toolbarActionSelected(left: left)

View File

@ -133,6 +133,47 @@ final class ChatListControllerNode: ASDisplayNode {
insets.left += layout.safeInsets.left insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right 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.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) 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 { if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) 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) { func activateSearch(placeholderNode: SearchBarPlaceholderNode) {

View File

@ -35,6 +35,10 @@ class ChatListItem: ListViewItem {
let selectable: Bool = true let selectable: Bool = true
var approximateHeight: CGFloat {
return self.hiddenOffset ? 0.0 : 44.0
}
let header: ListViewItemHeader? 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) { 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 var layoutOffset: CGFloat = 0.0
if item.hiddenOffset { 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)) 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 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 return (layout, { [weak self] synchronousLoads, animated in
if let strongSelf = self { if let strongSelf = self {
strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params) 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 { if let _ = updatedTheme {
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.chatList.itemSeparatorColor strongSelf.separatorNode.backgroundColor = item.presentationData.theme.chatList.itemSeparatorColor
@ -1049,21 +1060,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let previousBadgeFrame = strongSelf.badgeNode.frame 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) 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 { transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame)
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
}
} }
if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil { if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil {
let previousBadgeFrame = strongSelf.mentionBadgeNode.frame
let mentionBadgeOffset: CGFloat let mentionBadgeOffset: CGFloat
if badgeLayout.width.isZero { if badgeLayout.width.isZero {
mentionBadgeOffset = contentRect.maxX - mentionBadgeLayout.width 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) 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 { transition.updateFrame(node: strongSelf.mentionBadgeNode, frame: badgeFrame)
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)
}
} }
if let currentPinnedIconImage = currentPinnedIconImage { if let currentPinnedIconImage = currentPinnedIconImage {
@ -1219,7 +1214,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
let separatorInset: CGFloat let separatorInset: CGFloat
if (!nextIsPinned && item.index.pinningIndex != nil) || last || groupHiddenByDefault { if (!nextIsPinned && item.index.pinningIndex != nil) || last {
separatorInset = 0.0 separatorInset = 0.0
} else { } else {
separatorInset = editingOffset + leftInset + rawContentRect.origin.x 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.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 { if item.selected {
strongSelf.backgroundNode.backgroundColor = theme.itemSelectedBackgroundColor strongSelf.backgroundNode.backgroundColor = theme.itemSelectedBackgroundColor
} else if item.index.pinningIndex != nil { } else if item.index.pinningIndex != nil {
@ -1291,7 +1286,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var layoutOffset: CGFloat = 0.0 var layoutOffset: CGFloat = 0.0
if item.hiddenOffset { if item.hiddenOffset {
layoutOffset = -itemHeight //layoutOffset = -itemHeight
} }
if let reorderControlNode = self.reorderControlNode { if let reorderControlNode = self.reorderControlNode {
@ -1356,7 +1351,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: self.badgeNode, frame: updatedBadgeFrame) transition.updateFrame(node: self.badgeNode, frame: updatedBadgeFrame)
var mentionBadgeFrame = self.mentionBadgeNode.frame 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) transition.updateFrame(node: self.mentionBadgeNode, frame: mentionBadgeFrame)
let pinnedIconSize = self.pinnedIconNode.bounds.size let pinnedIconSize = self.pinnedIconNode.bounds.size
@ -1431,6 +1430,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
close = false close = false
case RevealOptionKey.hide.rawValue, RevealOptionKey.unhide.rawValue: case RevealOptionKey.hide.rawValue, RevealOptionKey.unhide.rawValue:
item.interaction.toggleArchivedFolderHiddenByDefault() item.interaction.toggleArchivedFolderHiddenByDefault()
close = false
default: default:
break break
} }
@ -1458,4 +1458,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self?.updateIsHighlighted(transition: .immediate) 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)
}
} }

View File

@ -367,6 +367,8 @@ final class ChatListNode: ListView {
let _ = self.currentRemovingPeerId.swap(peerId) 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) { 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.context = context
self.controlsHistoryPreload = controlsHistoryPreload self.controlsHistoryPreload = controlsHistoryPreload
@ -380,6 +382,7 @@ final class ChatListNode: ListView {
super.init() super.init()
self.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor self.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor
self.verticalScrollIndicatorFollowsOverscroll = true
let nodeInteraction = ChatListNodeInteraction(activateSearch: { [weak self] in let nodeInteraction = ChatListNodeInteraction(activateSearch: { [weak self] in
if let strongSelf = self, let activateSearch = strongSelf.activateSearch { if let strongSelf = self, let activateSearch = strongSelf.activateSearch {
@ -468,12 +471,17 @@ final class ChatListNode: ListView {
}) })
return updatedValue return updatedValue
} }
|> deliverOnMainQueue).start(next: { updatedValue in |> deliverOnMainQueue).start(next: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return 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<ChatListNodeState>(value: self.currentState) let previousState = Atomic<ChatListNodeState>(value: self.currentState)
let previousView = Atomic<ChatListNodeView?>(value: nil) let previousView = Atomic<ChatListNodeView?>(value: nil)
let previousHideArchivedFolderByDefault = Atomic<Bool?>(value: nil)
let currentRemovingPeerId = self.currentRemovingPeerId let currentRemovingPeerId = self.currentRemovingPeerId
let savedMessagesPeer: Signal<Peer?, NoError> let savedMessagesPeer: Signal<Peer?, NoError>
@ -509,6 +518,8 @@ final class ChatListNode: ListView {
let chatListNodeViewTransition = combineLatest(hideArchivedFolderByDefault, savedMessagesPeer, chatListViewUpdate, self.statePromise.get()) |> mapToQueue { (hideArchivedFolderByDefault, savedMessagesPeer, update, state) -> Signal<ChatListNodeListViewTransition, NoError> in let chatListNodeViewTransition = combineLatest(hideArchivedFolderByDefault, savedMessagesPeer, chatListViewUpdate, self.statePromise.get()) |> mapToQueue { (hideArchivedFolderByDefault, savedMessagesPeer, update, state) -> Signal<ChatListNodeListViewTransition, NoError> in
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.view, state: state, savedMessagesPeer: savedMessagesPeer, hideArchivedFolderByDefault: hideArchivedFolderByDefault, mode: mode) let (rawEntries, isLoading) = chatListNodeEntriesForView(update.view, state: state, savedMessagesPeer: savedMessagesPeer, hideArchivedFolderByDefault: hideArchivedFolderByDefault, mode: mode)
let entries = rawEntries.filter { entry in let entries = rawEntries.filter { entry in
switch entry { switch entry {
@ -644,7 +655,7 @@ final class ChatListNode: ListView {
if doesIncludeRemovingPeerId != didIncludeRemovingPeerId { if doesIncludeRemovingPeerId != didIncludeRemovingPeerId {
disableAnimations = false disableAnimations = false
} }
if previousState.archiveShouldBeTemporaryRevealed != state.archiveShouldBeTemporaryRevealed && doesIncludeArchive { if hideArchivedFolderByDefault && previousState.archiveShouldBeTemporaryRevealed != state.archiveShouldBeTemporaryRevealed && doesIncludeArchive {
disableAnimations = false disableAnimations = false
} }
if didIncludeHiddenByDefaultArchive != doesIncludeHiddenByDefaultArchive { if didIncludeHiddenByDefaultArchive != doesIncludeHiddenByDefaultArchive {
@ -652,6 +663,10 @@ final class ChatListNode: ListView {
} }
} }
if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault {
disableAnimations = false
}
var searchMode = false var searchMode = false
if case .peers = mode { if case .peers = mode {
searchMode = true searchMode = true
@ -689,6 +704,7 @@ final class ChatListNode: ListView {
var rawUnreadCount: Int32 = 0 var rawUnreadCount: Int32 = 0
var filteredUnreadCount: Int32 = 0 var filteredUnreadCount: Int32 = 0
var archiveVisible = false
if let range = range.visibleRange { if let range = range.visibleRange {
let entryCount = chatListView.filteredEntries.count let entryCount = chatListView.filteredEntries.count
for i in range.firstIndex ..< range.lastIndex { for i in range.firstIndex ..< range.lastIndex {
@ -705,6 +721,8 @@ final class ChatListNode: ListView {
filteredUnreadCount += count filteredUnreadCount += count
} }
} }
case .GroupReferenceEntry:
archiveVisible = true
default: default:
break break
} }
@ -714,6 +732,13 @@ final class ChatListNode: ListView {
visibleUnreadCountsValue.raw = rawUnreadCount visibleUnreadCountsValue.raw = rawUnreadCount
visibleUnreadCountsValue.filtered = filteredUnreadCount visibleUnreadCountsValue.filtered = filteredUnreadCount
strongSelf.visibleUnreadCountsValue = visibleUnreadCountsValue 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: case .none, .unknown:
revealHiddenItems = false revealHiddenItems = false
case let .known(value): case let .known(value):
revealHiddenItems = value <= 76.0 revealHiddenItems = value <= 54.0
} }
if !revealHiddenItems && strongSelf.currentState.archiveShouldBeTemporaryRevealed { if !revealHiddenItems && strongSelf.currentState.archiveShouldBeTemporaryRevealed {
strongSelf.updateState { state in strongSelf.updateState { state in
@ -974,16 +999,30 @@ final class ChatListNode: ListView {
case let .known(value): case let .known(value):
atTop = value <= 0.0 atTop = value <= 0.0
if startedScrollingAtUpperBound && strongSelf.isTracking { if startedScrollingAtUpperBound && strongSelf.isTracking {
revealHiddenItems = value <= -32.0 revealHiddenItems = value <= -76.0
} }
} }
strongSelf.scrolledAtTopValue = atTop strongSelf.scrolledAtTopValue = atTop
strongSelf.contentOffsetChanged?(offset) strongSelf.contentOffsetChanged?(offset)
if revealHiddenItems && !strongSelf.currentState.archiveShouldBeTemporaryRevealed { if revealHiddenItems && !strongSelf.currentState.archiveShouldBeTemporaryRevealed {
strongSelf.updateState { state in var isArchiveVisible = false
var state = state strongSelf.forEachItemNode({ itemNode in
state.archiveShouldBeTemporaryRevealed = true if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item {
return state 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
}
} }
} }
} }

View File

@ -40,6 +40,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableRaiseToSpeak(PresentationTheme, Bool) case enableRaiseToSpeak(PresentationTheme, Bool)
case keepChatNavigationStack(PresentationTheme, Bool) case keepChatNavigationStack(PresentationTheme, Bool)
case skipReadHistory(PresentationTheme, Bool) case skipReadHistory(PresentationTheme, Bool)
case crashOnSlowQueries(PresentationTheme, Bool)
case clearTips(PresentationTheme) case clearTips(PresentationTheme)
case reimport(PresentationTheme) case reimport(PresentationTheme)
case resetData(PresentationTheme) case resetData(PresentationTheme)
@ -54,7 +55,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logs.rawValue return DebugControllerSection.logs.rawValue
case .logToFile, .logToConsole, .redactSensitiveData: case .logToFile, .logToConsole, .redactSensitiveData:
return DebugControllerSection.logging.rawValue return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory: case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .clearTips, .reimport, .resetData, .animatedStickers: case .clearTips, .reimport, .resetData, .animatedStickers:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
@ -85,12 +86,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 8 return 8
case .skipReadHistory: case .skipReadHistory:
return 9 return 9
case .clearTips: case .crashOnSlowQueries:
return 10 return 10
case .reimport: case .clearTips:
return 11 return 11
case .resetData: case .reimport:
return 12 return 12
case .resetData:
return 13
case .animatedStickers: case .animatedStickers:
return 14 return 14
case .versionInfo: case .versionInfo:
@ -275,6 +278,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return settings return settings
}).start() }).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): case let .clearTips(theme):
return ItemListActionItem(theme: theme, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { 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 let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in
@ -346,6 +357,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
#if DEBUG #if DEBUG
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory)) entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
#endif #endif
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
entries.append(.clearTips(presentationData.theme)) entries.append(.clearTips(presentationData.theme))
if hasLegacyAppData { if hasLegacyAppData {
entries.append(.reimport(presentationData.theme)) entries.append(.reimport(presentationData.theme))

View File

@ -141,7 +141,8 @@ private let chatList = PresentationThemeChatList(
searchBarKeyboardColor: .dark, searchBarKeyboardColor: .dark,
verifiedIconFillColor: accentColor, verifiedIconFillColor: accentColor,
verifiedIconForegroundColor: .white, verifiedIconForegroundColor: .white,
secretIconColor: secretColor secretIconColor: secretColor,
neutralAvatarColor: UIColor(rgb: 0xDBF5FF, alpha: 0.4)
) )
private let bubble = PresentationThemeChatBubble( private let bubble = PresentationThemeChatBubble(

View File

@ -141,7 +141,8 @@ private let chatList = PresentationThemeChatList(
searchBarKeyboardColor: .dark, searchBarKeyboardColor: .dark,
verifiedIconFillColor: accentColor, verifiedIconFillColor: accentColor,
verifiedIconForegroundColor: .white, verifiedIconForegroundColor: .white,
secretIconColor: secretColor secretIconColor: secretColor,
neutralAvatarColor: UIColor(rgb: 0x666666)
) )
private let bubble = PresentationThemeChatBubble( private let bubble = PresentationThemeChatBubble(

View File

@ -141,7 +141,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
searchBarKeyboardColor: .light, searchBarKeyboardColor: .light,
verifiedIconFillColor: accentColor, verifiedIconFillColor: accentColor,
verifiedIconForegroundColor: .white, verifiedIconForegroundColor: .white,
secretIconColor: secretColor secretIconColor: secretColor,
neutralAvatarColor: UIColor(rgb: 0xb6b6ba)
) )
let chatListDay = PresentationThemeChatList( let chatListDay = PresentationThemeChatList(
@ -172,7 +173,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
searchBarKeyboardColor: .light, searchBarKeyboardColor: .light,
verifiedIconFillColor: accentColor, verifiedIconFillColor: accentColor,
verifiedIconForegroundColor: .white, verifiedIconForegroundColor: .white,
secretIconColor: secretColor secretIconColor: secretColor,
neutralAvatarColor: UIColor(rgb: 0xb6b6ba)
) )
let bubble = PresentationThemeChatBubble( let bubble = PresentationThemeChatBubble(

View File

@ -5,24 +5,28 @@ import SwiftSignalKit
public struct ExperimentalUISettings: Equatable, PreferencesEntry { public struct ExperimentalUISettings: Equatable, PreferencesEntry {
public var keepChatNavigationStack: Bool public var keepChatNavigationStack: Bool
public var skipReadHistory: Bool public var skipReadHistory: Bool
public var crashOnLongQueries: Bool
public static var defaultSettings: ExperimentalUISettings { 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.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory self.skipReadHistory = skipReadHistory
self.crashOnLongQueries = crashOnLongQueries
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
self.keepChatNavigationStack = decoder.decodeInt32ForKey("keepChatNavigationStack", orElse: 0) != 0 self.keepChatNavigationStack = decoder.decodeInt32ForKey("keepChatNavigationStack", orElse: 0) != 0
self.skipReadHistory = decoder.decodeInt32ForKey("skipReadHistory", orElse: 0) != 0 self.skipReadHistory = decoder.decodeInt32ForKey("skipReadHistory", orElse: 0) != 0
self.crashOnLongQueries = decoder.decodeInt32ForKey("crashOnLongQueries", orElse: 0) != 0
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.keepChatNavigationStack ? 1 : 0, forKey: "keepChatNavigationStack") encoder.encodeInt32(self.keepChatNavigationStack ? 1 : 0, forKey: "keepChatNavigationStack")
encoder.encodeInt32(self.skipReadHistory ? 1 : 0, forKey: "skipReadHistory") encoder.encodeInt32(self.skipReadHistory ? 1 : 0, forKey: "skipReadHistory")
encoder.encodeInt32(self.crashOnLongQueries ? 1 : 0, forKey: "crashOnLongQueries")
} }
public func isEqual(to: PreferencesEntry) -> Bool { public func isEqual(to: PreferencesEntry) -> Bool {

View File

@ -269,9 +269,21 @@ class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerDelega
reveal = false reveal = false
} }
} }
self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring))
if !reveal { var selectedOption: ItemListRevealOption?
self.revealOptionsInteractivelyClosed() 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: default:

View File

@ -128,6 +128,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
private let animationNode: ItemListRevealAnimationNode? private let animationNode: ItemListRevealAnimationNode?
private var animationNodeOffset: CGFloat = 0.0 private var animationNodeOffset: CGFloat = 0.0
var alignment: ItemListRevealOptionAlignment? var alignment: ItemListRevealOptionAlignment?
var isExpanded: Bool = false
init(title: String, icon: ItemListRevealOptionIcon, color: UIColor, textColor: UIColor) { init(title: String, icon: ItemListRevealOptionIcon, color: UIColor, textColor: UIColor) {
self.backgroundNode = ASDisplayNode() 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) self.highlightNode.frame = CGRect(origin: CGPoint(), size: baseSize)
var animateAdditive = false var animateAdditive = false
if transition.isAnimated, self.alignment != alignment { if additive && transition.isAnimated && self.isExpanded != isExpanded {
animateAdditive = true animateAdditive = true
} }
@ -192,15 +193,23 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
} else { } else {
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: extendedWidth, height: baseSize.height)) backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: extendedWidth, height: baseSize.height))
} }
let deltaX: CGFloat
if animateAdditive { if animateAdditive {
let previousFrame = self.backgroundNode.frame let previousFrame = self.backgroundNode.frame
self.backgroundNode.frame = backgroundFrame 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 { } else {
deltaX = 0.0
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
} }
self.alignment = alignment self.alignment = alignment
self.isExpanded = isExpanded
let titleSize = self.titleNode.calculatedSize let titleSize = self.titleNode.calculatedSize
var contentRect = CGRect(origin: CGPoint(), size: baseSize) var contentRect = CGRect(origin: CGPoint(), size: baseSize)
switch alignment { switch alignment {
@ -215,13 +224,13 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
let titleIconSpacing: CGFloat = 11.0 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) 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 { 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 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) let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize)
if animateAdditive { 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 self.titleNode.frame = titleFrame
@ -235,13 +244,13 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
let titleIconSpacing: CGFloat = 11.0 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) 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 { 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 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) let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize)
if animateAdditive { 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 self.titleNode.frame = titleFrame
} else { } else {
@ -345,33 +354,30 @@ final class ItemListRevealOptionsNode: ASDisplayNode {
let lastNodeWidth = size.width - basicNodeWidth * CGFloat(self.optionNodes.count - 1) let lastNodeWidth = size.width - basicNodeWidth * CGFloat(self.optionNodes.count - 1)
let revealFactor = self.revealOffset / size.width let revealFactor = self.revealOffset / size.width
let boundaryRevealFactor: CGFloat = 1.0 + basicNodeWidth / size.width * 0.7 let boundaryRevealFactor: CGFloat = 1.0 + basicNodeWidth / size.width * 0.7
var leftOffset: CGFloat = 0.0 var leftOffset: CGFloat
if self.isLeft { if self.isLeft {
leftOffset = size.width + max(0.0, abs(revealFactor) - 1.0) * size.width
} else {
leftOffset = 0.0 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 node = self.optionNodes[i]
let nodeWidth = i == (self.optionNodes.count - 1) ? lastNodeWidth : basicNodeWidth let nodeWidth = i == (self.optionNodes.count - 1) ? lastNodeWidth : basicNodeWidth
var extendedWidth = nodeWidth var extendedWidth = nodeWidth
let defaultAlignment: ItemListRevealOptionAlignment = isLeft ? .left : .right let defaultAlignment: ItemListRevealOptionAlignment = isLeft ? .right : .left
var alignment = defaultAlignment
var nodeTransition = transition var nodeTransition = transition
extendedWidth = nodeWidth * max(1.0, abs(revealFactor)) extendedWidth = floorToScreenPixels(nodeWidth * max(1.0, abs(revealFactor)))
var isExpanded = false var isExpanded = false
if (isLeft && i == 0) || (!isLeft && i == self.optionNodes.count - 1) { 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)) extendedWidth = size.width * max(1.0, abs(revealFactor))
isExpanded = true 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) nodeTransition = transition.isAnimated ? transition : .animated(duration: 0.2, curve: .spring)
if alignment != defaultAlignment || !transition.isAnimated { if !transition.isAnimated {
self.tapticAction() self.tapticAction()
} }
} }
@ -382,13 +388,25 @@ final class ItemListRevealOptionsNode: ASDisplayNode {
} }
var nodeLeftOffset = leftOffset var nodeLeftOffset = leftOffset
if self.isLeft {
nodeLeftOffset -= extendedWidth
} else {
nodeLeftOffset *= abs(revealFactor)
}
if isExpanded { if isExpanded {
nodeLeftOffset = 0.0 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))) transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(nodeLeftOffset), 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) 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)
leftOffset += nodeWidth
if self.isLeft {
leftOffset -= extendedWidth
i -= 1
} else {
leftOffset += nodeWidth
i += 1
}
} }
} }
@ -409,12 +427,6 @@ final class ItemListRevealOptionsNode: ASDisplayNode {
} }
func isDisplayingExtendedAction() -> Bool { func isDisplayingExtendedAction() -> Bool {
let defaultAlignment: ItemListRevealOptionAlignment = self.isLeft ? .left : .right return self.optionNodes.contains(where: { $0.isExpanded })
for node in self.optionNodes {
if let alignment = node.alignment, alignment != defaultAlignment {
return true
}
}
return false
} }
} }

View File

@ -72,7 +72,7 @@ class NavigationBarSearchContentNode: NavigationBarContentNode {
} }
func updateExpansionProgress(_ progress: CGFloat, animated: Bool = false) { 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 { if abs(newProgress - self.expansionProgress) > 0.0001 {
self.expansionProgress = newProgress self.expansionProgress = newProgress
@ -121,13 +121,16 @@ class NavigationBarSearchContentNode: NavigationBarContentNode {
let fieldHeight: CGFloat = 36.0 let fieldHeight: CGFloat = 36.0
let fraction = fieldHeight / self.nominalHeight 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 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) 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() 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) transition.updateFrame(node: self.placeholderNode, frame: searchBarFrame)
self.placeholderHeight = searchBarHeight self.placeholderHeight = searchBarHeight

View File

@ -375,8 +375,9 @@ public final class PresentationThemeChatList {
public let verifiedIconFillColor: UIColor public let verifiedIconFillColor: UIColor
public let verifiedIconForegroundColor: UIColor public let verifiedIconForegroundColor: UIColor
public let secretIconColor: 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.backgroundColor = backgroundColor
self.itemSeparatorColor = itemSeparatorColor self.itemSeparatorColor = itemSeparatorColor
self.itemBackgroundColor = itemBackgroundColor self.itemBackgroundColor = itemBackgroundColor
@ -405,6 +406,7 @@ public final class PresentationThemeChatList {
self.verifiedIconFillColor = verifiedIconFillColor self.verifiedIconFillColor = verifiedIconFillColor
self.verifiedIconForegroundColor = verifiedIconForegroundColor self.verifiedIconForegroundColor = verifiedIconForegroundColor
self.secretIconColor = secretIconColor self.secretIconColor = secretIconColor
self.neutralAvatarColor = neutralAvatarColor
} }
} }