From 718cc71fac2dbc78d839e9b9c14535d65c0e9952 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:51:22 -0300 Subject: [PATCH 01/34] WIP ArchiveNode resize by scroll offset --- ...ate_minimal_development_configuration.json | 8 +- .../Sources/ChatListControllerNode.swift | 48 +++++++++-- .../Sources/ChatListSearchListPaneNode.swift | 4 +- .../Node/ChatListArchiveTransitionItem.swift | 84 +++++++++++++++++++ .../Sources/Node/ChatListItem.swift | 84 +++++++++++++++---- .../Sources/Node/ChatListNode.swift | 39 ++++++++- .../Sources/Node/ChatListNodeEntries.swift | 4 + .../TextSizeSelectionController.swift | 1 + .../ThemeAccentColorControllerNode.swift | 1 + .../Themes/ThemePreviewControllerNode.swift | 1 + .../ChatSearchResultsContollerNode.swift | 1 + versions.json | 2 +- 12 files changed, 250 insertions(+), 27 deletions(-) create mode 100644 submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift diff --git a/build-system/template_minimal_development_configuration.json b/build-system/template_minimal_development_configuration.json index 1aad0aed955..6fdfb507347 100755 --- a/build-system/template_minimal_development_configuration.json +++ b/build-system/template_minimal_development_configuration.json @@ -1,8 +1,8 @@ { - "bundle_id": "org.{! a random string !}.Telegram", - "api_id": "{! get one at https://my.telegram.org/apps !}", - "api_hash": "{! get one at https://my.telegram.org/apps !}", - "team_id": "{! check README.md !}", + "bundle_id": "org.31375bc829c0233d.Telegraph", + "api_id": "8", + "api_hash": "7245de8e747a0d6fbe11f7cc14fcc0bb", + "team_id": "JTVJ6L7R86", "app_center_id": "0", "is_internal_build": "true", "is_appstore_build": "false", diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index bf9c314aca1..cc38d89928f 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -242,7 +242,7 @@ private final class ChatListShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, hiddenOffsetValue: .zero, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -2471,13 +2471,18 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { manuallyAllow = true } - if manuallyAllow, case let .known(value) = offset, value + listView.tempTopInset <= -40.0 { - overscrollHiddenChatItemsAllowed = true + + if manuallyAllow, case let .known(value) = offset { + let difference = value + listView.tempTopInset - 40.0 + print("offset difference: \(difference)") + if difference <= 0 { + overscrollHiddenChatItemsAllowed = true + } } } - if overscrollHiddenChatItemsAllowed { - if self.allowOverscrollItemExpansion { + if self.allowOverscrollItemExpansion { + if overscrollHiddenChatItemsAllowed { let timestamp = CACurrentMediaTime() if let _ = self.currentOverscrollItemExpansionTimestamp { } else { @@ -2493,6 +2498,39 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.inlineStackContainerNode?.currentItemNode.revealScrollHiddenItem() } } + } else if case let .known(value) = offset, value < 0 { + let difference = value + listView.tempTopInset - 40.0 + + let timestamp = CACurrentMediaTime() + if let _ = self.currentOverscrollItemExpansionTimestamp { + } else { + self.currentOverscrollItemExpansionTimestamp = timestamp + } + + if let currentOverscrollItemExpansionTimestamp = self.currentOverscrollItemExpansionTimestamp, currentOverscrollItemExpansionTimestamp <= timestamp - 0.0 { + self.allowOverscrollItemExpansion = false + + if isPrimary { + self.mainContainerNode.currentItemNode.forEachItemNode { node in + if let chatNode = node as? ChatListItemNode { + if case .groupReference(_) = chatNode.item?.content { + self.mainContainerNode.currentItemNode.updateArchiveTopOffset(offset: CGFloat(abs(difference))) + chatNode.updateHeightOffsetValue(offset: CGFloat(abs(difference)), transition: self.tempNavigationScrollingTransition ?? .immediate) + } + } + } + } else { + self.inlineStackContainerNode?.currentItemNode.revealScrollHiddenItem() + self.inlineStackContainerNode?.currentItemNode.forEachItemNode { node in + if let chatNode = node as? ChatListItemNode { + if case .groupReference(_) = chatNode.item?.content { + self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(offset: CGFloat(abs(difference))) + chatNode.updateHeightOffsetValue(offset: CGFloat(abs(difference)), transition: self.tempNavigationScrollingTransition ?? .immediate) + } + } + } + } + } } } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 8dc2cd66fa9..3481d9b83fa 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -850,7 +850,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) } - )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, hiddenOffsetValue: .zero, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -3591,7 +3591,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, hiddenOffsetValue: .zero, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift new file mode 100644 index 00000000000..9f9aaeebf8b --- /dev/null +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -0,0 +1,84 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import TelegramPresentationData +// +//class ChatListArchiveTransitionItem: ListViewItem { +// let theme: PresentationTheme +// +// let selectable: Bool = false +// +// init(theme: PresentationTheme) { +// self.theme = theme +// } +// +// func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { +// async { +// let node = ChatListArchiveTransitionItemNode() +// node.relativePosition = (first: previousItem == nil, last: nextItem == nil) +// node.insets = ChatListItemNode.insets(first: false, last: false, firstWithHeader: false) +// node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem) +// Queue.mainQueue().async { +// completion(node, { +// return (nil, { _ in }) +// }) +// } +// } +// } +// +// func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { +// Queue.mainQueue().async { +//// assert(node() is ChatListArchiveTransitionItemNode) +// if let nodeValue = node() as? ChatListArchiveTransitionItemNode { +// +// let layout = nodeValue.asyncLayout() +// async { +// let first = previousItem == nil +// let last = nextItem == nil +// +// let (nodeLayout, apply) = layout(self, params, first, last) +// Queue.mainQueue().async { +// completion(nodeLayout, { _ in +// apply() +// }) +// } +// } +// } +// } +// } +//} + +class ChatListArchiveTransitionNode: ASDisplayNode { + + required override init() { + super.init() + self.backgroundColor = .red + } + + func updateLayout(size: CGSize, synchronousLoads: Bool) { + self.frame = CGRect(origin: .zero, size: size) + } + +// override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { +// let layout = self.asyncLayout() +// let (_, apply) = layout(item as! ChatListArchiveTransitionItem, params, self.relativePosition.first, self.relativePosition.last) +// apply() +// } +// +// func asyncLayout() -> (_ item: ChatListArchiveTransitionItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) { +// return { item, params, first, last in +// let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 150.0), insets: UIEdgeInsets()) +// +// return (layout, { [weak self] in +// if let strongSelf = self { +// strongSelf.relativePosition = (first, last) +// +// strongSelf.contentSize = layout.contentSize +// strongSelf.insets = layout.insets +// } +// }) +// } +// } +} diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index bd7a1156a33..712900709ae 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -188,12 +188,18 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { let selected: Bool let enableContextActions: Bool let hiddenOffset: Bool + var hiddenOffsetValue: CGFloat let interaction: ChatListNodeInteraction public let selectable: Bool = true public var approximateHeight: CGFloat { - return self.hiddenOffset ? 0.0 : 44.0 +// if case let .groupReference(data) = self.content, data.groupId == .archive { +// return hiddenOffsetValue +// } else { +// return 44.0 +// } + return 44.0 } let header: ListViewItemHeader? @@ -211,7 +217,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { } } - public init(presentationData: ChatListPresentationData, context: AccountContext, chatListLocation: ChatListControllerLocation, filterData: ChatListItemFilterData?, index: EngineChatList.Item.Index, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { + public init(presentationData: ChatListPresentationData, context: AccountContext, chatListLocation: ChatListControllerLocation, filterData: ChatListItemFilterData?, index: EngineChatList.Item.Index, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, hiddenOffsetValue: CGFloat, interaction: ChatListNodeInteraction) { self.presentationData = presentationData self.chatListLocation = chatListLocation self.filterData = filterData @@ -224,6 +230,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { self.header = header self.enableContextActions = enableContextActions self.hiddenOffset = hiddenOffset + self.hiddenOffsetValue = hiddenOffsetValue self.interaction = interaction } @@ -932,6 +939,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { private let backgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode + let archiveTransitionNode: ChatListArchiveTransitionNode let contextContainer: ContextControllerSourceNode let mainContentContainerNode: ASDisplayNode @@ -1243,6 +1251,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true + self.archiveTransitionNode = ChatListArchiveTransitionNode() + self.archiveTransitionNode.isLayerBacked = true + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true @@ -1251,6 +1263,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.addSubnode(self.separatorNode) self.addSubnode(self.contextContainer) + self.addSubnode(self.archiveTransitionNode) + self.contextContainer.addSubnode(self.mainContentContainerNode) self.avatarContainerNode.addSubnode(self.avatarNode) @@ -2692,11 +2706,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + floor(item.presentationData.fontSize.itemListBaseFontSize * 8.0 / 17.0)), size: CGSize(width: rawContentWidth, height: itemHeight - 12.0 - 9.0)) let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) - var heightOffset: CGFloat = 0.0 - if item.hiddenOffset { - heightOffset = -itemHeight + var heightOffset: CGFloat = .zero + if case let .groupReference(data) = item.content, data.groupId == .archive { + heightOffset = -(itemHeight-item.hiddenOffsetValue) } - let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: max(0.0, itemHeight + heightOffset)), insets: insets) + print("height offset: \(heightOffset) with hiddenOffsetValue: \(item.hiddenOffsetValue) itemHeight: \(itemHeight)") + let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: itemHeight + heightOffset), insets: insets) var customActions: [ChatListItemAccessibilityCustomAction] = [] for option in peerLeftRevealOptions { @@ -2706,6 +2721,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key)) } + print("layout height: \(layout.contentSize.height)") return (layout, { [weak self] synchronousLoads, animated in if let strongSelf = self { strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize) @@ -2720,13 +2736,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { animateOnline = false } strongSelf.currentOnline = online - - if item.hiddenOffset { - strongSelf.layer.zPosition = -1.0 - } - - if case .groupReference = item.content { - strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) + + if case let .groupReference(data) = item.content { + if data.groupId == .archive { + strongSelf.layer.zPosition = -1.0 + } + let translationY = layout.contentSize.height - itemHeight + strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, translationY, 0.0) + print("set sublayer translation y: \(translationY)") } if let _ = updatedTheme { @@ -2742,11 +2759,24 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition = .immediate } + if case let .groupReference(data) = item.content, data.groupId == .archive { + transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) + } else { + transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + } + let contextContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) // strongSelf.contextContainer.position = contextContainerFrame.center transition.updatePosition(node: strongSelf.contextContainer, position: contextContainerFrame.center) transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) - +// strongSelf.archiveTransitionNode.updateLayout(size: layout.contentSize, synchronousLoads: true) +// print("top offset: \(item.hiddenOffsetValue) hiddenOffset: \(item.hiddenOffset)") +// let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) +// transition.updatePosition(node: strongSelf.archiveTransitionNode, position: archiveTransitionFrame.center) +// transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: archiveTransitionFrame) + var mainContentFrame: CGRect var mainContentBoundsOffset: CGFloat var mainContentAlpha: CGFloat = 1.0 @@ -3832,6 +3862,32 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { }) } + func updateHeightOffsetValue(offset: CGFloat, transition: ContainedViewLayoutTransition) { + self.item?.hiddenOffsetValue = offset + var heightOffset: CGFloat = .zero + var layoutHeight: CGFloat = .zero + if let item, let currentItemHeight, case let .groupReference(data) = self.item?.content, data.groupId == .archive { + heightOffset = -(currentItemHeight-item.hiddenOffsetValue) + layoutHeight = currentItemHeight + heightOffset + print("height offset: \(heightOffset) with hiddenOffsetValue: \(item.hiddenOffsetValue) currentItemHeight: \(currentItemHeight) layoutHeight: \(layoutHeight)") + } else { + layoutHeight = self.currentItemHeight ?? offset + } + + let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.layout.contentSize.width, height: currentItemHeight ?? offset)) + let layout = self.asyncLayout() +// let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem) + if let layoutParams = self.layoutParams { + let updatedParams = ListViewItemLayoutParams(width: layoutParams.5.width, leftInset: layoutParams.5.leftInset, rightInset: layoutParams.5.rightInset, availableHeight: layoutHeight) + let (nodeLayout, apply) = layout(self.item ?? layoutParams.0, updatedParams, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4) + apply(true, true) + self.contentSize = nodeLayout.contentSize + self.insets = nodeLayout.insets + } + transition.updatePosition(node: self.archiveTransitionNode, position: archiveTransitionFrame.center) + transition.updateBounds(node: self.archiveTransitionNode, bounds: archiveTransitionFrame) + } + func playArchiveAnimation() { guard let item = self.item, case .groupReference = item.content else { return diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index bc55fda784c..337ba49a524 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -246,6 +246,7 @@ public struct ChatListNodeState: Equatable { public var pendingRemovalItemIds: Set public var pendingClearHistoryPeerIds: Set public var hiddenItemShouldBeTemporaryRevealed: Bool + public var topOffset: CGFloat public var selectedAdditionalCategoryIds: Set public var hiddenPsaPeerId: EnginePeer.Id? public var foundPeers: [(EnginePeer, EnginePeer?)] @@ -265,6 +266,7 @@ public struct ChatListNodeState: Equatable { pendingRemovalItemIds: Set, pendingClearHistoryPeerIds: Set, hiddenItemShouldBeTemporaryRevealed: Bool, + topOffset: CGFloat, hiddenPsaPeerId: EnginePeer.Id?, selectedThreadIds: Set, archiveStoryState: StoryState? @@ -280,6 +282,7 @@ public struct ChatListNodeState: Equatable { self.pendingRemovalItemIds = pendingRemovalItemIds self.pendingClearHistoryPeerIds = pendingClearHistoryPeerIds self.hiddenItemShouldBeTemporaryRevealed = hiddenItemShouldBeTemporaryRevealed + self.topOffset = topOffset self.hiddenPsaPeerId = hiddenPsaPeerId self.selectedThreadIds = selectedThreadIds self.archiveStoryState = archiveStoryState @@ -319,6 +322,9 @@ public struct ChatListNodeState: Equatable { if lhs.hiddenItemShouldBeTemporaryRevealed != rhs.hiddenItemShouldBeTemporaryRevealed { return false } + if lhs.topOffset != rhs.topOffset { + return false + } if lhs.hiddenPsaPeerId != rhs.hiddenPsaPeerId { return false } @@ -419,6 +425,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, + hiddenOffsetValue: .zero, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -624,6 +631,11 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL case let .HoleEntry(_, theme): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint) case let .GroupReferenceEntry(groupReferenceEntry): +// if groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed { +// return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), +// directionHint: entry.directionHint) +// } else { + print("insert group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed)") return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, context: context, @@ -649,8 +661,11 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed, + hiddenOffsetValue: groupReferenceEntry.topOffset, interaction: nodeInteraction ), directionHint: entry.directionHint) + +// } case let .ContactEntry(contactEntry): let header: ChatListSearchItemHeader? = nil @@ -776,6 +791,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, + hiddenOffsetValue: .zero, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -935,6 +951,12 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL case let .HoleEntry(_, theme): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint) case let .GroupReferenceEntry(groupReferenceEntry): +// if groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed { +// return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), +// directionHint: entry.directionHint) +// } else { + print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.topOffset)") + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, context: context, @@ -960,8 +982,10 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed, + hiddenOffsetValue: groupReferenceEntry.topOffset, interaction: nodeInteraction ), directionHint: entry.directionHint) +// } case let .ContactEntry(contactEntry): let header: ChatListSearchItemHeader? = nil @@ -1253,7 +1277,7 @@ public final class ChatListNode: ListView { isSelecting = true } - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, topOffset: .zero, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme @@ -2928,6 +2952,9 @@ public final class ChatListNode: ListView { } } } +// if itemNode is ChatListArchiveTransitionItemNode { +// isHiddenItemVisible = true +// } }) if isHiddenItemVisible && !self.currentState.hiddenItemShouldBeTemporaryRevealed { if self.hapticFeedback == nil { @@ -2942,6 +2969,16 @@ public final class ChatListNode: ListView { } } + func updateArchiveTopOffset(offset: CGFloat) { + guard !self.currentState.hiddenItemShouldBeTemporaryRevealed else { return } + self.updateState { state in + var state = state + state.topOffset = offset + state.hiddenItemShouldBeTemporaryRevealed = true + return state + } + } + private func pollFilterUpdates() { self.chatFolderUpdates.set(.single(nil)) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index bf851892c7d..2e41e649c7c 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -312,6 +312,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { var unreadCount: Int var revealed: Bool var hiddenByDefault: Bool + var topOffset: CGFloat var storyState: ChatListNodeState.StoryState? init( @@ -324,6 +325,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { unreadCount: Int, revealed: Bool, hiddenByDefault: Bool, + topOffset: CGFloat, storyState: ChatListNodeState.StoryState? ) { self.index = index @@ -335,6 +337,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { self.unreadCount = unreadCount self.revealed = revealed self.hiddenByDefault = hiddenByDefault + self.topOffset = topOffset self.storyState = storyState } @@ -857,6 +860,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, unreadCount: groupReference.unreadCount, revealed: state.hiddenItemShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault, + topOffset: state.topOffset, storyState: mappedStoryState ))) if pinningIndex != 0 { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index d2ffb161b31..25932eddf23 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -298,6 +298,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView header: nil, enableContextActions: false, hiddenOffset: false, + hiddenOffsetValue: .zero, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 6b4d9af2bb3..3545a38882e 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -931,6 +931,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate header: nil, enableContextActions: false, hiddenOffset: false, + hiddenOffsetValue: .zero, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index e48ad2ef67b..7fb40b890a8 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -444,6 +444,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { header: nil, enableContextActions: false, hiddenOffset: false, + hiddenOffsetValue: .zero, interaction: interaction ) } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 9659de86ed6..9f781ddf888 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -111,6 +111,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { header: nil, enableContextActions: false, hiddenOffset: false, + hiddenOffsetValue: .zero, interaction: interaction ) } diff --git a/versions.json b/versions.json index 2c71d357afa..b1f7750f937 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { "app": "10.0.1", "bazel": "6.1.1", - "xcode": "14.2" + "xcode": "14.3.1" } From e6c425d3a4352946041bc6a418932c52ee18bf45 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Fri, 25 Aug 2023 17:45:39 -0300 Subject: [PATCH 02/34] [WIP] intermediate animation --- .../Sources/ChatListControllerNode.swift | 102 ++++++++++-------- .../Sources/Node/ChatListItem.swift | 58 +++++----- .../Sources/Node/ChatListNode.swift | 7 +- .../Sources/StoryPeerListComponent.swift | 3 + 4 files changed, 91 insertions(+), 79 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index cc38d89928f..355270137f8 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2428,9 +2428,11 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { if listView.isDragging { var overscrollSelectedId: EnginePeer.Id? var overscrollHiddenChatItemsAllowed = false + var overscrollFraction: CGFloat? if let controller = self.controller, let componentView = controller.chatListHeaderView(), let storyPeerListView = componentView.storyPeerListView() { overscrollSelectedId = storyPeerListView.overscrollSelectedId overscrollHiddenChatItemsAllowed = storyPeerListView.overscrollHiddenChatItemsAllowed + overscrollFraction = storyPeerListView.overscrollFraction } if let chatListNode = listView as? ChatListNode { @@ -2481,52 +2483,64 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } - if self.allowOverscrollItemExpansion { - if overscrollHiddenChatItemsAllowed { - let timestamp = CACurrentMediaTime() - if let _ = self.currentOverscrollItemExpansionTimestamp { - } else { - self.currentOverscrollItemExpansionTimestamp = timestamp - } - - if let currentOverscrollItemExpansionTimestamp = self.currentOverscrollItemExpansionTimestamp, currentOverscrollItemExpansionTimestamp <= timestamp - 0.0 { - self.allowOverscrollItemExpansion = false - - if isPrimary { - self.mainContainerNode.currentItemNode.revealScrollHiddenItem() - } else { - self.inlineStackContainerNode?.currentItemNode.revealScrollHiddenItem() - } - } - } else if case let .known(value) = offset, value < 0 { - let difference = value + listView.tempTopInset - 40.0 - - let timestamp = CACurrentMediaTime() - if let _ = self.currentOverscrollItemExpansionTimestamp { - } else { - self.currentOverscrollItemExpansionTimestamp = timestamp - } - - if let currentOverscrollItemExpansionTimestamp = self.currentOverscrollItemExpansionTimestamp, currentOverscrollItemExpansionTimestamp <= timestamp - 0.0 { - self.allowOverscrollItemExpansion = false - - if isPrimary { - self.mainContainerNode.currentItemNode.forEachItemNode { node in - if let chatNode = node as? ChatListItemNode { - if case .groupReference(_) = chatNode.item?.content { - self.mainContainerNode.currentItemNode.updateArchiveTopOffset(offset: CGFloat(abs(difference))) - chatNode.updateHeightOffsetValue(offset: CGFloat(abs(difference)), transition: self.tempNavigationScrollingTransition ?? .immediate) - } +// if self.allowOverscrollItemExpansion { +// if overscrollHiddenChatItemsAllowed { +// let timestamp = CACurrentMediaTime() +// if let _ = self.currentOverscrollItemExpansionTimestamp { +// } else { +// self.currentOverscrollItemExpansionTimestamp = timestamp +// } +// +// if let currentOverscrollItemExpansionTimestamp = self.currentOverscrollItemExpansionTimestamp, currentOverscrollItemExpansionTimestamp <= timestamp - 0.0 { +// self.allowOverscrollItemExpansion = false +// +// if isPrimary { +// self.mainContainerNode.currentItemNode.revealScrollHiddenItem() +// } else { +// self.inlineStackContainerNode?.currentItemNode.revealScrollHiddenItem() +// } +// } +// } +// } + + if + let overscrollFraction, overscrollFraction > 0, + let node = self.mainContainerNode.currentItemNode.itemNodeAtIndex(2) as? ChatListItemNode, node.isNodeLoaded, + let itemHeight = node.currentItemHeight, itemHeight > 0 + { + + let expandedHeight = (overscrollFraction * 2) * itemHeight //listView.tempTopInset - 40.0 + print("expandedHeight: \(expandedHeight) overscrollFraction: \(overscrollFraction) itemHeight: \(itemHeight)") + + let timestamp = CACurrentMediaTime() + if let _ = self.currentOverscrollItemExpansionTimestamp { + } else { + self.currentOverscrollItemExpansionTimestamp = timestamp + } + + if let currentOverscrollItemExpansionTimestamp = self.currentOverscrollItemExpansionTimestamp, currentOverscrollItemExpansionTimestamp <= timestamp - 0.0 { +// self.allowOverscrollItemExpansion = false + + if isPrimary { + self.mainContainerNode.currentItemNode.forEachItemNode { node in + if let chatNode = node as? ChatListItemNode { + if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.hiddenOffsetValue { + self.mainContainerNode.currentItemNode.updateArchiveTopOffset(offset: expandedHeight) + chatNode.updateExpandedHeight(height: expandedHeight, transition: .immediate) +// chatNode.animateFrameTransition(1.0, expandedHeight) +// chatNode.updateHeightOffsetValue(offset: expandedHeight, transition: self.tempNavigationScrollingTransition ?? .immediate) } } - } else { - self.inlineStackContainerNode?.currentItemNode.revealScrollHiddenItem() - self.inlineStackContainerNode?.currentItemNode.forEachItemNode { node in - if let chatNode = node as? ChatListItemNode { - if case .groupReference(_) = chatNode.item?.content { - self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(offset: CGFloat(abs(difference))) - chatNode.updateHeightOffsetValue(offset: CGFloat(abs(difference)), transition: self.tempNavigationScrollingTransition ?? .immediate) - } + } + } else { + self.inlineStackContainerNode?.currentItemNode.forEachItemNode { node in + if let chatNode = node as? ChatListItemNode { + if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.hiddenOffsetValue { + self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(offset: expandedHeight) + chatNode.updateExpandedHeight(height: expandedHeight, transition: .immediate) +// chatNode.animateFrameTransition(1.0, expandedHeight) + +// chatNode.updateHeightOffsetValue(offset: expandedHeight, transition: self.tempNavigationScrollingTransition ?? .immediate) } } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 712900709ae..44514970add 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -194,12 +194,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { public let selectable: Bool = true public var approximateHeight: CGFloat { -// if case let .groupReference(data) = self.content, data.groupId == .archive { -// return hiddenOffsetValue -// } else { -// return 44.0 -// } - return 44.0 + return self.hiddenOffset ? 0.0 : 44.0 } let header: ListViewItemHeader? @@ -958,7 +953,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { private var textArrowNode: ASImageNode? private var compoundTextButtonNode: HighlightTrackingButtonNode? let measureNode: TextNode - private var currentItemHeight: CGFloat? + var currentItemHeight: CGFloat? let forwardedIconNode: ASImageNode let textNode: TextNodeWithEntities var dustNode: InvisibleInkDustNode? @@ -2709,8 +2704,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var heightOffset: CGFloat = .zero if case let .groupReference(data) = item.content, data.groupId == .archive { heightOffset = -(itemHeight-item.hiddenOffsetValue) + print("height offset: \(heightOffset) with hiddenOffsetValue: \(item.hiddenOffsetValue) itemHeight: \(itemHeight)") } - print("height offset: \(heightOffset) with hiddenOffsetValue: \(item.hiddenOffsetValue) itemHeight: \(itemHeight)") let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: itemHeight + heightOffset), insets: insets) var customActions: [ChatListItemAccessibilityCustomAction] = [] @@ -2759,13 +2754,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition = .immediate } - if case let .groupReference(data) = item.content, data.groupId == .archive { - transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) - transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) - } else { - transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) - transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) - } +// if case let .groupReference(data) = item.content, data.groupId == .archive { +// transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) +// transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) +// } else { +// transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) +// transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) +// } let contextContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) // strongSelf.contextContainer.position = contextContainerFrame.center @@ -3862,23 +3857,23 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { }) } - func updateHeightOffsetValue(offset: CGFloat, transition: ContainedViewLayoutTransition) { - self.item?.hiddenOffsetValue = offset - var heightOffset: CGFloat = .zero - var layoutHeight: CGFloat = .zero - if let item, let currentItemHeight, case let .groupReference(data) = self.item?.content, data.groupId == .archive { - heightOffset = -(currentItemHeight-item.hiddenOffsetValue) - layoutHeight = currentItemHeight + heightOffset - print("height offset: \(heightOffset) with hiddenOffsetValue: \(item.hiddenOffsetValue) currentItemHeight: \(currentItemHeight) layoutHeight: \(layoutHeight)") - } else { - layoutHeight = self.currentItemHeight ?? offset - } + func updateExpandedHeight(height: CGFloat, transition: ContainedViewLayoutTransition) { + self.item?.hiddenOffsetValue = height +// var heightOffset: CGFloat = .zero +// var layoutHeight: CGFloat = .zero +// if let item, let currentItemHeight, case let .groupReference(data) = self.item?.content, data.groupId == .archive { +// heightOffset = -(currentItemHeight-item.hiddenOffsetValue) +// layoutHeight = currentItemHeight + heightOffset +// } else { +// layoutHeight = self.currentItemHeight ?? offset +// } + guard let currentItemHeight else { return } - let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.layout.contentSize.width, height: currentItemHeight ?? offset)) + let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.layout.contentSize.width, height: currentItemHeight)) let layout = self.asyncLayout() -// let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem) +// let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem) if let layoutParams = self.layoutParams { - let updatedParams = ListViewItemLayoutParams(width: layoutParams.5.width, leftInset: layoutParams.5.leftInset, rightInset: layoutParams.5.rightInset, availableHeight: layoutHeight) + let updatedParams = ListViewItemLayoutParams(width: layoutParams.5.width, leftInset: layoutParams.5.leftInset, rightInset: layoutParams.5.rightInset, availableHeight: height) let (nodeLayout, apply) = layout(self.item ?? layoutParams.0, updatedParams, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4) apply(true, true) self.contentSize = nodeLayout.contentSize @@ -3886,6 +3881,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } transition.updatePosition(node: self.archiveTransitionNode, position: archiveTransitionFrame.center) transition.updateBounds(node: self.archiveTransitionNode, bounds: archiveTransitionFrame) +// animateFrameTransition(1.0, self.contentSize.height) } func playArchiveAnimation() { @@ -3900,7 +3896,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let item = self.item { if case .groupReference = item.content { - self.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, currentValue - (self.currentItemHeight ?? 0.0), 0.0) + let translationY = currentValue - (self.currentItemHeight ?? 0.0) + self.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, translationY, 0.0) + print("set sublayer translation y #2: \(translationY)") } else { var separatorFrame = self.separatorNode.frame separatorFrame.origin.y = currentValue - UIScreenPixel diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 337ba49a524..d917b7e623e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2952,9 +2952,6 @@ public final class ChatListNode: ListView { } } } -// if itemNode is ChatListArchiveTransitionItemNode { -// isHiddenItemVisible = true -// } }) if isHiddenItemVisible && !self.currentState.hiddenItemShouldBeTemporaryRevealed { if self.hapticFeedback == nil { @@ -2970,11 +2967,11 @@ public final class ChatListNode: ListView { } func updateArchiveTopOffset(offset: CGFloat) { - guard !self.currentState.hiddenItemShouldBeTemporaryRevealed else { return } + let toggleTemporaryRevealHiddenItems = !self.currentState.hiddenItemShouldBeTemporaryRevealed self.updateState { state in var state = state state.topOffset = offset - state.hiddenItemShouldBeTemporaryRevealed = true + state.hiddenItemShouldBeTemporaryRevealed = toggleTemporaryRevealHiddenItems return state } } diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 348ed018b68..0a42d4910b8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -355,6 +355,7 @@ public final class StoryPeerListComponent: Component { public private(set) var overscrollSelectedId: EnginePeer.Id? public private(set) var overscrollHiddenChatItemsAllowed: Bool = false + public private(set) var overscrollFraction: CGFloat = 0.0 private var anchorForTooltipRect: CGRect? @@ -832,6 +833,8 @@ public final class StoryPeerListComponent: Component { } } + print("overscrollStage1: \(overscrollStage1) overscrollStage2: \(overscrollStage2) realTimeOverscrollFraction: \(realTimeOverscrollFraction)") + self.overscrollFraction = overscrollStage1 if overscrollStage1 >= 0.5 { self.overscrollHiddenChatItemsAllowed = true } else { From f5934f5fa21e1c3acfcd6d1ec3be56008ca99c26 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Fri, 25 Aug 2023 23:27:48 -0300 Subject: [PATCH 03/34] [WIP] arrow to archive animation node --- .../Sources/ChatListControllerNode.swift | 20 ++- .../Node/ChatListArchiveTransitionItem.swift | 166 ++++++++++-------- .../Sources/Node/ChatListItem.swift | 42 ++--- .../Sources/Node/ChatListNode.swift | 44 +++++ .../Sources/StoryPeerListComponent.swift | 2 +- .../Animations/anim_arrow_to_archive.json | 1 + 6 files changed, 179 insertions(+), 96 deletions(-) create mode 100644 submodules/TelegramUI/Resources/Animations/anim_arrow_to_archive.json diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 355270137f8..9395aade90d 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2503,10 +2503,20 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { // } // } + +// if case let .known(value) = offset { +// let listViewOffsetFraction = (value + listView.tempTopInset)/76 +// print("#1 listViewOffset: \(value) listViewOffsetFraction: \(listViewOffsetFraction)") +// } +// let scrollViewFraction = listView.scroller.contentOffset.y / 76 +// print("#1 overscrollFraction: \(overscrollFraction ?? .zero) scrollViewOffset: \(listView.scroller.contentOffset) topInset: \(listView.tempTopInset) scrollViewFraction: \(scrollViewFraction)") + + if let overscrollFraction, overscrollFraction > 0, - let node = self.mainContainerNode.currentItemNode.itemNodeAtIndex(2) as? ChatListItemNode, node.isNodeLoaded, - let itemHeight = node.currentItemHeight, itemHeight > 0 +// self.allowOverscrollItemExpansion, + let node = self.mainContainerNode.currentItemNode.itemNodeAtIndex(2) as? ChatListItemNode, node.isNodeLoaded, + let itemHeight = node.currentItemHeight, itemHeight > 0 { let expandedHeight = (overscrollFraction * 2) * itemHeight //listView.tempTopInset - 40.0 @@ -2519,8 +2529,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } if let currentOverscrollItemExpansionTimestamp = self.currentOverscrollItemExpansionTimestamp, currentOverscrollItemExpansionTimestamp <= timestamp - 0.0 { -// self.allowOverscrollItemExpansion = false - + if overscrollFraction >= 0.5 { + self.allowOverscrollItemExpansion = false + } if isPrimary { self.mainContainerNode.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { @@ -2539,7 +2550,6 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(offset: expandedHeight) chatNode.updateExpandedHeight(height: expandedHeight, transition: .immediate) // chatNode.animateFrameTransition(1.0, expandedHeight) - // chatNode.updateHeightOffsetValue(offset: expandedHeight, transition: self.tempNavigationScrollingTransition ?? .immediate) } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 9f9aaeebf8b..1746ac52986 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -3,82 +3,110 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit +import AnimationUI +import ComponentFlow import TelegramPresentationData -// -//class ChatListArchiveTransitionItem: ListViewItem { -// let theme: PresentationTheme -// -// let selectable: Bool = false -// -// init(theme: PresentationTheme) { -// self.theme = theme -// } -// -// func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { -// async { -// let node = ChatListArchiveTransitionItemNode() -// node.relativePosition = (first: previousItem == nil, last: nextItem == nil) -// node.insets = ChatListItemNode.insets(first: false, last: false, firstWithHeader: false) -// node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem) -// Queue.mainQueue().async { -// completion(node, { -// return (nil, { _ in }) -// }) -// } -// } -// } -// -// func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { -// Queue.mainQueue().async { -//// assert(node() is ChatListArchiveTransitionItemNode) -// if let nodeValue = node() as? ChatListArchiveTransitionItemNode { -// -// let layout = nodeValue.asyncLayout() -// async { -// let first = previousItem == nil -// let last = nextItem == nil -// -// let (nodeLayout, apply) = layout(self, params, first, last) -// Queue.mainQueue().async { -// completion(nodeLayout, { _ in -// apply() -// }) -// } -// } -// } -// } -// } -//} class ChatListArchiveTransitionNode: ASDisplayNode { + struct TransitionAnimation { + enum State { + case swipeDownInit + case swipeDownDisappear + case releaseAppear + case releaseDisappear + case swipeDownAppear + case transitionToArchive + } + + var state: State + var scrollOffset: CGFloat + var storiesFraction: CGFloat + } + + let backgroundNode: ASDisplayNode + let gradientContainerNode: ASDisplayNode + let gradientComponent: RoundedRectangle + let gradientContainerView: ComponentHostView + let titleNode: ASTextNode //centered + let arrowBackgroundNode: ASDisplayNode //20 with insets 10 + let arrowNode: AnimationNode //20x20 + let animation: TransitionAnimation + required override init() { + self.backgroundNode = ASDisplayNode() +// self.backgroundNode.isLayerBacked = true +// self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.backgroundColor = .clear + + self.gradientContainerNode = ASDisplayNode() + self.gradientContainerView = ComponentHostView() + + self.gradientComponent = RoundedRectangle(colors: [UIColor(hexString: "#A9AFB7")!, + UIColor(hexString: "#D3D4DA")!], + cornerRadius: 0, + gradientDirection: .horizontal) + self.animation = .init(state: .swipeDownInit, scrollOffset: .zero, storiesFraction: .zero) + self.titleNode = ASTextNode() +// self.titleNode.isLayerBacked = true + + self.arrowBackgroundNode = ASDisplayNode() +// self.arrowBackgroundNode.isLayerBacked = true + self.arrowBackgroundNode.backgroundColor = .white.withAlphaComponent(0.4) + + //"cap2.Fill 1": .blue + + self.arrowNode = AnimationNode(animation: "anim_arrow_to_archive", colors: ["archiveicon 3.Arrow 1.Arrow 1.Stroke 1": UIColor.clear, "archiveicon 3.Arrow 2.Stroke 1": .clear], scale: 0.1) + self.arrowNode.isUserInteractionEnabled = false + super.init() - self.backgroundColor = .red + self.addSubnode(self.gradientContainerNode) + self.addSubnode(self.backgroundNode) + self.backgroundNode.addSubnode(self.titleNode) + self.backgroundNode.addSubnode(self.arrowBackgroundNode) + self.arrowBackgroundNode.addSubnode(self.arrowNode) } - func updateLayout(size: CGSize, synchronousLoads: Bool) { - self.frame = CGRect(origin: .zero, size: size) + override func didLoad() { + super.didLoad() + self.gradientContainerNode.view.addSubview(self.gradientContainerView) + } + + func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, storiesFraction: CGFloat, scrollOffset: CGFloat, presentationData: ChatListPresentationData) { + let frame = CGRect(origin: .zero, size: size) + print("frame: \(frame)") + let _ = self.gradientContainerView.update( + transition: .immediate, + component: AnyComponent(self.gradientComponent), + environment: {}, + containerSize: size + ) + + transition.updateFrame(node: self, frame: frame) + transition.updateFrame(node: self.gradientContainerNode, frame: frame) + transition.updateFrame(node: self.backgroundNode, frame: frame) + transition.updateFrame(view: self.gradientContainerView, frame: frame) + let arrowBackgroundFrame = CGRect(x: 29, y: 10, width: 20, height: size.height - 20) + transition.updateFrame(node: self.arrowBackgroundNode, frame: arrowBackgroundFrame) + transition.updateCornerRadius(node: self.arrowBackgroundNode, cornerRadius: arrowBackgroundFrame.width / 2, completion: nil) + if var size = self.arrowNode.preferredSize() { + size = CGSize(width: ceil(size.width), height: ceil(size.height)) + self.arrowNode.frame = CGRect(x: floor((self.bounds.width - size.width) / 2.0), y: floor((self.bounds.height - size.height) / 2.0) + 1.0, width: size.width, height: size.height) + self.arrowNode.play() + } + + transition.updateFrame(node: self.arrowNode, frame: CGRect(x: .zero, y: arrowBackgroundFrame.height - arrowBackgroundFrame.width, width: arrowBackgroundFrame.width, height: arrowBackgroundFrame.width)) + self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ + .foregroundColor: UIColor.white, + .font: UIFont.systemFont(ofSize: 17) + ]) + + let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) + + transition.updateFrame(node: titleNode, frame: CGRect(x: (size.width - textLayout.size.width) / 2, + y: size.height - textLayout.size.height - 10, + width: textLayout.size.width, + height: textLayout.size.height)) + } - -// override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { -// let layout = self.asyncLayout() -// let (_, apply) = layout(item as! ChatListArchiveTransitionItem, params, self.relativePosition.first, self.relativePosition.last) -// apply() -// } -// -// func asyncLayout() -> (_ item: ChatListArchiveTransitionItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) { -// return { item, params, first, last in -// let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 150.0), insets: UIEdgeInsets()) -// -// return (layout, { [weak self] in -// if let strongSelf = self { -// strongSelf.relativePosition = (first, last) -// -// strongSelf.contentSize = layout.contentSize -// strongSelf.insets = layout.insets -// } -// }) -// } -// } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 44514970add..030cb75df72 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2754,19 +2754,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition = .immediate } -// if case let .groupReference(data) = item.content, data.groupId == .archive { -// transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) + if case let .groupReference(data) = item.content, data.groupId == .archive { + transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) + strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: layout.contentSize, storiesFraction: 0.5, scrollOffset: 0.5, presentationData: item.presentationData) // transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) -// } else { -// transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) + } else { + transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) // transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) -// } + } let contextContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) // strongSelf.contextContainer.position = contextContainerFrame.center transition.updatePosition(node: strongSelf.contextContainer, position: contextContainerFrame.center) transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) -// strongSelf.archiveTransitionNode.updateLayout(size: layout.contentSize, synchronousLoads: true) + // print("top offset: \(item.hiddenOffsetValue) hiddenOffset: \(item.hiddenOffset)") // let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) // transition.updatePosition(node: strongSelf.archiveTransitionNode, position: archiveTransitionFrame.center) @@ -3858,20 +3859,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } func updateExpandedHeight(height: CGFloat, transition: ContainedViewLayoutTransition) { + guard self.item?.hiddenOffsetValue != height else { return } self.item?.hiddenOffsetValue = height -// var heightOffset: CGFloat = .zero -// var layoutHeight: CGFloat = .zero -// if let item, let currentItemHeight, case let .groupReference(data) = self.item?.content, data.groupId == .archive { -// heightOffset = -(currentItemHeight-item.hiddenOffsetValue) -// layoutHeight = currentItemHeight + heightOffset -// } else { -// layoutHeight = self.currentItemHeight ?? offset -// } - guard let currentItemHeight else { return } - - let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.layout.contentSize.width, height: currentItemHeight)) + +// let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.layout.contentSize.width, height: currentItemHeight)) let layout = self.asyncLayout() -// let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem) + if let layoutParams = self.layoutParams { let updatedParams = ListViewItemLayoutParams(width: layoutParams.5.width, leftInset: layoutParams.5.leftInset, rightInset: layoutParams.5.rightInset, availableHeight: height) let (nodeLayout, apply) = layout(self.item ?? layoutParams.0, updatedParams, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4) @@ -3879,9 +3872,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.contentSize = nodeLayout.contentSize self.insets = nodeLayout.insets } - transition.updatePosition(node: self.archiveTransitionNode, position: archiveTransitionFrame.center) - transition.updateBounds(node: self.archiveTransitionNode, bounds: archiveTransitionFrame) -// animateFrameTransition(1.0, self.contentSize.height) + +// if let presentationData = self.item?.presentationData { +// self.archiveTransitionNode.updateLayout(transition: transition, +// size: archiveTransitionFrame.size, +// storiesFraction: 0.5, +// scrollOffset: .zero, +// presentationData: presentationData) +// } +// transition.updatePosition(node: self.archiveTransitionNode, position: archiveTransitionFrame.center) +// transition.updateBounds(node: self.archiveTransitionNode, bounds: archiveTransitionFrame) } func playArchiveAnimation() { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index d917b7e623e..b338b282207 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2375,6 +2375,9 @@ public final class ChatListNode: ListView { if didIncludeNotice != doesIncludeNotice { disableAnimations = false } + if previousState.topOffset != state.topOffset { + disableAnimations = false + } } if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault { @@ -2967,13 +2970,54 @@ public final class ChatListNode: ListView { } func updateArchiveTopOffset(offset: CGFloat) { +// var isHiddenItemVisible = false +// self.forEachItemNode({ itemNode in +// if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { +// if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { +// if threadInfo.isHidden { +// isHiddenItemVisible = true +// } +// } +// +// if case let .groupReference(groupReference) = item.content { +// if groupReference.hiddenByDefault { +// isHiddenItemVisible = true +// } +// +// if groupReference.groupId == .archive, let itemHeight = itemNode.currentItemHeight, item.hiddenOffsetValue < itemHeight { +// isHiddenItemVisible = true +// } +// } +// } +// }) +// guard isHiddenItemVisible else { return } + let toggleTemporaryRevealHiddenItems = !self.currentState.hiddenItemShouldBeTemporaryRevealed + print("toggle temporary reveal hidden items: \(toggleTemporaryRevealHiddenItems)") self.updateState { state in var state = state state.topOffset = offset state.hiddenItemShouldBeTemporaryRevealed = toggleTemporaryRevealHiddenItems return state } +// guard (abs(currentState.topOffset) - abs(offset) < 1.0 ) else { +// print("left: \(abs(currentState.topOffset)) right: \(abs(offset))") +// return +// } +// if !self.currentState.hiddenItemShouldBeTemporaryRevealed { +// self.updateState { state in +// var state = state +// state.topOffset = offset +// state.hiddenItemShouldBeTemporaryRevealed = true +// return state +// } +// } else { +// self.updateState { state in +// var state = state +// state.topOffset = offset +// return state +// } +// } } private func pollFilterUpdates() { diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 0a42d4910b8..145aa18e168 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -833,7 +833,7 @@ public final class StoryPeerListComponent: Component { } } - print("overscrollStage1: \(overscrollStage1) overscrollStage2: \(overscrollStage2) realTimeOverscrollFraction: \(realTimeOverscrollFraction)") +// print("overscrollStage1: \(overscrollStage1) overscrollStage2: \(overscrollStage2) realTimeOverscrollFraction: \(realTimeOverscrollFraction)") self.overscrollFraction = overscrollStage1 if overscrollStage1 >= 0.5 { self.overscrollHiddenChatItemsAllowed = true diff --git a/submodules/TelegramUI/Resources/Animations/anim_arrow_to_archive.json b/submodules/TelegramUI/Resources/Animations/anim_arrow_to_archive.json new file mode 100644 index 00000000000..a897adb1c42 --- /dev/null +++ b/submodules/TelegramUI/Resources/Animations/anim_arrow_to_archive.json @@ -0,0 +1 @@ +{"v":"5.9.0","fr":60,"ip":0,"op":60,"w":180,"h":180,"nm":"archiveicon 3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Arrow 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[90]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[90]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[90]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":35,"s":[90]},{"t":45,"s":[90]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[95.09]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[81.698]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[84.496]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":35,"s":[83.879]},{"t":45,"s":[84.017]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.5,-2.25],[0,2.25],[-4.5,-2.25]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,0],[0,0],[-4,0]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Arrow 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":-60,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Arrow 1","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-8.25,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,4],[0,-4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Arrow 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[15]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":10,"s":[15]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":60,"st":-60,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Cap","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[90,101.445,0],"to":[0,-7.737,0],"ti":[0,6.121,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[90,55.02,0],"to":[0,-6.121,0],"ti":[0,-1.26,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[90,64.72,0],"to":[0,1.26,0],"ti":[0,0.277,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[90,62.579,0],"to":[0,-0.277,0],"ti":[0,-0.08,0]},{"t":45,"s":[90,63.059,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":2,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[20.083,19.766]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":10,"s":[25.802,4.562]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":25,"s":[26.014,3.96]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":35,"s":[25.995,4.014]},{"t":45,"s":[26,3.999]}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[0,0.021],"to":[0,0.155],"ti":[0,-0.164]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[0,0.95],"to":[0,0.164],"ti":[0,-0.008]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[0,1.004],"to":[0,0.008],"ti":[0,0.001]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[0,0.999],"to":[0,-0.001],"ti":[0,0]},{"t":45,"s":[0,1]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[9.876]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[1.298]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[0.979]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":35,"s":[1.007]},{"t":45,"s":[1]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"cap2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[20.083,19.807]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":10,"s":[25.802,6.463]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":25,"s":[26.014,5.967]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":35,"s":[25.995,6.011]},{"t":45,"s":[26,5.999]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[9.903]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[3.231]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[2.983]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":35,"s":[3.006]},{"t":45,"s":[3]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"cap1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":-60,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Box","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[90,102,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[20.057,19.972]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":10,"s":[24.819,17.591]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":25,"s":[23.824,18.088]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":35,"s":[24.043,17.978]},{"t":45,"s":[23.994,18.003]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[9.903]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[3.231]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[2.983]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":35,"s":[3.006]},{"t":45,"s":[3]}],"ix":4},"nm":"Box 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[20.057,19.858]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":10,"s":[24.819,7.954]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":25,"s":[23.824,10.441]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":35,"s":[24.043,9.892]},{"t":45,"s":[23.994,10.015]}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[0,-0.057],"to":[0,-0.794],"ti":[0,0.628]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[0,-4.819],"to":[0,-0.628],"ti":[0,-0.129]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[0,-3.824],"to":[0,0.129],"ti":[0,0.028]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[0,-4.043],"to":[0,-0.028],"ti":[0,-0.008]},{"t":45,"s":[0,-3.994]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[9.876]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[1.298]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[0.979]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":35,"s":[1.007]},{"t":45,"s":[1]}],"ix":4},"nm":"Box 2","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"box1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":-60,"bm":0}],"markers":[]} \ No newline at end of file From b80e5ea159938288ebee495072532d3175c10bd9 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sat, 26 Aug 2023 06:53:22 -0300 Subject: [PATCH 04/34] [wip] navigation on archive placeholder, added params models for transition --- .../Sources/ChatListControllerNode.swift | 31 ++++- .../Sources/ChatListSearchListPaneNode.swift | 4 +- .../Node/ChatListArchiveTransitionItem.swift | 113 ++++++++++++++---- .../Sources/Node/ChatListItem.swift | 31 +++-- .../Sources/Node/ChatListNode.swift | 26 ++-- .../Sources/Node/ChatListNodeEntries.swift | 12 +- .../Archive/IconArrow.imageset/Contents.json | 15 +++ .../Archive/IconArrow.imageset/ic_arrow.pdf | Bin 0 -> 3994 bytes 8 files changed, 170 insertions(+), 62 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArrow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArrow.imageset/ic_arrow.pdf diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 9395aade90d..6793073a0df 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -242,7 +242,7 @@ private final class ChatListShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, hiddenOffsetValue: .zero, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -2528,6 +2528,11 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.currentOverscrollItemExpansionTimestamp = timestamp } + var scrollOffset = listView.scroller.contentOffset.y + if case let .known(value) = offset { + scrollOffset = value + } + if let currentOverscrollItemExpansionTimestamp = self.currentOverscrollItemExpansionTimestamp, currentOverscrollItemExpansionTimestamp <= timestamp - 0.0 { if overscrollFraction >= 0.5 { self.allowOverscrollItemExpansion = false @@ -2535,9 +2540,17 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { if isPrimary { self.mainContainerNode.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { - if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.hiddenOffsetValue { + if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { self.mainContainerNode.currentItemNode.updateArchiveTopOffset(offset: expandedHeight) - chatNode.updateExpandedHeight(height: expandedHeight, transition: .immediate) + + chatNode.updateExpandedHeight( + transition: .immediate, + params: .init( + scrollOffset: scrollOffset, + storiesFraction: overscrollFraction, + expandedHeight: expandedHeight + ) + ) // chatNode.animateFrameTransition(1.0, expandedHeight) // chatNode.updateHeightOffsetValue(offset: expandedHeight, transition: self.tempNavigationScrollingTransition ?? .immediate) } @@ -2546,9 +2559,17 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } else { self.inlineStackContainerNode?.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { - if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.hiddenOffsetValue { + if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(offset: expandedHeight) - chatNode.updateExpandedHeight(height: expandedHeight, transition: .immediate) + + chatNode.updateExpandedHeight( + transition: .immediate, + params: .init( + scrollOffset: scrollOffset, + storiesFraction: overscrollFraction, + expandedHeight: expandedHeight + ) + ) // chatNode.animateFrameTransition(1.0, expandedHeight) // chatNode.updateHeightOffsetValue(offset: expandedHeight, transition: self.tempNavigationScrollingTransition ?? .immediate) } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 3481d9b83fa..77519096eec 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -850,7 +850,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) } - )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, hiddenOffsetValue: .zero, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -3591,7 +3591,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, hiddenOffsetValue: .zero, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 1746ac52986..f5167831b18 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -7,8 +7,18 @@ import AnimationUI import ComponentFlow import TelegramPresentationData -class ChatListArchiveTransitionNode: ASDisplayNode { +public struct ArchiveAnimationParams: Equatable { + public let scrollOffset: CGFloat + public let storiesFraction: CGFloat + public let expandedHeight: CGFloat + public static var empty: ArchiveAnimationParams{ + return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero) + } +} + +class ChatListArchiveTransitionNode: ASDisplayNode { + struct TransitionAnimation { enum State { case swipeDownInit @@ -22,15 +32,21 @@ class ChatListArchiveTransitionNode: ASDisplayNode { var state: State var scrollOffset: CGFloat var storiesFraction: CGFloat + + static func degreesToRadians(_ x: CGFloat) -> CGFloat { + return .pi * x / 180.0 + } } let backgroundNode: ASDisplayNode let gradientContainerNode: ASDisplayNode let gradientComponent: RoundedRectangle - let gradientContainerView: ComponentHostView + var gradientContainerView: ComponentHostView? let titleNode: ASTextNode //centered let arrowBackgroundNode: ASDisplayNode //20 with insets 10 - let arrowNode: AnimationNode //20x20 + let arrowContainerNode: ASDisplayNode + let arrowAnimationNode: AnimationNode //20x20 + let arrowImageNode: ASImageNode let animation: TransitionAnimation required override init() { @@ -40,42 +56,55 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.backgroundNode.backgroundColor = .clear self.gradientContainerNode = ASDisplayNode() - self.gradientContainerView = ComponentHostView() + self.animation = .init(state: .swipeDownInit, scrollOffset: .zero, storiesFraction: .zero) + self.titleNode = ASTextNode() +// self.titleNode.isLayerBacked = true self.gradientComponent = RoundedRectangle(colors: [UIColor(hexString: "#A9AFB7")!, UIColor(hexString: "#D3D4DA")!], cornerRadius: 0, gradientDirection: .horizontal) - self.animation = .init(state: .swipeDownInit, scrollOffset: .zero, storiesFraction: .zero) - self.titleNode = ASTextNode() -// self.titleNode.isLayerBacked = true - + self.arrowBackgroundNode = ASDisplayNode() -// self.arrowBackgroundNode.isLayerBacked = true self.arrowBackgroundNode.backgroundColor = .white.withAlphaComponent(0.4) + + self.arrowContainerNode = ASDisplayNode() + self.arrowImageNode = ASImageNode() + self.arrowImageNode.image = UIImage(bundleImageName: "Chat List/Archive/IconArrow") - //"cap2.Fill 1": .blue - - self.arrowNode = AnimationNode(animation: "anim_arrow_to_archive", colors: ["archiveicon 3.Arrow 1.Arrow 1.Stroke 1": UIColor.clear, "archiveicon 3.Arrow 2.Stroke 1": .clear], scale: 0.1) - self.arrowNode.isUserInteractionEnabled = false + let mixedBackgroundColor = UIColor(hexString: "#A9AFB7")!.mixedWith(.white, alpha: 0.4) + self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", colors: [ + "Arrow 1.Arrow 1.Stroke 1": mixedBackgroundColor, + "Arrow 2.Arrow 2.Stroke 1": mixedBackgroundColor, + "Cap.cap2.Fill 1": .white, + "Cap.cap1.Fill 1": .white, + "Box.box1.Fill 1": .white + ], scale: 0.11) + self.arrowAnimationNode.backgroundColor = .clear + self.arrowAnimationNode.isUserInteractionEnabled = false super.init() self.addSubnode(self.gradientContainerNode) self.addSubnode(self.backgroundNode) self.backgroundNode.addSubnode(self.titleNode) self.backgroundNode.addSubnode(self.arrowBackgroundNode) - self.arrowBackgroundNode.addSubnode(self.arrowNode) + self.arrowBackgroundNode.addSubnode(self.arrowContainerNode) + self.arrowContainerNode.addSubnode(self.arrowImageNode) } override func didLoad() { super.didLoad() - self.gradientContainerNode.view.addSubview(self.gradientContainerView) } - func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, storiesFraction: CGFloat, scrollOffset: CGFloat, presentationData: ChatListPresentationData) { + func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData) { let frame = CGRect(origin: .zero, size: size) print("frame: \(frame)") - let _ = self.gradientContainerView.update( + if self.gradientContainerView == nil { + self.gradientContainerView = ComponentHostView() + self.gradientContainerNode.view.addSubview(self.gradientContainerView!) + } + + let _ = self.gradientContainerView?.update( transition: .immediate, component: AnyComponent(self.gradientComponent), environment: {}, @@ -85,20 +114,53 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updateFrame(node: self, frame: frame) transition.updateFrame(node: self.gradientContainerNode, frame: frame) transition.updateFrame(node: self.backgroundNode, frame: frame) - transition.updateFrame(view: self.gradientContainerView, frame: frame) + if let gradientContainerView { + transition.updateFrame(view: gradientContainerView, frame: frame) + } let arrowBackgroundFrame = CGRect(x: 29, y: 10, width: 20, height: size.height - 20) + let arrowFrame = CGRect(x: 0, y: arrowBackgroundFrame.height - 20, width: 20, height: 20) transition.updateFrame(node: self.arrowBackgroundNode, frame: arrowBackgroundFrame) transition.updateCornerRadius(node: self.arrowBackgroundNode, cornerRadius: arrowBackgroundFrame.width / 2, completion: nil) - if var size = self.arrowNode.preferredSize() { - size = CGSize(width: ceil(size.width), height: ceil(size.height)) - self.arrowNode.frame = CGRect(x: floor((self.bounds.width - size.width) / 2.0), y: floor((self.bounds.height - size.height) / 2.0) + 1.0, width: size.width, height: size.height) - self.arrowNode.play() - } +// if var size = self.arrowAnimationNode.preferredSize() { +// let scale = 2.7//size.width / arrowBackgroundFrame.width +// transition.updateTransformScale(layer: self.arrowBackgroundNode.layer, scale: scale) { [weak arrowNode] finished in +// guard let arrowNode, finished else { return } +// transition.updateTransformScale(layer: arrowNode.layer, scale: 1.0 / scale) +// } +// animationBackgroundNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.12, removeOnCompletion: false, completion: { [weak animationBackgroundNode] finished in +// animationBackgroundNode?.layer.animateScale(from: 1.07, to: 1.0, duration: 0.12, removeOnCompletion: false) +// }) - transition.updateFrame(node: self.arrowNode, frame: CGRect(x: .zero, y: arrowBackgroundFrame.height - arrowBackgroundFrame.width, width: arrowBackgroundFrame.width, height: arrowBackgroundFrame.width)) +// print("size before: \(size)") +// size = CGSize(width: ceil(arrowBackgroundFrame.width), height: ceil(arrowBackgroundFrame.width)) +// print("size after: \(size)") +// size = CGSize(width: ceil(size.width), height: ceil(size.width)) +// let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), +// y: floor(arrowBackgroundFrame.height - size.height), +// width: size.width, height: size.height) +// transition.updateFrame(node: self.arrowNode, frame: arrowFrame) +// self.arrowNode.play() +// transition.updateTransformRotation(node: arrowAnimationNode, angle: TransitionAnimation.degreesToRadians(-180)) +// +// size = CGSize(width: ceil(size.width * scale), height: ceil(size.width * scale)) +// +// let arrowCenter = (size.height / scale)/2 +// let scaledArrowCenter = size.height / 2 +// let difference = scaledArrowCenter - arrowCenter +// +// let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), +// y: floor(arrowBackgroundFrame.height - size.height/scale - difference), +// width: size.width, height: size.height) +// transition.updateFrame(node: arrowAnimationNode, frame: arrowFrame) +// } + +// transition.updateFrame(node: self.arrowNode, frame: CGRect(x: .zero, y: arrowBackgroundFrame.height - arrowBackgroundFrame.width, width: arrowBackgroundFrame.width, height: arrowBackgroundFrame.width)) + + transition.updateFrame(node: self.arrowContainerNode, frame: arrowFrame) + transition.updateFrame(node: self.arrowImageNode, frame: self.arrowContainerNode.bounds) self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ .foregroundColor: UIColor.white, - .font: UIFont.systemFont(ofSize: 17) + .font: Font.medium(floor(presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) ]) let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) @@ -110,3 +172,4 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } } + diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 030cb75df72..4a11d9b0d7e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -188,7 +188,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { let selected: Bool let enableContextActions: Bool let hiddenOffset: Bool - var hiddenOffsetValue: CGFloat + var params: ArchiveAnimationParams let interaction: ChatListNodeInteraction public let selectable: Bool = true @@ -212,7 +212,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { } } - public init(presentationData: ChatListPresentationData, context: AccountContext, chatListLocation: ChatListControllerLocation, filterData: ChatListItemFilterData?, index: EngineChatList.Item.Index, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, hiddenOffsetValue: CGFloat, interaction: ChatListNodeInteraction) { + public init(presentationData: ChatListPresentationData, context: AccountContext, chatListLocation: ChatListControllerLocation, filterData: ChatListItemFilterData?, index: EngineChatList.Item.Index, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, params: ArchiveAnimationParams, interaction: ChatListNodeInteraction) { self.presentationData = presentationData self.chatListLocation = chatListLocation self.filterData = filterData @@ -225,7 +225,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { self.header = header self.enableContextActions = enableContextActions self.hiddenOffset = hiddenOffset - self.hiddenOffsetValue = hiddenOffsetValue + self.params = params self.interaction = interaction } @@ -1462,7 +1462,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } strongSelf.hierarchyTrackingLayer?.removeFromSuperlayer() strongSelf.hierarchyTrackingLayer = nil - } + } strongSelf.updateVideoVisibility() } else { if let photo = peer.largeProfileImage, photo.hasVideo { @@ -2703,8 +2703,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) var heightOffset: CGFloat = .zero if case let .groupReference(data) = item.content, data.groupId == .archive { - heightOffset = -(itemHeight-item.hiddenOffsetValue) - print("height offset: \(heightOffset) with hiddenOffsetValue: \(item.hiddenOffsetValue) itemHeight: \(itemHeight)") + heightOffset = -(itemHeight-item.params.expandedHeight) + print("height offset: \(heightOffset) with params: \(item.params) itemHeight: \(itemHeight)") } let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: itemHeight + heightOffset), insets: insets) @@ -2716,7 +2716,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key)) } - print("layout height: \(layout.contentSize.height)") +// print("layout height: \(layout.contentSize.height)") return (layout, { [weak self] synchronousLoads, animated in if let strongSelf = self { strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize) @@ -2738,7 +2738,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let translationY = layout.contentSize.height - itemHeight strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, translationY, 0.0) - print("set sublayer translation y: \(translationY)") +// print("set sublayer translation y: \(translationY)") } if let _ = updatedTheme { @@ -2756,7 +2756,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if case let .groupReference(data) = item.content, data.groupId == .archive { transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) - strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: layout.contentSize, storiesFraction: 0.5, scrollOffset: 0.5, presentationData: item.presentationData) + strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: layout.contentSize, params: item.params, presentationData: item.presentationData) // transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) } else { transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) @@ -3858,15 +3858,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { }) } - func updateExpandedHeight(height: CGFloat, transition: ContainedViewLayoutTransition) { - guard self.item?.hiddenOffsetValue != height else { return } - self.item?.hiddenOffsetValue = height + func updateExpandedHeight(transition: ContainedViewLayoutTransition, params: ArchiveAnimationParams) { + guard self.item?.params.expandedHeight != params.expandedHeight else { return } + self.item?.params = params // let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.layout.contentSize.width, height: currentItemHeight)) let layout = self.asyncLayout() if let layoutParams = self.layoutParams { - let updatedParams = ListViewItemLayoutParams(width: layoutParams.5.width, leftInset: layoutParams.5.leftInset, rightInset: layoutParams.5.rightInset, availableHeight: height) + let updatedParams = ListViewItemLayoutParams( + width: layoutParams.5.width, + leftInset: layoutParams.5.leftInset, + rightInset: layoutParams.5.rightInset, + availableHeight: params.expandedHeight + ) let (nodeLayout, apply) = layout(self.item ?? layoutParams.0, updatedParams, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4) apply(true, true) self.contentSize = nodeLayout.contentSize diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index b338b282207..689e979ea68 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -246,7 +246,7 @@ public struct ChatListNodeState: Equatable { public var pendingRemovalItemIds: Set public var pendingClearHistoryPeerIds: Set public var hiddenItemShouldBeTemporaryRevealed: Bool - public var topOffset: CGFloat + public var archiveParams: ArchiveAnimationParams public var selectedAdditionalCategoryIds: Set public var hiddenPsaPeerId: EnginePeer.Id? public var foundPeers: [(EnginePeer, EnginePeer?)] @@ -266,7 +266,7 @@ public struct ChatListNodeState: Equatable { pendingRemovalItemIds: Set, pendingClearHistoryPeerIds: Set, hiddenItemShouldBeTemporaryRevealed: Bool, - topOffset: CGFloat, + archiveParams: ArchiveAnimationParams, hiddenPsaPeerId: EnginePeer.Id?, selectedThreadIds: Set, archiveStoryState: StoryState? @@ -282,7 +282,7 @@ public struct ChatListNodeState: Equatable { self.pendingRemovalItemIds = pendingRemovalItemIds self.pendingClearHistoryPeerIds = pendingClearHistoryPeerIds self.hiddenItemShouldBeTemporaryRevealed = hiddenItemShouldBeTemporaryRevealed - self.topOffset = topOffset + self.archiveParams = archiveParams self.hiddenPsaPeerId = hiddenPsaPeerId self.selectedThreadIds = selectedThreadIds self.archiveStoryState = archiveStoryState @@ -322,7 +322,7 @@ public struct ChatListNodeState: Equatable { if lhs.hiddenItemShouldBeTemporaryRevealed != rhs.hiddenItemShouldBeTemporaryRevealed { return false } - if lhs.topOffset != rhs.topOffset { + if lhs.archiveParams != rhs.archiveParams { return false } if lhs.hiddenPsaPeerId != rhs.hiddenPsaPeerId { @@ -425,7 +425,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, - hiddenOffsetValue: .zero, + params: .empty, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -661,7 +661,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed, - hiddenOffsetValue: groupReferenceEntry.topOffset, + params: groupReferenceEntry.archiveParams, interaction: nodeInteraction ), directionHint: entry.directionHint) @@ -791,7 +791,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, - hiddenOffsetValue: .zero, + params: .empty, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -955,7 +955,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { - print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.topOffset)") + print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.params)") return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, @@ -982,7 +982,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed, - hiddenOffsetValue: groupReferenceEntry.topOffset, + params: groupReferenceEntry.archiveParams, interaction: nodeInteraction ), directionHint: entry.directionHint) // } @@ -1277,7 +1277,7 @@ public final class ChatListNode: ListView { isSelecting = true } - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, topOffset: .zero, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, archiveParams: .empty, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme @@ -2375,7 +2375,7 @@ public final class ChatListNode: ListView { if didIncludeNotice != doesIncludeNotice { disableAnimations = false } - if previousState.topOffset != state.topOffset { + if previousState.archiveParams != state.archiveParams { disableAnimations = false } } @@ -2969,7 +2969,7 @@ public final class ChatListNode: ListView { } } - func updateArchiveTopOffset(offset: CGFloat) { + func updateArchiveTopOffset(params: ArchiveAnimationParams) { // var isHiddenItemVisible = false // self.forEachItemNode({ itemNode in // if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { @@ -2996,7 +2996,7 @@ public final class ChatListNode: ListView { print("toggle temporary reveal hidden items: \(toggleTemporaryRevealHiddenItems)") self.updateState { state in var state = state - state.topOffset = offset + state.archiveParams = params state.hiddenItemShouldBeTemporaryRevealed = toggleTemporaryRevealHiddenItems return state } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 2e41e649c7c..45db042c6aa 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -312,7 +312,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { var unreadCount: Int var revealed: Bool var hiddenByDefault: Bool - var topOffset: CGFloat + var archiveParams: ArchiveAnimationParams var storyState: ChatListNodeState.StoryState? init( @@ -325,7 +325,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { unreadCount: Int, revealed: Bool, hiddenByDefault: Bool, - topOffset: CGFloat, + archiveParams: ArchiveAnimationParams, storyState: ChatListNodeState.StoryState? ) { self.index = index @@ -337,7 +337,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { self.unreadCount = unreadCount self.revealed = revealed self.hiddenByDefault = hiddenByDefault - self.topOffset = topOffset + self.archiveParams = archiveParams self.storyState = storyState } @@ -373,6 +373,10 @@ enum ChatListNodeEntry: Comparable, Identifiable { return false } + if lhs.archiveParams != rhs.archiveParams { + return false + } + return true } } @@ -860,7 +864,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, unreadCount: groupReference.unreadCount, revealed: state.hiddenItemShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault, - topOffset: state.topOffset, + archiveParams: <#T##ArchiveAnimationParams#>, storyState: mappedStoryState ))) if pinningIndex != 0 { diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArrow.imageset/Contents.json new file mode 100644 index 00000000000..63fa90a793d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArrow.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_arrow.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArrow.imageset/ic_arrow.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArrow.imageset/ic_arrow.pdf new file mode 100644 index 0000000000000000000000000000000000000000..663eb37f931806394dd236eb46754ca5e00fbd4c GIT binary patch literal 3994 zcmai%c|4SB|Hmy;7!qZPRCf*|kr}h0vJQsG+G1zM48}GZW6zr9*s^B}S+is*ifG6Z z6~`nY6_PDmBH5>YqjP#r&+k0X>$zXo>%Ono_xfJn>waCI&mZrvq>-lfDS0^rSh8bk zWorKRms>A7I>87429WX2;In4|s5Z%!N~AEqt~e@DlW0#S5CNz@(cO{i1RxY)fQky( zlS(1t+`zu97ftjGF!DU_W(1m9Am1kIr$N?NLZtX~_Hg+JM_+Ez|a!j z5bPNigD|PUKr3ALUhX1ziVWeh%3b` zWX%WhC0EW^{U*pid+x(c?2&tXrCcJ}D?uZugs;s+YhjN$gKV;{BKK&u(dV%!8CsG^ zYz)asnr~>Fw=J@8354%I?ga6a6A2`1o~iQ=UwU+{sOCIRf*<7%A(TQ=(Y4|TFP#jX z)KD&x0wN5zg_)n?iJ_ z0&wQC2>>-AdXl{;_C!wr`DXw_cBeAeJ%L>hnCzIfwLz9yQaZ-$^)ps3j9(7{Ywo4<_S>sU2nNFjRin8iQXi8q6t>xU+#jV z<>CL?f4_Pz=Z4P((_x_~I!%H0;($0Hxhm?<1-v|?BM35%)(tUYiwuW&Ywpn&;FrEc zxbT=28zpY9CBS_x3_rxR!e`XkYOJX>H4^8-wN#1vGMdHcG9MeBsUI44TnZd&WHH(a z1GQ1jSVNHcW&)q)TdfWp?Cw}+JH*GzYsUI`+`$2a-OyxvwL??R%q-REtX=yg7eZ)y zE8AiO@f`dfoFuM*k=V-u)*3*c4T13hszcbX2>p z-JX_ddX|w6name8?-hB3%#CX@8qJa%FN_1_TV+VTcPzZhL8M*K&0?8aKhfX6aZ{fK zCXi{{GXu$YjfQIphu?a>`}#EpeQT0Y&TLOZbN}NK+r5I&3Z1 znr`CdWm4PEkN@tgPhYL$xegN!sV3jhVwE&^%XCUwtQ1>ZG#{LH+H4sfI~rulP=4ya zeQ_DGK+jc=%QYW!UAP0K39+n6SY#imZBDz$Qs=f;ZEF0E;S#{IvWKN^^yZhNk*+q$ z9Upk4xMLRLyT;yavDs1P4k)O9jDy?UBf7-rurbJjZ^fz5W1W{|M%6&~%>6md4xEK!aC4i6p}4?cvJ;t&bS zaS?FxvSkU3P%>)J<#hiByy{|cYu)GTOe*rQGLwoGC0$qN*&x4yM1gi#)Y}JFYj)syr!#?u~ z7O0me3Ki|o)EMMDY{BV=Q8!W)V1I&%PUK5g+k62jJ#_L~c|OKSdG$A{Xd|cfwXItm zF976T25VqAe+FAWZr^K`2O+~XXT&(MZQbuReFblF2tR&1ey%}Cg}1&M5Z7TE~f zwiHgar=0A4+Gd=Gmo+sK%_La)kGmv^7qOLTWhL?!9ex3h3M_@~Z}t3)zk0kG94r=m zbS&XmXNy!Kmj%crZ2$2afP|~W1wK*E8BRtxe;d*`wA7F)9?9p|VTG5zd;PWgBV93w zL5hHGq=c}Egmh+I=mlM8achV&gmygT*=Yina>!iG#;{5v53rTopR8(>dIBO~cuq`9 zW7t6H+(R`#H9t(T$-YweS7zlIjX4&EG|j+sflzD;BK=GH#IwqjiX@k``Sbuu!_=X4 zpLAQfJSp4b!RNk}>@=_$x2m<8h8EUb%GVyRYg3NU)ygVJ!~c%Y!bjt;eLy`imVA^P znp}UtuuN^X=A~ruaZ!!_V!xLSb?56&zahSX9lEWtCU|^wRrnf zpGwh4g>t2RX|0)Kp+Rl$LF;5WO9l0Mt9nx(4MRPs8PvAgcqw1SIIl9Vs0G_%8T|b5 z3*e1ita^59X?{$83oaK|IBx%2c~V8+$wG_5Go6a|53a;4t4vrzYNB&=lXc^toi}~` zYV?9#BfXB!<1+08K~|uex(aIEnVa9y9=xU0e)j3rLci=y8E)lRv)CM=5g}XIMOo8R z^RmaxrOo%4xz)|pY_-4X$a1l^5A2F`p0Ka&Eg6}E3<4h1@*C zpM_tYIz;KaGTtCYNs=ST#o79L2ljY@+FMy$o5GGkU)oAoy%J*6*ui*5oaI9CSPO1> z9+Hzq-hnLmHC*%!^7R3o!fR<_I+}D6p1ypFjg1%56Y|l1hJB(nfIY7jpp~wrmu@C& zUOTBBXAuXKC<`4sEEpi+O(|P{IieRevG4&r@ktJ0b)$CS4dwn+SvS2Z(YDq6%E_aL zj~yA~`yPF;t!zraYSs3k&lG4Vr73DVd8SilCVts^gSe9fN)Nvm-lv(~VI6I+TcsMg$+FbM)Iw=QSzp;7r0}|px_%8wTuhCOH9}d625m2t%HhX`I+-XI9a^bCv7_72-`_RD0y0vlqYx8`98{noh{JC4ZJDzxW%spoGmGLd( znr1tv+NJ4EjfWb>KAj~biEyF8FLHnXN_ln!l80hKa_8NND_vy9`>&XQ zgB7zzh{A^(AjRWK%OAQnjxFW#oE`Ry2`rzLV&ttocwrG^kw5ruF#16eniQZPFg6>~ zpL3-5$SKwA?^jpj)-tndpF8T&lX?YjW-I0S1g&UvMl8oh3rC&4OjT*Dn|zs5RZ}(E z9hzQkv;2@!_LjE2b1}Wbu}aDMOkhqRZ84G&KX|k$_lE645NY=Qk{fN-qn2@FqVjt4 zp1_$c>uu=hT4UhU_3t@{*<<%^eUfVq_-^w~c{XsA(Z^A?-$u2^Uy@~WDIN!Ih4^>7j6yN(QAKqOo(j2K)EOAzL1R>X8VZOitdV zab~q&SXkj71u-}(&Xw%=C#ZY=Md*J6xZ*Dxm;g@jvS+qsC){xMhNi$JBE^$Lb_e7U zawx#+goYQ%l>oruSOuh&G+^w7qfq?;CaM2`dta(F6VRC*8-rQk@eM9Pnr z*OcT>1eBP~5t&Q{c6DMlO1kb2WMEf<-MJ=H(X()QBpwY{B%l=)kwgR Date: Sat, 26 Aug 2023 10:41:14 -0300 Subject: [PATCH 05/34] [WIP] animated arrow, gradient and title --- .../Sources/ChatListControllerNode.swift | 12 +- .../Node/ChatListArchiveTransitionItem.swift | 141 +++++++++++++++++- .../Sources/Node/ChatListItem.swift | 1 - .../Sources/Node/ChatListNode.swift | 2 +- .../Sources/Node/ChatListNodeEntries.swift | 2 +- .../TextSizeSelectionController.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 2 +- .../Themes/ThemePreviewControllerNode.swift | 2 +- .../ChatSearchResultsContollerNode.swift | 2 +- 9 files changed, 150 insertions(+), 16 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 6793073a0df..ae2fe0af871 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2541,7 +2541,11 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.mainContainerNode.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { - self.mainContainerNode.currentItemNode.updateArchiveTopOffset(offset: expandedHeight) + self.mainContainerNode.currentItemNode.updateArchiveTopOffset(params: .init( + scrollOffset: scrollOffset, + storiesFraction: overscrollFraction, + expandedHeight: expandedHeight + )) chatNode.updateExpandedHeight( transition: .immediate, @@ -2560,7 +2564,11 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.inlineStackContainerNode?.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { - self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(offset: expandedHeight) + self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(params: .init( + scrollOffset: scrollOffset, + storiesFraction: overscrollFraction, + expandedHeight: expandedHeight + )) chatNode.updateExpandedHeight( transition: .immediate, diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index f5167831b18..25b315e3035 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -22,20 +22,121 @@ class ChatListArchiveTransitionNode: ASDisplayNode { struct TransitionAnimation { enum State { case swipeDownInit - case swipeDownDisappear case releaseAppear - case releaseDisappear case swipeDownAppear case transitionToArchive + + init(params: ArchiveAnimationParams, previousState: TransitionAnimation.State) { + let fraction = params.storiesFraction * 2 + if fraction < 0.8 { + self = .swipeDownAppear + } else if fraction > 0.8 && fraction < 1.0 { + self = .releaseAppear + } else { + self = .transitionToArchive + } + } } var state: State - var scrollOffset: CGFloat - var storiesFraction: CGFloat + var params: ArchiveAnimationParams + var isAnimated = false + var overlayGradientNode: ASDisplayNode? + var releaseTextNode: ASTextNode? static func degreesToRadians(_ x: CGFloat) -> CGFloat { return .pi * x / 180.0 } + + static func distance(from: CGPoint, to point: CGPoint) -> CGFloat { + return sqrt(pow((point.x - from.x), 2) + pow((point.y - from.y), 2)) + } + + + mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, completion: (() -> Void)?) { + CATransaction.begin() + CATransaction.setCompletionBlock { + completion?() + } + CATransaction.completionBlock() + CATransaction.setAnimationDuration(1.0) + switch state { + case .swipeDownInit: + print("swipe dowm init transition called") + case .releaseAppear: + let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode) + rotationAnimation.beginTime = .zero + + if self.releaseTextNode == nil { + self.releaseTextNode = ASTextNode() + let attributes: [NSAttributedString.Key: Any] = textNode.attributedText?.attributes(at: 0, effectiveRange: nil) ?? [:] + self.releaseTextNode?.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) + guard let supernode = textNode.supernode else { return } + supernode.addSubnode(self.releaseTextNode!) + + let textLayout = self.releaseTextNode!.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.frame.width - 120, height: 25))) + + self.releaseTextNode?.frame = CGRect(x: -textLayout.size.width, y: supernode.frame.height - textLayout.size.height - 8, width: textLayout.size.width, height: textLayout.size.height) + } + + let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode) + textSwipeAnimation.beginTime = .zero + + case .swipeDownAppear: + let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode) + rotationAnimation.beginTime = .zero + case .transitionToArchive: + break + } + CATransaction.commit() + self.isAnimated = true + } + + func makeArrowRotationAnimation(arrowContainerNode: ASDisplayNode) -> CAAnimation { + let rotatedDegree = TransitionAnimation.degreesToRadians(-180) + let animation = arrowContainerNode.layer.makeAnimation( + from: 0.0 as NSNumber, + to: rotatedDegree as NSNumber, + keyPath: "transform.rotation.z", + timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, + duration: 0.5, + removeOnCompletion: false, + additive: true + ) + arrowContainerNode.layer.animationKeys()?.filter({ $0 == "arrow_rotation" }).forEach({ arrowContainerNode.layer.cancelAnimationsRecursive(key: $0) }) + arrowContainerNode.layer.add(animation, forKey: "arrow_rotation") + return animation + } + + func makeTextSwipeAnimation(textNode: ASTextNode) -> CAAnimation { + guard let superNode = textNode.supernode else { + return CAAnimation() + } + let targetPosition: CGPoint + + if textNode.frame.origin.x < 0 { + let distanceToCenter = TransitionAnimation.distance(from: textNode.frame.center, to: superNode.frame.center) + targetPosition = CGPoint(x: textNode.layer.position.x + distanceToCenter, y: textNode.layer.position.y) + } else { + targetPosition = CGPoint(x: textNode.position.x + (superNode.frame.width - textNode.frame.center.x) + textNode.frame.width / 2, y: textNode.layer.position.y) + } + + let animation = textNode.layer.springAnimation( + from: NSValue(cgPoint: textNode.layer.position), + to: NSValue(cgPoint: targetPosition), + keyPath: "position", + duration: 1.0, + removeOnCompletion: true, + additive: false + ) + textNode.layer.animationKeys()?.filter({ $0 == "translate_text" }).forEach({ textNode.layer.cancelAnimationsRecursive(key: $0) }) + textNode.layer.add(animation, forKey: "translate_text") + return animation + } + +// func makeGradientOverlay(gradientContainer: ASDisplayNode) -> CAAnimation { +// +// } } let backgroundNode: ASDisplayNode @@ -47,7 +148,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let arrowContainerNode: ASDisplayNode let arrowAnimationNode: AnimationNode //20x20 let arrowImageNode: ASImageNode - let animation: TransitionAnimation + var animation: TransitionAnimation required override init() { self.backgroundNode = ASDisplayNode() @@ -56,9 +157,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.backgroundNode.backgroundColor = .clear self.gradientContainerNode = ASDisplayNode() - self.animation = .init(state: .swipeDownInit, scrollOffset: .zero, storiesFraction: .zero) + self.animation = .init(state: .swipeDownInit, params: .empty) self.titleNode = ASTextNode() -// self.titleNode.isLayerBacked = true + self.titleNode.isLayerBacked = true self.gradientComponent = RoundedRectangle(colors: [UIColor(hexString: "#A9AFB7")!, UIColor(hexString: "#D3D4DA")!], @@ -69,8 +170,11 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.arrowBackgroundNode.backgroundColor = .white.withAlphaComponent(0.4) self.arrowContainerNode = ASDisplayNode() + self.arrowContainerNode.isLayerBacked = true + self.arrowImageNode = ASImageNode() self.arrowImageNode.image = UIImage(bundleImageName: "Chat List/Archive/IconArrow") + self.arrowImageNode.isLayerBacked = true let mixedBackgroundColor = UIColor(hexString: "#A9AFB7")!.mixedWith(.white, alpha: 0.4) self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", colors: [ @@ -99,6 +203,21 @@ class ChatListArchiveTransitionNode: ASDisplayNode { func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData) { let frame = CGRect(origin: .zero, size: size) print("frame: \(frame)") + + var transition = transition + + guard self.animation.params != params || self.frame.size != size else { return } + if self.animation.params != params { print("new params") } + if self.frame.size != size { print("new size") } + + self.animation.params = params + let previousState = self.animation.state + self.animation.state = .init(params: params, previousState: previousState) + + if self.animation.state != previousState { + transition = .immediate + } + if self.gradientContainerView == nil { self.gradientContainerView = ComponentHostView() self.gradientContainerNode.view.addSubview(self.gradientContainerView!) @@ -170,6 +289,14 @@ class ChatListArchiveTransitionNode: ASDisplayNode { width: textLayout.size.width, height: textLayout.size.height)) + if self.animation.state != previousState { + self.animation.animateLayers(gradientNode: self.gradientContainerNode, + textNode: self.titleNode, + arrowContainerNode: self.arrowContainerNode) { + print("animation finished") + } + } + } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 4a11d9b0d7e..d2b68cefe06 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3903,7 +3903,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if case .groupReference = item.content { let translationY = currentValue - (self.currentItemHeight ?? 0.0) self.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, translationY, 0.0) - print("set sublayer translation y #2: \(translationY)") } else { var separatorFrame = self.separatorNode.frame separatorFrame.origin.y = currentValue - UIScreenPixel diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 689e979ea68..61542dd5152 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -955,7 +955,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { - print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.params)") + print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 45db042c6aa..4ea6091a3ee 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -864,7 +864,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, unreadCount: groupReference.unreadCount, revealed: state.hiddenItemShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault, - archiveParams: <#T##ArchiveAnimationParams#>, + archiveParams: state.archiveParams, storyState: mappedStoryState ))) if pinningIndex != 0 { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 25932eddf23..86e7f8f0ad1 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -298,7 +298,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView header: nil, enableContextActions: false, hiddenOffset: false, - hiddenOffsetValue: .zero, + params: .empty, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 3545a38882e..250a4b2232d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -931,7 +931,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate header: nil, enableContextActions: false, hiddenOffset: false, - hiddenOffsetValue: .zero, + params: .empty, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 7fb40b890a8..4aabe9f7ab2 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -444,7 +444,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { header: nil, enableContextActions: false, hiddenOffset: false, - hiddenOffsetValue: .zero, + params: .empty, interaction: interaction ) } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 9f781ddf888..74304418d46 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -111,7 +111,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { header: nil, enableContextActions: false, hiddenOffset: false, - hiddenOffsetValue: .zero, + params: .empty, interaction: interaction ) } From addfce2dafe7651f83582efa061d42bb4f06ff49 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sat, 26 Aug 2023 14:52:45 -0300 Subject: [PATCH 06/34] [wip] gradient animation --- .../Node/ChatListArchiveTransitionItem.swift | 138 +++++++++++++++--- 1 file changed, 116 insertions(+), 22 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 25b315e3035..6590f915353 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -1,4 +1,4 @@ -import Foundation +//import Foundation import UIKit import AsyncDisplayKit import Display @@ -41,7 +41,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { var state: State var params: ArchiveAnimationParams var isAnimated = false - var overlayGradientNode: ASDisplayNode? + var overlayGradientNode: ASImageNode? + var gradientLayer: CAShapeLayer? var releaseTextNode: ASTextNode? static func degreesToRadians(_ x: CGFloat) -> CGFloat { @@ -64,24 +65,26 @@ class ChatListArchiveTransitionNode: ASDisplayNode { case .swipeDownInit: print("swipe dowm init transition called") case .releaseAppear: + updateReleaseTextNode(from: textNode) + updateGradientOverlay(from: gradientNode) + let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode) rotationAnimation.beginTime = .zero - - if self.releaseTextNode == nil { - self.releaseTextNode = ASTextNode() - let attributes: [NSAttributedString.Key: Any] = textNode.attributedText?.attributes(at: 0, effectiveRange: nil) ?? [:] - self.releaseTextNode?.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) - guard let supernode = textNode.supernode else { return } - supernode.addSubnode(self.releaseTextNode!) - - let textLayout = self.releaseTextNode!.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.frame.width - 120, height: 25))) - - self.releaseTextNode?.frame = CGRect(x: -textLayout.size.width, y: supernode.frame.height - textLayout.size.height - 8, width: textLayout.size.width, height: textLayout.size.height) - } let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode) textSwipeAnimation.beginTime = .zero - + + if let releaseTextNode { + let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode) + releaseTextAppearAnimation.beginTime = .zero + } + +// if overlayGradientNode != nil { + let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode) + overlayGradientAnimation.beginTime = .zero +// } + + case .swipeDownAppear: let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode) rotationAnimation.beginTime = .zero @@ -92,7 +95,48 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.isAnimated = true } - func makeArrowRotationAnimation(arrowContainerNode: ASDisplayNode) -> CAAnimation { + private mutating func updateGradientOverlay(from gradientNode: ASDisplayNode) { + if (self.overlayGradientNode == nil) { + self.overlayGradientNode = ASImageNode() + self.gradientLayer = CAShapeLayer() + self.gradientLayer?.frame = gradientNode.bounds + self.gradientLayer?.masksToBounds = true + self.gradientLayer?.cornerRadius = 10 +// self.gradientLayer?.fillColor = UIColor.blue.cgColor + self.gradientLayer?.contents = generateGradientImage(size: gradientNode.frame.size, colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], + locations: [0.0, 1.0], direction: .horizontal) + +// self.overlayGradientNode?.image = generateGradientImage(size: gradientNode.frame.size, colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], +// locations: [0.0, 1.0], direction: .horizontal) +// self.overlayGradientNode?.isLayerBacked = true +// self.overlayGradientNode?.setLayerBlock({ +// let layer = CAShapeLayer() +// return layer +// }) + gradientNode.layer.addSublayer(self.gradientLayer!) +// gradientNode.addSubnode(self.overlayGradientNode!) + } + + self.overlayGradientNode?.frame = gradientNode.bounds + } + + private mutating func updateReleaseTextNode(from textNode: ASTextNode) { + if self.releaseTextNode == nil { + self.releaseTextNode = ASTextNode() + self.releaseTextNode?.isLayerBacked = true + let attributes: [NSAttributedString.Key: Any] = textNode.attributedText?.attributes(at: 0, effectiveRange: nil) ?? [:] + self.releaseTextNode?.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) + guard let supernode = textNode.supernode else { return } + supernode.addSubnode(self.releaseTextNode!) + } + + if let releaseTextNode, let supernode = releaseTextNode.supernode, state != .transitionToArchive { + let textLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.frame.width - 120, height: 25))) + self.releaseTextNode?.frame = CGRect(x: -textLayout.size.width, y: supernode.frame.height - textLayout.size.height - 8, width: textLayout.size.width, height: textLayout.size.height) + } + } + + private func makeArrowRotationAnimation(arrowContainerNode: ASDisplayNode) -> CAAnimation { let rotatedDegree = TransitionAnimation.degreesToRadians(-180) let animation = arrowContainerNode.layer.makeAnimation( from: 0.0 as NSNumber, @@ -100,7 +144,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { keyPath: "transform.rotation.z", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.5, - removeOnCompletion: false, + removeOnCompletion: true, additive: true ) arrowContainerNode.layer.animationKeys()?.filter({ $0 == "arrow_rotation" }).forEach({ arrowContainerNode.layer.cancelAnimationsRecursive(key: $0) }) @@ -108,7 +152,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { return animation } - func makeTextSwipeAnimation(textNode: ASTextNode) -> CAAnimation { + private func makeTextSwipeAnimation(textNode: ASTextNode) -> CAAnimation { guard let superNode = textNode.supernode else { return CAAnimation() } @@ -134,9 +178,59 @@ class ChatListArchiveTransitionNode: ASDisplayNode { return animation } -// func makeGradientOverlay(gradientContainer: ASDisplayNode) -> CAAnimation { + private func makeGradientOverlay(gradientContainer: ASDisplayNode, arrowContainer: ASDisplayNode) -> CAAnimation { +// guard let overlayGradientNode else { return CAAnimation() } +// let startPosition = arrowContainer.convert(arrowContainer.frame.center, to: gradientContainer) +// overlayGradientNode.frame = gradientContainer.bounds // -// } +// let startCirclePath = UIBezierPath(ovalIn: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer)) +// startCirclePath.close() +// +// let gs = gradientContainer.frame.size +// let finalRectPath = UIBezierPath(ovalIn: CGRect(x: -gs.width*0.2, y: -gs.width*0.2, width: gs.width+gs.width*0.4, height: gs.width+gs.width*0.4)) +// finalRectPath.close() +// +// if let shapeLayer = overlayGradientNode.layer as? CAShapeLayer { +// shapeLayer.path = startCirclePath.cgPath +// shapeLayer.fillColor = UIColor.clear.cgColor +// shapeLayer.strokeColor = UIColor.black.cgColor +// shapeLayer.masksToBounds = true +// +// } +// +// let animation = overlayGradientNode.layer.springAnimation(from: startCirclePath.cgPath, to: finalRectPath.cgPath, keyPath: "path", duration: 1.0, removeOnCompletion: true, additive: false) +// animation.fillMode = .forwards +// overlayGradientNode.layer.animationKeys()?.filter({ $0 == "gradient_path_transition" }).forEach({ overlayGradientNode.layer.cancelAnimationsRecursive(key: $0) }) +// overlayGradientNode.layer.add(animation, forKey: "gradient_path_transition") +// +// return animation + guard let gradientLayer else { return CAAnimation() } + gradientLayer.frame = gradientContainer.bounds//arrowContainer.convert(arrowContainer.frame, to: gradientContainer) + gradientLayer.cornerRadius = 10 + +// let startPosition = arrowContainer.layer.convert(arrowContainer.layer.frame.origin, to: gradientLayer) +// let distanceFromCenter = TransitionAnimation.distance(from: arrowContainer.layer.frame.center, to: arrowContainer.layer.frame.origin) + + let startCirclePath = UIBezierPath(rect: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer))//UIBezierPath(ovalIn: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer)) + startCirclePath.close() + +// let gs = gradientContainer.frame.size + let finalRectPath = UIBezierPath(rect: gradientContainer.bounds)//UIBezierPath(ovalIn: gradientContainer.bounds) + finalRectPath.close() + + gradientLayer.path = startCirclePath.cgPath + gradientLayer.fillColor = UIColor.blue.cgColor + gradientLayer.strokeColor = UIColor.red.cgColor + + let animation2 = gradientLayer.makeAnimation(from: gradientLayer.cornerRadius as NSNumber, to: 0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 3.0, removeOnCompletion: true) + let animation = gradientLayer.springAnimation(from: startCirclePath.cgPath, to: finalRectPath.cgPath, keyPath: "path", duration: 3.0, removeOnCompletion: true, additive: false) + animation.fillMode = .forwards + gradientLayer.removeAllAnimations() + gradientLayer.add(animation2, forKey: "gradient_corner") + gradientLayer.add(animation, forKey: "gradient_path_transition") + + return animation + } } let backgroundNode: ASDisplayNode @@ -246,8 +340,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // guard let arrowNode, finished else { return } // transition.updateTransformScale(layer: arrowNode.layer, scale: 1.0 / scale) // } -// animationBackgroundNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.12, removeOnCompletion: false, completion: { [weak animationBackgroundNode] finished in -// animationBackgroundNode?.layer.animateScale(from: 1.07, to: 1.0, duration: 0.12, removeOnCompletion: false) +// animationBackgroundNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.12, removeOnCompletion: true, completion: { [weak animationBackgroundNode] finished in +// animationBackgroundNode?.layer.animateScale(from: 1.07, to: 1.0, duration: 0.12, removeOnCompletion: true) // }) // print("size before: \(size)") From 41e0ef9e451814943b0dab280e3e07a5e5f56c4c Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sat, 26 Aug 2023 20:42:35 -0300 Subject: [PATCH 07/34] [WI{P] archiv transaction improvements --- .../Sources/ChatListControllerNode.swift | 10 +- .../Node/ChatListArchiveTransitionItem.swift | 206 +++++++++++------- submodules/Display/Source/GenerateImage.swift | 2 +- 3 files changed, 134 insertions(+), 84 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index ae2fe0af871..be5da998917 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2508,19 +2508,19 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { // let listViewOffsetFraction = (value + listView.tempTopInset)/76 // print("#1 listViewOffset: \(value) listViewOffsetFraction: \(listViewOffsetFraction)") // } -// let scrollViewFraction = listView.scroller.contentOffset.y / 76 + let scrollViewFraction = (listView.scroller.contentOffset.y - listView.tempTopInset) / 76 // print("#1 overscrollFraction: \(overscrollFraction ?? .zero) scrollViewOffset: \(listView.scroller.contentOffset) topInset: \(listView.tempTopInset) scrollViewFraction: \(scrollViewFraction)") + print("###1 \noverscrollFraction: \(overscrollFraction ?? .zero) \nscrollViewOffset: \(listView.scroller.contentOffset) \ntopInset: \(listView.tempTopInset) \nscrollViewFraction: \(scrollViewFraction)") if let overscrollFraction, overscrollFraction > 0, // self.allowOverscrollItemExpansion, let node = self.mainContainerNode.currentItemNode.itemNodeAtIndex(2) as? ChatListItemNode, node.isNodeLoaded, - let itemHeight = node.currentItemHeight, itemHeight > 0 - { + let itemHeight = node.currentItemHeight, itemHeight > 0 { let expandedHeight = (overscrollFraction * 2) * itemHeight //listView.tempTopInset - 40.0 - print("expandedHeight: \(expandedHeight) overscrollFraction: \(overscrollFraction) itemHeight: \(itemHeight)") +// print("expandedHeight: \(expandedHeight) overscrollFraction: \(overscrollFraction) itemHeight: \(itemHeight)") let timestamp = CACurrentMediaTime() if let _ = self.currentOverscrollItemExpansionTimestamp { @@ -2541,6 +2541,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.mainContainerNode.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { + print("expandedHeight: \(expandedHeight) overscrollFraction: \(overscrollFraction) itemHeight: \(itemHeight)") self.mainContainerNode.currentItemNode.updateArchiveTopOffset(params: .init( scrollOffset: scrollOffset, storiesFraction: overscrollFraction, @@ -2564,6 +2565,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.inlineStackContainerNode?.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { + print("expandedHeight: \(expandedHeight) overscrollFraction: \(overscrollFraction) itemHeight: \(itemHeight)") self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(params: .init( scrollOffset: scrollOffset, storiesFraction: overscrollFraction, diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 6590f915353..0321693ceb6 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -20,6 +20,11 @@ public struct ArchiveAnimationParams: Equatable { class ChatListArchiveTransitionNode: ASDisplayNode { struct TransitionAnimation { + enum Direction { + case left + case right + } + enum State { case swipeDownInit case releaseAppear @@ -41,9 +46,20 @@ class ChatListArchiveTransitionNode: ASDisplayNode { var state: State var params: ArchiveAnimationParams var isAnimated = false - var overlayGradientNode: ASImageNode? var gradientLayer: CAShapeLayer? var releaseTextNode: ASTextNode? + lazy var gradientImage: UIImage? = { + guard let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 else { return nil } + var size = gradientLayer.frame.size + let fraction = (params.storiesFraction * 2) + if fraction < 1.0 { + size.height = self.params.expandedHeight / fraction + } + return generateGradientImage(size: gradientLayer.frame.size, + colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], + locations: [0.0, 1.0], direction: .horizontal) + }() + static func degreesToRadians(_ x: CGFloat) -> CGFloat { return .pi * x / 180.0 @@ -52,9 +68,10 @@ class ChatListArchiveTransitionNode: ASDisplayNode { static func distance(from: CGPoint, to point: CGPoint) -> CGFloat { return sqrt(pow((point.x - from.x), 2) + pow((point.y - from.y), 2)) } - + mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, completion: (() -> Void)?) { + print("animate layers with fraction: \(self.params.storiesFraction) state: \(self.state)") CATransaction.begin() CATransaction.setCompletionBlock { completion?() @@ -64,6 +81,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { switch state { case .swipeDownInit: print("swipe dowm init transition called") +// self.gradientLayer case .releaseAppear: updateReleaseTextNode(from: textNode) updateGradientOverlay(from: gradientNode) @@ -71,23 +89,43 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode) rotationAnimation.beginTime = .zero - let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode) + let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) textSwipeAnimation.beginTime = .zero if let releaseTextNode { - let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode) + let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) releaseTextAppearAnimation.beginTime = .zero } -// if overlayGradientNode != nil { - let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode) + if let gradientLayer { + let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientLayer) overlayGradientAnimation.beginTime = .zero -// } + } case .swipeDownAppear: let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode) rotationAnimation.beginTime = .zero + + let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .left) + textSwipeAnimation.beginTime = .zero + + if let releaseTextNode { + let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .left) + releaseTextAppearAnimation.beginTime = .zero + } + + if let gradientLayer { + let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientLayer) + overlayGradientAnimation.completion = { finished in + guard finished else { return } + gradientLayer.isHidden = true + gradientLayer.removeFromSuperlayer() + } + overlayGradientAnimation.beginTime = .zero + } + updateGradientOverlay(from: gradientNode) + case .transitionToArchive: break } @@ -96,30 +134,48 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } private mutating func updateGradientOverlay(from gradientNode: ASDisplayNode) { - if (self.overlayGradientNode == nil) { - self.overlayGradientNode = ASImageNode() - self.gradientLayer = CAShapeLayer() - self.gradientLayer?.frame = gradientNode.bounds - self.gradientLayer?.masksToBounds = true - self.gradientLayer?.cornerRadius = 10 -// self.gradientLayer?.fillColor = UIColor.blue.cgColor - self.gradientLayer?.contents = generateGradientImage(size: gradientNode.frame.size, colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], - locations: [0.0, 1.0], direction: .horizontal) + switch state { + case .releaseAppear: + if (self.gradientLayer == nil) { + self.gradientLayer = CAShapeLayer() + self.gradientLayer?.masksToBounds = true + self.gradientLayer?.cornerRadius = 10 + self.gradientLayer?.contentsGravity = .center + + self.gradientLayer?.fillColor = UIColor.clear.cgColor + self.gradientLayer?.strokeColor = UIColor.red.cgColor + self.gradientLayer?.lineWidth = 3.0 + self.gradientLayer?.fillRule = .evenOdd + + gradientNode.layer.addSublayer(self.gradientLayer!) + } -// self.overlayGradientNode?.image = generateGradientImage(size: gradientNode.frame.size, colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], -// locations: [0.0, 1.0], direction: .horizontal) -// self.overlayGradientNode?.isLayerBacked = true -// self.overlayGradientNode?.setLayerBlock({ -// let layer = CAShapeLayer() -// return layer -// }) - gradientNode.layer.addSublayer(self.gradientLayer!) -// gradientNode.addSubnode(self.overlayGradientNode!) + if (self.gradientLayer?.frame != gradientNode.bounds || self.gradientLayer?.contents == nil) { + self.gradientLayer?.frame = gradientNode.bounds + self.gradientLayer?.contents = self.getGradientImageOrUpdate()?.cgImage + } + case .swipeDownInit, .swipeDownAppear, .transitionToArchive: + break } - - self.overlayGradientNode?.frame = gradientNode.bounds } + mutating func getGradientImageOrUpdate() -> UIImage? { + if let gradientImage, gradientImage.size.height > 100 { + return gradientImage + } else if let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 { + self.gradientImage = generateGradientImage( + size: gradientLayer.frame.size, + colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], + locations: [0.0, 1.0], + direction: .horizontal + ) + return self.gradientImage + } else { + return nil + } + } + + private mutating func updateReleaseTextNode(from textNode: ASTextNode) { if self.releaseTextNode == nil { self.releaseTextNode = ASTextNode() @@ -144,7 +200,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { keyPath: "transform.rotation.z", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.5, - removeOnCompletion: true, + removeOnCompletion: false, additive: true ) arrowContainerNode.layer.animationKeys()?.filter({ $0 == "arrow_rotation" }).forEach({ arrowContainerNode.layer.cancelAnimationsRecursive(key: $0) }) @@ -152,17 +208,27 @@ class ChatListArchiveTransitionNode: ASDisplayNode { return animation } - private func makeTextSwipeAnimation(textNode: ASTextNode) -> CAAnimation { + private func makeTextSwipeAnimation(textNode: ASTextNode, direction: TransitionAnimation.Direction) -> CAAnimation { guard let superNode = textNode.supernode else { return CAAnimation() } let targetPosition: CGPoint - if textNode.frame.origin.x < 0 { - let distanceToCenter = TransitionAnimation.distance(from: textNode.frame.center, to: superNode.frame.center) - targetPosition = CGPoint(x: textNode.layer.position.x + distanceToCenter, y: textNode.layer.position.y) - } else { - targetPosition = CGPoint(x: textNode.position.x + (superNode.frame.width - textNode.frame.center.x) + textNode.frame.width / 2, y: textNode.layer.position.y) + switch direction { + case .left: + if textNode.frame.origin.x > superNode.frame.width { + let distanceToCenter = TransitionAnimation.distance(from: textNode.frame.center, to: superNode.frame.center) + targetPosition = CGPoint(x: textNode.layer.position.x - distanceToCenter, y: textNode.layer.position.y) + } else { + targetPosition = CGPoint(x: textNode.position.x - (superNode.frame.width - textNode.frame.center.x) + textNode.frame.width / 2, y: textNode.layer.position.y) + } + case .right: + if textNode.frame.origin.x < 0 { + let distanceToCenter = TransitionAnimation.distance(from: textNode.frame.center, to: superNode.frame.center) + targetPosition = CGPoint(x: textNode.layer.position.x + distanceToCenter, y: textNode.layer.position.y) + } else { + targetPosition = CGPoint(x: textNode.position.x + (superNode.frame.width - textNode.frame.center.x) + textNode.frame.width / 2, y: textNode.layer.position.y) + } } let animation = textNode.layer.springAnimation( @@ -170,7 +236,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { to: NSValue(cgPoint: targetPosition), keyPath: "position", duration: 1.0, - removeOnCompletion: true, + removeOnCompletion: false, additive: false ) textNode.layer.animationKeys()?.filter({ $0 == "translate_text" }).forEach({ textNode.layer.cancelAnimationsRecursive(key: $0) }) @@ -178,57 +244,38 @@ class ChatListArchiveTransitionNode: ASDisplayNode { return animation } - private func makeGradientOverlay(gradientContainer: ASDisplayNode, arrowContainer: ASDisplayNode) -> CAAnimation { -// guard let overlayGradientNode else { return CAAnimation() } -// let startPosition = arrowContainer.convert(arrowContainer.frame.center, to: gradientContainer) -// overlayGradientNode.frame = gradientContainer.bounds -// -// let startCirclePath = UIBezierPath(ovalIn: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer)) -// startCirclePath.close() -// -// let gs = gradientContainer.frame.size -// let finalRectPath = UIBezierPath(ovalIn: CGRect(x: -gs.width*0.2, y: -gs.width*0.2, width: gs.width+gs.width*0.4, height: gs.width+gs.width*0.4)) -// finalRectPath.close() -// -// if let shapeLayer = overlayGradientNode.layer as? CAShapeLayer { -// shapeLayer.path = startCirclePath.cgPath -// shapeLayer.fillColor = UIColor.clear.cgColor -// shapeLayer.strokeColor = UIColor.black.cgColor -// shapeLayer.masksToBounds = true -// -// } -// -// let animation = overlayGradientNode.layer.springAnimation(from: startCirclePath.cgPath, to: finalRectPath.cgPath, keyPath: "path", duration: 1.0, removeOnCompletion: true, additive: false) -// animation.fillMode = .forwards -// overlayGradientNode.layer.animationKeys()?.filter({ $0 == "gradient_path_transition" }).forEach({ overlayGradientNode.layer.cancelAnimationsRecursive(key: $0) }) -// overlayGradientNode.layer.add(animation, forKey: "gradient_path_transition") -// -// return animation - guard let gradientLayer else { return CAAnimation() } + private func makeGradientOverlay(gradientContainer: ASDisplayNode, arrowContainer: ASDisplayNode, gradientLayer: CAShapeLayer) -> CAAnimation { gradientLayer.frame = gradientContainer.bounds//arrowContainer.convert(arrowContainer.frame, to: gradientContainer) - gradientLayer.cornerRadius = 10 - -// let startPosition = arrowContainer.layer.convert(arrowContainer.layer.frame.origin, to: gradientLayer) -// let distanceFromCenter = TransitionAnimation.distance(from: arrowContainer.layer.frame.center, to: arrowContainer.layer.frame.origin) + + let startCirclePath: UIBezierPath + let finalRectPath: UIBezierPath + switch state { + case .swipeDownInit, .swipeDownAppear: + startCirclePath = UIBezierPath(roundedRect: gradientContainer.bounds, cornerRadius: 10) + finalRectPath = UIBezierPath(roundedRect: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer), cornerRadius: 10) + case .releaseAppear: + startCirclePath = UIBezierPath(roundedRect: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer), cornerRadius: 10) + finalRectPath = UIBezierPath(roundedRect: gradientContainer.bounds, cornerRadius: 10) + case .transitionToArchive: + //TODO: update gradient path + startCirclePath = UIBezierPath(roundedRect: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer), cornerRadius: 10) + finalRectPath = UIBezierPath(roundedRect: gradientContainer.bounds, cornerRadius: 10) + } - let startCirclePath = UIBezierPath(rect: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer))//UIBezierPath(ovalIn: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer)) startCirclePath.close() - -// let gs = gradientContainer.frame.size - let finalRectPath = UIBezierPath(rect: gradientContainer.bounds)//UIBezierPath(ovalIn: gradientContainer.bounds) finalRectPath.close() gradientLayer.path = startCirclePath.cgPath - gradientLayer.fillColor = UIColor.blue.cgColor - gradientLayer.strokeColor = UIColor.red.cgColor - - let animation2 = gradientLayer.makeAnimation(from: gradientLayer.cornerRadius as NSNumber, to: 0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 3.0, removeOnCompletion: true) - let animation = gradientLayer.springAnimation(from: startCirclePath.cgPath, to: finalRectPath.cgPath, keyPath: "path", duration: 3.0, removeOnCompletion: true, additive: false) +// gradientLayer.mask = +// let animation2 = gradientLayer.makeAnimation(from: gradientLayer.cornerRadius as NSNumber, to: 0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 3.0, removeOnCompletion: false) + let animation = gradientLayer.springAnimation(from: startCirclePath.cgPath, to: finalRectPath.cgPath, keyPath: "path", duration: 3.0, removeOnCompletion: false, additive: false) animation.fillMode = .forwards gradientLayer.removeAllAnimations() - gradientLayer.add(animation2, forKey: "gradient_corner") +// gradientLayer.add(animation2, forKey: "gradient_corner") gradientLayer.add(animation, forKey: "gradient_path_transition") - + + gradientLayer.path = finalRectPath.cgPath + return animation } } @@ -340,8 +387,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // guard let arrowNode, finished else { return } // transition.updateTransformScale(layer: arrowNode.layer, scale: 1.0 / scale) // } -// animationBackgroundNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.12, removeOnCompletion: true, completion: { [weak animationBackgroundNode] finished in -// animationBackgroundNode?.layer.animateScale(from: 1.07, to: 1.0, duration: 0.12, removeOnCompletion: true) +// animationBackgroundNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.12, removeOnCompletion: false, completion: { [weak animationBackgroundNode] finished in +// animationBackgroundNode?.layer.animateScale(from: 1.07, to: 1.0, duration: 0.12, removeOnCompletion: false) // }) // print("size before: \(size)") @@ -387,6 +434,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.animation.animateLayers(gradientNode: self.gradientContainerNode, textNode: self.titleNode, arrowContainerNode: self.arrowContainerNode) { + print("animation finished") } } diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift index 35d1cee10a4..eda95c8571f 100644 --- a/submodules/Display/Source/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -389,7 +389,7 @@ public func generateGradientImage(size: CGSize, scale: CGFloat = 0.0, colors: [U context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: direction == .horizontal ? CGPoint(x: size.width, y: 0.0) : CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) } - let image = UIGraphicsGetImageFromCurrentImageContext()! + let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image From 3685476c58841adfc4add8be7b611350a2098ef4 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sat, 26 Aug 2023 23:38:15 -0300 Subject: [PATCH 08/34] fixed update percentage of enough scrolled archieve --- .../Sources/ChatListControllerNode.swift | 82 +++++++++++++------ .../Node/ChatListArchiveTransitionItem.swift | 81 ++++++++++-------- .../Sources/Node/ChatListItem.swift | 2 +- .../Sources/StoryPeerListComponent.swift | 4 +- 4 files changed, 103 insertions(+), 66 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index be5da998917..9eed609efa5 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2429,10 +2429,14 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { var overscrollSelectedId: EnginePeer.Id? var overscrollHiddenChatItemsAllowed = false var overscrollFraction: CGFloat? + var currentFraction: CGFloat? + var currentActivityFraction: CGFloat? if let controller = self.controller, let componentView = controller.chatListHeaderView(), let storyPeerListView = componentView.storyPeerListView() { overscrollSelectedId = storyPeerListView.overscrollSelectedId overscrollHiddenChatItemsAllowed = storyPeerListView.overscrollHiddenChatItemsAllowed overscrollFraction = storyPeerListView.overscrollFraction + currentFraction = storyPeerListView.currentFraction + currentActivityFraction = storyPeerListView.currentActivityFraction } if let chatListNode = listView as? ChatListNode { @@ -2511,15 +2515,41 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { let scrollViewFraction = (listView.scroller.contentOffset.y - listView.tempTopInset) / 76 // print("#1 overscrollFraction: \(overscrollFraction ?? .zero) scrollViewOffset: \(listView.scroller.contentOffset) topInset: \(listView.tempTopInset) scrollViewFraction: \(scrollViewFraction)") print("###1 \noverscrollFraction: \(overscrollFraction ?? .zero) \nscrollViewOffset: \(listView.scroller.contentOffset) \ntopInset: \(listView.tempTopInset) \nscrollViewFraction: \(scrollViewFraction)") + print("###2 currentFraction: \(currentFraction ?? .zero) \ncurrentActivityFraction: \(currentActivityFraction ?? .zero)") + + + var archiveFraction: CGFloat = 0.0 + + if let overscrollFraction, let currentFraction { + archiveFraction = (overscrollFraction / 0.5) * 0.8 + if currentFraction < 0 { + let lastOverscrol = max(-1.0, min(1.0, (currentFraction / -0.2))) * 0.2 + print("###3\nlastOverscrol: \(archiveFraction)") + archiveFraction += lastOverscrol + print("###3\nopen archiveFraction before reastriction: \(archiveFraction)") + archiveFraction = max(-1.0, archiveFraction) + archiveFraction = min(1.0, archiveFraction) + print("###3\nopen archiveFraction after: \(archiveFraction)") + } else if currentFraction >= 1.0 { + archiveFraction = -currentFraction + print("###3\nhide archiveFraction: \(archiveFraction)") + } + } if - let overscrollFraction, overscrollFraction > 0, + archiveFraction != 0, // self.allowOverscrollItemExpansion, let node = self.mainContainerNode.currentItemNode.itemNodeAtIndex(2) as? ChatListItemNode, node.isNodeLoaded, let itemHeight = node.currentItemHeight, itemHeight > 0 { - let expandedHeight = (overscrollFraction * 2) * itemHeight //listView.tempTopInset - 40.0 + let expandedHeight: CGFloat + if archiveFraction < 0 { + expandedHeight = itemHeight - (-archiveFraction * itemHeight) + } else { + expandedHeight = archiveFraction * itemHeight + } +//listView.tempTopInset - 40.0 // print("expandedHeight: \(expandedHeight) overscrollFraction: \(overscrollFraction) itemHeight: \(itemHeight)") let timestamp = CACurrentMediaTime() @@ -2532,30 +2562,30 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { if case let .known(value) = offset { scrollOffset = value } - + if let currentOverscrollItemExpansionTimestamp = self.currentOverscrollItemExpansionTimestamp, currentOverscrollItemExpansionTimestamp <= timestamp - 0.0 { - if overscrollFraction >= 0.5 { + if archiveFraction >= 0.8 { self.allowOverscrollItemExpansion = false } if isPrimary { self.mainContainerNode.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { - print("expandedHeight: \(expandedHeight) overscrollFraction: \(overscrollFraction) itemHeight: \(itemHeight)") + print("expandedHeight: \(expandedHeight) archiveFraction: \(archiveFraction) itemHeight: \(itemHeight)") self.mainContainerNode.currentItemNode.updateArchiveTopOffset(params: .init( scrollOffset: scrollOffset, - storiesFraction: overscrollFraction, + storiesFraction: archiveFraction, expandedHeight: expandedHeight )) - chatNode.updateExpandedHeight( - transition: .immediate, - params: .init( - scrollOffset: scrollOffset, - storiesFraction: overscrollFraction, - expandedHeight: expandedHeight - ) - ) +// chatNode.updateExpandedHeight( +// transition: .immediate, +// params: .init( +// scrollOffset: scrollOffset, +// storiesFraction: archiveFraction, +// expandedHeight: expandedHeight +// ) +// ) // chatNode.animateFrameTransition(1.0, expandedHeight) // chatNode.updateHeightOffsetValue(offset: expandedHeight, transition: self.tempNavigationScrollingTransition ?? .immediate) } @@ -2565,23 +2595,21 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.inlineStackContainerNode?.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { - print("expandedHeight: \(expandedHeight) overscrollFraction: \(overscrollFraction) itemHeight: \(itemHeight)") + print("expandedHeight: \(expandedHeight) archiveFraction: \(archiveFraction) itemHeight: \(itemHeight)") self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(params: .init( scrollOffset: scrollOffset, - storiesFraction: overscrollFraction, + storiesFraction: archiveFraction, expandedHeight: expandedHeight )) - - chatNode.updateExpandedHeight( - transition: .immediate, - params: .init( - scrollOffset: scrollOffset, - storiesFraction: overscrollFraction, - expandedHeight: expandedHeight - ) - ) -// chatNode.animateFrameTransition(1.0, expandedHeight) -// chatNode.updateHeightOffsetValue(offset: expandedHeight, transition: self.tempNavigationScrollingTransition ?? .immediate) + +// chatNode.updateExpandedHeight( +// transition: .immediate, +// params: .init( +// scrollOffset: scrollOffset, +// storiesFraction: archiveFraction, +// expandedHeight: expandedHeight +// ) +// ) } } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 0321693ceb6..ef0e4a8a754 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -32,13 +32,15 @@ class ChatListArchiveTransitionNode: ASDisplayNode { case transitionToArchive init(params: ArchiveAnimationParams, previousState: TransitionAnimation.State) { - let fraction = params.storiesFraction * 2 - if fraction < 0.8 { + let fraction = params.storiesFraction + if params.storiesFraction <= 0.92 { self = .swipeDownAppear - } else if fraction > 0.8 && fraction < 1.0 { + } else if fraction > 0.92 && fraction < 1.0 { self = .releaseAppear - } else { + } else if fraction >= 1.0 { self = .transitionToArchive + } else { + self = .swipeDownInit } } } @@ -46,16 +48,18 @@ class ChatListArchiveTransitionNode: ASDisplayNode { var state: State var params: ArchiveAnimationParams var isAnimated = false - var gradientLayer: CAShapeLayer? + var gradientShapeLayer: CAShapeLayer? + var gradientMaskLayer: CAShapeLayer? + var gradientLayer: CALayer? var releaseTextNode: ASTextNode? lazy var gradientImage: UIImage? = { - guard let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 else { return nil } - var size = gradientLayer.frame.size - let fraction = (params.storiesFraction * 2) + guard let gradientShapeLayer, gradientShapeLayer.frame.size.height > 0, self.params.storiesFraction > 0 else { return nil } + var size = gradientShapeLayer.frame.size + let fraction = params.storiesFraction if fraction < 1.0 { size.height = self.params.expandedHeight / fraction } - return generateGradientImage(size: gradientLayer.frame.size, + return generateGradientImage(size: gradientShapeLayer.frame.size, colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], locations: [0.0, 1.0], direction: .horizontal) }() @@ -71,7 +75,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, completion: (() -> Void)?) { - print("animate layers with fraction: \(self.params.storiesFraction) state: \(self.state)") + print("animate layers with fraction: \(self.params.storiesFraction) state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight)") CATransaction.begin() CATransaction.setCompletionBlock { completion?() @@ -86,7 +90,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { updateReleaseTextNode(from: textNode) updateGradientOverlay(from: gradientNode) - let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode) + let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) rotationAnimation.beginTime = .zero let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) @@ -97,14 +101,14 @@ class ChatListArchiveTransitionNode: ASDisplayNode { releaseTextAppearAnimation.beginTime = .zero } - if let gradientLayer { - let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientLayer) + if let gradientShapeLayer { + let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientShapeLayer) overlayGradientAnimation.beginTime = .zero } case .swipeDownAppear: - let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode) + let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: false) rotationAnimation.beginTime = .zero let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .left) @@ -115,19 +119,21 @@ class ChatListArchiveTransitionNode: ASDisplayNode { releaseTextAppearAnimation.beginTime = .zero } - if let gradientLayer { - let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientLayer) + if let gradientShapeLayer { + let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientShapeLayer) overlayGradientAnimation.completion = { finished in guard finished else { return } - gradientLayer.isHidden = true - gradientLayer.removeFromSuperlayer() + gradientShapeLayer.isHidden = true + gradientShapeLayer.removeFromSuperlayer() } overlayGradientAnimation.beginTime = .zero } updateGradientOverlay(from: gradientNode) case .transitionToArchive: - break + let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) + rotationAnimation.beginTime = .zero + } CATransaction.commit() self.isAnimated = true @@ -136,23 +142,26 @@ class ChatListArchiveTransitionNode: ASDisplayNode { private mutating func updateGradientOverlay(from gradientNode: ASDisplayNode) { switch state { case .releaseAppear: - if (self.gradientLayer == nil) { - self.gradientLayer = CAShapeLayer() - self.gradientLayer?.masksToBounds = true - self.gradientLayer?.cornerRadius = 10 - self.gradientLayer?.contentsGravity = .center + if (self.gradientShapeLayer == nil) { + self.gradientShapeLayer = CAShapeLayer() + self.gradientShapeLayer?.masksToBounds = true + self.gradientShapeLayer?.cornerRadius = 10 + self.gradientShapeLayer?.contentsGravity = .center - self.gradientLayer?.fillColor = UIColor.clear.cgColor - self.gradientLayer?.strokeColor = UIColor.red.cgColor - self.gradientLayer?.lineWidth = 3.0 - self.gradientLayer?.fillRule = .evenOdd + self.gradientShapeLayer?.fillColor = UIColor.clear.cgColor + self.gradientShapeLayer?.strokeColor = UIColor.red.cgColor + self.gradientShapeLayer?.lineWidth = 3.0 + self.gradientShapeLayer?.fillRule = .evenOdd - gradientNode.layer.addSublayer(self.gradientLayer!) } - if (self.gradientLayer?.frame != gradientNode.bounds || self.gradientLayer?.contents == nil) { - self.gradientLayer?.frame = gradientNode.bounds - self.gradientLayer?.contents = self.getGradientImageOrUpdate()?.cgImage + if let gradientShapeLayer, gradientShapeLayer.superlayer == nil { + gradientNode.layer.addSublayer(gradientShapeLayer) + } + + if (self.gradientShapeLayer?.frame != gradientNode.bounds || self.gradientShapeLayer?.contents == nil) { + self.gradientShapeLayer?.frame = gradientNode.bounds + self.gradientShapeLayer?.contents = self.getGradientImageOrUpdate()?.cgImage } case .swipeDownInit, .swipeDownAppear, .transitionToArchive: break @@ -162,9 +171,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { mutating func getGradientImageOrUpdate() -> UIImage? { if let gradientImage, gradientImage.size.height > 100 { return gradientImage - } else if let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 { + } else if let gradientShapeLayer, gradientShapeLayer.frame.size.height > 0, self.params.storiesFraction > 0 { self.gradientImage = generateGradientImage( - size: gradientLayer.frame.size, + size: gradientShapeLayer.frame.size, colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], locations: [0.0, 1.0], direction: .horizontal @@ -192,8 +201,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } } - private func makeArrowRotationAnimation(arrowContainerNode: ASDisplayNode) -> CAAnimation { - let rotatedDegree = TransitionAnimation.degreesToRadians(-180) + private func makeArrowRotationAnimation(arrowContainerNode: ASDisplayNode, isRotated: Bool) -> CAAnimation { + let rotatedDegree = TransitionAnimation.degreesToRadians(isRotated ? -180 : 0) let animation = arrowContainerNode.layer.makeAnimation( from: 0.0 as NSNumber, to: rotatedDegree as NSNumber, diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index d2b68cefe06..16558302f15 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2704,7 +2704,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var heightOffset: CGFloat = .zero if case let .groupReference(data) = item.content, data.groupId == .archive { heightOffset = -(itemHeight-item.params.expandedHeight) - print("height offset: \(heightOffset) with params: \(item.params) itemHeight: \(itemHeight)") +// print("height offset: \(heightOffset) with params: \(item.params) itemHeight: \(itemHeight)") } let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: itemHeight + heightOffset), insets: insets) diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 145aa18e168..691ab3d361a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -349,9 +349,9 @@ public final class StoryPeerListComponent: Component { private var animationState: AnimationState? private var animator: ConstantDisplayLinkAnimator? - private var currentFraction: CGFloat = 0.0 + public private(set) var currentFraction: CGFloat = 0.0 private var currentTitleWidth: CGFloat = 0.0 - private var currentActivityFraction: CGFloat = 0.0 + public private(set) var currentActivityFraction: CGFloat = 0.0 public private(set) var overscrollSelectedId: EnginePeer.Id? public private(set) var overscrollHiddenChatItemsAllowed: Bool = false From a94fd71fc10162523a6c1537815b2a04cc6bcd41 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sun, 27 Aug 2023 00:55:21 -0300 Subject: [PATCH 09/34] scroll handling updates --- .../Sources/ChatListControllerNode.swift | 14 ++----- .../Node/ChatListArchiveTransitionItem.swift | 39 +++++++++++++++---- .../Sources/StoryPeerListComponent.swift | 2 +- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 9eed609efa5..cec3072890d 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2430,13 +2430,11 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { var overscrollHiddenChatItemsAllowed = false var overscrollFraction: CGFloat? var currentFraction: CGFloat? - var currentActivityFraction: CGFloat? if let controller = self.controller, let componentView = controller.chatListHeaderView(), let storyPeerListView = componentView.storyPeerListView() { overscrollSelectedId = storyPeerListView.overscrollSelectedId overscrollHiddenChatItemsAllowed = storyPeerListView.overscrollHiddenChatItemsAllowed overscrollFraction = storyPeerListView.overscrollFraction currentFraction = storyPeerListView.currentFraction - currentActivityFraction = storyPeerListView.currentActivityFraction } if let chatListNode = listView as? ChatListNode { @@ -2512,10 +2510,10 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { // let listViewOffsetFraction = (value + listView.tempTopInset)/76 // print("#1 listViewOffset: \(value) listViewOffsetFraction: \(listViewOffsetFraction)") // } - let scrollViewFraction = (listView.scroller.contentOffset.y - listView.tempTopInset) / 76 +// let scrollViewFraction = (listView.scroller.contentOffset.y - listView.tempTopInset) / 76 // print("#1 overscrollFraction: \(overscrollFraction ?? .zero) scrollViewOffset: \(listView.scroller.contentOffset) topInset: \(listView.tempTopInset) scrollViewFraction: \(scrollViewFraction)") - print("###1 \noverscrollFraction: \(overscrollFraction ?? .zero) \nscrollViewOffset: \(listView.scroller.contentOffset) \ntopInset: \(listView.tempTopInset) \nscrollViewFraction: \(scrollViewFraction)") - print("###2 currentFraction: \(currentFraction ?? .zero) \ncurrentActivityFraction: \(currentActivityFraction ?? .zero)") +// print("###1 \noverscrollFraction: \(overscrollFraction ?? .zero) \nscrollViewOffset: \(listView.scroller.contentOffset) \ntopInset: \(listView.tempTopInset) \nscrollViewFraction: \(scrollViewFraction)") +// print("###2 currentFraction: \(currentFraction ?? .zero) \ncurrentActivityFraction: \(currentActivityFraction ?? .zero)") var archiveFraction: CGFloat = 0.0 @@ -2524,17 +2522,11 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { archiveFraction = (overscrollFraction / 0.5) * 0.8 if currentFraction < 0 { let lastOverscrol = max(-1.0, min(1.0, (currentFraction / -0.2))) * 0.2 - print("###3\nlastOverscrol: \(archiveFraction)") - archiveFraction += lastOverscrol - print("###3\nopen archiveFraction before reastriction: \(archiveFraction)") - archiveFraction = max(-1.0, archiveFraction) archiveFraction = min(1.0, archiveFraction) - print("###3\nopen archiveFraction after: \(archiveFraction)") } else if currentFraction >= 1.0 { archiveFraction = -currentFraction - print("###3\nhide archiveFraction: \(archiveFraction)") } } if diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index ef0e4a8a754..fb11c8afe38 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -145,24 +145,47 @@ class ChatListArchiveTransitionNode: ASDisplayNode { if (self.gradientShapeLayer == nil) { self.gradientShapeLayer = CAShapeLayer() self.gradientShapeLayer?.masksToBounds = true - self.gradientShapeLayer?.cornerRadius = 10 self.gradientShapeLayer?.contentsGravity = .center self.gradientShapeLayer?.fillColor = UIColor.clear.cgColor - self.gradientShapeLayer?.strokeColor = UIColor.red.cgColor - self.gradientShapeLayer?.lineWidth = 3.0 + self.gradientShapeLayer?.strokeColor = UIColor.clear.cgColor + self.gradientShapeLayer?.lineWidth = 0.0 self.gradientShapeLayer?.fillRule = .evenOdd } + if (self.gradientMaskLayer == nil) { + self.gradientMaskLayer = CAShapeLayer() + } + + if (self.gradientLayer == nil) { + self.gradientLayer = CALayer() + } - if let gradientShapeLayer, gradientShapeLayer.superlayer == nil { + guard let gradientShapeLayer else { return } + + + if gradientShapeLayer.superlayer == nil { gradientNode.layer.addSublayer(gradientShapeLayer) } - if (self.gradientShapeLayer?.frame != gradientNode.bounds || self.gradientShapeLayer?.contents == nil) { - self.gradientShapeLayer?.frame = gradientNode.bounds - self.gradientShapeLayer?.contents = self.getGradientImageOrUpdate()?.cgImage + if let gradientMaskLayer, gradientMaskLayer.superlayer == nil { + gradientShapeLayer.addSublayer(gradientMaskLayer) } + + if (gradientShapeLayer.frame != gradientNode.bounds || gradientShapeLayer.contents == nil) { + gradientShapeLayer.frame = gradientNode.bounds + gradientShapeLayer.contents = self.getGradientImageOrUpdate()?.cgImage + } + + if self.gradientLayer?.superlayer == nil { + gradientShapeLayer.addSublayer(self.gradientLayer!) + } + + self.gradientMaskLayer?.path = gradientShapeLayer.path + self.gradientMaskLayer?.frame = gradientShapeLayer.bounds + gradientShapeLayer.mask = self.gradientMaskLayer + gradientShapeLayer.frame = gradientShapeLayer.bounds + case .swipeDownInit, .swipeDownAppear, .transitionToArchive: break } @@ -414,7 +437,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // size = CGSize(width: ceil(size.width * scale), height: ceil(size.width * scale)) // // let arrowCenter = (size.height / scale)/2 -// let scaledArrowCenter = size.height / 2 +// let scaledArrowCenter = size.height / 2 // let difference = scaledArrowCenter - arrowCenter // // let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 691ab3d361a..ad6573edc9e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -351,7 +351,7 @@ public final class StoryPeerListComponent: Component { public private(set) var currentFraction: CGFloat = 0.0 private var currentTitleWidth: CGFloat = 0.0 - public private(set) var currentActivityFraction: CGFloat = 0.0 + private var currentActivityFraction: CGFloat = 0.0 public private(set) var overscrollSelectedId: EnginePeer.Id? public private(set) var overscrollHiddenChatItemsAllowed: Bool = false From 05c8fd86ec6deb5aa57447059536aa171a3f361a Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Tue, 29 Aug 2023 18:57:58 -0300 Subject: [PATCH 10/34] [WIP Animation archive] fixed positions of subnode during transition --- .../Sources/ChatListControllerNode.swift | 8 +- .../Node/ChatListArchiveTransitionItem.swift | 188 ++++++++++-------- .../Sources/Node/ChatListItem.swift | 19 +- .../Sources/Node/ChatListNode.swift | 4 +- 4 files changed, 117 insertions(+), 102 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index cec3072890d..8aa4d9678f6 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2563,9 +2563,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.mainContainerNode.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { - print("expandedHeight: \(expandedHeight) archiveFraction: \(archiveFraction) itemHeight: \(itemHeight)") +// print("expandedHeight: \(expandedHeight) archiveFraction: \(archiveFraction) itemHeight: \(itemHeight)") self.mainContainerNode.currentItemNode.updateArchiveTopOffset(params: .init( - scrollOffset: scrollOffset, + scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight )) @@ -2587,9 +2587,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.inlineStackContainerNode?.currentItemNode.forEachItemNode { node in if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { - print("expandedHeight: \(expandedHeight) archiveFraction: \(archiveFraction) itemHeight: \(itemHeight)") +// print("expandedHeight: \(expandedHeight) archiveFraction: \(archiveFraction) itemHeight: \(itemHeight)") self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(params: .init( - scrollOffset: scrollOffset, + scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight )) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index fb11c8afe38..997bfb450d1 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -87,8 +87,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { print("swipe dowm init transition called") // self.gradientLayer case .releaseAppear: - updateReleaseTextNode(from: textNode) - updateGradientOverlay(from: gradientNode) +// updateReleaseTextNode(from: textNode) +// updateGradientOverlay(from: gradientNode) let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) rotationAnimation.beginTime = .zero @@ -96,15 +96,15 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) textSwipeAnimation.beginTime = .zero - if let releaseTextNode { - let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) - releaseTextAppearAnimation.beginTime = .zero - } - - if let gradientShapeLayer { - let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientShapeLayer) - overlayGradientAnimation.beginTime = .zero - } +// if let releaseTextNode { +// let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) +// releaseTextAppearAnimation.beginTime = .zero +// } +// +// if let gradientShapeLayer { +// let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientShapeLayer) +// overlayGradientAnimation.beginTime = .zero +// } case .swipeDownAppear: @@ -114,21 +114,21 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .left) textSwipeAnimation.beginTime = .zero - if let releaseTextNode { - let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .left) - releaseTextAppearAnimation.beginTime = .zero - } - - if let gradientShapeLayer { - let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientShapeLayer) - overlayGradientAnimation.completion = { finished in - guard finished else { return } - gradientShapeLayer.isHidden = true - gradientShapeLayer.removeFromSuperlayer() - } - overlayGradientAnimation.beginTime = .zero - } - updateGradientOverlay(from: gradientNode) +// if let releaseTextNode { +// let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .left) +// releaseTextAppearAnimation.beginTime = .zero +// } +// +// if let gradientShapeLayer { +// let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientShapeLayer) +// overlayGradientAnimation.completion = { finished in +// guard finished else { return } +// gradientShapeLayer.isHidden = true +// gradientShapeLayer.removeFromSuperlayer() +// } +// overlayGradientAnimation.beginTime = .zero +// } +// updateGradientOverlay(from: gradientNode) case .transitionToArchive: let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) @@ -302,6 +302,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // let animation2 = gradientLayer.makeAnimation(from: gradientLayer.cornerRadius as NSNumber, to: 0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 3.0, removeOnCompletion: false) let animation = gradientLayer.springAnimation(from: startCirclePath.cgPath, to: finalRectPath.cgPath, keyPath: "path", duration: 3.0, removeOnCompletion: false, additive: false) animation.fillMode = .forwards + animation.speed = 0 +// animation.timeOffse gradientLayer.removeAllAnimations() // gradientLayer.add(animation2, forKey: "gradient_corner") gradientLayer.add(animation, forKey: "gradient_path_transition") @@ -314,8 +316,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let backgroundNode: ASDisplayNode let gradientContainerNode: ASDisplayNode - let gradientComponent: RoundedRectangle - var gradientContainerView: ComponentHostView? + let gradientImageNode: ASImageNode let titleNode: ASTextNode //centered let arrowBackgroundNode: ASDisplayNode //20 with insets 10 let arrowContainerNode: ASDisplayNode @@ -325,22 +326,21 @@ class ChatListArchiveTransitionNode: ASDisplayNode { required override init() { self.backgroundNode = ASDisplayNode() -// self.backgroundNode.isLayerBacked = true -// self.backgroundNode.displaysAsynchronously = false self.backgroundNode.backgroundColor = .clear + self.backgroundNode.isLayerBacked = true - self.gradientContainerNode = ASDisplayNode() self.animation = .init(state: .swipeDownInit, params: .empty) self.titleNode = ASTextNode() self.titleNode.isLayerBacked = true - - self.gradientComponent = RoundedRectangle(colors: [UIColor(hexString: "#A9AFB7")!, - UIColor(hexString: "#D3D4DA")!], - cornerRadius: 0, - gradientDirection: .horizontal) + + self.gradientContainerNode = ASDisplayNode() + self.gradientContainerNode.isLayerBacked = true + self.gradientImageNode = ASImageNode() + self.gradientImageNode.isLayerBacked = true self.arrowBackgroundNode = ASDisplayNode() self.arrowBackgroundNode.backgroundColor = .white.withAlphaComponent(0.4) + self.arrowBackgroundNode.isLayerBacked = true self.arrowContainerNode = ASDisplayNode() self.arrowContainerNode.isLayerBacked = true @@ -358,10 +358,11 @@ class ChatListArchiveTransitionNode: ASDisplayNode { "Box.box1.Fill 1": .white ], scale: 0.11) self.arrowAnimationNode.backgroundColor = .clear - self.arrowAnimationNode.isUserInteractionEnabled = false super.init() + self.backgroundColor = .red self.addSubnode(self.gradientContainerNode) + self.gradientContainerNode.addSubnode(self.gradientImageNode) self.addSubnode(self.backgroundNode) self.backgroundNode.addSubnode(self.titleNode) self.backgroundNode.addSubnode(self.arrowBackgroundNode) @@ -374,45 +375,56 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData) { - let frame = CGRect(origin: .zero, size: size) - print("frame: \(frame)") +// let frame = CGRect(origin: .zero, size: size) +// print("frame: \(frame)") - var transition = transition +// var transition = transition guard self.animation.params != params || self.frame.size != size else { return } - if self.animation.params != params { print("new params") } - if self.frame.size != size { print("new size") } - +// if self.animation.params != params { print("new params") } +// if self.frame.size != size { print("new size") } +// let updateLayers = self.animation.params != params + self.animation.params = params + print("params: \(params) previous params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") let previousState = self.animation.state self.animation.state = .init(params: params, previousState: previousState) - if self.animation.state != previousState { - transition = .immediate - } +// if self.animation.state != previousState { +// transition = .immediate +// } - if self.gradientContainerView == nil { - self.gradientContainerView = ComponentHostView() - self.gradientContainerNode.view.addSubview(self.gradientContainerView!) + if self.gradientImageNode.image == nil || self.gradientImageNode.image?.size.width != size.width { + let gradientImageSize = CGSize(width: size.width, height: 76.0) + self.gradientImageNode.image = generateGradientImage( + size: gradientImageSize, + colors: [UIColor(hexString: "#A9AFB7")!, UIColor(hexString: "#D3D4DA")!], + locations: [0.0, 1.0], + direction: .horizontal + ) } + + transition.updatePosition(node: self.backgroundNode, position: self.position) + transition.updateBounds(node: self.backgroundNode, bounds: self.bounds) - let _ = self.gradientContainerView?.update( - transition: .immediate, - component: AnyComponent(self.gradientComponent), - environment: {}, - containerSize: size - ) + transition.updatePosition(node: self.gradientContainerNode, position: self.position) + transition.updateBounds(node: self.gradientContainerNode, bounds: self.bounds) - transition.updateFrame(node: self, frame: frame) - transition.updateFrame(node: self.gradientContainerNode, frame: frame) - transition.updateFrame(node: self.backgroundNode, frame: frame) - if let gradientContainerView { - transition.updateFrame(view: gradientContainerView, frame: frame) + transition.updatePosition(node: self.gradientImageNode, position: self.position) + transition.updateBounds(node: self.gradientImageNode, bounds: self.bounds) + + if size.height >= 20 { + let arrowBackgroundFrame = CGRect(x: 29, y: 10, width: 20, height: size.height - 20) + let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 20, width: 20, height: 20) + transition.updatePosition(node: self.arrowBackgroundNode, position: arrowBackgroundFrame.center) + transition.updateBounds(node: self.arrowBackgroundNode, bounds: arrowBackgroundFrame) + transition.updateCornerRadius(node: self.arrowBackgroundNode, cornerRadius: 10) + transition.updatePosition(node: self.arrowContainerNode, position: arrowFrame.center) + transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) + transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) + transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) } - let arrowBackgroundFrame = CGRect(x: 29, y: 10, width: 20, height: size.height - 20) - let arrowFrame = CGRect(x: 0, y: arrowBackgroundFrame.height - 20, width: 20, height: 20) - transition.updateFrame(node: self.arrowBackgroundNode, frame: arrowBackgroundFrame) - transition.updateCornerRadius(node: self.arrowBackgroundNode, cornerRadius: arrowBackgroundFrame.width / 2, completion: nil) + // if var size = self.arrowAnimationNode.preferredSize() { // let scale = 2.7//size.width / arrowBackgroundFrame.width // transition.updateTransformScale(layer: self.arrowBackgroundNode.layer, scale: scale) { [weak arrowNode] finished in @@ -437,7 +449,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // size = CGSize(width: ceil(size.width * scale), height: ceil(size.width * scale)) // // let arrowCenter = (size.height / scale)/2 -// let scaledArrowCenter = size.height / 2 +// let scaledArrowCenter = size.height / 2 // let difference = scaledArrowCenter - arrowCenter // // let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), @@ -446,30 +458,30 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // transition.updateFrame(node: arrowAnimationNode, frame: arrowFrame) // } -// transition.updateFrame(node: self.arrowNode, frame: CGRect(x: .zero, y: arrowBackgroundFrame.height - arrowBackgroundFrame.width, width: arrowBackgroundFrame.width, height: arrowBackgroundFrame.width)) - - transition.updateFrame(node: self.arrowContainerNode, frame: arrowFrame) - transition.updateFrame(node: self.arrowImageNode, frame: self.arrowContainerNode.bounds) - self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ - .foregroundColor: UIColor.white, - .font: Font.medium(floor(presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) - ]) - - let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) - - transition.updateFrame(node: titleNode, frame: CGRect(x: (size.width - textLayout.size.width) / 2, - y: size.height - textLayout.size.height - 10, - width: textLayout.size.width, - height: textLayout.size.height)) +// self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ +// .foregroundColor: UIColor.white, +// .font: Font.medium(floor(presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) +// ]) +// +// let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) +// +// self.titleNode.frame = CGRect(x: (size.width - textLayout.size.width) / 2, +// y: size.height - textLayout.size.height - 10, +// width: textLayout.size.width, +// height: textLayout.size.height) +// transition.updateFrame(node: titleNode, frame: CGRect(x: (size.width - textLayout.size.width) / 2, +// y: size.height - textLayout.size.height - 10, +// width: textLayout.size.width, +// height: textLayout.size.height)) - if self.animation.state != previousState { - self.animation.animateLayers(gradientNode: self.gradientContainerNode, - textNode: self.titleNode, - arrowContainerNode: self.arrowContainerNode) { - - print("animation finished") - } - } +// if updateLayers { +// self.animation.animateLayers(gradientNode: self.gradientContainerNode, +// textNode: self.titleNode, +// arrowContainerNode: self.arrowContainerNode) { +// +// print("animation finished") +// } +// } } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 16558302f15..1836be237aa 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2754,20 +2754,23 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition = .immediate } + let contextContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) +// strongSelf.contextContainer.position = contextContainerFrame.center + transition.updatePosition(node: strongSelf.contextContainer, position: contextContainerFrame.center) + transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) + transition.updatePosition(node: strongSelf.archiveTransitionNode, position: contextContainerFrame.center) + transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: contextContainerFrame) + if case let .groupReference(data) = item.content, data.groupId == .archive { transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: layout.contentSize, params: item.params, presentationData: item.presentationData) -// transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) + transition.updateAlpha(node: strongSelf.contextContainer, alpha: .zero) } else { transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) -// transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + transition.updateAlpha(node: strongSelf.contextContainer, alpha: 1.0) } - - let contextContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) -// strongSelf.contextContainer.position = contextContainerFrame.center - transition.updatePosition(node: strongSelf.contextContainer, position: contextContainerFrame.center) - transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) - // print("top offset: \(item.hiddenOffsetValue) hiddenOffset: \(item.hiddenOffset)") // let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) // transition.updatePosition(node: strongSelf.archiveTransitionNode, position: archiveTransitionFrame.center) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 61542dd5152..8e348fd3b5d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -955,7 +955,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { - print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") +// print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, @@ -2993,7 +2993,7 @@ public final class ChatListNode: ListView { // guard isHiddenItemVisible else { return } let toggleTemporaryRevealHiddenItems = !self.currentState.hiddenItemShouldBeTemporaryRevealed - print("toggle temporary reveal hidden items: \(toggleTemporaryRevealHiddenItems)") +// print("toggle temporary reveal hidden items: \(toggleTemporaryRevealHiddenItems)") self.updateState { state in var state = state state.archiveParams = params From b44e287ddbdae9290fe3685d8b87a1b829bcd50e Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:21:11 -0300 Subject: [PATCH 11/34] [WIP] fixed size updates --- .../Node/ChatListArchiveTransitionItem.swift | 77 +++++++++---------- .../Sources/Node/ChatListItem.swift | 2 +- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 997bfb450d1..e41c11e8515 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -375,24 +375,20 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData) { -// let frame = CGRect(origin: .zero, size: size) -// print("frame: \(frame)") - -// var transition = transition + let frame = self.bounds + var transition = transition guard self.animation.params != params || self.frame.size != size else { return } -// if self.animation.params != params { print("new params") } -// if self.frame.size != size { print("new size") } -// let updateLayers = self.animation.params != params + let updateLayers = self.animation.params != params self.animation.params = params - print("params: \(params) previous params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") +// print("params: \(params) previous params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") let previousState = self.animation.state self.animation.state = .init(params: params, previousState: previousState) -// if self.animation.state != previousState { -// transition = .immediate -// } + if self.animation.state != previousState { + transition = .immediate + } if self.gradientImageNode.image == nil || self.gradientImageNode.image?.size.width != size.width { let gradientImageSize = CGSize(width: size.width, height: 76.0) @@ -404,14 +400,14 @@ class ChatListArchiveTransitionNode: ASDisplayNode { ) } - transition.updatePosition(node: self.backgroundNode, position: self.position) - transition.updateBounds(node: self.backgroundNode, bounds: self.bounds) + transition.updatePosition(node: self.backgroundNode, position: frame.center) + transition.updateBounds(node: self.backgroundNode, bounds: frame) - transition.updatePosition(node: self.gradientContainerNode, position: self.position) - transition.updateBounds(node: self.gradientContainerNode, bounds: self.bounds) + transition.updatePosition(node: self.gradientContainerNode, position: frame.center) + transition.updateBounds(node: self.gradientContainerNode, bounds: frame) - transition.updatePosition(node: self.gradientImageNode, position: self.position) - transition.updateBounds(node: self.gradientImageNode, bounds: self.bounds) + transition.updatePosition(node: self.gradientImageNode, position: frame.center) + transition.updateBounds(node: self.gradientImageNode, bounds: frame) if size.height >= 20 { let arrowBackgroundFrame = CGRect(x: 29, y: 10, width: 20, height: size.height - 20) @@ -458,30 +454,31 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // transition.updateFrame(node: arrowAnimationNode, frame: arrowFrame) // } -// self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ -// .foregroundColor: UIColor.white, -// .font: Font.medium(floor(presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) -// ]) -// -// let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) -// -// self.titleNode.frame = CGRect(x: (size.width - textLayout.size.width) / 2, -// y: size.height - textLayout.size.height - 10, -// width: textLayout.size.width, -// height: textLayout.size.height) -// transition.updateFrame(node: titleNode, frame: CGRect(x: (size.width - textLayout.size.width) / 2, -// y: size.height - textLayout.size.height - 10, -// width: textLayout.size.width, -// height: textLayout.size.height)) + if self.titleNode.attributedText == nil { + self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ + .foregroundColor: UIColor.white, + .font: Font.medium(floor(presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) + ]) + } + + let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) + let titleFrame = CGRect(x: (size.width - textLayout.size.width) / 2, + y: size.height - textLayout.size.height - 10, + width: textLayout.size.width, + height: textLayout.size.height) + + + transition.updatePosition(node: self.titleNode, position: titleFrame.center) + transition.updateBounds(node: self.titleNode, bounds: titleFrame) -// if updateLayers { -// self.animation.animateLayers(gradientNode: self.gradientContainerNode, -// textNode: self.titleNode, -// arrowContainerNode: self.arrowContainerNode) { -// -// print("animation finished") -// } -// } + if updateLayers { + self.animation.animateLayers(gradientNode: self.gradientContainerNode, + textNode: self.titleNode, + arrowContainerNode: self.arrowContainerNode) { + + print("animation finished") + } + } } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 1836be237aa..eb3e9899153 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2763,7 +2763,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if case let .groupReference(data) = item.content, data.groupId == .archive { transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) - strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: layout.contentSize, params: item.params, presentationData: item.presentationData) + strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: contextContainerFrame.size, params: item.params, presentationData: item.presentationData) transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) transition.updateAlpha(node: strongSelf.contextContainer, alpha: .zero) } else { From ebf99d3997e8e6a652d6d77abf14e98052ae8663 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Tue, 29 Aug 2023 21:40:56 -0300 Subject: [PATCH 12/34] [WIP] animation. fixed updates according to animation progress --- .../Node/ChatListArchiveTransitionItem.swift | 109 +++++++++++++----- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index e41c11e8515..1b415cec911 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -33,9 +33,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { init(params: ArchiveAnimationParams, previousState: TransitionAnimation.State) { let fraction = params.storiesFraction - if params.storiesFraction <= 0.92 { + if params.storiesFraction < 0.7 { self = .swipeDownAppear - } else if fraction > 0.92 && fraction < 1.0 { + } else if fraction >= 0.7 && fraction < 1.0 { self = .releaseAppear } else if fraction >= 1.0 { self = .transitionToArchive @@ -43,10 +43,25 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self = .swipeDownInit } } + + func animationProgress(fraction: CGFloat) -> CGFloat { + switch self { + case .swipeDownAppear: + return max(0.01, min(0.99, fraction / 0.8)) + case .releaseAppear: + return max(0.01, min(0.99, (fraction - 0.8) / 0.3)) + default: + return 1.0 + } + } } var state: State var params: ArchiveAnimationParams + var rotationPausedTime: CFTimeInterval = .zero + var releaseSwipePausedTime: CFTimeInterval = .zero + var swipeTextPausedTime: CFTimeInterval = .zero + var isAnimated = false var gradientShapeLayer: CAShapeLayer? var gradientMaskLayer: CAShapeLayer? @@ -75,26 +90,53 @@ class ChatListArchiveTransitionNode: ASDisplayNode { mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, completion: (() -> Void)?) { - print("animate layers with fraction: \(self.params.storiesFraction) state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight)") - CATransaction.begin() - CATransaction.setCompletionBlock { - completion?() + print(""" + animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) + state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) + ## + """) +// CATransaction.begin() +// CATransaction.setCompletionBlock { +// completion?() +// } +// CATransaction.completionBlock() +// CATransaction.setAnimationDuration(1.0) + if !(arrowContainerNode.layer.animationKeys()?.contains(where: { $0 == "arrow_rotation" }) ?? false) { + let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) + self.rotationPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) + arrowContainerNode.layer.speed = .zero + arrowContainerNode.layer.timeOffset = self.rotationPausedTime + arrowContainerNode.layer.add(rotationAnimation, forKey: "arrow_rotation") + } + + updateReleaseTextNode(from: textNode) + if let releaseTextNode, !(releaseTextNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { + let releaseTextAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) + self.releaseSwipePausedTime = releaseTextNode.layer.convertTime(CACurrentMediaTime(), from: nil) + releaseTextNode.layer.speed = .zero + releaseTextNode.layer.timeOffset = self.releaseSwipePausedTime + releaseTextNode.layer.add(releaseTextAnimation, forKey: "translate_text") + } + + if !(textNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { + let swipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) + self.swipeTextPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) + textNode.layer.speed = .zero + textNode.layer.timeOffset = self.swipeTextPausedTime + textNode.layer.add(swipeAnimation, forKey: "translate_text") } - CATransaction.completionBlock() - CATransaction.setAnimationDuration(1.0) switch state { - case .swipeDownInit: - print("swipe dowm init transition called") -// self.gradientLayer case .releaseAppear: // updateReleaseTextNode(from: textNode) // updateGradientOverlay(from: gradientNode) - let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) - rotationAnimation.beginTime = .zero + let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) + arrowContainerNode.layer.timeOffset = self.rotationPausedTime + animationProgress + releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + animationProgress + textNode.layer.timeOffset = self.swipeTextPausedTime + animationProgress - let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) - textSwipeAnimation.beginTime = .zero +// let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) +// textSwipeAnimation.beginTime = .zero // if let releaseTextNode { // let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) @@ -107,12 +149,22 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // } - case .swipeDownAppear: - let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: false) - rotationAnimation.beginTime = .zero + case .swipeDownAppear, .swipeDownInit: + arrowContainerNode.layer.beginTime = CACurrentMediaTime() + arrowContainerNode.layer.speed = -1 + arrowContainerNode.layer.removeAllAnimations() - let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .left) - textSwipeAnimation.beginTime = .zero + textNode.layer.beginTime = CACurrentMediaTime() + textNode.layer.speed = -1 + textNode.layer.removeAllAnimations() + + releaseTextNode?.layer.beginTime = CACurrentMediaTime() + releaseTextNode?.layer.speed = -1 + releaseTextNode?.layer.removeAllAnimations() + print("set speed -1") + +// let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .left) +// textSwipeAnimation.beginTime = .zero // if let releaseTextNode { // let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .left) @@ -131,11 +183,11 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // updateGradientOverlay(from: gradientNode) case .transitionToArchive: - let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) - rotationAnimation.beginTime = .zero - + arrowContainerNode.layer.timeOffset = self.rotationPausedTime + 0.99 + releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + 0.99 + textNode.layer.timeOffset = self.swipeTextPausedTime + 0.99 } - CATransaction.commit() +// CATransaction.commit() self.isAnimated = true } @@ -231,12 +283,11 @@ class ChatListArchiveTransitionNode: ASDisplayNode { to: rotatedDegree as NSNumber, keyPath: "transform.rotation.z", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, - duration: 0.5, + duration: 1.0, removeOnCompletion: false, additive: true ) - arrowContainerNode.layer.animationKeys()?.filter({ $0 == "arrow_rotation" }).forEach({ arrowContainerNode.layer.cancelAnimationsRecursive(key: $0) }) - arrowContainerNode.layer.add(animation, forKey: "arrow_rotation") + animation.fillMode = .forwards return animation } @@ -263,6 +314,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } } + print("makeTextSwipeAnimation from position: \(textNode.layer.position) to position: \(targetPosition)") let animation = textNode.layer.springAnimation( from: NSValue(cgPoint: textNode.layer.position), to: NSValue(cgPoint: targetPosition), @@ -271,8 +323,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { removeOnCompletion: false, additive: false ) - textNode.layer.animationKeys()?.filter({ $0 == "translate_text" }).forEach({ textNode.layer.cancelAnimationsRecursive(key: $0) }) - textNode.layer.add(animation, forKey: "translate_text") + animation.fillMode = .forwards return animation } From 8d6d10798c881d738b051e2948123ac044360186 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:11:55 -0300 Subject: [PATCH 13/34] updated gradient appears animation --- .../Node/ChatListArchiveTransitionItem.swift | 567 ++++++++---------- 1 file changed, 253 insertions(+), 314 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 1b415cec911..e2abb1e783e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -18,7 +18,172 @@ public struct ArchiveAnimationParams: Equatable { } class ChatListArchiveTransitionNode: ASDisplayNode { + let backgroundNode: ASDisplayNode + let gradientContainerNode: ASDisplayNode + let gradientImageNode: ASImageNode + let titleNode: ASTextNode //centered + let arrowBackgroundNode: ASDisplayNode //20 with insets 10 + let arrowContainerNode: ASDisplayNode + let arrowAnimationNode: AnimationNode //20x20 + let arrowImageNode: ASImageNode + var animation: TransitionAnimation + + required override init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = .clear + self.backgroundNode.isLayerBacked = true + self.animation = .init(state: .swipeDownInit, params: .empty) + self.titleNode = ASTextNode() + self.titleNode.isLayerBacked = true + + self.gradientContainerNode = ASDisplayNode() + self.gradientContainerNode.isLayerBacked = true + self.gradientImageNode = ASImageNode() + self.gradientImageNode.isLayerBacked = true + + self.arrowBackgroundNode = ASDisplayNode() + self.arrowBackgroundNode.backgroundColor = .white.withAlphaComponent(0.4) + self.arrowBackgroundNode.isLayerBacked = true + + self.arrowContainerNode = ASDisplayNode() + self.arrowContainerNode.isLayerBacked = true + + self.arrowImageNode = ASImageNode() + self.arrowImageNode.image = UIImage(bundleImageName: "Chat List/Archive/IconArrow") + self.arrowImageNode.isLayerBacked = true + + let mixedBackgroundColor = UIColor(hexString: "#A9AFB7")!.mixedWith(.white, alpha: 0.4) + self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", colors: [ + "Arrow 1.Arrow 1.Stroke 1": mixedBackgroundColor, + "Arrow 2.Arrow 2.Stroke 1": mixedBackgroundColor, + "Cap.cap2.Fill 1": .white, + "Cap.cap1.Fill 1": .white, + "Box.box1.Fill 1": .white + ], scale: 0.11) + self.arrowAnimationNode.backgroundColor = .clear + + super.init() + self.backgroundColor = .red + self.addSubnode(self.gradientContainerNode) + self.gradientContainerNode.addSubnode(self.gradientImageNode) + self.addSubnode(self.backgroundNode) + self.backgroundNode.addSubnode(self.titleNode) + self.backgroundNode.addSubnode(self.arrowBackgroundNode) + self.arrowBackgroundNode.addSubnode(self.arrowContainerNode) + self.arrowContainerNode.addSubnode(self.arrowImageNode) + } + + override func didLoad() { + super.didLoad() + } + + func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData) { + let frame = self.bounds + var transition = transition + + guard self.animation.params != params || self.frame.size != size else { return } + let updateLayers = self.animation.params != params + + self.animation.params = params +// print("params: \(params) previous params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") + let previousState = self.animation.state + self.animation.state = .init(params: params, previousState: previousState) + + if self.animation.state != previousState { + transition = .immediate + } + + if self.gradientImageNode.image == nil || self.gradientImageNode.image?.size.width != size.width { + let gradientImageSize = CGSize(width: size.width, height: 76.0) + self.gradientImageNode.image = generateGradientImage( + size: gradientImageSize, + colors: [UIColor(hexString: "#A9AFB7")!, UIColor(hexString: "#D3D4DA")!], + locations: [0.0, 1.0], + direction: .horizontal + ) + } + + transition.updatePosition(node: self.backgroundNode, position: frame.center) + transition.updateBounds(node: self.backgroundNode, bounds: frame) + + transition.updatePosition(node: self.gradientContainerNode, position: frame.center) + transition.updateBounds(node: self.gradientContainerNode, bounds: frame) + + transition.updatePosition(node: self.gradientImageNode, position: frame.center) + transition.updateBounds(node: self.gradientImageNode, bounds: frame) + + if size.height >= 20 { + let arrowBackgroundFrame = CGRect(x: 29, y: 10, width: 20, height: size.height - 20) + let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 20, width: 20, height: 20) + transition.updatePosition(node: self.arrowBackgroundNode, position: arrowBackgroundFrame.center) + transition.updateBounds(node: self.arrowBackgroundNode, bounds: arrowBackgroundFrame) + transition.updateCornerRadius(node: self.arrowBackgroundNode, cornerRadius: 10) + transition.updatePosition(node: self.arrowContainerNode, position: arrowFrame.center) + transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) + transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) + transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) + } + + if self.titleNode.attributedText == nil { + self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ + .foregroundColor: UIColor.white, + .font: Font.medium(floor(presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) + ]) + } + + let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) + let titleFrame = CGRect(x: (size.width - textLayout.size.width) / 2, + y: size.height - textLayout.size.height - 10, + width: textLayout.size.width, + height: textLayout.size.height) + + + transition.updatePosition(node: self.titleNode, position: titleFrame.center) + transition.updateBounds(node: self.titleNode, bounds: titleFrame) + + if updateLayers { + self.animation.animateLayers(gradientNode: self.gradientContainerNode, + textNode: self.titleNode, + arrowContainerNode: self.arrowContainerNode) { + + print("animation finished") + } + } +// if var size = self.arrowAnimationNode.preferredSize() { +// let scale = 2.7//size.width / arrowBackgroundFrame.width +// transition.updateTransformScale(layer: self.arrowBackgroundNode.layer, scale: scale) { [weak arrowNode] finished in +// guard let arrowNode, finished else { return } +// transition.updateTransformScale(layer: arrowNode.layer, scale: 1.0 / scale) +// } +// animationBackgroundNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.12, removeOnCompletion: false, completion: { [weak animationBackgroundNode] finished in +// animationBackgroundNode?.layer.animateScale(from: 1.07, to: 1.0, duration: 0.12, removeOnCompletion: false) +// }) + +// print("size before: \(size)") +// size = CGSize(width: ceil(arrowBackgroundFrame.width), height: ceil(arrowBackgroundFrame.width)) +// print("size after: \(size)") +// size = CGSize(width: ceil(size.width), height: ceil(size.width)) +// let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), +// y: floor(arrowBackgroundFrame.height - size.height), +// width: size.width, height: size.height) +// transition.updateFrame(node: self.arrowNode, frame: arrowFrame) +// self.arrowNode.play() +// transition.updateTransformRotation(node: arrowAnimationNode, angle: TransitionAnimation.degreesToRadians(-180)) +// +// size = CGSize(width: ceil(size.width * scale), height: ceil(size.width * scale)) +// +// let arrowCenter = (size.height / scale)/2 +// let scaledArrowCenter = size.height / 2 +// let difference = scaledArrowCenter - arrowCenter +// +// let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), +// y: floor(arrowBackgroundFrame.height - size.height/scale - difference), +// width: size.width, height: size.height) +// transition.updateFrame(node: arrowAnimationNode, frame: arrowFrame) +// } + } + struct TransitionAnimation { enum Direction { case left @@ -61,25 +226,24 @@ class ChatListArchiveTransitionNode: ASDisplayNode { var rotationPausedTime: CFTimeInterval = .zero var releaseSwipePausedTime: CFTimeInterval = .zero var swipeTextPausedTime: CFTimeInterval = .zero + var gradientPathPausedTime: CFTimeInterval = .zero var isAnimated = false - var gradientShapeLayer: CAShapeLayer? var gradientMaskLayer: CAShapeLayer? var gradientLayer: CALayer? var releaseTextNode: ASTextNode? lazy var gradientImage: UIImage? = { - guard let gradientShapeLayer, gradientShapeLayer.frame.size.height > 0, self.params.storiesFraction > 0 else { return nil } - var size = gradientShapeLayer.frame.size + guard let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 else { return nil } + var size = gradientLayer.frame.size let fraction = params.storiesFraction if fraction < 1.0 { size.height = self.params.expandedHeight / fraction } - return generateGradientImage(size: gradientShapeLayer.frame.size, + return generateGradientImage(size: gradientLayer.frame.size, colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], locations: [0.0, 1.0], direction: .horizontal) }() - static func degreesToRadians(_ x: CGFloat) -> CGFloat { return .pi * x / 180.0 } @@ -125,30 +289,22 @@ class ChatListArchiveTransitionNode: ASDisplayNode { textNode.layer.timeOffset = self.swipeTextPausedTime textNode.layer.add(swipeAnimation, forKey: "translate_text") } + makeGradientOverlay(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) + if let gradientMaskLayer, !(gradientMaskLayer.animationKeys()?.contains(where: { $0 == "gradient_path_transition" }) ?? false) { + let pathAnimatin = makeGradientAppearingAnimation(gradientMaskLayer: gradientMaskLayer, gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) + self.gradientPathPausedTime = gradientMaskLayer.convertTime(CACurrentMediaTime(), from: nil) + gradientMaskLayer.speed = .zero + gradientMaskLayer.timeOffset = self.gradientPathPausedTime + gradientMaskLayer.add(pathAnimatin, forKey: "gradient_path_transition") + } + switch state { case .releaseAppear: -// updateReleaseTextNode(from: textNode) -// updateGradientOverlay(from: gradientNode) - let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) arrowContainerNode.layer.timeOffset = self.rotationPausedTime + animationProgress releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + animationProgress textNode.layer.timeOffset = self.swipeTextPausedTime + animationProgress - -// let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) -// textSwipeAnimation.beginTime = .zero - -// if let releaseTextNode { -// let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) -// releaseTextAppearAnimation.beginTime = .zero -// } -// -// if let gradientShapeLayer { -// let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientShapeLayer) -// overlayGradientAnimation.beginTime = .zero -// } - - + gradientMaskLayer?.timeOffset = self.gradientPathPausedTime + animationProgress case .swipeDownAppear, .swipeDownInit: arrowContainerNode.layer.beginTime = CACurrentMediaTime() arrowContainerNode.layer.speed = -1 @@ -161,121 +317,22 @@ class ChatListArchiveTransitionNode: ASDisplayNode { releaseTextNode?.layer.beginTime = CACurrentMediaTime() releaseTextNode?.layer.speed = -1 releaseTextNode?.layer.removeAllAnimations() - print("set speed -1") -// let textSwipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .left) -// textSwipeAnimation.beginTime = .zero - -// if let releaseTextNode { -// let releaseTextAppearAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .left) -// releaseTextAppearAnimation.beginTime = .zero -// } -// -// if let gradientShapeLayer { -// let overlayGradientAnimation = makeGradientOverlay(gradientContainer: gradientNode, arrowContainer: arrowContainerNode, gradientLayer: gradientShapeLayer) -// overlayGradientAnimation.completion = { finished in -// guard finished else { return } -// gradientShapeLayer.isHidden = true -// gradientShapeLayer.removeFromSuperlayer() -// } -// overlayGradientAnimation.beginTime = .zero -// } -// updateGradientOverlay(from: gradientNode) - + gradientMaskLayer?.beginTime = CACurrentMediaTime() + gradientMaskLayer?.speed = -1 + gradientMaskLayer?.removeAllAnimations() + print("set speed -1") + case .transitionToArchive: arrowContainerNode.layer.timeOffset = self.rotationPausedTime + 0.99 releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + 0.99 textNode.layer.timeOffset = self.swipeTextPausedTime + 0.99 + gradientMaskLayer?.timeOffset = self.gradientPathPausedTime + 0.99 } // CATransaction.commit() self.isAnimated = true } - private mutating func updateGradientOverlay(from gradientNode: ASDisplayNode) { - switch state { - case .releaseAppear: - if (self.gradientShapeLayer == nil) { - self.gradientShapeLayer = CAShapeLayer() - self.gradientShapeLayer?.masksToBounds = true - self.gradientShapeLayer?.contentsGravity = .center - - self.gradientShapeLayer?.fillColor = UIColor.clear.cgColor - self.gradientShapeLayer?.strokeColor = UIColor.clear.cgColor - self.gradientShapeLayer?.lineWidth = 0.0 - self.gradientShapeLayer?.fillRule = .evenOdd - - } - if (self.gradientMaskLayer == nil) { - self.gradientMaskLayer = CAShapeLayer() - } - - if (self.gradientLayer == nil) { - self.gradientLayer = CALayer() - } - - guard let gradientShapeLayer else { return } - - - if gradientShapeLayer.superlayer == nil { - gradientNode.layer.addSublayer(gradientShapeLayer) - } - - if let gradientMaskLayer, gradientMaskLayer.superlayer == nil { - gradientShapeLayer.addSublayer(gradientMaskLayer) - } - - if (gradientShapeLayer.frame != gradientNode.bounds || gradientShapeLayer.contents == nil) { - gradientShapeLayer.frame = gradientNode.bounds - gradientShapeLayer.contents = self.getGradientImageOrUpdate()?.cgImage - } - - if self.gradientLayer?.superlayer == nil { - gradientShapeLayer.addSublayer(self.gradientLayer!) - } - - self.gradientMaskLayer?.path = gradientShapeLayer.path - self.gradientMaskLayer?.frame = gradientShapeLayer.bounds - gradientShapeLayer.mask = self.gradientMaskLayer - gradientShapeLayer.frame = gradientShapeLayer.bounds - - case .swipeDownInit, .swipeDownAppear, .transitionToArchive: - break - } - } - - mutating func getGradientImageOrUpdate() -> UIImage? { - if let gradientImage, gradientImage.size.height > 100 { - return gradientImage - } else if let gradientShapeLayer, gradientShapeLayer.frame.size.height > 0, self.params.storiesFraction > 0 { - self.gradientImage = generateGradientImage( - size: gradientShapeLayer.frame.size, - colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], - locations: [0.0, 1.0], - direction: .horizontal - ) - return self.gradientImage - } else { - return nil - } - } - - - private mutating func updateReleaseTextNode(from textNode: ASTextNode) { - if self.releaseTextNode == nil { - self.releaseTextNode = ASTextNode() - self.releaseTextNode?.isLayerBacked = true - let attributes: [NSAttributedString.Key: Any] = textNode.attributedText?.attributes(at: 0, effectiveRange: nil) ?? [:] - self.releaseTextNode?.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) - guard let supernode = textNode.supernode else { return } - supernode.addSubnode(self.releaseTextNode!) - } - - if let releaseTextNode, let supernode = releaseTextNode.supernode, state != .transitionToArchive { - let textLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.frame.width - 120, height: 25))) - self.releaseTextNode?.frame = CGRect(x: -textLayout.size.width, y: supernode.frame.height - textLayout.size.height - 8, width: textLayout.size.width, height: textLayout.size.height) - } - } - private func makeArrowRotationAnimation(arrowContainerNode: ASDisplayNode, isRotated: Bool) -> CAAnimation { let rotatedDegree = TransitionAnimation.degreesToRadians(isRotated ? -180 : 0) let animation = arrowContainerNode.layer.makeAnimation( @@ -327,210 +384,92 @@ class ChatListArchiveTransitionNode: ASDisplayNode { return animation } - private func makeGradientOverlay(gradientContainer: ASDisplayNode, arrowContainer: ASDisplayNode, gradientLayer: CAShapeLayer) -> CAAnimation { - gradientLayer.frame = gradientContainer.bounds//arrowContainer.convert(arrowContainer.frame, to: gradientContainer) - - let startCirclePath: UIBezierPath - let finalRectPath: UIBezierPath - switch state { - case .swipeDownInit, .swipeDownAppear: - startCirclePath = UIBezierPath(roundedRect: gradientContainer.bounds, cornerRadius: 10) - finalRectPath = UIBezierPath(roundedRect: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer), cornerRadius: 10) - case .releaseAppear: - startCirclePath = UIBezierPath(roundedRect: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer), cornerRadius: 10) - finalRectPath = UIBezierPath(roundedRect: gradientContainer.bounds, cornerRadius: 10) - case .transitionToArchive: - //TODO: update gradient path - startCirclePath = UIBezierPath(roundedRect: arrowContainer.convert(arrowContainer.bounds, to: gradientContainer), cornerRadius: 10) - finalRectPath = UIBezierPath(roundedRect: gradientContainer.bounds, cornerRadius: 10) - } + private func makeGradientAppearingAnimation(gradientMaskLayer: CAShapeLayer, gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode) -> CAAnimation { + let finalPath = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: 1.0) + let startPath = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: .zero) - startCirclePath.close() - finalRectPath.close() - - gradientLayer.path = startCirclePath.cgPath -// gradientLayer.mask = -// let animation2 = gradientLayer.makeAnimation(from: gradientLayer.cornerRadius as NSNumber, to: 0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 3.0, removeOnCompletion: false) - let animation = gradientLayer.springAnimation(from: startCirclePath.cgPath, to: finalRectPath.cgPath, keyPath: "path", duration: 3.0, removeOnCompletion: false, additive: false) + let animation = gradientMaskLayer.makeAnimation( + from: startPath.cgPath, + to: finalPath.cgPath, + keyPath: "path", + timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, + duration: 1.0, + removeOnCompletion: false, + additive: false + ) animation.fillMode = .forwards - animation.speed = 0 -// animation.timeOffse - gradientLayer.removeAllAnimations() -// gradientLayer.add(animation2, forKey: "gradient_corner") - gradientLayer.add(animation, forKey: "gradient_path_transition") - - gradientLayer.path = finalRectPath.cgPath - return animation } } +} - let backgroundNode: ASDisplayNode - let gradientContainerNode: ASDisplayNode - let gradientImageNode: ASImageNode - let titleNode: ASTextNode //centered - let arrowBackgroundNode: ASDisplayNode //20 with insets 10 - let arrowContainerNode: ASDisplayNode - let arrowAnimationNode: AnimationNode //20x20 - let arrowImageNode: ASImageNode - var animation: TransitionAnimation +extension ChatListArchiveTransitionNode.TransitionAnimation { - required override init() { - self.backgroundNode = ASDisplayNode() - self.backgroundNode.backgroundColor = .clear - self.backgroundNode.isLayerBacked = true - - self.animation = .init(state: .swipeDownInit, params: .empty) - self.titleNode = ASTextNode() - self.titleNode.isLayerBacked = true - - self.gradientContainerNode = ASDisplayNode() - self.gradientContainerNode.isLayerBacked = true - self.gradientImageNode = ASImageNode() - self.gradientImageNode.isLayerBacked = true + private mutating func updateReleaseTextNode(from textNode: ASTextNode) { + if self.releaseTextNode == nil { + self.releaseTextNode = ASTextNode() + self.releaseTextNode?.isLayerBacked = true + let attributes: [NSAttributedString.Key: Any] = textNode.attributedText?.attributes(at: 0, effectiveRange: nil) ?? [:] + self.releaseTextNode?.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) + guard let supernode = textNode.supernode else { return } + supernode.addSubnode(self.releaseTextNode!) + } - self.arrowBackgroundNode = ASDisplayNode() - self.arrowBackgroundNode.backgroundColor = .white.withAlphaComponent(0.4) - self.arrowBackgroundNode.isLayerBacked = true - - self.arrowContainerNode = ASDisplayNode() - self.arrowContainerNode.isLayerBacked = true - - self.arrowImageNode = ASImageNode() - self.arrowImageNode.image = UIImage(bundleImageName: "Chat List/Archive/IconArrow") - self.arrowImageNode.isLayerBacked = true - - let mixedBackgroundColor = UIColor(hexString: "#A9AFB7")!.mixedWith(.white, alpha: 0.4) - self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", colors: [ - "Arrow 1.Arrow 1.Stroke 1": mixedBackgroundColor, - "Arrow 2.Arrow 2.Stroke 1": mixedBackgroundColor, - "Cap.cap2.Fill 1": .white, - "Cap.cap1.Fill 1": .white, - "Box.box1.Fill 1": .white - ], scale: 0.11) - self.arrowAnimationNode.backgroundColor = .clear - - super.init() - self.backgroundColor = .red - self.addSubnode(self.gradientContainerNode) - self.gradientContainerNode.addSubnode(self.gradientImageNode) - self.addSubnode(self.backgroundNode) - self.backgroundNode.addSubnode(self.titleNode) - self.backgroundNode.addSubnode(self.arrowBackgroundNode) - self.arrowBackgroundNode.addSubnode(self.arrowContainerNode) - self.arrowContainerNode.addSubnode(self.arrowImageNode) + if let releaseTextNode, let supernode = releaseTextNode.supernode, state != .transitionToArchive { + let textLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.frame.width - 120, height: 25))) + self.releaseTextNode?.frame = CGRect(x: -textLayout.size.width, y: supernode.frame.height - textLayout.size.height - 8, width: textLayout.size.width, height: textLayout.size.height) + } } - override func didLoad() { - super.didLoad() - } - - func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData) { - let frame = self.bounds - var transition = transition - - guard self.animation.params != params || self.frame.size != size else { return } - let updateLayers = self.animation.params != params - - self.animation.params = params -// print("params: \(params) previous params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") - let previousState = self.animation.state - self.animation.state = .init(params: params, previousState: previousState) - - if self.animation.state != previousState { - transition = .immediate + mutating internal func makeGradientOverlay(gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode) { + if self.gradientLayer == nil { + self.gradientLayer = CALayer() + gradientContainerNode.layer.addSublayer(self.gradientLayer!) } - - if self.gradientImageNode.image == nil || self.gradientImageNode.image?.size.width != size.width { - let gradientImageSize = CGSize(width: size.width, height: 76.0) - self.gradientImageNode.image = generateGradientImage( - size: gradientImageSize, - colors: [UIColor(hexString: "#A9AFB7")!, UIColor(hexString: "#D3D4DA")!], - locations: [0.0, 1.0], - direction: .horizontal - ) + if self.gradientMaskLayer == nil { + self.gradientMaskLayer = CAShapeLayer() } - transition.updatePosition(node: self.backgroundNode, position: frame.center) - transition.updateBounds(node: self.backgroundNode, bounds: frame) - - transition.updatePosition(node: self.gradientContainerNode, position: frame.center) - transition.updateBounds(node: self.gradientContainerNode, bounds: frame) + guard let gradientLayer, let gradientMaskLayer else { return } + gradientMaskLayer.frame = gradientContainerNode.bounds - transition.updatePosition(node: self.gradientImageNode, position: frame.center) - transition.updateBounds(node: self.gradientImageNode, bounds: frame) + gradientMaskLayer.path = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: 0).cgPath - if size.height >= 20 { - let arrowBackgroundFrame = CGRect(x: 29, y: 10, width: 20, height: size.height - 20) - let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 20, width: 20, height: 20) - transition.updatePosition(node: self.arrowBackgroundNode, position: arrowBackgroundFrame.center) - transition.updateBounds(node: self.arrowBackgroundNode, bounds: arrowBackgroundFrame) - transition.updateCornerRadius(node: self.arrowBackgroundNode, cornerRadius: 10) - transition.updatePosition(node: self.arrowContainerNode, position: arrowFrame.center) - transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) - transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) - transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) - } + gradientLayer.frame = gradientContainerNode.bounds + gradientLayer.mask = gradientMaskLayer + gradientLayer.contents = self.getGradientImageOrUpdate()?.cgImage + } + + internal func generateGradientMaskPath(gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode, fraction: CGFloat) -> UIBezierPath { + let startRect = arrowContainerNode.convert(arrowContainerNode.bounds, to: gradientContainerNode) + let startRadius = startRect.width / 2 -// if var size = self.arrowAnimationNode.preferredSize() { -// let scale = 2.7//size.width / arrowBackgroundFrame.width -// transition.updateTransformScale(layer: self.arrowBackgroundNode.layer, scale: scale) { [weak arrowNode] finished in -// guard let arrowNode, finished else { return } -// transition.updateTransformScale(layer: arrowNode.layer, scale: 1.0 / scale) -// } -// animationBackgroundNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.12, removeOnCompletion: false, completion: { [weak animationBackgroundNode] finished in -// animationBackgroundNode?.layer.animateScale(from: 1.07, to: 1.0, duration: 0.12, removeOnCompletion: false) -// }) - -// print("size before: \(size)") -// size = CGSize(width: ceil(arrowBackgroundFrame.width), height: ceil(arrowBackgroundFrame.width)) -// print("size after: \(size)") -// size = CGSize(width: ceil(size.width), height: ceil(size.width)) -// let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), -// y: floor(arrowBackgroundFrame.height - size.height), -// width: size.width, height: size.height) -// transition.updateFrame(node: self.arrowNode, frame: arrowFrame) -// self.arrowNode.play() -// transition.updateTransformRotation(node: arrowAnimationNode, angle: TransitionAnimation.degreesToRadians(-180)) -// -// size = CGSize(width: ceil(size.width * scale), height: ceil(size.width * scale)) -// -// let arrowCenter = (size.height / scale)/2 -// let scaledArrowCenter = size.height / 2 -// let difference = scaledArrowCenter - arrowCenter -// -// let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), -// y: floor(arrowBackgroundFrame.height - size.height/scale - difference), -// width: size.width, height: size.height) -// transition.updateFrame(node: arrowAnimationNode, frame: arrowFrame) -// } - - if self.titleNode.attributedText == nil { - self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ - .foregroundColor: UIColor.white, - .font: Font.medium(floor(presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) - ]) - } - - let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) - let titleFrame = CGRect(x: (size.width - textLayout.size.width) / 2, - y: size.height - textLayout.size.height - 10, - width: textLayout.size.width, - height: textLayout.size.height) - - - transition.updatePosition(node: self.titleNode, position: titleFrame.center) - transition.updateBounds(node: self.titleNode, bounds: titleFrame) + let finalScale = gradientContainerNode.bounds.width/startRect.width + gradientContainerNode.bounds.width/startRect.width*(gradientContainerNode.bounds.width - startRect.midX)/gradientContainerNode.bounds.width + let scale: CGFloat = max(1.0, (finalScale * fraction)) + let scaleTransform = CGAffineTransform(scaleX: scale, y: scale) + var transformedRect = startRect.applying(scaleTransform) + let translation = CGPoint(x: startRect.center.x - transformedRect.center.x, y: startRect.center.y - transformedRect.center.y) + let translateTransform = CGAffineTransform(translationX: translation.x, y: translation.y) + let scaledRadius = startRadius * scale + transformedRect = transformedRect.applying(translateTransform) - if updateLayers { - self.animation.animateLayers(gradientNode: self.gradientContainerNode, - textNode: self.titleNode, - arrowContainerNode: self.arrowContainerNode) { + let path = UIBezierPath(roundedRect: transformedRect, cornerRadius: scaledRadius) + return path + } - print("animation finished") - } + mutating func getGradientImageOrUpdate() -> UIImage? { + if let gradientImage, gradientImage.size.height > 1 { + return gradientImage + } else if let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 { + self.gradientImage = generateGradientImage( + size: gradientLayer.frame.size, + colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], + locations: [0.0, 1.0], + direction: .horizontal + ) + return self.gradientImage + } else { + return nil } - } } - From a24e0cf287c022aa6dcaea7e9e7ed2535b2622a0 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 01:19:27 -0300 Subject: [PATCH 14/34] alternative implementation using transition --- .../Node/ChatListArchiveTransitionItem.swift | 228 +++++++++++++----- 1 file changed, 162 insertions(+), 66 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index e2abb1e783e..35028fb6789 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -80,7 +80,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData) { let frame = self.bounds - var transition = transition +// var transition = transition guard self.animation.params != params || self.frame.size != size else { return } let updateLayers = self.animation.params != params @@ -90,9 +90,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let previousState = self.animation.state self.animation.state = .init(params: params, previousState: previousState) - if self.animation.state != previousState { - transition = .immediate - } +// if self.animation.state != previousState { +// transition = .immediate +// } if self.gradientImageNode.image == nil || self.gradientImageNode.image?.size.width != size.width { let gradientImageSize = CGSize(width: size.width, height: 76.0) @@ -145,10 +145,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { if updateLayers { self.animation.animateLayers(gradientNode: self.gradientContainerNode, textNode: self.titleNode, - arrowContainerNode: self.arrowContainerNode) { - - print("animation finished") - } + arrowContainerNode: self.arrowContainerNode, transition: transition) } // if var size = self.arrowAnimationNode.preferredSize() { // let scale = 2.7//size.width / arrowBackgroundFrame.width @@ -252,86 +249,185 @@ class ChatListArchiveTransitionNode: ASDisplayNode { return sqrt(pow((point.x - from.x), 2) + pow((point.y - from.y), 2)) } +// +// mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, completion: (() -> Void)?) { +// print(""" +// animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) +// state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) +// ## +// """) +// CATransaction.begin() +// CATransaction.setCompletionBlock { +// completion?() +// } +// CATransaction.completionBlock() +// CATransaction.setAnimationDuration(1.0) +// if !(arrowContainerNode.layer.animationKeys()?.contains(where: { $0 == "arrow_rotation" }) ?? false) { +// let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) +// self.rotationPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) +// arrowContainerNode.layer.speed = .zero +// arrowContainerNode.layer.timeOffset = self.rotationPausedTime +// arrowContainerNode.layer.add(rotationAnimation, forKey: "arrow_rotation") +// } +// +// updateReleaseTextNode(from: textNode) +// if let releaseTextNode, !(releaseTextNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { +// let releaseTextAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) +// self.releaseSwipePausedTime = releaseTextNode.layer.convertTime(CACurrentMediaTime(), from: nil) +// releaseTextNode.layer.speed = .zero +// releaseTextNode.layer.timeOffset = self.releaseSwipePausedTime +// releaseTextNode.layer.add(releaseTextAnimation, forKey: "translate_text") +// } +// +// if !(textNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { +// let swipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) +// self.swipeTextPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) +// textNode.layer.speed = .zero +// textNode.layer.timeOffset = self.swipeTextPausedTime +// textNode.layer.add(swipeAnimation, forKey: "translate_text") +// } +// makeGradientOverlay(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) +// if let gradientMaskLayer, !(gradientMaskLayer.animationKeys()?.contains(where: { $0 == "gradient_path_transition" }) ?? false) { +// let pathAnimatin = makeGradientAppearingAnimation(gradientMaskLayer: gradientMaskLayer, gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) +// self.gradientPathPausedTime = gradientMaskLayer.convertTime(CACurrentMediaTime(), from: nil) +// gradientMaskLayer.speed = .zero +// gradientMaskLayer.timeOffset = self.gradientPathPausedTime +// gradientMaskLayer.add(pathAnimatin, forKey: "gradient_path_transition") +// } +// +// switch state { +// case .releaseAppear: +// let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) +// arrowContainerNode.layer.timeOffset = self.rotationPausedTime + animationProgress +// releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + animationProgress +// textNode.layer.timeOffset = self.swipeTextPausedTime + animationProgress +// gradientMaskLayer?.timeOffset = self.gradientPathPausedTime + animationProgress +// case .swipeDownAppear, .swipeDownInit: +// arrowContainerNode.layer.beginTime = CACurrentMediaTime() +// arrowContainerNode.layer.speed = -1 +// arrowContainerNode.layer.removeAllAnimations() +// +// textNode.layer.beginTime = CACurrentMediaTime() +// textNode.layer.speed = -1 +// textNode.layer.removeAllAnimations() +// +// releaseTextNode?.layer.beginTime = CACurrentMediaTime() +// releaseTextNode?.layer.speed = -1 +// releaseTextNode?.layer.removeAllAnimations() +// +// gradientMaskLayer?.beginTime = CACurrentMediaTime() +// gradientMaskLayer?.speed = -1 +// gradientMaskLayer?.removeAllAnimations() +// print("set speed -1") +// +// case .transitionToArchive: +// arrowContainerNode.layer.timeOffset = self.rotationPausedTime + 0.99 +// releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + 0.99 +// textNode.layer.timeOffset = self.swipeTextPausedTime + 0.99 +// gradientMaskLayer?.timeOffset = self.gradientPathPausedTime + 0.99 +// } +// CATransaction.commit() +// self.isAnimated = true +// } - mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, completion: (() -> Void)?) { + mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, transition: ContainedViewLayoutTransition) { print(""" animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) ## """) -// CATransaction.begin() -// CATransaction.setCompletionBlock { -// completion?() +// if !(arrowContainerNode.layer.animationKeys()?.contains(where: { $0 == "arrow_rotation" }) ?? false) { +// let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) +// self.rotationPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) +// arrowContainerNode.layer.speed = .zero +// arrowContainerNode.layer.timeOffset = self.rotationPausedTime +// arrowContainerNode.layer.add(rotationAnimation, forKey: "arrow_rotation") // } -// CATransaction.completionBlock() -// CATransaction.setAnimationDuration(1.0) - if !(arrowContainerNode.layer.animationKeys()?.contains(where: { $0 == "arrow_rotation" }) ?? false) { - let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) - self.rotationPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) - arrowContainerNode.layer.speed = .zero - arrowContainerNode.layer.timeOffset = self.rotationPausedTime - arrowContainerNode.layer.add(rotationAnimation, forKey: "arrow_rotation") - } updateReleaseTextNode(from: textNode) - if let releaseTextNode, !(releaseTextNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { - let releaseTextAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) - self.releaseSwipePausedTime = releaseTextNode.layer.convertTime(CACurrentMediaTime(), from: nil) - releaseTextNode.layer.speed = .zero - releaseTextNode.layer.timeOffset = self.releaseSwipePausedTime - releaseTextNode.layer.add(releaseTextAnimation, forKey: "translate_text") - } +// if let releaseTextNode, !(releaseTextNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { +// let releaseTextAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) +// self.releaseSwipePausedTime = releaseTextNode.layer.convertTime(CACurrentMediaTime(), from: nil) +// releaseTextNode.layer.speed = .zero +// releaseTextNode.layer.timeOffset = self.releaseSwipePausedTime +// releaseTextNode.layer.add(releaseTextAnimation, forKey: "translate_text") +// } - if !(textNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { - let swipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) - self.swipeTextPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) - textNode.layer.speed = .zero - textNode.layer.timeOffset = self.swipeTextPausedTime - textNode.layer.add(swipeAnimation, forKey: "translate_text") - } +// if !(textNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { +// let swipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) +// self.swipeTextPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) +// textNode.layer.speed = .zero +// textNode.layer.timeOffset = self.swipeTextPausedTime +// textNode.layer.add(swipeAnimation, forKey: "translate_text") +// } makeGradientOverlay(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) - if let gradientMaskLayer, !(gradientMaskLayer.animationKeys()?.contains(where: { $0 == "gradient_path_transition" }) ?? false) { - let pathAnimatin = makeGradientAppearingAnimation(gradientMaskLayer: gradientMaskLayer, gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) - self.gradientPathPausedTime = gradientMaskLayer.convertTime(CACurrentMediaTime(), from: nil) - gradientMaskLayer.speed = .zero - gradientMaskLayer.timeOffset = self.gradientPathPausedTime - gradientMaskLayer.add(pathAnimatin, forKey: "gradient_path_transition") - } +// if let gradientMaskLayer, !(gradientMaskLayer.animationKeys()?.contains(where: { $0 == "gradient_path_transition" }) ?? false) { +// let pathAnimatin = makeGradientAppearingAnimation(gradientMaskLayer: gradientMaskLayer, gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) +// self.gradientPathPausedTime = gradientMaskLayer.convertTime(CACurrentMediaTime(), from: nil) +// gradientMaskLayer.speed = .zero +// gradientMaskLayer.timeOffset = self.gradientPathPausedTime +// gradientMaskLayer.add(pathAnimatin, forKey: "gradient_path_transition") +// } switch state { case .releaseAppear: let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) - arrowContainerNode.layer.timeOffset = self.rotationPausedTime + animationProgress - releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + animationProgress - textNode.layer.timeOffset = self.swipeTextPausedTime + animationProgress - gradientMaskLayer?.timeOffset = self.gradientPathPausedTime + animationProgress + + let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) + transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) + + if let releaseTextNode, let supernode = releaseTextNode.supernode { + let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) + transition.updatePosition(node: releaseTextNode, position: targetPosition) + + let textNodeTargetPosition = supernode.bounds.center.interpolate(to: supernode.bounds.center.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) + transition.updatePosition(node: textNode, position: textNodeTargetPosition) + } + + if let gradientMaskLayer { + let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) + transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) + } case .swipeDownAppear, .swipeDownInit: - arrowContainerNode.layer.beginTime = CACurrentMediaTime() - arrowContainerNode.layer.speed = -1 - arrowContainerNode.layer.removeAllAnimations() + let animationProgress: CGFloat = 0.0 - textNode.layer.beginTime = CACurrentMediaTime() - textNode.layer.speed = -1 - textNode.layer.removeAllAnimations() - - releaseTextNode?.layer.beginTime = CACurrentMediaTime() - releaseTextNode?.layer.speed = -1 - releaseTextNode?.layer.removeAllAnimations() + let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) + transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) - gradientMaskLayer?.beginTime = CACurrentMediaTime() - gradientMaskLayer?.speed = -1 - gradientMaskLayer?.removeAllAnimations() - print("set speed -1") - + if let releaseTextNode, let supernode = releaseTextNode.supernode { + let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) + transition.updatePosition(node: releaseTextNode, position: targetPosition) + + let textNodeTargetPosition = supernode.bounds.center.interpolate(to: supernode.bounds.center.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) + transition.updatePosition(node: textNode, position: textNodeTargetPosition) + } + + if let gradientMaskLayer { + let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) + transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) + } case .transitionToArchive: - arrowContainerNode.layer.timeOffset = self.rotationPausedTime + 0.99 - releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + 0.99 - textNode.layer.timeOffset = self.swipeTextPausedTime + 0.99 - gradientMaskLayer?.timeOffset = self.gradientPathPausedTime + 0.99 + let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) + + let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) + transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) + + if let releaseTextNode, let supernode = releaseTextNode.supernode { + let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) + transition.updatePosition(node: releaseTextNode, position: targetPosition) + + let textNodeTargetPosition = supernode.bounds.center.interpolate(to: supernode.bounds.center.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) + transition.updatePosition(node: textNode, position: textNodeTargetPosition) + } + + if let gradientMaskLayer { + let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) + transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) + } } -// CATransaction.commit() self.isAnimated = true } + private func makeArrowRotationAnimation(arrowContainerNode: ASDisplayNode, isRotated: Bool) -> CAAnimation { let rotatedDegree = TransitionAnimation.degreesToRadians(isRotated ? -180 : 0) From 6c206113081620946c5c7993076b4a5403c8b3cd Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 06:29:11 -0300 Subject: [PATCH 15/34] increased archive item size --- .../Sources/ChatListControllerNode.swift | 15 +++-- .../Node/ChatListArchiveTransitionItem.swift | 63 +++++++++++-------- .../Sources/Node/ChatListItem.swift | 44 +++---------- .../Sources/Node/ChatListNode.swift | 4 +- 4 files changed, 56 insertions(+), 70 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 8aa4d9678f6..4674d96bb77 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2533,8 +2533,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { archiveFraction != 0, // self.allowOverscrollItemExpansion, let node = self.mainContainerNode.currentItemNode.itemNodeAtIndex(2) as? ChatListItemNode, node.isNodeLoaded, - let itemHeight = node.currentItemHeight, itemHeight > 0 { - + var itemHeight = node.currentItemHeight, itemHeight > 0 { + itemHeight *= 1.2 + let expandedHeight: CGFloat if archiveFraction < 0 { expandedHeight = itemHeight - (-archiveFraction * itemHeight) @@ -2567,7 +2568,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.mainContainerNode.currentItemNode.updateArchiveTopOffset(params: .init( scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, - expandedHeight: expandedHeight + expandedHeight: expandedHeight, + finalizeAnimation: false )) // chatNode.updateExpandedHeight( @@ -2591,7 +2593,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(params: .init( scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, - expandedHeight: expandedHeight + expandedHeight: expandedHeight, + finalizeAnimation: false )) // chatNode.updateExpandedHeight( @@ -2657,6 +2660,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } self.allowOverscrollItemExpansion = false self.currentOverscrollItemExpansionTimestamp = nil + let params = self.mainContainerNode.currentItemNode.currentState.archiveParams + self.mainContainerNode.currentItemNode.updateArchiveTopOffset(params: .init(scrollOffset: params.scrollOffset, storiesFraction: params.storiesFraction, expandedHeight: params.expandedHeight, finalizeAnimation: true)) } private func contentScrollingEnded(listView: ListView, isPrimary: Bool) -> Bool { @@ -2668,7 +2673,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { guard let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View else { return false } - + if let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset { let searchScrollOffset = clippedScrollOffset if searchScrollOffset > 0.0 && searchScrollOffset < ChatListNavigationBar.searchScrollHeight { diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 35028fb6789..eead022af50 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -2,6 +2,7 @@ import UIKit import AsyncDisplayKit import Display +import AvatarNode import SwiftSignalKit import AnimationUI import ComponentFlow @@ -11,9 +12,10 @@ public struct ArchiveAnimationParams: Equatable { public let scrollOffset: CGFloat public let storiesFraction: CGFloat public let expandedHeight: CGFloat + public let finalizeAnimation: Bool public static var empty: ArchiveAnimationParams{ - return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero) + return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false) } } @@ -78,15 +80,15 @@ class ChatListArchiveTransitionNode: ASDisplayNode { super.didLoad() } - func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData) { - let frame = self.bounds + func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData, avatarNode: AvatarNode) { + let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width, height: self.bounds.height + 10)) // var transition = transition - guard self.animation.params != params || self.frame.size != size else { return } +// guard self.animation.params != params || self.frame.size != size else { return } let updateLayers = self.animation.params != params self.animation.params = params -// print("params: \(params) previous params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") + print("params: \(params) \nprevious params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") let previousState = self.animation.state self.animation.state = .init(params: params, previousState: previousState) @@ -331,11 +333,11 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // } mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, transition: ContainedViewLayoutTransition) { - print(""" - animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) - state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) - ## - """) +// print(""" +// animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) +// state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) +// ## +// """) // if !(arrowContainerNode.layer.animationKeys()?.contains(where: { $0 == "arrow_rotation" }) ?? false) { // let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) // self.rotationPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) @@ -407,22 +409,31 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) } case .transitionToArchive: - let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) - - let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) - transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) - - if let releaseTextNode, let supernode = releaseTextNode.supernode { - let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) - transition.updatePosition(node: releaseTextNode, position: targetPosition) - - let textNodeTargetPosition = supernode.bounds.center.interpolate(to: supernode.bounds.center.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) - transition.updatePosition(node: textNode, position: textNodeTargetPosition) - } - - if let gradientMaskLayer { - let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) - transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) + if params.finalizeAnimation { + print("should finalize animation") + //duration = 0.5 + //show animation arrow node + //play animation arrow to archive + //update gradient mask path to avatar node frame + //scale up then scale down avatar node gradient + } else { + let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) + + let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) + transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) + + if let releaseTextNode, let supernode = releaseTextNode.supernode { + let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) + transition.updatePosition(node: releaseTextNode, position: targetPosition) + + let textNodeTargetPosition = supernode.bounds.center.interpolate(to: supernode.bounds.center.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) + transition.updatePosition(node: textNode, position: textNodeTargetPosition) + } + + if let gradientMaskLayer { + let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) + transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) + } } } self.isAnimated = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index eb3e9899153..fb50da64abd 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2703,6 +2703,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) var heightOffset: CGFloat = .zero if case let .groupReference(data) = item.content, data.groupId == .archive { + itemHeight *= 1.2 heightOffset = -(itemHeight-item.params.expandedHeight) // print("height offset: \(heightOffset) with params: \(item.params) itemHeight: \(itemHeight)") } @@ -2716,7 +2717,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key)) } -// print("layout height: \(layout.contentSize.height)") + print("layout height: \(layout.contentSize.height)") return (layout, { [weak self] synchronousLoads, animated in if let strongSelf = self { strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize) @@ -2763,13 +2764,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if case let .groupReference(data) = item.content, data.groupId == .archive { transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) - strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: contextContainerFrame.size, params: item.params, presentationData: item.presentationData) - transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) - transition.updateAlpha(node: strongSelf.contextContainer, alpha: .zero) + strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: contextContainerFrame.size, params: item.params, presentationData: item.presentationData, avatarNode: strongSelf.avatarNode) +// transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) +// transition.updateAlpha(node: strongSelf.contextContainer, alpha: .zero) } else { transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) - transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) - transition.updateAlpha(node: strongSelf.contextContainer, alpha: 1.0) +// transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) +// transition.updateAlpha(node: strongSelf.contextContainer, alpha: 1.0) } // print("top offset: \(item.hiddenOffsetValue) hiddenOffset: \(item.hiddenOffset)") // let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) @@ -3861,37 +3862,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { }) } - func updateExpandedHeight(transition: ContainedViewLayoutTransition, params: ArchiveAnimationParams) { - guard self.item?.params.expandedHeight != params.expandedHeight else { return } - self.item?.params = params - -// let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.layout.contentSize.width, height: currentItemHeight)) - let layout = self.asyncLayout() - - if let layoutParams = self.layoutParams { - let updatedParams = ListViewItemLayoutParams( - width: layoutParams.5.width, - leftInset: layoutParams.5.leftInset, - rightInset: layoutParams.5.rightInset, - availableHeight: params.expandedHeight - ) - let (nodeLayout, apply) = layout(self.item ?? layoutParams.0, updatedParams, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4) - apply(true, true) - self.contentSize = nodeLayout.contentSize - self.insets = nodeLayout.insets - } - -// if let presentationData = self.item?.presentationData { -// self.archiveTransitionNode.updateLayout(transition: transition, -// size: archiveTransitionFrame.size, -// storiesFraction: 0.5, -// scrollOffset: .zero, -// presentationData: presentationData) -// } -// transition.updatePosition(node: self.archiveTransitionNode, position: archiveTransitionFrame.center) -// transition.updateBounds(node: self.archiveTransitionNode, bounds: archiveTransitionFrame) - } - func playArchiveAnimation() { guard let item = self.item, case .groupReference = item.content else { return diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 8e348fd3b5d..b78c1ebd77b 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2992,12 +2992,12 @@ public final class ChatListNode: ListView { // }) // guard isHiddenItemVisible else { return } - let toggleTemporaryRevealHiddenItems = !self.currentState.hiddenItemShouldBeTemporaryRevealed +// let toggleTemporaryRevealHiddenItems = !self.currentState.hiddenItemShouldBeTemporaryRevealed // print("toggle temporary reveal hidden items: \(toggleTemporaryRevealHiddenItems)") self.updateState { state in var state = state state.archiveParams = params - state.hiddenItemShouldBeTemporaryRevealed = toggleTemporaryRevealHiddenItems + state.hiddenItemShouldBeTemporaryRevealed = true return state } // guard (abs(currentState.topOffset) - abs(offset) < 1.0 ) else { From 3518dc72d31d76e0af1376396134d2245d105c75 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 07:37:11 -0300 Subject: [PATCH 16/34] fixed intermediate animatio --- .../Node/ChatListArchiveTransitionItem.swift | 275 +++++++----------- 1 file changed, 98 insertions(+), 177 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index eead022af50..86c37217002 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -1,4 +1,4 @@ -//import Foundation +import Foundation import UIKit import AsyncDisplayKit import Display @@ -81,14 +81,14 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData, avatarNode: AvatarNode) { - let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width, height: self.bounds.height + 10)) + let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width, height: self.bounds.height)) // var transition = transition // guard self.animation.params != params || self.frame.size != size else { return } let updateLayers = self.animation.params != params self.animation.params = params - print("params: \(params) \nprevious params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") +// print("params: \(params) \nprevious params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") let previousState = self.animation.state self.animation.state = .init(params: params, previousState: previousState) @@ -134,15 +134,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { ]) } - let textLayout = self.titleNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: size.width - 120, height: 25))) - let titleFrame = CGRect(x: (size.width - textLayout.size.width) / 2, - y: size.height - textLayout.size.height - 10, - width: textLayout.size.width, - height: textLayout.size.height) - - transition.updatePosition(node: self.titleNode, position: titleFrame.center) - transition.updateBounds(node: self.titleNode, bounds: titleFrame) +// transition.updatePosition(node: self.titleNode, position: titleFrame.center) +// transition.updateBounds(node: self.titleNode, bounds: titleFrame) if updateLayers { self.animation.animateLayers(gradientNode: self.gradientContainerNode, @@ -189,20 +183,29 @@ class ChatListArchiveTransitionNode: ASDisplayNode { case right } - enum State { + enum State: Int { case swipeDownInit case releaseAppear + case releaseDidAppear case swipeDownAppear - case transitionToArchive + case swipeDownDidAppear init(params: ArchiveAnimationParams, previousState: TransitionAnimation.State) { let fraction = params.storiesFraction - if params.storiesFraction < 0.7 { - self = .swipeDownAppear - } else if fraction >= 0.7 && fraction < 1.0 { - self = .releaseAppear - } else if fraction >= 1.0 { - self = .transitionToArchive + if params.storiesFraction < 0.8 { + switch previousState { + case .swipeDownAppear, .swipeDownInit, .swipeDownDidAppear: + self = .swipeDownDidAppear + default: + self = .swipeDownAppear + } + } else if fraction >= 0.8 && fraction <= 1.0 { + switch previousState { + case .releaseAppear, .releaseDidAppear: + self = .releaseDidAppear + default: + self = .releaseAppear + } } else { self = .swipeDownInit } @@ -251,145 +254,57 @@ class ChatListArchiveTransitionNode: ASDisplayNode { return sqrt(pow((point.x - from.x), 2) + pow((point.y - from.y), 2)) } -// -// mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, completion: (() -> Void)?) { -// print(""" -// animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) -// state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) -// ## -// """) -// CATransaction.begin() -// CATransaction.setCompletionBlock { -// completion?() -// } -// CATransaction.completionBlock() -// CATransaction.setAnimationDuration(1.0) -// if !(arrowContainerNode.layer.animationKeys()?.contains(where: { $0 == "arrow_rotation" }) ?? false) { -// let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) -// self.rotationPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) -// arrowContainerNode.layer.speed = .zero -// arrowContainerNode.layer.timeOffset = self.rotationPausedTime -// arrowContainerNode.layer.add(rotationAnimation, forKey: "arrow_rotation") -// } -// -// updateReleaseTextNode(from: textNode) -// if let releaseTextNode, !(releaseTextNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { -// let releaseTextAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) -// self.releaseSwipePausedTime = releaseTextNode.layer.convertTime(CACurrentMediaTime(), from: nil) -// releaseTextNode.layer.speed = .zero -// releaseTextNode.layer.timeOffset = self.releaseSwipePausedTime -// releaseTextNode.layer.add(releaseTextAnimation, forKey: "translate_text") -// } -// -// if !(textNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { -// let swipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) -// self.swipeTextPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) -// textNode.layer.speed = .zero -// textNode.layer.timeOffset = self.swipeTextPausedTime -// textNode.layer.add(swipeAnimation, forKey: "translate_text") -// } -// makeGradientOverlay(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) -// if let gradientMaskLayer, !(gradientMaskLayer.animationKeys()?.contains(where: { $0 == "gradient_path_transition" }) ?? false) { -// let pathAnimatin = makeGradientAppearingAnimation(gradientMaskLayer: gradientMaskLayer, gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) -// self.gradientPathPausedTime = gradientMaskLayer.convertTime(CACurrentMediaTime(), from: nil) -// gradientMaskLayer.speed = .zero -// gradientMaskLayer.timeOffset = self.gradientPathPausedTime -// gradientMaskLayer.add(pathAnimatin, forKey: "gradient_path_transition") -// } -// -// switch state { -// case .releaseAppear: -// let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) -// arrowContainerNode.layer.timeOffset = self.rotationPausedTime + animationProgress -// releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + animationProgress -// textNode.layer.timeOffset = self.swipeTextPausedTime + animationProgress -// gradientMaskLayer?.timeOffset = self.gradientPathPausedTime + animationProgress -// case .swipeDownAppear, .swipeDownInit: -// arrowContainerNode.layer.beginTime = CACurrentMediaTime() -// arrowContainerNode.layer.speed = -1 -// arrowContainerNode.layer.removeAllAnimations() -// -// textNode.layer.beginTime = CACurrentMediaTime() -// textNode.layer.speed = -1 -// textNode.layer.removeAllAnimations() -// -// releaseTextNode?.layer.beginTime = CACurrentMediaTime() -// releaseTextNode?.layer.speed = -1 -// releaseTextNode?.layer.removeAllAnimations() -// -// gradientMaskLayer?.beginTime = CACurrentMediaTime() -// gradientMaskLayer?.speed = -1 -// gradientMaskLayer?.removeAllAnimations() -// print("set speed -1") -// -// case .transitionToArchive: -// arrowContainerNode.layer.timeOffset = self.rotationPausedTime + 0.99 -// releaseTextNode?.layer.timeOffset = self.releaseSwipePausedTime + 0.99 -// textNode.layer.timeOffset = self.swipeTextPausedTime + 0.99 -// gradientMaskLayer?.timeOffset = self.gradientPathPausedTime + 0.99 -// } -// CATransaction.commit() -// self.isAnimated = true -// } - mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, transition: ContainedViewLayoutTransition) { -// print(""" -// animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) -// state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) -// ## -// """) -// if !(arrowContainerNode.layer.animationKeys()?.contains(where: { $0 == "arrow_rotation" }) ?? false) { -// let rotationAnimation = makeArrowRotationAnimation(arrowContainerNode: arrowContainerNode, isRotated: true) -// self.rotationPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) -// arrowContainerNode.layer.speed = .zero -// arrowContainerNode.layer.timeOffset = self.rotationPausedTime -// arrowContainerNode.layer.add(rotationAnimation, forKey: "arrow_rotation") -// } - - updateReleaseTextNode(from: textNode) -// if let releaseTextNode, !(releaseTextNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { -// let releaseTextAnimation = makeTextSwipeAnimation(textNode: releaseTextNode, direction: .right) -// self.releaseSwipePausedTime = releaseTextNode.layer.convertTime(CACurrentMediaTime(), from: nil) -// releaseTextNode.layer.speed = .zero -// releaseTextNode.layer.timeOffset = self.releaseSwipePausedTime -// releaseTextNode.layer.add(releaseTextAnimation, forKey: "translate_text") -// } - -// if !(textNode.layer.animationKeys()?.contains(where: { $0 == "translate_text" }) ?? false) { -// let swipeAnimation = makeTextSwipeAnimation(textNode: textNode, direction: .right) -// self.swipeTextPausedTime = arrowContainerNode.layer.convertTime(CACurrentMediaTime(), from: nil) -// textNode.layer.speed = .zero -// textNode.layer.timeOffset = self.swipeTextPausedTime -// textNode.layer.add(swipeAnimation, forKey: "translate_text") -// } - makeGradientOverlay(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) -// if let gradientMaskLayer, !(gradientMaskLayer.animationKeys()?.contains(where: { $0 == "gradient_path_transition" }) ?? false) { -// let pathAnimatin = makeGradientAppearingAnimation(gradientMaskLayer: gradientMaskLayer, gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) -// self.gradientPathPausedTime = gradientMaskLayer.convertTime(CACurrentMediaTime(), from: nil) -// gradientMaskLayer.speed = .zero -// gradientMaskLayer.timeOffset = self.gradientPathPausedTime -// gradientMaskLayer.add(pathAnimatin, forKey: "gradient_path_transition") -// } + print(""" + animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) + state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) + ## + """) switch state { case .releaseAppear: - let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) + updateReleaseTextNode(from: textNode) + makeGradientOverlay(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) + + let animationProgress = 1.0//self.state.animationProgress(fraction: self.params.storiesFraction) let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) if let releaseTextNode, let supernode = releaseTextNode.supernode { - let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) - transition.updatePosition(node: releaseTextNode, position: targetPosition) - - let textNodeTargetPosition = supernode.bounds.center.interpolate(to: supernode.bounds.center.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) - transition.updatePosition(node: textNode, position: textNodeTargetPosition) + let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.bounds.width - 120, height: 25))) + let titleFrame = CGRect(x: supernode.bounds.width + textLayout.size.width/2, + y: supernode.bounds.height - textLayout.size.height - 10, + width: textLayout.size.width, + height: textLayout.size.height) + + let releaseTextLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.bounds.width - 120, height: 25))) + let releaseNodeFrame = CGRect(x: (supernode.bounds.width - releaseTextLayout.size.width) / 2, y: supernode.bounds.height - releaseTextLayout.size.height - 8, width: releaseTextLayout.size.width, height: releaseTextLayout.size.height) + + +// let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) + transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center) + transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame) + +// let textNodeTargetPosition = textNode.position.interpolate(to: textNode.position.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) + transition.updatePosition(node: textNode, position: titleFrame.center) + transition.updateBounds(node: textNode, bounds: titleFrame) } if let gradientMaskLayer { let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) } + + case .releaseDidAppear: + if params.finalizeAnimation { + print("should finalize animation") + //duration = 0.5 + //show animation arrow node + //play animation arrow to archive + //update gradient mask path to avatar node frame + //scale up then scale down avatar node gradient + } case .swipeDownAppear, .swipeDownInit: let animationProgress: CGFloat = 0.0 @@ -397,18 +312,34 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) if let releaseTextNode, let supernode = releaseTextNode.supernode { - let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) - transition.updatePosition(node: releaseTextNode, position: targetPosition) - - let textNodeTargetPosition = supernode.bounds.center.interpolate(to: supernode.bounds.center.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) - transition.updatePosition(node: textNode, position: textNodeTargetPosition) + let releaseTextLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), + max: CGSize(width: supernode.bounds.width - 120, height: 25))) + let releaseNodeFrame = CGRect(x: -releaseTextLayout.size.width, + y: supernode.bounds.height - releaseTextLayout.size.height - 8, + width: releaseTextLayout.size.width, + height: releaseTextLayout.size.height) + + +// let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) + transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center) + transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame) + + let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.bounds.width - 120, height: 25))) + let titleFrame = CGRect(x: (supernode.bounds.width - textLayout.size.width)/2, + y: supernode.bounds.height - textLayout.size.height - 10, + width: textLayout.size.width, + height: textLayout.size.height) + +// let textNodeTargetPosition = textNode.position.interpolate(to: textNode.position.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) + transition.updatePosition(node: textNode, position: titleFrame.center) + transition.updateBounds(node: textNode, bounds: titleFrame) } if let gradientMaskLayer { let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) } - case .transitionToArchive: + case .swipeDownDidAppear: if params.finalizeAnimation { print("should finalize animation") //duration = 0.5 @@ -416,24 +347,6 @@ class ChatListArchiveTransitionNode: ASDisplayNode { //play animation arrow to archive //update gradient mask path to avatar node frame //scale up then scale down avatar node gradient - } else { - let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) - - let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) - transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) - - if let releaseTextNode, let supernode = releaseTextNode.supernode { - let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) - transition.updatePosition(node: releaseTextNode, position: targetPosition) - - let textNodeTargetPosition = supernode.bounds.center.interpolate(to: supernode.bounds.center.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) - transition.updatePosition(node: textNode, position: textNodeTargetPosition) - } - - if let gradientMaskLayer { - let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) - transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) - } } } self.isAnimated = true @@ -520,12 +433,11 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { self.releaseTextNode?.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) guard let supernode = textNode.supernode else { return } supernode.addSubnode(self.releaseTextNode!) + +// let textLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.frame.width - 120, height: 25))) +// self.releaseTextNode?.frame = CGRect(x: -textLayout.size.width, y: supernode.frame.height - textLayout.size.height - 8, width: textLayout.size.width, height: textLayout.size.height) } - if let releaseTextNode, let supernode = releaseTextNode.supernode, state != .transitionToArchive { - let textLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.frame.width - 120, height: 25))) - self.releaseTextNode?.frame = CGRect(x: -textLayout.size.width, y: supernode.frame.height - textLayout.size.height - 8, width: textLayout.size.width, height: textLayout.size.height) - } } mutating internal func makeGradientOverlay(gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode) { @@ -538,13 +450,22 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { } guard let gradientLayer, let gradientMaskLayer else { return } - gradientMaskLayer.frame = gradientContainerNode.bounds + if gradientMaskLayer.frame != gradientContainerNode.bounds { + gradientMaskLayer.frame = gradientContainerNode.bounds + gradientMaskLayer.path = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: 0).cgPath + } + + if gradientLayer.frame != gradientContainerNode.bounds { + gradientLayer.frame = gradientContainerNode.bounds + } - gradientMaskLayer.path = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: 0).cgPath + if gradientLayer.mask == nil { + gradientLayer.mask = gradientMaskLayer + } - gradientLayer.frame = gradientContainerNode.bounds - gradientLayer.mask = gradientMaskLayer - gradientLayer.contents = self.getGradientImageOrUpdate()?.cgImage + if gradientLayer.contents == nil { + gradientLayer.contents = self.getGradientImageOrUpdate()?.cgImage + } } internal func generateGradientMaskPath(gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode, fraction: CGFloat) -> UIBezierPath { @@ -552,7 +473,7 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { let startRadius = startRect.width / 2 let finalScale = gradientContainerNode.bounds.width/startRect.width + gradientContainerNode.bounds.width/startRect.width*(gradientContainerNode.bounds.width - startRect.midX)/gradientContainerNode.bounds.width - let scale: CGFloat = max(1.0, (finalScale * fraction)) + let scale: CGFloat = (finalScale * fraction)//max(1.0, (finalScale * fraction)) let scaleTransform = CGAffineTransform(scaleX: scale, y: scale) var transformedRect = startRect.applying(scaleTransform) let translation = CGPoint(x: startRect.center.x - transformedRect.center.x, y: startRect.center.y - transformedRect.center.y) From 117535bdc369bc7d204ebe9d1b6fd527789a422b Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:30:04 -0300 Subject: [PATCH 17/34] [WIP] animation from list item to archive icon --- .../Node/ChatListArchiveTransitionItem.swift | 172 ++++++------------ 1 file changed, 57 insertions(+), 115 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 86c37217002..2736e119748 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -55,15 +55,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.arrowImageNode.image = UIImage(bundleImageName: "Chat List/Archive/IconArrow") self.arrowImageNode.isLayerBacked = true - let mixedBackgroundColor = UIColor(hexString: "#A9AFB7")!.mixedWith(.white, alpha: 0.4) - self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", colors: [ - "Arrow 1.Arrow 1.Stroke 1": mixedBackgroundColor, - "Arrow 2.Arrow 2.Stroke 1": mixedBackgroundColor, - "Cap.cap2.Fill 1": .white, - "Cap.cap1.Fill 1": .white, - "Box.box1.Fill 1": .white - ], scale: 0.11) + self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", scale: 0.33) self.arrowAnimationNode.backgroundColor = .clear + self.arrowAnimationNode.isHidden = true super.init() self.backgroundColor = .red @@ -74,6 +68,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.backgroundNode.addSubnode(self.arrowBackgroundNode) self.arrowBackgroundNode.addSubnode(self.arrowContainerNode) self.arrowContainerNode.addSubnode(self.arrowImageNode) + self.addSubnode(self.arrowAnimationNode) } override func didLoad() { @@ -125,6 +120,24 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) + + if let size = self.arrowAnimationNode.preferredSize(), !params.finalizeAnimation { + let arrowAnimationFrame = CGRect(x: arrowFrame.midX - size.width / 2, y: arrowFrame.midY - size.height / 2, width: size.width, height: size.height) + let arrowCenterFraction = arrowAnimationFrame.midX / frame.size.width + let gradientColorAtFraction = UIColor(hexString: "#0E7AF1")!.interpolateTo(UIColor(hexString: "#69BEFE")!, fraction: arrowCenterFraction) + if let gradientColorAtFraction, arrowAnimationNode.position != arrowAnimationFrame.center { + print("animation node set color: \(gradientColorAtFraction.hexString)") + arrowAnimationNode.setAnimation(name: "anim_arrow_to_archive", colors: [ + "Arrow 1.Arrow 1.Stroke 1": gradientColorAtFraction, + "Arrow 2.Arrow 2.Stroke 1": gradientColorAtFraction, + "Cap.cap2.Fill 1": .white, + "Cap.cap1.Fill 1": .white, + "Box.box1.Fill 1": .white + ]) + } + transition.updatePosition(node: arrowAnimationNode, position: arrowAnimationFrame.center) + transition.updateBounds(node: arrowAnimationNode, bounds: arrowAnimationFrame) + } } if self.titleNode.attributedText == nil { @@ -133,48 +146,15 @@ class ChatListArchiveTransitionNode: ASDisplayNode { .font: Font.medium(floor(presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) ]) } - - -// transition.updatePosition(node: self.titleNode, position: titleFrame.center) -// transition.updateBounds(node: self.titleNode, bounds: titleFrame) if updateLayers { self.animation.animateLayers(gradientNode: self.gradientContainerNode, textNode: self.titleNode, - arrowContainerNode: self.arrowContainerNode, transition: transition) + arrowContainerNode: self.arrowContainerNode, + arrowAnimationNode: self.arrowAnimationNode, + avatarNode: avatarNode, + transition: transition) } -// if var size = self.arrowAnimationNode.preferredSize() { -// let scale = 2.7//size.width / arrowBackgroundFrame.width -// transition.updateTransformScale(layer: self.arrowBackgroundNode.layer, scale: scale) { [weak arrowNode] finished in -// guard let arrowNode, finished else { return } -// transition.updateTransformScale(layer: arrowNode.layer, scale: 1.0 / scale) -// } -// animationBackgroundNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.12, removeOnCompletion: false, completion: { [weak animationBackgroundNode] finished in -// animationBackgroundNode?.layer.animateScale(from: 1.07, to: 1.0, duration: 0.12, removeOnCompletion: false) -// }) - -// print("size before: \(size)") -// size = CGSize(width: ceil(arrowBackgroundFrame.width), height: ceil(arrowBackgroundFrame.width)) -// print("size after: \(size)") -// size = CGSize(width: ceil(size.width), height: ceil(size.width)) -// let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), -// y: floor(arrowBackgroundFrame.height - size.height), -// width: size.width, height: size.height) -// transition.updateFrame(node: self.arrowNode, frame: arrowFrame) -// self.arrowNode.play() -// transition.updateTransformRotation(node: arrowAnimationNode, angle: TransitionAnimation.degreesToRadians(-180)) -// -// size = CGSize(width: ceil(size.width * scale), height: ceil(size.width * scale)) -// -// let arrowCenter = (size.height / scale)/2 -// let scaledArrowCenter = size.height / 2 -// let difference = scaledArrowCenter - arrowCenter -// -// let arrowFrame = CGRect(x: floor((arrowBackgroundFrame.width - size.width) / 2.0), -// y: floor(arrowBackgroundFrame.height - size.height/scale - difference), -// width: size.width, height: size.height) -// transition.updateFrame(node: arrowAnimationNode, frame: arrowFrame) -// } } struct TransitionAnimation { @@ -254,13 +234,25 @@ class ChatListArchiveTransitionNode: ASDisplayNode { return sqrt(pow((point.x - from.x), 2) + pow((point.y - from.y), 2)) } - mutating func animateLayers(gradientNode: ASDisplayNode, textNode: ASTextNode, arrowContainerNode: ASDisplayNode, transition: ContainedViewLayoutTransition) { + mutating func animateLayers( + gradientNode: ASDisplayNode, + textNode: ASTextNode, + arrowContainerNode: ASDisplayNode, + arrowAnimationNode: AnimationNode, + avatarNode: AvatarNode, + transition: ContainedViewLayoutTransition + ) { print(""" animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) state: \(self.state), offset: \(self.params.scrollOffset) height: \(self.params.expandedHeight) ## """) + if !arrowAnimationNode.isHidden, state != .releaseDidAppear { + arrowContainerNode.subnodes?.filter({ $0.isHidden }).forEach({ $0.isHidden = false }) + arrowAnimationNode.isHidden = true + } + switch state { case .releaseAppear: updateReleaseTextNode(from: textNode) @@ -304,6 +296,24 @@ class ChatListArchiveTransitionNode: ASDisplayNode { //play animation arrow to archive //update gradient mask path to avatar node frame //scale up then scale down avatar node gradient + arrowContainerNode.subnodes?.forEach({ $0.isHidden = true }) + arrowAnimationNode.isHidden = false +// let newPosition = CGPoint(x: arrowAnimationNode.position.x, y: gradientNode.position.y) +// transition.updatePosition(node: arrowAnimationNode, position: newPosition) + + arrowAnimationNode.completion = { + print("arrow animation node finish animation") + } + + arrowAnimationNode.reset() + arrowAnimationNode.play() + +// let avatarNodeFrame = avatarNode.convert(avatarNode.frame, to: gradientNode) +// if let gradientMaskLayer { +// let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2).cgPath +// transition.updatePath(layer: gradientMaskLayer, path: targetPath) +// } +// print("avatar node frame: \(avatarNode.convert(avatarNode.frame, to: gradientNode))") } case .swipeDownAppear, .swipeDownInit: let animationProgress: CGFloat = 0.0 @@ -347,79 +357,11 @@ class ChatListArchiveTransitionNode: ASDisplayNode { //play animation arrow to archive //update gradient mask path to avatar node frame //scale up then scale down avatar node gradient + } } self.isAnimated = true } - - - private func makeArrowRotationAnimation(arrowContainerNode: ASDisplayNode, isRotated: Bool) -> CAAnimation { - let rotatedDegree = TransitionAnimation.degreesToRadians(isRotated ? -180 : 0) - let animation = arrowContainerNode.layer.makeAnimation( - from: 0.0 as NSNumber, - to: rotatedDegree as NSNumber, - keyPath: "transform.rotation.z", - timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, - duration: 1.0, - removeOnCompletion: false, - additive: true - ) - animation.fillMode = .forwards - return animation - } - - private func makeTextSwipeAnimation(textNode: ASTextNode, direction: TransitionAnimation.Direction) -> CAAnimation { - guard let superNode = textNode.supernode else { - return CAAnimation() - } - let targetPosition: CGPoint - - switch direction { - case .left: - if textNode.frame.origin.x > superNode.frame.width { - let distanceToCenter = TransitionAnimation.distance(from: textNode.frame.center, to: superNode.frame.center) - targetPosition = CGPoint(x: textNode.layer.position.x - distanceToCenter, y: textNode.layer.position.y) - } else { - targetPosition = CGPoint(x: textNode.position.x - (superNode.frame.width - textNode.frame.center.x) + textNode.frame.width / 2, y: textNode.layer.position.y) - } - case .right: - if textNode.frame.origin.x < 0 { - let distanceToCenter = TransitionAnimation.distance(from: textNode.frame.center, to: superNode.frame.center) - targetPosition = CGPoint(x: textNode.layer.position.x + distanceToCenter, y: textNode.layer.position.y) - } else { - targetPosition = CGPoint(x: textNode.position.x + (superNode.frame.width - textNode.frame.center.x) + textNode.frame.width / 2, y: textNode.layer.position.y) - } - } - - print("makeTextSwipeAnimation from position: \(textNode.layer.position) to position: \(targetPosition)") - let animation = textNode.layer.springAnimation( - from: NSValue(cgPoint: textNode.layer.position), - to: NSValue(cgPoint: targetPosition), - keyPath: "position", - duration: 1.0, - removeOnCompletion: false, - additive: false - ) - animation.fillMode = .forwards - return animation - } - - private func makeGradientAppearingAnimation(gradientMaskLayer: CAShapeLayer, gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode) -> CAAnimation { - let finalPath = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: 1.0) - let startPath = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: .zero) - - let animation = gradientMaskLayer.makeAnimation( - from: startPath.cgPath, - to: finalPath.cgPath, - keyPath: "path", - timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, - duration: 1.0, - removeOnCompletion: false, - additive: false - ) - animation.fillMode = .forwards - return animation - } } } From cf363e282c34596014d13f3a9a9166c5373fb7bb Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 10:23:15 -0300 Subject: [PATCH 18/34] [WIP] transition to archive cell, implemented animated gradient and arrow to archive --- .../Node/ChatListArchiveTransitionItem.swift | 49 ++++++++++--------- .../Sources/Node/ChatListItem.swift | 1 - 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 2736e119748..5dbc10c3d7c 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -57,10 +57,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", scale: 0.33) self.arrowAnimationNode.backgroundColor = .clear - self.arrowAnimationNode.isHidden = true +// self.arrowAnimationNode.isHidden = true super.init() - self.backgroundColor = .red self.addSubnode(self.gradientContainerNode) self.gradientContainerNode.addSubnode(self.gradientImageNode) self.addSubnode(self.backgroundNode) @@ -77,7 +76,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData, avatarNode: AvatarNode) { let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width, height: self.bounds.height)) -// var transition = transition + var transition = transition // guard self.animation.params != params || self.frame.size != size else { return } let updateLayers = self.animation.params != params @@ -87,9 +86,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let previousState = self.animation.state self.animation.state = .init(params: params, previousState: previousState) -// if self.animation.state != previousState { -// transition = .immediate -// } + if self.animation.state != previousState { + transition = .immediate + } if self.gradientImageNode.image == nil || self.gradientImageNode.image?.size.width != size.width { let gradientImageSize = CGSize(width: size.width, height: 76.0) @@ -154,9 +153,19 @@ class ChatListArchiveTransitionNode: ASDisplayNode { arrowAnimationNode: self.arrowAnimationNode, avatarNode: avatarNode, transition: transition) + + let nodesToHide: [ASDisplayNode] = [self.gradientImageNode, self.backgroundNode] + + if self.animation.state == .releaseDidAppear && params.finalizeAnimation { + nodesToHide.forEach({ $0.isHidden = true }) + } else { + nodesToHide.forEach({ $0.isHidden = false }) + } } } + + struct TransitionAnimation { enum Direction { case left @@ -249,7 +258,6 @@ class ChatListArchiveTransitionNode: ASDisplayNode { """) if !arrowAnimationNode.isHidden, state != .releaseDidAppear { - arrowContainerNode.subnodes?.filter({ $0.isHidden }).forEach({ $0.isHidden = false }) arrowAnimationNode.isHidden = true } @@ -273,12 +281,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let releaseTextLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.bounds.width - 120, height: 25))) let releaseNodeFrame = CGRect(x: (supernode.bounds.width - releaseTextLayout.size.width) / 2, y: supernode.bounds.height - releaseTextLayout.size.height - 8, width: releaseTextLayout.size.width, height: releaseTextLayout.size.height) - -// let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center) transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame) -// let textNodeTargetPosition = textNode.position.interpolate(to: textNode.position.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) transition.updatePosition(node: textNode, position: titleFrame.center) transition.updateBounds(node: textNode, bounds: titleFrame) } @@ -296,24 +301,24 @@ class ChatListArchiveTransitionNode: ASDisplayNode { //play animation arrow to archive //update gradient mask path to avatar node frame //scale up then scale down avatar node gradient - arrowContainerNode.subnodes?.forEach({ $0.isHidden = true }) arrowAnimationNode.isHidden = false -// let newPosition = CGPoint(x: arrowAnimationNode.position.x, y: gradientNode.position.y) -// transition.updatePosition(node: arrowAnimationNode, position: newPosition) + let newPosition = CGPoint(x: arrowAnimationNode.position.x, y: gradientNode.position.y) + transition.updatePosition(node: arrowAnimationNode, position: newPosition) - arrowAnimationNode.completion = { + arrowAnimationNode.completion = { [weak arrowAnimationNode, weak gradientLayer] in print("arrow animation node finish animation") + arrowAnimationNode?.isHidden = true + gradientLayer?.removeFromSuperlayer() } - - arrowAnimationNode.reset() + arrowAnimationNode.play() -// let avatarNodeFrame = avatarNode.convert(avatarNode.frame, to: gradientNode) -// if let gradientMaskLayer { -// let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2).cgPath -// transition.updatePath(layer: gradientMaskLayer, path: targetPath) -// } -// print("avatar node frame: \(avatarNode.convert(avatarNode.frame, to: gradientNode))") + let avatarNodeFrame = avatarNode.convert(avatarNode.frame, to: gradientNode) + if let gradientMaskLayer { + let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2).cgPath + transition.updatePath(layer: gradientMaskLayer, path: targetPath) + } + print("avatar node frame: \(avatarNode.convert(avatarNode.frame, to: gradientNode))") } case .swipeDownAppear, .swipeDownInit: let animationProgress: CGFloat = 0.0 diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index fb50da64abd..d819b1b24a8 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1247,7 +1247,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.separatorNode.isLayerBacked = true self.archiveTransitionNode = ChatListArchiveTransitionNode() - self.archiveTransitionNode.isLayerBacked = true super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) From 6ea7fa8680fc41aae30adf182945f4af98e910f6 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:04:57 -0300 Subject: [PATCH 19/34] updated gradient colors --- .../AvatarNode/Sources/AvatarNode.swift | 25 ++++---- .../Sources/ChatListControllerNode.swift | 24 +------- .../Node/ChatListArchiveTransitionItem.swift | 61 +++++++++++-------- .../Sources/Node/ChatListItem.swift | 28 ++++----- .../Sources/Node/ChatListNode.swift | 41 ++++++------- 5 files changed, 85 insertions(+), 94 deletions(-) diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 7a39ef44d19..393960634cf 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -92,10 +92,11 @@ private func calculateColors(explicitColorIndex: Int?, peerId: EnginePeer.Id?, i } else if case let .archivedChatsIcon(hiddenByDefault) = icon, let theme = theme { let backgroundColors: (UIColor, UIColor) if hiddenByDefault { - backgroundColors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors - } else { - backgroundColors = theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors - } + print("hidden by default") +// backgroundColors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors + } //else { + backgroundColors = theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors +// } colors = [backgroundColors.1, backgroundColors.0] } else { colors = AvatarNode.grayscaleColors @@ -348,12 +349,13 @@ public final class AvatarNode: ASDisplayNode { if let overrideImage = self.overrideImage, case let .archivedChatsIcon(hiddenByDefault) = overrideImage { let backgroundColors: (UIColor, UIColor) if hiddenByDefault { - backgroundColors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors - iconColor = theme.chatList.unpinnedArchiveAvatarColor.foregroundColor - } else { +// backgroundColors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors +// iconColor = theme.chatList.unpinnedArchiveAvatarColor.foregroundColor + print("archieve hidden by default") + } //else { backgroundColors = theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors iconColor = theme.chatList.pinnedArchiveAvatarColor.foregroundColor - } +// } let colors: NSArray = [backgroundColors.1.cgColor, backgroundColors.0.cgColor] backgroundColor = backgroundColors.1.mixedWith(backgroundColors.0, alpha: 0.5) animationBackgroundNode.image = generateGradientFilledCircleImage(diameter: self.imageNode.frame.width, colors: colors) @@ -566,10 +568,11 @@ public final class AvatarNode: ASDisplayNode { if let parameters = parameters as? AvatarNodeParameters, parameters.icon != .none { if case let .archivedChatsIcon(hiddenByDefault) = parameters.icon, let theme = parameters.theme { if hiddenByDefault { - iconColor = theme.chatList.unpinnedArchiveAvatarColor.foregroundColor - } else { +// iconColor = theme.chatList.unpinnedArchiveAvatarColor.foregroundColor + + } //else { iconColor = theme.chatList.pinnedArchiveAvatarColor.foregroundColor - } +// } } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 4674d96bb77..0a43ba65a96 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2534,7 +2534,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { // self.allowOverscrollItemExpansion, let node = self.mainContainerNode.currentItemNode.itemNodeAtIndex(2) as? ChatListItemNode, node.isNodeLoaded, var itemHeight = node.currentItemHeight, itemHeight > 0 { - itemHeight *= 1.2 +// if !self.mainContainerNode.currentItemNode.currentState.archiveParams.finalizeAnimation { + itemHeight *= 1.2 +// } let expandedHeight: CGFloat if archiveFraction < 0 { @@ -2571,17 +2573,6 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { expandedHeight: expandedHeight, finalizeAnimation: false )) - -// chatNode.updateExpandedHeight( -// transition: .immediate, -// params: .init( -// scrollOffset: scrollOffset, -// storiesFraction: archiveFraction, -// expandedHeight: expandedHeight -// ) -// ) -// chatNode.animateFrameTransition(1.0, expandedHeight) -// chatNode.updateHeightOffsetValue(offset: expandedHeight, transition: self.tempNavigationScrollingTransition ?? .immediate) } } } @@ -2596,15 +2587,6 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { expandedHeight: expandedHeight, finalizeAnimation: false )) - -// chatNode.updateExpandedHeight( -// transition: .immediate, -// params: .init( -// scrollOffset: scrollOffset, -// storiesFraction: archiveFraction, -// expandedHeight: expandedHeight -// ) -// ) } } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 5dbc10c3d7c..adf78322efd 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -29,6 +29,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let arrowAnimationNode: AnimationNode //20x20 let arrowImageNode: ASImageNode var animation: TransitionAnimation + var presentationData: ChatListPresentationData? required override init() { self.backgroundNode = ASDisplayNode() @@ -57,7 +58,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", scale: 0.33) self.arrowAnimationNode.backgroundColor = .clear -// self.arrowAnimationNode.isHidden = true + self.arrowAnimationNode.isHidden = true super.init() self.addSubnode(self.gradientContainerNode) @@ -76,7 +77,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData, avatarNode: AvatarNode) { let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width, height: self.bounds.height)) - var transition = transition +// var transition = transition // guard self.animation.params != params || self.frame.size != size else { return } let updateLayers = self.animation.params != params @@ -85,17 +86,20 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // print("params: \(params) \nprevious params: \(self.animation.params) \nsize: \(size) previous size: \(self.frame.size)") let previousState = self.animation.state self.animation.state = .init(params: params, previousState: previousState) + self.animation.presentationData = presentationData + self.presentationData = presentationData - if self.animation.state != previousState { - transition = .immediate - } +// if updateLayers { +// transition = .animated(duration: 1.0, curve: .easeInOut) +// } if self.gradientImageNode.image == nil || self.gradientImageNode.image?.size.width != size.width { let gradientImageSize = CGSize(width: size.width, height: 76.0) + let gradientColors: [UIColor] = [UIColor(hexString: "#A9AFB7")!, UIColor(hexString: "#D3D4DA")!] self.gradientImageNode.image = generateGradientImage( size: gradientImageSize, - colors: [UIColor(hexString: "#A9AFB7")!, UIColor(hexString: "#D3D4DA")!], - locations: [0.0, 1.0], + colors: gradientColors, + locations: [0.0, 0.1], direction: .horizontal ) } @@ -123,7 +127,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { if let size = self.arrowAnimationNode.preferredSize(), !params.finalizeAnimation { let arrowAnimationFrame = CGRect(x: arrowFrame.midX - size.width / 2, y: arrowFrame.midY - size.height / 2, width: size.width, height: size.height) let arrowCenterFraction = arrowAnimationFrame.midX / frame.size.width - let gradientColorAtFraction = UIColor(hexString: "#0E7AF1")!.interpolateTo(UIColor(hexString: "#69BEFE")!, fraction: arrowCenterFraction) + let backgrpundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors + let gradientColorAtFraction = backgrpundColors.1.interpolateTo(backgrpundColors.0, fraction: arrowCenterFraction) if let gradientColorAtFraction, arrowAnimationNode.position != arrowAnimationFrame.center { print("animation node set color: \(gradientColorAtFraction.hexString)") arrowAnimationNode.setAnimation(name: "anim_arrow_to_archive", colors: [ @@ -214,25 +219,21 @@ class ChatListArchiveTransitionNode: ASDisplayNode { var state: State var params: ArchiveAnimationParams - var rotationPausedTime: CFTimeInterval = .zero - var releaseSwipePausedTime: CFTimeInterval = .zero - var swipeTextPausedTime: CFTimeInterval = .zero - var gradientPathPausedTime: CFTimeInterval = .zero + var presentationData: ChatListPresentationData? var isAnimated = false var gradientMaskLayer: CAShapeLayer? var gradientLayer: CALayer? var releaseTextNode: ASTextNode? + lazy var gradientImage: UIImage? = { - guard let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 else { return nil } - var size = gradientLayer.frame.size - let fraction = params.storiesFraction - if fraction < 1.0 { - size.height = self.params.expandedHeight / fraction - } - return generateGradientImage(size: gradientLayer.frame.size, - colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], - locations: [0.0, 1.0], direction: .horizontal) + guard let presentationData, let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 else { return nil } + let size = gradientLayer.frame.size + let backgroundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors + let gradientColors = [backgroundColors.0, backgroundColors.1] + return generateGradientImage(size: size, + colors: gradientColors, + locations: [1.0, 0.0], direction: .horizontal) }() static func degreesToRadians(_ x: CGFloat) -> CGFloat { @@ -390,13 +391,18 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { mutating internal func makeGradientOverlay(gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode) { if self.gradientLayer == nil { self.gradientLayer = CALayer() - gradientContainerNode.layer.addSublayer(self.gradientLayer!) } + if self.gradientMaskLayer == nil { self.gradientMaskLayer = CAShapeLayer() } guard let gradientLayer, let gradientMaskLayer else { return } + + if gradientLayer.superlayer == nil { + gradientContainerNode.layer.addSublayer(gradientLayer) + } + if gradientMaskLayer.frame != gradientContainerNode.bounds { gradientMaskLayer.frame = gradientContainerNode.bounds gradientMaskLayer.path = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: 0).cgPath @@ -435,11 +441,14 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { mutating func getGradientImageOrUpdate() -> UIImage? { if let gradientImage, gradientImage.size.height > 1 { return gradientImage - } else if let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 { + } else if let presentationData, let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 { + let size = gradientLayer.frame.size + let backgroundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors + let gradientColors: [UIColor] = [backgroundColors.0, backgroundColors.1] self.gradientImage = generateGradientImage( - size: gradientLayer.frame.size, - colors: [UIColor(hexString: "#0E7AF1")!, UIColor(hexString: "#69BEFE")!], - locations: [0.0, 1.0], + size: size, + colors: gradientColors, + locations: [1.0, 0.0], direction: .horizontal ) return self.gradientImage diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index d819b1b24a8..6a62c720c16 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1247,7 +1247,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.separatorNode.isLayerBacked = true self.archiveTransitionNode = ChatListArchiveTransitionNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) @@ -2702,7 +2701,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) var heightOffset: CGFloat = .zero if case let .groupReference(data) = item.content, data.groupId == .archive { - itemHeight *= 1.2 +// if !item.params.finalizeAnimation { + itemHeight *= 1.2 +// } heightOffset = -(itemHeight-item.params.expandedHeight) // print("height offset: \(heightOffset) with params: \(item.params) itemHeight: \(itemHeight)") } @@ -2755,27 +2756,24 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let contextContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) -// strongSelf.contextContainer.position = contextContainerFrame.center transition.updatePosition(node: strongSelf.contextContainer, position: contextContainerFrame.center) transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) - transition.updatePosition(node: strongSelf.archiveTransitionNode, position: contextContainerFrame.center) - transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: contextContainerFrame) if case let .groupReference(data) = item.content, data.groupId == .archive { + transition.updatePosition(node: strongSelf.archiveTransitionNode, position: contextContainerFrame.center) + transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: contextContainerFrame) transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) - strongSelf.archiveTransitionNode.updateLayout(transition: transition, size: contextContainerFrame.size, params: item.params, presentationData: item.presentationData, avatarNode: strongSelf.avatarNode) -// transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: .zero) -// transition.updateAlpha(node: strongSelf.contextContainer, alpha: .zero) + strongSelf.archiveTransitionNode.updateLayout( + transition: transition, + size: contextContainerFrame.size, + params: item.params, + presentationData: item.presentationData, + avatarNode: strongSelf.avatarNode + ) } else { transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) -// transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) -// transition.updateAlpha(node: strongSelf.contextContainer, alpha: 1.0) } -// print("top offset: \(item.hiddenOffsetValue) hiddenOffset: \(item.hiddenOffset)") -// let archiveTransitionFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) -// transition.updatePosition(node: strongSelf.archiveTransitionNode, position: archiveTransitionFrame.center) -// transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: archiveTransitionFrame) - + var mainContentFrame: CGRect var mainContentBoundsOffset: CGFloat var mainContentAlpha: CGFloat = 1.0 diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index b78c1ebd77b..a7c299806a1 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2970,27 +2970,26 @@ public final class ChatListNode: ListView { } func updateArchiveTopOffset(params: ArchiveAnimationParams) { -// var isHiddenItemVisible = false -// self.forEachItemNode({ itemNode in -// if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { -// if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { -// if threadInfo.isHidden { -// isHiddenItemVisible = true -// } -// } -// -// if case let .groupReference(groupReference) = item.content { -// if groupReference.hiddenByDefault { -// isHiddenItemVisible = true -// } -// -// if groupReference.groupId == .archive, let itemHeight = itemNode.currentItemHeight, item.hiddenOffsetValue < itemHeight { -// isHiddenItemVisible = true -// } -// } -// } -// }) -// guard isHiddenItemVisible else { return } + var isHiddenItemVisible = false + self.forEachItemNode({ itemNode in + if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { + if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { + if threadInfo.isHidden { + isHiddenItemVisible = true + } + } + + if case let .groupReference(groupReference) = item.content { + if groupReference.hiddenByDefault { + isHiddenItemVisible = true + } + } + } + }) + guard isHiddenItemVisible else { + print("isHiddenItemVisible: \(isHiddenItemVisible)") + return + } // let toggleTemporaryRevealHiddenItems = !self.currentState.hiddenItemShouldBeTemporaryRevealed // print("toggle temporary reveal hidden items: \(toggleTemporaryRevealHiddenItems)") From 2baa154c64e055cf687b206df38cb56849ff4b06 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:05:21 -0300 Subject: [PATCH 20/34] updated arrow color --- .../Sources/ChatListControllerNode.swift | 3 ++ .../Node/ChatListArchiveTransitionItem.swift | 33 +++++++++++++++---- .../Sources/Node/ChatListNode.swift | 18 ---------- .../PresentationResourcesItemList.swift | 18 ++++++++++ 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 0a43ba65a96..01df306fb9f 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2518,6 +2518,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { var archiveFraction: CGFloat = 0.0 + let currentParams = self.mainContainerNode.currentItemNode.currentState.archiveParams + print("current params: \(currentParams)") + if let overscrollFraction, let currentFraction { archiveFraction = (overscrollFraction / 0.5) * 0.8 if currentFraction < 0 { diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index adf78322efd..26b59a74eb8 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -53,7 +53,6 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.arrowContainerNode.isLayerBacked = true self.arrowImageNode = ASImageNode() - self.arrowImageNode.image = UIImage(bundleImageName: "Chat List/Archive/IconArrow") self.arrowImageNode.isLayerBacked = true self.arrowAnimationNode = AnimationNode(animation: "anim_arrow_to_archive", scale: 0.33) @@ -113,14 +112,34 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updatePosition(node: self.gradientImageNode, position: frame.center) transition.updateBounds(node: self.gradientImageNode, bounds: frame) - if size.height >= 20 { - let arrowBackgroundFrame = CGRect(x: 29, y: 10, width: 20, height: size.height - 20) + if params.expandedHeight >= 20 { + let yOffset = size.height - params.expandedHeight + let arrowBackgroundFrame = CGRect(x: 29, y: yOffset + 10, width: 20, height: params.expandedHeight - 20) let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 20, width: 20, height: 20) transition.updatePosition(node: self.arrowBackgroundNode, position: arrowBackgroundFrame.center) transition.updateBounds(node: self.arrowBackgroundNode, bounds: arrowBackgroundFrame) transition.updateCornerRadius(node: self.arrowBackgroundNode, cornerRadius: 10) transition.updatePosition(node: self.arrowContainerNode, position: arrowFrame.center) transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) + switch self.animation.state { + case .swipeDownInit, .swipeDownAppear, .swipeDownDidAppear: + guard previousState == .releaseAppear || previousState == .releaseDidAppear || self.arrowImageNode.image == nil else { return } + let gradientColorAtFraction = UIColor(hexString: "#A9AFB7")!.interpolateTo(UIColor(hexString: "#D3D4DA")!, fraction: arrowFrame.midX / frame.size.width) + if let gradientColorAtFraction { + print("arrowImageNode set color: \(gradientColorAtFraction.hexString)") + self.arrowImageNode.image = PresentationResourcesItemList.archiveTransitionArrowIcon(presentationData.theme, backgroundColor: gradientColorAtFraction) + } + case .releaseDidAppear, .releaseAppear: + guard previousState == .swipeDownInit || previousState == .swipeDownAppear || previousState == .swipeDownDidAppear || self.arrowImageNode.image == nil else { return } + let backgrpundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors + let gradientColorAtFraction = backgrpundColors.1.interpolateTo(backgrpundColors.0, fraction: arrowFrame.midX / frame.size.width) + if let gradientColorAtFraction { + print("arrowImageNode set color: \(gradientColorAtFraction.hexString)") + self.arrowImageNode.image = PresentationResourcesItemList.archiveTransitionArrowIcon(presentationData.theme, backgroundColor: gradientColorAtFraction) + } + } +// self.arrowImageNode.layer.cornerRadius = arrowFrame.width / 2 +// self.arrowImageNode.layer.masksToBounds = true transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) @@ -269,8 +288,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let animationProgress = 1.0//self.state.animationProgress(fraction: self.params.storiesFraction) - let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) - transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) +// let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) + transition.updateTransformRotation(node: arrowContainerNode, angle: TransitionAnimation.degreesToRadians(-180)) + transition.updateTransformRotation(node: arrowContainerNode, angle: TransitionAnimation.degreesToRadians(180)) if let releaseTextNode, let supernode = releaseTextNode.supernode { let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.bounds.width - 120, height: 25))) @@ -324,8 +344,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { case .swipeDownAppear, .swipeDownInit: let animationProgress: CGFloat = 0.0 - let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) - transition.updateTransformRotation(node: arrowContainerNode, angle: rotationDegree) + transition.updateTransform(node: arrowContainerNode, transform: .identity) if let releaseTextNode, let supernode = releaseTextNode.supernode { let releaseTextLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index a7c299806a1..9c45d91f667 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2999,24 +2999,6 @@ public final class ChatListNode: ListView { state.hiddenItemShouldBeTemporaryRevealed = true return state } -// guard (abs(currentState.topOffset) - abs(offset) < 1.0 ) else { -// print("left: \(abs(currentState.topOffset)) right: \(abs(offset))") -// return -// } -// if !self.currentState.hiddenItemShouldBeTemporaryRevealed { -// self.updateState { state in -// var state = state -// state.topOffset = offset -// state.hiddenItemShouldBeTemporaryRevealed = true -// return state -// } -// } else { -// self.updateState { state in -// var state = state -// state.topOffset = offset -// return state -// } -// } } private func pollFilterUpdates() { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 9d009cdc176..22d5d71a2db 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -99,6 +99,24 @@ public struct PresentationResourcesItemList { }) } + public static func archiveTransitionArrowIcon(_ theme: PresentationTheme, backgroundColor: UIColor) -> UIImage? { + guard + let icon = generateTintedImage( + image: UIImage(bundleImageName: "Chat List/Archive/IconArrow"), + color: theme.chatList.pinnedArchiveAvatarColor.foregroundColor + ) + else { return nil } + + return generateImage(icon.size, rotatedContext: { size, context in + if let iconCgImage = icon.cgImage { + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(backgroundColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 2, y: 2), size: CGSize(width: size.width - 4.0, height: size.height - 4.0))) + context.draw(iconCgImage, in: CGRect(origin: CGPoint(), size: size)) + } + }) + } + public static func itemListDeleteIndicatorIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListDeleteIndicatorIcon.rawValue, { theme in guard let image = generateTintedImage(image: UIImage(bundleImageName: "Item List/RemoveItemIcon"), color: theme.list.itemDestructiveColor) else { From ba509b86c9ad39b1cf584420b407a6b4ada72b01 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:10:35 -0300 Subject: [PATCH 21/34] chat list updates if archive is visible --- .../Sources/ChatListControllerNode.swift | 9 +++-- .../Node/ChatListArchiveTransitionItem.swift | 37 ++++++++++++++----- .../Sources/Node/ChatListItem.swift | 6 +-- .../Sources/Node/ChatListNode.swift | 12 ++++-- .../PresentationResourcesItemList.swift | 2 +- 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 01df306fb9f..9d13279c924 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -2519,7 +2519,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { var archiveFraction: CGFloat = 0.0 let currentParams = self.mainContainerNode.currentItemNode.currentState.archiveParams - print("current params: \(currentParams)") + guard !currentParams.isArchiveGroupVisible else { return } if let overscrollFraction, let currentFraction { archiveFraction = (overscrollFraction / 0.5) * 0.8 @@ -2570,7 +2570,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { // print("expandedHeight: \(expandedHeight) archiveFraction: \(archiveFraction) itemHeight: \(itemHeight)") - self.mainContainerNode.currentItemNode.updateArchiveTopOffset(params: .init( + self.mainContainerNode.currentItemNode.updateArchiveParams(params: .init( scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight, @@ -2584,7 +2584,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { if let chatNode = node as? ChatListItemNode { if case let .groupReference(data) = chatNode.item?.content, data.groupId == .archive, expandedHeight != chatNode.item?.params.expandedHeight { // print("expandedHeight: \(expandedHeight) archiveFraction: \(archiveFraction) itemHeight: \(itemHeight)") - self.inlineStackContainerNode?.currentItemNode.updateArchiveTopOffset(params: .init( + self.inlineStackContainerNode?.currentItemNode.updateArchiveParams(params: .init( scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight, @@ -2645,8 +2645,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } self.allowOverscrollItemExpansion = false self.currentOverscrollItemExpansionTimestamp = nil + let params = self.mainContainerNode.currentItemNode.currentState.archiveParams - self.mainContainerNode.currentItemNode.updateArchiveTopOffset(params: .init(scrollOffset: params.scrollOffset, storiesFraction: params.storiesFraction, expandedHeight: params.expandedHeight, finalizeAnimation: true)) + self.mainContainerNode.currentItemNode.updateArchiveParams(params: params.withUpdatedFinalizeAnimation(true)) } private func contentScrollingEnded(listView: ListView, isPrimary: Bool) -> Bool { diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 26b59a74eb8..d608c01f12e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -11,12 +11,34 @@ import TelegramPresentationData public struct ArchiveAnimationParams: Equatable { public let scrollOffset: CGFloat public let storiesFraction: CGFloat - public let expandedHeight: CGFloat + public private(set)var expandedHeight: CGFloat public let finalizeAnimation: Bool - public static var empty: ArchiveAnimationParams{ + public static var empty: ArchiveAnimationParams { return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false) } + + public func withUpdatedFinalizeAnimation(_ finalizeAnimation: Bool) -> ArchiveAnimationParams { + var newParams = ArchiveAnimationParams( + scrollOffset: self.scrollOffset, + storiesFraction: self.storiesFraction, + expandedHeight: self.expandedHeight, + finalizeAnimation: finalizeAnimation + ) + if finalizeAnimation { + if newParams.isArchiveGroupVisible { + newParams.expandedHeight /= 1.2 + } else { + newParams = ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: finalizeAnimation) + } + } + return newParams + } + + var isArchiveGroupVisible: Bool { + return storiesFraction >= 0.8 && finalizeAnimation + } + } class ChatListArchiveTransitionNode: ASDisplayNode { @@ -123,23 +145,19 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) switch self.animation.state { case .swipeDownInit, .swipeDownAppear, .swipeDownDidAppear: - guard previousState == .releaseAppear || previousState == .releaseDidAppear || self.arrowImageNode.image == nil else { return } +// guard previousState == .releaseAppear || previousState == .releaseDidAppear || self.arrowImageNode.image == nil else { return } let gradientColorAtFraction = UIColor(hexString: "#A9AFB7")!.interpolateTo(UIColor(hexString: "#D3D4DA")!, fraction: arrowFrame.midX / frame.size.width) if let gradientColorAtFraction { - print("arrowImageNode set color: \(gradientColorAtFraction.hexString)") self.arrowImageNode.image = PresentationResourcesItemList.archiveTransitionArrowIcon(presentationData.theme, backgroundColor: gradientColorAtFraction) } case .releaseDidAppear, .releaseAppear: - guard previousState == .swipeDownInit || previousState == .swipeDownAppear || previousState == .swipeDownDidAppear || self.arrowImageNode.image == nil else { return } +// guard previousState == .swipeDownInit || previousState == .swipeDownAppear || previousState == .swipeDownDidAppear || self.arrowImageNode.image == nil else { return } let backgrpundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors let gradientColorAtFraction = backgrpundColors.1.interpolateTo(backgrpundColors.0, fraction: arrowFrame.midX / frame.size.width) if let gradientColorAtFraction { - print("arrowImageNode set color: \(gradientColorAtFraction.hexString)") self.arrowImageNode.image = PresentationResourcesItemList.archiveTransitionArrowIcon(presentationData.theme, backgroundColor: gradientColorAtFraction) } } -// self.arrowImageNode.layer.cornerRadius = arrowFrame.width / 2 -// self.arrowImageNode.layer.masksToBounds = true transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) @@ -149,7 +167,6 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let backgrpundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors let gradientColorAtFraction = backgrpundColors.1.interpolateTo(backgrpundColors.0, fraction: arrowCenterFraction) if let gradientColorAtFraction, arrowAnimationNode.position != arrowAnimationFrame.center { - print("animation node set color: \(gradientColorAtFraction.hexString)") arrowAnimationNode.setAnimation(name: "anim_arrow_to_archive", colors: [ "Arrow 1.Arrow 1.Stroke 1": gradientColorAtFraction, "Arrow 2.Arrow 2.Stroke 1": gradientColorAtFraction, @@ -315,7 +332,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } case .releaseDidAppear: - if params.finalizeAnimation { + if params.finalizeAnimation, gradientLayer?.superlayer != nil { print("should finalize animation") //duration = 0.5 //show animation arrow node diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 6a62c720c16..3564e38bbde 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2701,11 +2701,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) var heightOffset: CGFloat = .zero if case let .groupReference(data) = item.content, data.groupId == .archive { -// if !item.params.finalizeAnimation { + if !item.params.isArchiveGroupVisible { itemHeight *= 1.2 -// } + } heightOffset = -(itemHeight-item.params.expandedHeight) -// print("height offset: \(heightOffset) with params: \(item.params) itemHeight: \(itemHeight)") + print("height offset: \(heightOffset) with params: \(item.params) itemHeight: \(itemHeight)") } let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: itemHeight + heightOffset), insets: insets) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 9c45d91f667..89615fae177 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2462,9 +2462,11 @@ public final class ChatListNode: ListView { } } if !isHiddenItemVisible && strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed { + strongSelf.updateState { state in var state = state state.hiddenItemShouldBeTemporaryRevealed = false + state.archiveParams = .empty return state } } @@ -2969,7 +2971,7 @@ public final class ChatListNode: ListView { } } - func updateArchiveTopOffset(params: ArchiveAnimationParams) { + func updateArchiveParams(params: ArchiveAnimationParams) { var isHiddenItemVisible = false self.forEachItemNode({ itemNode in if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { @@ -2991,12 +2993,14 @@ public final class ChatListNode: ListView { return } -// let toggleTemporaryRevealHiddenItems = !self.currentState.hiddenItemShouldBeTemporaryRevealed -// print("toggle temporary reveal hidden items: \(toggleTemporaryRevealHiddenItems)") + let hiddenItemShouldBeTemporaryRevealed = params.finalizeAnimation ? params.isArchiveGroupVisible : true + if hiddenItemShouldBeTemporaryRevealed != self.currentState.hiddenItemShouldBeTemporaryRevealed { + print("hiddenItemShouldBeTemporaryRevealed: \(hiddenItemShouldBeTemporaryRevealed)") + } self.updateState { state in var state = state state.archiveParams = params - state.hiddenItemShouldBeTemporaryRevealed = true + state.hiddenItemShouldBeTemporaryRevealed = hiddenItemShouldBeTemporaryRevealed return state } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 22d5d71a2db..41bf6b8e961 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -107,7 +107,7 @@ public struct PresentationResourcesItemList { ) else { return nil } - return generateImage(icon.size, rotatedContext: { size, context in + return generateImage(icon.size, contextGenerator: { size, context in if let iconCgImage = icon.cgImage { context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(backgroundColor.cgColor) From 19f6b17814828cfc60ae73d1228088d0ca2a8f60 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:45:01 -0300 Subject: [PATCH 22/34] fixed gradient, fixed animation trigger if archive already presented --- .../Node/ChatListArchiveTransitionItem.swift | 30 +++++++++------ .../Sources/Node/ChatListItem.swift | 37 +++++++++---------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index d608c01f12e..ac3d83eb4e4 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -100,7 +100,11 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width, height: self.bounds.height)) // var transition = transition -// guard self.animation.params != params || self.frame.size != size else { return } + + guard !(self.animation.params.finalizeAnimation && params.finalizeAnimation) else { + return + } + guard self.animation.params != params || self.frame.size != size else { return } let updateLayers = self.animation.params != params self.animation.params = params @@ -120,7 +124,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.gradientImageNode.image = generateGradientImage( size: gradientImageSize, colors: gradientColors, - locations: [0.0, 0.1], + locations: [0.0, 1.0], direction: .horizontal ) } @@ -193,7 +197,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { arrowContainerNode: self.arrowContainerNode, arrowAnimationNode: self.arrowAnimationNode, avatarNode: avatarNode, - transition: transition) + transition: transition, finalizeCompletion: { + print("finalize compeltion") + }) let nodesToHide: [ASDisplayNode] = [self.gradientImageNode, self.backgroundNode] @@ -286,7 +292,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { arrowContainerNode: ASDisplayNode, arrowAnimationNode: AnimationNode, avatarNode: AvatarNode, - transition: ContainedViewLayoutTransition + transition: ContainedViewLayoutTransition, + finalizeCompletion: (() -> Void)? ) { print(""" animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) @@ -343,20 +350,21 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let newPosition = CGPoint(x: arrowAnimationNode.position.x, y: gradientNode.position.y) transition.updatePosition(node: arrowAnimationNode, position: newPosition) + let avatarNodeFrame = avatarNode.convert(avatarNode.frame, to: gradientNode) + if let gradientMaskLayer { + let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2).cgPath + transition.updatePath(layer: gradientMaskLayer, path: targetPath) + } + print("avatar node frame: \(avatarNode.convert(avatarNode.frame, to: gradientNode))") + arrowAnimationNode.completion = { [weak arrowAnimationNode, weak gradientLayer] in print("arrow animation node finish animation") arrowAnimationNode?.isHidden = true gradientLayer?.removeFromSuperlayer() + finalizeCompletion?() } arrowAnimationNode.play() - - let avatarNodeFrame = avatarNode.convert(avatarNode.frame, to: gradientNode) - if let gradientMaskLayer { - let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2).cgPath - transition.updatePath(layer: gradientMaskLayer, path: targetPath) - } - print("avatar node frame: \(avatarNode.convert(avatarNode.frame, to: gradientNode))") } case .swipeDownAppear, .swipeDownInit: let animationProgress: CGFloat = 0.0 diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 3564e38bbde..52e1ebf9502 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2700,10 +2700,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) var heightOffset: CGFloat = .zero - if case let .groupReference(data) = item.content, data.groupId == .archive { - if !item.params.isArchiveGroupVisible { - itemHeight *= 1.2 - } + if case let .groupReference(data) = item.content, data.groupId == .archive, !item.params.isArchiveGroupVisible { + itemHeight *= 1.2 heightOffset = -(itemHeight-item.params.expandedHeight) print("height offset: \(heightOffset) with params: \(item.params) itemHeight: \(itemHeight)") } @@ -2716,8 +2714,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { for option in peerRevealOptions { customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key)) } - - print("layout height: \(layout.contentSize.height)") return (layout, { [weak self] synchronousLoads, animated in if let strongSelf = self { strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize) @@ -2759,20 +2755,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition.updatePosition(node: strongSelf.contextContainer, position: contextContainerFrame.center) transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) - if case let .groupReference(data) = item.content, data.groupId == .archive { - transition.updatePosition(node: strongSelf.archiveTransitionNode, position: contextContainerFrame.center) - transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: contextContainerFrame) - transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) - strongSelf.archiveTransitionNode.updateLayout( - transition: transition, - size: contextContainerFrame.size, - params: item.params, - presentationData: item.presentationData, - avatarNode: strongSelf.avatarNode - ) - } else { - transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) - } var mainContentFrame: CGRect var mainContentBoundsOffset: CGFloat @@ -2883,6 +2865,21 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.avatarNode.updateSize(size: avatarFrame.size) strongSelf.updateVideoVisibility() + if case let .groupReference(data) = item.content, data.groupId == .archive { + transition.updatePosition(node: strongSelf.archiveTransitionNode, position: contextContainerFrame.center) + transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: contextContainerFrame) + transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) + strongSelf.archiveTransitionNode.updateLayout( + transition: transition, + size: contextContainerFrame.size, + params: item.params, + presentationData: item.presentationData, + avatarNode: strongSelf.avatarNode + ) + } else { + transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: .zero) + } + var itemPeerId: EnginePeer.Id? if case let .chatList(index) = item.index { itemPeerId = index.messageIndex.id.peerId From b761d495c55b0f62dde95c3ab7c4f20e4c5091fe Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 17:51:55 -0300 Subject: [PATCH 23/34] fixed arrow background resize, text positioning --- .../Node/ChatListArchiveTransitionItem.swift | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index ac3d83eb4e4..cb8496ea3a9 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -87,13 +87,15 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.addSubnode(self.backgroundNode) self.backgroundNode.addSubnode(self.titleNode) self.backgroundNode.addSubnode(self.arrowBackgroundNode) - self.arrowBackgroundNode.addSubnode(self.arrowContainerNode) + self.backgroundNode.addSubnode(self.arrowContainerNode) self.arrowContainerNode.addSubnode(self.arrowImageNode) self.addSubnode(self.arrowAnimationNode) } override func didLoad() { super.didLoad() + self.arrowBackgroundNode.layer.cornerRadius = 11 + self.arrowBackgroundNode.layer.masksToBounds = true } func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData, avatarNode: AvatarNode) { @@ -138,13 +140,13 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updatePosition(node: self.gradientImageNode, position: frame.center) transition.updateBounds(node: self.gradientImageNode, bounds: frame) - if params.expandedHeight >= 20 { - let yOffset = size.height - params.expandedHeight - let arrowBackgroundFrame = CGRect(x: 29, y: yOffset + 10, width: 20, height: params.expandedHeight - 20) - let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 20, width: 20, height: 20) - transition.updatePosition(node: self.arrowBackgroundNode, position: arrowBackgroundFrame.center) - transition.updateBounds(node: self.arrowBackgroundNode, bounds: arrowBackgroundFrame) - transition.updateCornerRadius(node: self.arrowBackgroundNode, cornerRadius: 10) + if params.expandedHeight >= 22 { + let difference = (frame.height - params.expandedHeight).rounded() + let arrowBackgroundHeight = frame.height - difference - 22 + let arrowBackgroundFrame = CGRect(x: 29, y: frame.height - arrowBackgroundHeight - 11, width: 22, height: arrowBackgroundHeight) + let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 22, width: 22, height: 22) + transition.updatePosition(node: self.arrowBackgroundNode, position: arrowBackgroundFrame.center, beginWithCurrentState: true) + transition.updateBounds(node: self.arrowBackgroundNode, bounds: arrowBackgroundFrame, force: true, beginWithCurrentState: true) transition.updatePosition(node: self.arrowContainerNode, position: arrowFrame.center) transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) switch self.animation.state { @@ -323,8 +325,17 @@ class ChatListArchiveTransitionNode: ASDisplayNode { width: textLayout.size.width, height: textLayout.size.height) - let releaseTextLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.bounds.width - 120, height: 25))) - let releaseNodeFrame = CGRect(x: (supernode.bounds.width - releaseTextLayout.size.width) / 2, y: supernode.bounds.height - releaseTextLayout.size.height - 8, width: releaseTextLayout.size.width, height: releaseTextLayout.size.height) + let releaseTextLayout = releaseTextNode + .calculateLayoutThatFits(ASSizeRange( + min: .zero, + max: CGSize(width: supernode.bounds.width - 120, height: 25) + )) + let releaseNodeFrame = CGRect( + x: (supernode.bounds.width - releaseTextLayout.size.width) / 2, + y: supernode.bounds.height - releaseTextLayout.size.height - 10, + width: releaseTextLayout.size.width, + height: releaseTextLayout.size.height + ) transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center) transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame) @@ -365,6 +376,25 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } arrowAnimationNode.play() + } else { + if let releaseTextNode, let supernode = releaseTextNode.supernode { + let releaseTextLayout = releaseTextNode + .calculateLayoutThatFits(ASSizeRange( + min: .zero, + max: CGSize(width: supernode.bounds.width - 120, height: 25) + )) + let releaseNodeFrame = CGRect( + x: (supernode.bounds.width - releaseTextLayout.size.width) / 2, + y: supernode.bounds.height - releaseTextLayout.size.height - 10, + width: releaseTextLayout.size.width, + height: releaseTextLayout.size.height + ) + + print("release node frame: \(releaseNodeFrame)") + + transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center, beginWithCurrentState: true) + transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame, beginWithCurrentState: true) + } } case .swipeDownAppear, .swipeDownInit: let animationProgress: CGFloat = 0.0 @@ -372,7 +402,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updateTransform(node: arrowContainerNode, transform: .identity) if let releaseTextNode, let supernode = releaseTextNode.supernode { - let releaseTextLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), + let releaseTextLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 20, height: .zero), max: CGSize(width: supernode.bounds.width - 120, height: 25))) let releaseNodeFrame = CGRect(x: -releaseTextLayout.size.width, y: supernode.bounds.height - releaseTextLayout.size.height - 8, @@ -384,12 +414,13 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center) transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame) - let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.bounds.width - 120, height: 25))) + let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 20, height: .zero), max: CGSize(width: supernode.bounds.width - 120, height: 25))) let titleFrame = CGRect(x: (supernode.bounds.width - textLayout.size.width)/2, y: supernode.bounds.height - textLayout.size.height - 10, width: textLayout.size.width, height: textLayout.size.height) + print("title frame: \(titleFrame) release node frame: \(releaseNodeFrame)") // let textNodeTargetPosition = textNode.position.interpolate(to: textNode.position.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) transition.updatePosition(node: textNode, position: titleFrame.center) transition.updateBounds(node: textNode, bounds: titleFrame) @@ -408,6 +439,19 @@ class ChatListArchiveTransitionNode: ASDisplayNode { //update gradient mask path to avatar node frame //scale up then scale down avatar node gradient + } else { + if let supernode = textNode.supernode { + let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: .zero, max: CGSize(width: supernode.bounds.width - 120, height: 25))) + let titleFrame = CGRect(x: (supernode.bounds.width - textLayout.size.width)/2, + y: supernode.bounds.height - textLayout.size.height - 10, + width: textLayout.size.width, + height: textLayout.size.height) + + print("title frame: \(titleFrame)") + // let textNodeTargetPosition = textNode.position.interpolate(to: textNode.position.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) + transition.updatePosition(node: textNode, position: titleFrame.center, beginWithCurrentState: true) + transition.updateBounds(node: textNode, bounds: titleFrame, beginWithCurrentState: true) + } } } self.isAnimated = true From 86f5a9f65209589896a8e29093e805eea15bdfe6 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Wed, 30 Aug 2023 19:18:37 -0300 Subject: [PATCH 24/34] gradients generation optimized --- .../Node/ChatListArchiveTransitionItem.swift | 134 +++++++++--------- .../DefaultDarkPresentationTheme.swift | 4 +- .../Sources/DefaultDayPresentationTheme.swift | 4 +- 3 files changed, 73 insertions(+), 69 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index cb8496ea3a9..f3c9515db4a 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -45,13 +45,19 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let backgroundNode: ASDisplayNode let gradientContainerNode: ASDisplayNode let gradientImageNode: ASImageNode +// let topShadowNode: ASImageNode let titleNode: ASTextNode //centered let arrowBackgroundNode: ASDisplayNode //20 with insets 10 let arrowContainerNode: ASDisplayNode let arrowAnimationNode: AnimationNode //20x20 let arrowImageNode: ASImageNode + var arrowSwipeDownIcon: UIImage? + var arrowReleaseBackgroundColor: UIColor? + var arrowReleaseIcon: UIImage? + var animation: TransitionAnimation var presentationData: ChatListPresentationData? + var hapticFeedback: HapticFeedback? required override init() { self.backgroundNode = ASDisplayNode() @@ -97,6 +103,42 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.arrowBackgroundNode.layer.cornerRadius = 11 self.arrowBackgroundNode.layer.masksToBounds = true } + + override func layout() { + super.layout() + guard let theme = presentationData?.theme else { return } + print("bounds: \(self.bounds)") + let gradientImageSize = self.bounds.size + let greyColors = theme.chatList.unpinnedArchiveAvatarColor.backgroundColors.colors + if self.gradientImageNode.image == nil { + let greyGradientImage = generateGradientImage(size: gradientImageSize, colors: [greyColors.0, greyColors.1], locations: [1.0, 0.0], direction: .horizontal) + self.gradientImageNode.image = greyGradientImage + } + + let blueColors = theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors + if self.animation.gradientImage == nil { + let blueGradientImage = generateGradientImage(size: gradientImageSize, colors: [blueColors.0, blueColors.1], locations: [1.0, 0.0], direction: .horizontal) + self.animation.gradientImage = blueGradientImage + } + + if self.animation.rotatedGradientImage == nil { + let blueRotatedGradientImage = generateGradientImage(size: gradientImageSize, colors: [blueColors.1, blueColors.0], locations: [1.0, 0.0], direction: .vertical) + self.animation.rotatedGradientImage = blueRotatedGradientImage + } + + let greyGradientColorAtFraction = greyColors.0.interpolateTo(greyColors.1, fraction: 40 / gradientImageSize.width) + if let greyGradientColorAtFraction, self.arrowSwipeDownIcon == nil { + self.arrowSwipeDownIcon = PresentationResourcesItemList.archiveTransitionArrowIcon(theme, backgroundColor: greyGradientColorAtFraction) + } + + let blueGradientColorAtFraction = blueColors.0.interpolateTo(blueColors.1, fraction: 40 / gradientImageSize.width) + if let blueGradientColorAtFraction { + self.arrowReleaseBackgroundColor = blueGradientColorAtFraction + if self.arrowReleaseIcon == nil { + self.arrowReleaseIcon = PresentationResourcesItemList.archiveTransitionArrowIcon(theme, backgroundColor: blueGradientColorAtFraction) + } + } + } func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData, avatarNode: AvatarNode) { let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width, height: self.bounds.height)) @@ -120,17 +162,6 @@ class ChatListArchiveTransitionNode: ASDisplayNode { // transition = .animated(duration: 1.0, curve: .easeInOut) // } - if self.gradientImageNode.image == nil || self.gradientImageNode.image?.size.width != size.width { - let gradientImageSize = CGSize(width: size.width, height: 76.0) - let gradientColors: [UIColor] = [UIColor(hexString: "#A9AFB7")!, UIColor(hexString: "#D3D4DA")!] - self.gradientImageNode.image = generateGradientImage( - size: gradientImageSize, - colors: gradientColors, - locations: [0.0, 1.0], - direction: .horizontal - ) - } - transition.updatePosition(node: self.backgroundNode, position: frame.center) transition.updateBounds(node: self.backgroundNode, bounds: frame) @@ -151,31 +182,19 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) switch self.animation.state { case .swipeDownInit, .swipeDownAppear, .swipeDownDidAppear: -// guard previousState == .releaseAppear || previousState == .releaseDidAppear || self.arrowImageNode.image == nil else { return } - let gradientColorAtFraction = UIColor(hexString: "#A9AFB7")!.interpolateTo(UIColor(hexString: "#D3D4DA")!, fraction: arrowFrame.midX / frame.size.width) - if let gradientColorAtFraction { - self.arrowImageNode.image = PresentationResourcesItemList.archiveTransitionArrowIcon(presentationData.theme, backgroundColor: gradientColorAtFraction) - } + self.arrowImageNode.image = self.arrowSwipeDownIcon case .releaseDidAppear, .releaseAppear: -// guard previousState == .swipeDownInit || previousState == .swipeDownAppear || previousState == .swipeDownDidAppear || self.arrowImageNode.image == nil else { return } - let backgrpundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors - let gradientColorAtFraction = backgrpundColors.1.interpolateTo(backgrpundColors.0, fraction: arrowFrame.midX / frame.size.width) - if let gradientColorAtFraction { - self.arrowImageNode.image = PresentationResourcesItemList.archiveTransitionArrowIcon(presentationData.theme, backgroundColor: gradientColorAtFraction) - } + self.arrowImageNode.image = self.arrowReleaseIcon } transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) if let size = self.arrowAnimationNode.preferredSize(), !params.finalizeAnimation { let arrowAnimationFrame = CGRect(x: arrowFrame.midX - size.width / 2, y: arrowFrame.midY - size.height / 2, width: size.width, height: size.height) - let arrowCenterFraction = arrowAnimationFrame.midX / frame.size.width - let backgrpundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors - let gradientColorAtFraction = backgrpundColors.1.interpolateTo(backgrpundColors.0, fraction: arrowCenterFraction) - if let gradientColorAtFraction, arrowAnimationNode.position != arrowAnimationFrame.center { + if let arrowReleaseBackgroundColor, arrowAnimationNode.position != arrowAnimationFrame.center { arrowAnimationNode.setAnimation(name: "anim_arrow_to_archive", colors: [ - "Arrow 1.Arrow 1.Stroke 1": gradientColorAtFraction, - "Arrow 2.Arrow 2.Stroke 1": gradientColorAtFraction, + "Arrow 1.Arrow 1.Stroke 1": arrowReleaseBackgroundColor, + "Arrow 2.Arrow 2.Stroke 1": arrowReleaseBackgroundColor, "Cap.cap2.Fill 1": .white, "Cap.cap1.Fill 1": .white, "Box.box1.Fill 1": .white @@ -199,8 +218,15 @@ class ChatListArchiveTransitionNode: ASDisplayNode { arrowContainerNode: self.arrowContainerNode, arrowAnimationNode: self.arrowAnimationNode, avatarNode: avatarNode, - transition: transition, finalizeCompletion: { - print("finalize compeltion") + transition: transition, finalizeCompletion: { [weak self] isFinished in + guard let self else { return } + if !isFinished { + if self.hapticFeedback == nil { + self.hapticFeedback = HapticFeedback() + } + self.hapticFeedback?.impact(.medium) + } + print("finalize compeltion: \(isFinished)") }) let nodesToHide: [ASDisplayNode] = [self.gradientImageNode, self.backgroundNode] @@ -270,15 +296,15 @@ class ChatListArchiveTransitionNode: ASDisplayNode { var gradientLayer: CALayer? var releaseTextNode: ASTextNode? - lazy var gradientImage: UIImage? = { - guard let presentationData, let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 else { return nil } - let size = gradientLayer.frame.size - let backgroundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors - let gradientColors = [backgroundColors.0, backgroundColors.1] - return generateGradientImage(size: size, - colors: gradientColors, - locations: [1.0, 0.0], direction: .horizontal) - }() + var gradientImage: UIImage? { + didSet { + if let gradientLayer, + gradientLayer.contents == nil { + gradientLayer.contents = self.gradientImage + } + } + } + var rotatedGradientImage: UIImage? static func degreesToRadians(_ x: CGFloat) -> CGFloat { return .pi * x / 180.0 @@ -295,7 +321,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { arrowAnimationNode: AnimationNode, avatarNode: AvatarNode, transition: ContainedViewLayoutTransition, - finalizeCompletion: (() -> Void)? + finalizeCompletion: ((Bool) -> Void)? ) { print(""" animate layers with fraction: \(self.params.storiesFraction) animation progress: \(self.state.animationProgress(fraction: self.params.storiesFraction)) @@ -357,6 +383,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { //play animation arrow to archive //update gradient mask path to avatar node frame //scale up then scale down avatar node gradient + finalizeCompletion?(false) arrowAnimationNode.isHidden = false let newPosition = CGPoint(x: arrowAnimationNode.position.x, y: gradientNode.position.y) transition.updatePosition(node: arrowAnimationNode, position: newPosition) @@ -372,7 +399,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { print("arrow animation node finish animation") arrowAnimationNode?.isHidden = true gradientLayer?.removeFromSuperlayer() - finalizeCompletion?() + finalizeCompletion?(true) } arrowAnimationNode.play() @@ -469,11 +496,7 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { self.releaseTextNode?.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) guard let supernode = textNode.supernode else { return } supernode.addSubnode(self.releaseTextNode!) - -// let textLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.frame.width - 120, height: 25))) -// self.releaseTextNode?.frame = CGRect(x: -textLayout.size.width, y: supernode.frame.height - textLayout.size.height - 8, width: textLayout.size.width, height: textLayout.size.height) } - } mutating internal func makeGradientOverlay(gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode) { @@ -505,7 +528,7 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { } if gradientLayer.contents == nil { - gradientLayer.contents = self.getGradientImageOrUpdate()?.cgImage + gradientLayer.contents = self.gradientImage?.cgImage } } @@ -525,23 +548,4 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { let path = UIBezierPath(roundedRect: transformedRect, cornerRadius: scaledRadius) return path } - - mutating func getGradientImageOrUpdate() -> UIImage? { - if let gradientImage, gradientImage.size.height > 1 { - return gradientImage - } else if let presentationData, let gradientLayer, gradientLayer.frame.size.height > 0, self.params.storiesFraction > 0 { - let size = gradientLayer.frame.size - let backgroundColors = presentationData.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors - let gradientColors: [UIColor] = [backgroundColors.0, backgroundColors.1] - self.gradientImage = generateGradientImage( - size: size, - colors: gradientColors, - locations: [1.0, 0.0], - direction: .horizontal - ) - return self.gradientImage - } else { - return nil - } - } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index c9a2e45a2b6..403e6ff77f6 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -509,8 +509,8 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati verifiedIconFillColor: UIColor(rgb: 0xffffff), verifiedIconForegroundColor: UIColor(rgb: 0x000000), secretIconColor: UIColor(rgb: 0x00b12c), - pinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x72d5fd), bottomColor: UIColor(rgb: 0x2a9ef1)), foregroundColor: UIColor(rgb: 0xffffff)), - unpinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x666666), bottomColor: UIColor(rgb: 0x666666)), foregroundColor: UIColor(rgb: 0x000000)), + pinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x60b7fc), bottomColor: UIColor(rgb: 0x027df0)), foregroundColor: UIColor(rgb: 0xffffff)), + unpinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0xb2b3b7), bottomColor: UIColor(rgb: 0x8c8e92)), foregroundColor: UIColor(rgb: 0x000000)), onlineDotColor: UIColor(rgb: 0x4cc91f), storyUnseenColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x34C76F), bottomColor: UIColor(rgb: 0x3DA1FD)), storyUnseenPrivateColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x7CD636), bottomColor: UIColor(rgb: 0x26B470)), diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index b5fcd56959f..7a52fbcab22 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -567,8 +567,8 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio verifiedIconFillColor: defaultDayAccentColor, verifiedIconForegroundColor: UIColor(rgb: 0xffffff), secretIconColor: UIColor(rgb: 0x00b12c), - pinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x72d5fd), bottomColor: UIColor(rgb: 0x2a9ef1)), foregroundColor: UIColor(rgb: 0xffffff)), - unpinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0xdedee5), bottomColor: UIColor(rgb: 0xc5c6cc)), foregroundColor: UIColor(rgb: 0xffffff)), + pinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x60b7fc), bottomColor: UIColor(rgb: 0x027df0)), foregroundColor: UIColor(rgb: 0xffffff)), + unpinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0xd9d9de), bottomColor: UIColor(rgb: 0xb0b6be)), foregroundColor: UIColor(rgb: 0xffffff)), onlineDotColor: UIColor(rgb: 0x4cc91f), storyUnseenColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x34C76F), bottomColor: UIColor(rgb: 0x3DA1FD)), storyUnseenPrivateColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x7CD636), bottomColor: UIColor(rgb: 0x26B470)), From c99dded4fab1320f2ac2091c0adc7aecce7b496a Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Thu, 31 Aug 2023 04:37:50 -0300 Subject: [PATCH 25/34] updated gradient to archive transition --- .../Node/ChatListArchiveTransitionItem.swift | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index f3c9515db4a..becb65b7818 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -122,7 +122,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } if self.animation.rotatedGradientImage == nil { - let blueRotatedGradientImage = generateGradientImage(size: gradientImageSize, colors: [blueColors.1, blueColors.0], locations: [1.0, 0.0], direction: .vertical) +// let blueRotatedGradientImage = generateGradientFilledCircleImage(diameter: 60, colors: [blueColors.1.cgColor, blueColors.0.cgColor]) + let blueRotatedGradientImage = generateGradientImage(size: gradientImageSize, colors: [blueColors.1, blueColors.1, blueColors.0, blueColors.0], locations: [1.0, 0.65, 0.25, 0.05], direction: .vertical) self.animation.rotatedGradientImage = blueRotatedGradientImage } @@ -133,10 +134,19 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let blueGradientColorAtFraction = blueColors.0.interpolateTo(blueColors.1, fraction: 40 / gradientImageSize.width) if let blueGradientColorAtFraction { - self.arrowReleaseBackgroundColor = blueGradientColorAtFraction if self.arrowReleaseIcon == nil { self.arrowReleaseIcon = PresentationResourcesItemList.archiveTransitionArrowIcon(theme, backgroundColor: blueGradientColorAtFraction) } + if self.arrowReleaseBackgroundColor == nil { + arrowAnimationNode.setAnimation(name: "anim_arrow_to_archive", colors: [ + "Arrow 1.Arrow 1.Stroke 1": blueGradientColorAtFraction, + "Arrow 2.Arrow 2.Stroke 1": blueGradientColorAtFraction, + "Cap.cap2.Fill 1": .white, + "Cap.cap1.Fill 1": .white, + "Box.box1.Fill 1": .white + ]) + self.arrowReleaseBackgroundColor = blueGradientColorAtFraction + } } } @@ -156,6 +166,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let previousState = self.animation.state self.animation.state = .init(params: params, previousState: previousState) self.animation.presentationData = presentationData + if self.presentationData?.theme != presentationData.theme { + print("need to update gradients") + } self.presentationData = presentationData // if updateLayers { @@ -191,15 +204,6 @@ class ChatListArchiveTransitionNode: ASDisplayNode { if let size = self.arrowAnimationNode.preferredSize(), !params.finalizeAnimation { let arrowAnimationFrame = CGRect(x: arrowFrame.midX - size.width / 2, y: arrowFrame.midY - size.height / 2, width: size.width, height: size.height) - if let arrowReleaseBackgroundColor, arrowAnimationNode.position != arrowAnimationFrame.center { - arrowAnimationNode.setAnimation(name: "anim_arrow_to_archive", colors: [ - "Arrow 1.Arrow 1.Stroke 1": arrowReleaseBackgroundColor, - "Arrow 2.Arrow 2.Stroke 1": arrowReleaseBackgroundColor, - "Cap.cap2.Fill 1": .white, - "Cap.cap1.Fill 1": .white, - "Box.box1.Fill 1": .white - ]) - } transition.updatePosition(node: arrowAnimationNode, position: arrowAnimationFrame.center) transition.updateBounds(node: arrowAnimationNode, bounds: arrowAnimationFrame) } @@ -388,10 +392,13 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let newPosition = CGPoint(x: arrowAnimationNode.position.x, y: gradientNode.position.y) transition.updatePosition(node: arrowAnimationNode, position: newPosition) - let avatarNodeFrame = avatarNode.convert(avatarNode.frame, to: gradientNode) - if let gradientMaskLayer { + let avatarNodeFrame = gradientNode.convert(avatarNode.contentNode.layer.frame, from: avatarNode.contentNode) +// avatarNodeFrame = avatarNode.supernode?.convert(avatarNodeFrame, to: gradientNode) ?? avatarNodeFrame + if let gradientMaskLayer, let gradientLayer { let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2).cgPath transition.updatePath(layer: gradientMaskLayer, path: targetPath) + + gradientLayer.contents = rotatedGradientImage?.cgImage } print("avatar node frame: \(avatarNode.convert(avatarNode.frame, to: gradientNode))") @@ -465,7 +472,6 @@ class ChatListArchiveTransitionNode: ASDisplayNode { //play animation arrow to archive //update gradient mask path to avatar node frame //scale up then scale down avatar node gradient - } else { if let supernode = textNode.supernode { let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: .zero, max: CGSize(width: supernode.bounds.width - 120, height: 25))) @@ -502,6 +508,8 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { mutating internal func makeGradientOverlay(gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode) { if self.gradientLayer == nil { self.gradientLayer = CALayer() + self.gradientLayer?.contentsGravity = .resizeAspect + self.gradientLayer?.contentsScale = 3.0 } if self.gradientMaskLayer == nil { @@ -527,9 +535,7 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { gradientLayer.mask = gradientMaskLayer } - if gradientLayer.contents == nil { - gradientLayer.contents = self.gradientImage?.cgImage - } + gradientLayer.contents = self.gradientImage?.cgImage } internal func generateGradientMaskPath(gradientContainerNode: ASDisplayNode, arrowContainerNode: ASDisplayNode, fraction: CGFloat) -> UIBezierPath { From 7fb3f6c623ca2e046d342d6d3a004eef5ffde021 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Thu, 31 Aug 2023 06:23:24 -0300 Subject: [PATCH 26/34] archive transition, shadows --- .../Node/ChatListArchiveTransitionItem.swift | 84 ++++++++++++------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index becb65b7818..511622cc3e3 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -36,7 +36,7 @@ public struct ArchiveAnimationParams: Equatable { } var isArchiveGroupVisible: Bool { - return storiesFraction >= 0.8 && finalizeAnimation + return storiesFraction >= 0.85 && finalizeAnimation } } @@ -45,7 +45,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let backgroundNode: ASDisplayNode let gradientContainerNode: ASDisplayNode let gradientImageNode: ASImageNode -// let topShadowNode: ASImageNode + let topShadowNode: ASImageNode + let bottomShadowNode: ASImageNode let titleNode: ASTextNode //centered let arrowBackgroundNode: ASDisplayNode //20 with insets 10 let arrowContainerNode: ASDisplayNode @@ -73,6 +74,16 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.gradientImageNode = ASImageNode() self.gradientImageNode.isLayerBacked = true + self.topShadowNode = ASImageNode() + self.topShadowNode.isLayerBacked = true + self.topShadowNode.displayWithoutProcessing = true + self.topShadowNode.alpha = 0.5 + + self.bottomShadowNode = ASImageNode() + self.bottomShadowNode.isLayerBacked = true + self.bottomShadowNode.displayWithoutProcessing = true + self.bottomShadowNode.alpha = 0.5 + self.arrowBackgroundNode = ASDisplayNode() self.arrowBackgroundNode.backgroundColor = .white.withAlphaComponent(0.4) self.arrowBackgroundNode.isLayerBacked = true @@ -90,6 +101,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { super.init() self.addSubnode(self.gradientContainerNode) self.gradientContainerNode.addSubnode(self.gradientImageNode) + self.gradientContainerNode.addSubnode(self.topShadowNode) + self.gradientContainerNode.addSubnode(self.bottomShadowNode) self.addSubnode(self.backgroundNode) self.backgroundNode.addSubnode(self.titleNode) self.backgroundNode.addSubnode(self.arrowBackgroundNode) @@ -123,7 +136,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { if self.animation.rotatedGradientImage == nil { // let blueRotatedGradientImage = generateGradientFilledCircleImage(diameter: 60, colors: [blueColors.1.cgColor, blueColors.0.cgColor]) - let blueRotatedGradientImage = generateGradientImage(size: gradientImageSize, colors: [blueColors.1, blueColors.1, blueColors.0, blueColors.0], locations: [1.0, 0.65, 0.25, 0.05], direction: .vertical) + let blueRotatedGradientImage = generateGradientImage(size: gradientImageSize, colors: [blueColors.1, blueColors.1, blueColors.0, blueColors.0], locations: [1.0, 0.65, 0.2, 0.0], direction: .vertical) self.animation.rotatedGradientImage = blueRotatedGradientImage } @@ -148,6 +161,11 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.arrowReleaseBackgroundColor = blueGradientColorAtFraction } } + if self.topShadowNode.image == nil { + let shadowGradient = generateGradientImage(size: CGSize(width: gradientImageSize.width, height: 20), colors: [.black.withAlphaComponent(0.1), .black.withAlphaComponent(0.0)], locations: [0.0, 1.0], direction: .vertical) + self.topShadowNode.image = shadowGradient + self.bottomShadowNode.image = shadowGradient + } } func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData, avatarNode: AvatarNode) { @@ -183,9 +201,20 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition.updatePosition(node: self.gradientImageNode, position: frame.center) transition.updateBounds(node: self.gradientImageNode, bounds: frame) + + let difference = (frame.height - params.expandedHeight).rounded() + + let topShadowFrame = CGRect(x: .zero, y: difference - 10, width: frame.width, height: 20) + transition.updatePosition(node: self.topShadowNode, position: topShadowFrame.center, beginWithCurrentState: true) + transition.updateBounds(node: self.topShadowNode, bounds: topShadowFrame, force: true, beginWithCurrentState: true) + + let bottomShadowFrame = CGRect(x: .zero, y: frame.height - 10, width: frame.width, height: 20) + transition.updateTransformRotation(node: self.bottomShadowNode, angle: TransitionAnimation.degreesToRadians(180)) + transition.updatePosition(node: self.bottomShadowNode, position: bottomShadowFrame.center, beginWithCurrentState: true) + transition.updateBounds(node: self.bottomShadowNode, bounds: bottomShadowFrame, force: true, beginWithCurrentState: true ) + if params.expandedHeight >= 22 { - let difference = (frame.height - params.expandedHeight).rounded() let arrowBackgroundHeight = frame.height - difference - 22 let arrowBackgroundFrame = CGRect(x: 29, y: frame.height - arrowBackgroundHeight - 11, width: 22, height: arrowBackgroundHeight) let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 22, width: 22, height: 22) @@ -217,6 +246,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } if updateLayers { + let nodesToHide: [ASDisplayNode] = [self.gradientImageNode, self.backgroundNode] + nodesToHide.filter({ $0.isHidden }).forEach({ $0.isHidden = false }) + self.animation.animateLayers(gradientNode: self.gradientContainerNode, textNode: self.titleNode, arrowContainerNode: self.arrowContainerNode, @@ -229,17 +261,10 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.hapticFeedback = HapticFeedback() } self.hapticFeedback?.impact(.medium) + nodesToHide.forEach({ $0.isHidden = true }) } print("finalize compeltion: \(isFinished)") }) - - let nodesToHide: [ASDisplayNode] = [self.gradientImageNode, self.backgroundNode] - - if self.animation.state == .releaseDidAppear && params.finalizeAnimation { - nodesToHide.forEach({ $0.isHidden = true }) - } else { - nodesToHide.forEach({ $0.isHidden = false }) - } } } @@ -260,14 +285,14 @@ class ChatListArchiveTransitionNode: ASDisplayNode { init(params: ArchiveAnimationParams, previousState: TransitionAnimation.State) { let fraction = params.storiesFraction - if params.storiesFraction < 0.8 { + if params.storiesFraction < 0.85 { switch previousState { case .swipeDownAppear, .swipeDownInit, .swipeDownDidAppear: self = .swipeDownDidAppear default: self = .swipeDownAppear } - } else if fraction >= 0.8 && fraction <= 1.0 { + } else if fraction >= 0.85 && fraction <= 1.0 { switch previousState { case .releaseAppear, .releaseDidAppear: self = .releaseDidAppear @@ -282,9 +307,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { func animationProgress(fraction: CGFloat) -> CGFloat { switch self { case .swipeDownAppear: - return max(0.01, min(0.99, fraction / 0.8)) + return max(0.01, min(0.99, fraction / 0.85)) case .releaseAppear: - return max(0.01, min(0.99, (fraction - 0.8) / 0.3)) + return max(0.01, min(0.99, (fraction - 0.85) / 0.15)) default: return 1.0 } @@ -389,27 +414,28 @@ class ChatListArchiveTransitionNode: ASDisplayNode { //scale up then scale down avatar node gradient finalizeCompletion?(false) arrowAnimationNode.isHidden = false - let newPosition = CGPoint(x: arrowAnimationNode.position.x, y: gradientNode.position.y) - transition.updatePosition(node: arrowAnimationNode, position: newPosition) + + arrowAnimationNode.completion = { [weak gradientLayer] in + print("arrow animation node finish animation") + finalizeCompletion?(true) + arrowAnimationNode.isHidden = true + arrowAnimationNode.reset() + gradientLayer?.contents = nil + gradientLayer?.removeFromSuperlayer() + } let avatarNodeFrame = gradientNode.convert(avatarNode.contentNode.layer.frame, from: avatarNode.contentNode) -// avatarNodeFrame = avatarNode.supernode?.convert(avatarNodeFrame, to: gradientNode) ?? avatarNodeFrame + transition.updateTransformScale(node: arrowAnimationNode, scale: CGPoint(x: 0.9, y: 0.9)) { _ in + arrowAnimationNode.play() + } + transition.updatePosition(node: arrowAnimationNode, position: avatarNodeFrame.center) + if let gradientMaskLayer, let gradientLayer { let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2).cgPath transition.updatePath(layer: gradientMaskLayer, path: targetPath) - gradientLayer.contents = rotatedGradientImage?.cgImage } print("avatar node frame: \(avatarNode.convert(avatarNode.frame, to: gradientNode))") - - arrowAnimationNode.completion = { [weak arrowAnimationNode, weak gradientLayer] in - print("arrow animation node finish animation") - arrowAnimationNode?.isHidden = true - gradientLayer?.removeFromSuperlayer() - finalizeCompletion?(true) - } - - arrowAnimationNode.play() } else { if let releaseTextNode, let supernode = releaseTextNode.supernode { let releaseTextLayout = releaseTextNode From c797b7bed0d72e84aed1f089bc5c9177330a1567 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Fri, 1 Sep 2023 05:48:17 -0300 Subject: [PATCH 27/34] implemented shake and alpha animations for a textnode, scale for the avatar node and updated archive logo for the avatar --- .../Node/ChatListArchiveTransitionItem.swift | 308 +++++++++--------- .../ArchiveAvatarIcon.imageset/Contents.json | 23 +- .../archiveavatar.pdf | Bin 0 -> 4060 bytes .../archiveavatar@2x.png | Bin 443 -> 0 bytes .../archiveavatar@3x.png | Bin 730 -> 0 bytes 5 files changed, 170 insertions(+), 161 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/archiveavatar.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/archiveavatar@2x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/archiveavatar@3x.png diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 511622cc3e3..826fb7bf63e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -135,8 +135,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { } if self.animation.rotatedGradientImage == nil { -// let blueRotatedGradientImage = generateGradientFilledCircleImage(diameter: 60, colors: [blueColors.1.cgColor, blueColors.0.cgColor]) - let blueRotatedGradientImage = generateGradientImage(size: gradientImageSize, colors: [blueColors.1, blueColors.1, blueColors.0, blueColors.0], locations: [1.0, 0.65, 0.2, 0.0], direction: .vertical) + let blueRotatedGradientImage = generateGradientImage(size: gradientImageSize, colors: [blueColors.1, blueColors.1, blueColors.0, blueColors.0], locations: [1.0, 0.65, 0.15, 0.0], direction: .vertical) self.animation.rotatedGradientImage = blueRotatedGradientImage } @@ -170,13 +169,17 @@ class ChatListArchiveTransitionNode: ASDisplayNode { func updateLayout(transition: ContainedViewLayoutTransition, size: CGSize, params: ArchiveAnimationParams, presentationData: ChatListPresentationData, avatarNode: AvatarNode) { let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.width, height: self.bounds.height)) -// var transition = transition - guard !(self.animation.params.finalizeAnimation && params.finalizeAnimation) else { return } + guard self.animation.params != params || self.frame.size != size else { return } + + if self.hapticFeedback == nil { + self.hapticFeedback = HapticFeedback() + } + let updateLayers = self.animation.params != params self.animation.params = params @@ -212,32 +215,36 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let bottomShadowFrame = CGRect(x: .zero, y: frame.height - 10, width: frame.width, height: 20) transition.updateTransformRotation(node: self.bottomShadowNode, angle: TransitionAnimation.degreesToRadians(180)) transition.updatePosition(node: self.bottomShadowNode, position: bottomShadowFrame.center, beginWithCurrentState: true) - transition.updateBounds(node: self.bottomShadowNode, bounds: bottomShadowFrame, force: true, beginWithCurrentState: true ) + transition.updateBounds(node: self.bottomShadowNode, bounds: bottomShadowFrame, force: true, beginWithCurrentState: true) - if params.expandedHeight >= 22 { - let arrowBackgroundHeight = frame.height - difference - 22 - let arrowBackgroundFrame = CGRect(x: 29, y: frame.height - arrowBackgroundHeight - 11, width: 22, height: arrowBackgroundHeight) - let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 22, width: 22, height: 22) - transition.updatePosition(node: self.arrowBackgroundNode, position: arrowBackgroundFrame.center, beginWithCurrentState: true) - transition.updateBounds(node: self.arrowBackgroundNode, bounds: arrowBackgroundFrame, force: true, beginWithCurrentState: true) - transition.updatePosition(node: self.arrowContainerNode, position: arrowFrame.center) - transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) - switch self.animation.state { - case .swipeDownInit, .swipeDownAppear, .swipeDownDidAppear: - self.arrowImageNode.image = self.arrowSwipeDownIcon - case .releaseDidAppear, .releaseAppear: - self.arrowImageNode.image = self.arrowReleaseIcon - } - transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) - transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) - - if let size = self.arrowAnimationNode.preferredSize(), !params.finalizeAnimation { - let arrowAnimationFrame = CGRect(x: arrowFrame.midX - size.width / 2, y: arrowFrame.midY - size.height / 2, width: size.width, height: size.height) - transition.updatePosition(node: arrowAnimationNode, position: arrowAnimationFrame.center) - transition.updateBounds(node: arrowAnimationNode, bounds: arrowAnimationFrame) - } + let arrowBackgroundHeight = max(0, (frame.height - difference - 22)) + let arrowBackgroundFrame = CGRect(x: 29, y: frame.height - arrowBackgroundHeight - 11, width: 22, height: arrowBackgroundHeight) + let arrowFrame = CGRect(x: arrowBackgroundFrame.minX, y: arrowBackgroundFrame.maxY - 22, width: 22, height: 22) + if self.arrowBackgroundNode.position == .zero || self.arrowBackgroundNode.bounds.height == .zero { + self.arrowBackgroundNode.position = arrowBackgroundFrame.center + self.arrowBackgroundNode.bounds = arrowBackgroundFrame } + transition.updatePosition(node: self.arrowBackgroundNode, position: arrowBackgroundFrame.center, beginWithCurrentState: true) + transition.updateBounds(node: self.arrowBackgroundNode, bounds: arrowBackgroundFrame, force: true, beginWithCurrentState: true) + + transition.updatePosition(node: self.arrowContainerNode, position: arrowFrame.center) + transition.updateBounds(node: self.arrowContainerNode, bounds: arrowFrame) + switch self.animation.state { + case .swipeDownInit, .swipeDownAppear, .swipeDownDidAppear: + self.arrowImageNode.image = self.arrowSwipeDownIcon + case .releaseDidAppear, .releaseAppear: + self.arrowImageNode.image = self.arrowReleaseIcon + } + transition.updatePosition(node: self.arrowImageNode, position: arrowFrame.center) + transition.updateBounds(node: self.arrowImageNode, bounds: arrowFrame) + + if let size = self.arrowAnimationNode.preferredSize(), !params.finalizeAnimation { + let arrowAnimationFrame = CGRect(x: arrowFrame.midX - size.width / 2, y: arrowFrame.midY - size.height / 2, width: size.width, height: size.height) + transition.updatePosition(node: arrowAnimationNode, position: arrowAnimationFrame.center) + transition.updateBounds(node: arrowAnimationNode, bounds: arrowAnimationFrame) + } + if self.titleNode.attributedText == nil { self.titleNode.attributedText = NSAttributedString(string: "Swipe down for archive", attributes: [ .foregroundColor: UIColor.white, @@ -249,6 +256,12 @@ class ChatListArchiveTransitionNode: ASDisplayNode { let nodesToHide: [ASDisplayNode] = [self.gradientImageNode, self.backgroundNode] nodesToHide.filter({ $0.isHidden }).forEach({ $0.isHidden = false }) + if animation.state == .releaseAppear { + self.hapticFeedback?.impact(.medium) + } else if animation.state == .swipeDownAppear && previousState == .releaseDidAppear { + self.hapticFeedback?.impact(.medium) + } + self.animation.animateLayers(gradientNode: self.gradientContainerNode, textNode: self.titleNode, arrowContainerNode: self.arrowContainerNode, @@ -257,13 +270,9 @@ class ChatListArchiveTransitionNode: ASDisplayNode { transition: transition, finalizeCompletion: { [weak self] isFinished in guard let self else { return } if !isFinished { - if self.hapticFeedback == nil { - self.hapticFeedback = HapticFeedback() - } self.hapticFeedback?.impact(.medium) nodesToHide.forEach({ $0.isHidden = true }) } - print("finalize compeltion: \(isFinished)") }) } } @@ -271,7 +280,8 @@ class ChatListArchiveTransitionNode: ASDisplayNode { struct TransitionAnimation { - enum Direction { + enum TextPosition { + case centered case left case right } @@ -366,44 +376,21 @@ class ChatListArchiveTransitionNode: ASDisplayNode { case .releaseAppear: updateReleaseTextNode(from: textNode) makeGradientOverlay(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) - - let animationProgress = 1.0//self.state.animationProgress(fraction: self.params.storiesFraction) +// let animationProgress = self.state.animationProgress(fraction: self.params.storiesFraction) -// let rotationDegree = TransitionAnimation.degreesToRadians(CGFloat(0).interpolate(to: CGFloat(-180), amount: animationProgress)) - transition.updateTransformRotation(node: arrowContainerNode, angle: TransitionAnimation.degreesToRadians(-180)) - transition.updateTransformRotation(node: arrowContainerNode, angle: TransitionAnimation.degreesToRadians(180)) + if let releaseTextNode { transition.updateAlpha(node: releaseTextNode, alpha: 1.0) } + self.animateTextNodePositionIfNeeded(textNode: releaseTextNode, targetTextPosition: .centered, transition: transition, needShake: true) - if let releaseTextNode, let supernode = releaseTextNode.supernode { - let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 100, height: 25), max: CGSize(width: supernode.bounds.width - 120, height: 25))) - let titleFrame = CGRect(x: supernode.bounds.width + textLayout.size.width/2, - y: supernode.bounds.height - textLayout.size.height - 10, - width: textLayout.size.width, - height: textLayout.size.height) - - let releaseTextLayout = releaseTextNode - .calculateLayoutThatFits(ASSizeRange( - min: .zero, - max: CGSize(width: supernode.bounds.width - 120, height: 25) - )) - let releaseNodeFrame = CGRect( - x: (supernode.bounds.width - releaseTextLayout.size.width) / 2, - y: supernode.bounds.height - releaseTextLayout.size.height - 10, - width: releaseTextLayout.size.width, - height: releaseTextLayout.size.height - ) - - transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center) - transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame) - - transition.updatePosition(node: textNode, position: titleFrame.center) - transition.updateBounds(node: textNode, bounds: titleFrame) - } - - if let gradientMaskLayer { - let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) + if let gradientMaskLayer, let gradientLayer { + transition.updateAlpha(layer: gradientLayer, alpha: 1.0) + let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: 1.0) transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) } + transition.updateTransformRotation(node: arrowContainerNode, angle: TransitionAnimation.degreesToRadians(-180)) + transition.updateTransformRotation(node: arrowContainerNode, angle: TransitionAnimation.degreesToRadians(180)) + self.animateTextNodePositionIfNeeded(textNode: textNode, targetTextPosition: .right, transition: transition) + transition.updateAlpha(node: textNode, alpha: .zero) case .releaseDidAppear: if params.finalizeAnimation, gradientLayer?.superlayer != nil { print("should finalize animation") @@ -415,106 +402,119 @@ class ChatListArchiveTransitionNode: ASDisplayNode { finalizeCompletion?(false) arrowAnimationNode.isHidden = false - arrowAnimationNode.completion = { [weak gradientLayer] in + let avatarNodeFrame = gradientNode.convert(avatarNode.contentNode.layer.frame, from: avatarNode.contentNode) + let avatarContentTranform = avatarNode.contentNode.layer.affineTransform() + + arrowAnimationNode.completion = { //[weak gradientLayer] in print("arrow animation node finish animation") - finalizeCompletion?(true) - arrowAnimationNode.isHidden = true - arrowAnimationNode.reset() - gradientLayer?.contents = nil - gradientLayer?.removeFromSuperlayer() +// guard let gradientLayer else { return } } - let avatarNodeFrame = gradientNode.convert(avatarNode.contentNode.layer.frame, from: avatarNode.contentNode) - transition.updateTransformScale(node: arrowAnimationNode, scale: CGPoint(x: 0.9, y: 0.9)) { _ in - arrowAnimationNode.play() - } + avatarNode.transform = CATransform3DMakeAffineTransform(avatarContentTranform) + transition.updatePosition(node: arrowAnimationNode, position: avatarNodeFrame.center) + transition.updateTransform(node: arrowAnimationNode, transform: avatarContentTranform, beginWithCurrentState: true) { _ in + transition.updateTransform(node: arrowAnimationNode, transform: avatarContentTranform.scaledBy(x: 1.0, y: 0.9)) { finished in + guard finished else { return } + transition.updateTransform(node: arrowAnimationNode, transform: avatarContentTranform.scaledBy(x: 0.9, y: 1.0)) { finished in + guard finished else { return } + transition.updateTransform(node: arrowAnimationNode, transform: avatarContentTranform) { _ in + arrowAnimationNode.isHidden = true + arrowAnimationNode.reset() + finalizeCompletion?(true) + } + } + } + } + arrowAnimationNode.play() + if let gradientMaskLayer, let gradientLayer { - let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2).cgPath - transition.updatePath(layer: gradientMaskLayer, path: targetPath) gradientLayer.contents = rotatedGradientImage?.cgImage + let targetPath = UIBezierPath(roundedRect: avatarNodeFrame, cornerRadius: avatarNodeFrame.width / 2) + let scaledInset = avatarNodeFrame.width - avatarNodeFrame.width * 0.83 + let scaledAvatarNodeFrame = avatarNodeFrame.insetBy(dx: scaledInset, dy: scaledInset)//.applying(CGAffineTransform(scaleX: 0.83, y: 0.83)) + + let scaledTargetPath = UIBezierPath(roundedRect: scaledAvatarNodeFrame, cornerRadius: scaledAvatarNodeFrame.width / 2) + transition.updatePath(layer: gradientMaskLayer, path: scaledTargetPath.cgPath) { _ in + transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) + transition.updateTransform(node: avatarNode, transform: .identity) { _ in + transition.updateAlpha(layer: gradientLayer, alpha: .zero) { _ in + gradientLayer.removeFromSuperlayer() + gradientLayer.opacity = 1.0 + gradientLayer.contents = nil + } + } + } } print("avatar node frame: \(avatarNode.convert(avatarNode.frame, to: gradientNode))") } else { - if let releaseTextNode, let supernode = releaseTextNode.supernode { - let releaseTextLayout = releaseTextNode - .calculateLayoutThatFits(ASSizeRange( - min: .zero, - max: CGSize(width: supernode.bounds.width - 120, height: 25) - )) - let releaseNodeFrame = CGRect( - x: (supernode.bounds.width - releaseTextLayout.size.width) / 2, - y: supernode.bounds.height - releaseTextLayout.size.height - 10, - width: releaseTextLayout.size.width, - height: releaseTextLayout.size.height - ) - - print("release node frame: \(releaseNodeFrame)") - - transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center, beginWithCurrentState: true) - transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame, beginWithCurrentState: true) - } + self.animateTextNodePositionIfNeeded(textNode: releaseTextNode, targetTextPosition: .centered, transition: transition) } case .swipeDownAppear, .swipeDownInit: - let animationProgress: CGFloat = 0.0 +// let animationProgress: CGFloat = 0.0 transition.updateTransform(node: arrowContainerNode, transform: .identity) + transition.updateAlpha(node: textNode, alpha: 1.0) - if let releaseTextNode, let supernode = releaseTextNode.supernode { - let releaseTextLayout = releaseTextNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 20, height: .zero), - max: CGSize(width: supernode.bounds.width - 120, height: 25))) - let releaseNodeFrame = CGRect(x: -releaseTextLayout.size.width, - y: supernode.bounds.height - releaseTextLayout.size.height - 8, - width: releaseTextLayout.size.width, - height: releaseTextLayout.size.height) - - -// let targetPosition = supernode.bounds.center.offsetBy(dx: -supernode.bounds.width, dy: .zero).interpolate(to: supernode.bounds.center, amount: animationProgress) - transition.updatePosition(node: releaseTextNode, position: releaseNodeFrame.center) - transition.updateBounds(node: releaseTextNode, bounds: releaseNodeFrame) - - let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: CGSize(width: 20, height: .zero), max: CGSize(width: supernode.bounds.width - 120, height: 25))) - let titleFrame = CGRect(x: (supernode.bounds.width - textLayout.size.width)/2, - y: supernode.bounds.height - textLayout.size.height - 10, - width: textLayout.size.width, - height: textLayout.size.height) - - print("title frame: \(titleFrame) release node frame: \(releaseNodeFrame)") -// let textNodeTargetPosition = textNode.position.interpolate(to: textNode.position.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) - transition.updatePosition(node: textNode, position: titleFrame.center) - transition.updateBounds(node: textNode, bounds: titleFrame) - } - - if let gradientMaskLayer { - let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: animationProgress) + animateTextNodePositionIfNeeded(textNode: textNode, targetTextPosition: .centered, transition: transition, needShake: true) + self.animateTextNodePositionIfNeeded(textNode: releaseTextNode, targetTextPosition: .left, transition: transition) + + if let releaseTextNode { transition.updateAlpha(node: releaseTextNode, alpha: .zero) } + + if let gradientMaskLayer, let gradientLayer { + let targetPath = generateGradientMaskPath(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode, fraction: 0.02) transition.updatePath(layer: gradientMaskLayer, path: targetPath.cgPath) + transition.updateAlpha(layer: gradientLayer, alpha: 0.6) } case .swipeDownDidAppear: - if params.finalizeAnimation { - print("should finalize animation") - //duration = 0.5 - //show animation arrow node - //play animation arrow to archive - //update gradient mask path to avatar node frame - //scale up then scale down avatar node gradient - } else { - if let supernode = textNode.supernode { - let textLayout = textNode.calculateLayoutThatFits(ASSizeRange(min: .zero, max: CGSize(width: supernode.bounds.width - 120, height: 25))) - let titleFrame = CGRect(x: (supernode.bounds.width - textLayout.size.width)/2, - y: supernode.bounds.height - textLayout.size.height - 10, - width: textLayout.size.width, - height: textLayout.size.height) - - print("title frame: \(titleFrame)") - // let textNodeTargetPosition = textNode.position.interpolate(to: textNode.position.offsetBy(dx: supernode.bounds.width, dy: .zero), amount: animationProgress) - transition.updatePosition(node: textNode, position: titleFrame.center, beginWithCurrentState: true) - transition.updateBounds(node: textNode, bounds: titleFrame, beginWithCurrentState: true) - } + if !params.finalizeAnimation { + updateReleaseTextNode(from: textNode) + makeGradientOverlay(gradientContainerNode: gradientNode, arrowContainerNode: arrowContainerNode) + self.animateTextNodePositionIfNeeded(textNode: textNode, targetTextPosition: .centered, transition: transition) } } self.isAnimated = true } + + private func animateTextNodePositionIfNeeded(textNode: ASTextNode?, targetTextPosition: TextPosition, transition: ContainedViewLayoutTransition, needShake: Bool = false) { + guard let textNode, let supernode = textNode.supernode else { return } + + let textLayout = textNode.calculateLayoutThatFits(ASSizeRange( + min: .zero, + max: CGSize(width: supernode.bounds.width - 120, height: 25) + )) + + let targetX: CGFloat + switch targetTextPosition { + case .centered: + targetX = (supernode.bounds.width - textLayout.size.width) / 2 + case .left: + targetX = -textLayout.size.width + case .right: + targetX = supernode.bounds.width + } + + let targetFrame = CGRect( + x: targetX, + y: supernode.bounds.height - textLayout.size.height - 10, + width: textLayout.size.width, + height: textLayout.size.height + ) + + let positionDifference = textNode.position.x - targetFrame.center.x + + guard textNode.position != targetFrame.center || textNode.bounds != targetFrame else { return } + + transition.updateBounds(node: textNode, bounds: targetFrame, beginWithCurrentState: true) + transition.updatePosition(node: textNode, position: targetFrame.center, beginWithCurrentState: true) + + if needShake { + transition.updateTransform(node: textNode, transform: .init(translationX: positionDifference < 0 ? 10 : -10, y: .zero)) { _ in + transition.updateTransform(node: textNode, transform: .identity) + } + } + } } } @@ -524,9 +524,24 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { if self.releaseTextNode == nil { self.releaseTextNode = ASTextNode() self.releaseTextNode?.isLayerBacked = true + guard let supernode = textNode.supernode, let releaseTextNode else { return } + let attributes: [NSAttributedString.Key: Any] = textNode.attributedText?.attributes(at: 0, effectiveRange: nil) ?? [:] - self.releaseTextNode?.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) - guard let supernode = textNode.supernode else { return } + releaseTextNode.attributedText = NSAttributedString(string: "Release for archive", attributes: attributes) + + let textLayout = textNode.calculateLayoutThatFits(ASSizeRange( + min: .zero, + max: CGSize(width: supernode.bounds.width - 120, height: 25) + )) + + releaseTextNode.frame = CGRect( + x: -textLayout.size.width, + y: supernode.bounds.height - textLayout.size.height - 10, + width: textLayout.size.width, + height: textLayout.size.height + ) + releaseTextNode.alpha = 0.0 + supernode.addSubnode(self.releaseTextNode!) } } @@ -550,11 +565,12 @@ extension ChatListArchiveTransitionNode.TransitionAnimation { if gradientMaskLayer.frame != gradientContainerNode.bounds { gradientMaskLayer.frame = gradientContainerNode.bounds - gradientMaskLayer.path = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: 0).cgPath + gradientMaskLayer.path = generateGradientMaskPath(gradientContainerNode: gradientContainerNode, arrowContainerNode: arrowContainerNode, fraction: 0.02).cgPath } if gradientLayer.frame != gradientContainerNode.bounds { gradientLayer.frame = gradientContainerNode.bounds + gradientLayer.opacity = 0.6 } if gradientLayer.mask == nil { diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/Contents.json index adc5e58e9c9..f629023a91d 100644 --- a/submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/Contents.json @@ -1,22 +1,15 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "archiveavatar@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "archiveavatar@3x.png", - "scale" : "3x" + "filename" : "archiveavatar.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/archiveavatar.pdf b/submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/archiveavatar.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d0dacb92c9263cd993b8cc28bc063f0c9b7786e6 GIT binary patch literal 4060 zcmai%c|4ST_s1<$7(!)FxsoMi88c%f*@q!&EGcB0F*A&9G{%xbWXYC@CJI>+%2E{3 zke#?q_LL>rx2#!{XLR2^ch7S_zt`{kx?b1yeVy+)=X<@*`TX%dlEz3qIYoIGSh8+x zX>8`n@}u{44PY1m0Z3S9@R>7!f*#S8f+sV+t{4g)iFY93@PL95-rbSn1i;}4KwTZ| zNg?AgZeU+#b2A%fm?GEpA+t#qFFDKj{E5^@XtlT6#G~7fMt>~CyjgTl))bG)Zy#Ex z4o*?C5^rhs8Y)qfMSgL()o~5P+rc|D>2?%qCPJV4G2KbTHp_H zPJyzf$Xa2XJ)vmah`6HP#Iv^ic0)Pyln}>!9+I@&%Xg=dl^rDt)QLWE76)53*c_a> z2N9)(K&?iYd*FgDwc}+jh3D9l+<`dIM$aa-Wzxzrk6Sme>=X6gb%RNsW)x7%( zFJ!qM6k}Tnpo23h7}c14w=)(N`9g5ZLJwPzWBR3MS`U~i?)Q+3PRm(G-(I=6bkf~? zwZGx^tD=wuF{JX#X~LiwI|q`bxvR-A+};SH%Ej)-jpQip>RYi2(GJjeoAn>DmT{CA zf8n~~rJ|fC)bF%zZGY~|P3Pp#LcTm+@4N`859R_okCQ>DgA|e1b$iA))hER|HdfCS zl6*=$=w}yb^DDvBxRLhj1x`PT+66|_=Z} z3s*fDZ8yd^{(O`1?i2vZI5q_oO!1y1FR}yP6M+9|&>^`~80VhAu8K@|_3>A}-Sc0n zGA5H8%Jym@2X9?=X1fVPavF5Q>MK?APz{bi1={;9jf~LAd?7# z8^$bQAyVGRJ$iimWiH|_yk$m(i#zD@ao!BZ_Hr!o8aGs%Aa%zEqFp%V^OcqdleZf! zhx#T;d;1*c{d>!qjK2qiYA6=WH{j_Od|zj(tq&Y*sasQdv*fj#Fcd<_A-HWyAfvsqy)!U63mM0g`qp3cPoi9 z&@e@)KsrapGx3qSLf6$N<0mVc?^C5l(?u-Xgx|nZqbrODlO=~Uqe1D`_a#3&W?pB* z(=He!GmWhs@9bQEXv75JOR{U7kVxIe~nmTVPa3HQ+Y$?j@nRhN@eoRb{vfa zMHpbS_xIi>j!iZZHW!P{Hn55(6Kc;5-|{u0uat1zh6vs$B;C_xmb7$Ba*CVF7oD54 z>=}33sOlR!5@5Ei*5tQkz9=``QM0qIp3G@3@+T*ix6*=1@2bL7){yBkV$u>l`wsLV(>A3AR-kp)RD#SmUYh@p%y%k&xoisO@OyCHKaH>dsjLP)a-2c)?0IeFPJ`G=|)ml?1%k<^%Ujlmx89HWSov)kOnEV;VLcet|-y^hh3 zyE4kUH#{A4Ur3-qcqH61dKh~V(}O(=>Avr9|DuuLID)_n3)%>NjO~7&Z6WnTVC|0bYO8O3m704x+=jc5^!d2n|uL%1D`*z6w8n{VN4w@n! z#(TBS8Y}bs_6Myu2BK2u@A4UhNeG%s$Rw2nT`+JKw~V%$3t4K~q6;L_ z06R&Zcn#x(<5GO+v!c4%edkrqzSg{|c~$4J>AoEIE{nYT@93EHUfL|i&DLbIahM}BmN`gFiJUVtMl$u z{fgKmZG{6biN8E{d`xsqWUTm|>E0TM{W{E4_;K{z=!)prW%&4O`Uw3KqL662^F}6d zxtrtghb}V*gPY1Qa4|BN}gLDx(nhg<&1IebKdHfe;Px7Lib6fr`W-FM!$zo z7nMi&m2EnItDRPxzAKU}QYj)VVlG03Hz)}wyC*j%k0npQ8%vz5IA4OS#;hh@+CmbO zzP4T ze7d?xT7Ftq6{_lT;Jdf)fsgi4S`Vso(j(KWFsYc#VTWURaZlS%WLjmaHk@*JbtQ69 zedMxKQACPCyg^L!IkOL4gBR?}=_Pb7mvJX4_*12d#*CtVOUq|^J&#ms&oo`ny!v3{ zB&S-GMO2EwfPkIsoUEDLjI5ZYjO88+x030i&DxK3$u2ey{*7VIBM!xF*#qM*^K<2f zysPH?Rg-Os1_Ub3SI}3vSH-uawiMaa!hOSO+-n~w-p4CK{JZzhOb(Z|Ek=*9NnewG z-f8e;D8;f(rfpwaCMTERf?$`N5V`%zaG5AMP97&8ZRhKe-evwNal~q7v-MnW%!GHH zTiw71wZ84Lt&=>4JaMR~#)Kz#Cq(F+wzsH-m*OrJmd3AP=bh)DsGnAkP>)eRSm|B) z)L&s8v(2*Yy*aSlyD+iYyM+dw0zKoH1ZRQtKo{B6IiVZ}_IQD6tC_1Sg2fa%>?Ewa z1X#4c>v)Hr;ZWkT;otNWl9z=)lbXF+X6_r{>jRR*>LPXOD)eKUI+{>XF#?7HK6=fl zdfjf+Io)fzR9!==g{)=qs9v;HG?1+(Aa;oVnuIqwcdcWfUm=dAWHjK#8E_U^mk)odoQZV<-1Pev zTJ&0C@P~%nBL}-o9+?zX+B+4`k2h$));1ZY$+cbPcCB~icbyv`6z4mCqKezLf5WzX ziTr|=?2n%rvh=^SG0?giGDmy7`gqIdvp2g>|G-rFvPBZs(zfq?!DpNNvDT%GrP7K^ zvy~Sym#^6K5Myz%CD5D~w6lfIsO~4zS<^uxvB~4K>1(BPDIXg@^;QkJ1$HvWu-3Dw zAK?>y6WFw7?2@nLR*&f)epXAL$(Sy$atyQ!M9%3i#?eussK<>pv0mM(UA@XV$_a>? znD~b0BUc(p+n>MbTs!!5${3dUdL4A?sLJA(#&xmzRIW39S0nxNrlhyiR$slhinL1a z>FW^gHsw*geQzZHcI6)biA|d=g~8Qw|FPRUDTi32_HKTaue`Qn+pjj|Ke*k_mdj(S z(du_<+iBZjb|ga@j6RW?r;)$iz1~a6rLD|9>b0jA-*Su%E7MWfN!%zz*Y2U`(ogD@ z=uM;YQS|7I=mEdyEA*&O^7!9B69N(9GrQ8VPLu(<+TmNL` zT~ys=TL#Y>=;&x;Jn=YSmsL#xn_u|5%gev;_ivo`1QgEUaYT$Z$rrc`VYomM095HG z4|_2nTMR>1sSCZqOpziq>q5lowr+y1y065Oefic(} zcf&ZK&47z|vL}(`4k*Ipl>qDG+FnFg8~}x)l;PGgfQc7|Oz{I4r2Z4`eJL^wKxfQ+ z6h;J+!QSBG$kR{=6b4a&C_T&DZ*4vDZ`)y2nL3LW0VQ%;Q!y{PepiA7!CSaJ(w~S0hW~1MeBk81<*YZ A3IG5A literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/archiveavatar@2x.png b/submodules/TelegramUI/Images.xcassets/Avatar/ArchiveAvatarIcon.imageset/archiveavatar@2x.png deleted file mode 100644 index ceff2065d3ba26e0b6b986858b9c0eb00dd301f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 443 zcmV;s0Yv_ZP)(`DwLSWt|zoVWR=mFqmDO0brI?EfMGVHL zLpE!EOL7UB5~nUGB`N{&Urg!uFyY`FdSx}fCa>K^_ce0WP^hl~BGZUic%|VLcu*dmJl^+tHIk*anjQ=?;E` lPZFZtstHEH+C}VloPTWNn~YUV4CRZ z;uuoF`1a0O-@^_9tPf@w^Ez;g9FPrUdvwU3d!ZPM`6-7v3mKvmSP!u3CCY7F>{D?u zTC!~Ef#}TC_Z5>))_FZ@nekSwcJ}$_v2W(N+jrLq)SB(sL`>PY#?hOz#Xg@r5qy zS8r55u-EN`deajn_WXw5R^BH<)KwbintWOiaiHL6h~>qCn~N(_b|kHuldc~p+tTU& ze0MzDc-1>AnjY@x?ve5n`p9y)P+Zb;p>w#Rx#Sr(p0grg4By1ZQd;`a}R$juAg z;<-(4o7NPsxUlSP+J+S(#WTWYxc_=s_fwQfHE_)#m5)BNLwDToEtkA}AT?WGTG&nQ z!0OZ;FN@e4r3zLbZCj+fZR_((k8Ibq27i`nllF|0U01(f^5=(Rch|U{eN|xi^>X3m z{}4C*Ph&{^{G80)07Rx)AorH`3TrpfYoZ`)Pa zn6kMpQSn&Z!mrkMl2otN@g~Ulo!_zJk-&e2A|ct%JZ{E_gtzyKc@ElPACRPSdS3j3^P6 Date: Fri, 1 Sep 2023 16:48:58 -0300 Subject: [PATCH 28/34] fixed hidden archive by default --- .../Sources/ChatListController.swift | 9 +- .../Sources/ChatListControllerNode.swift | 8 +- .../Sources/ChatListSearchListPaneNode.swift | 4 +- .../Node/ChatListArchiveTransitionItem.swift | 40 ++++++-- .../Sources/Node/ChatListItem.swift | 2 +- .../Sources/Node/ChatListNode.swift | 91 ++++++++++++------- .../Sources/Node/ChatListNodeEntries.swift | 7 +- .../TextSizeSelectionController.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 2 +- .../Themes/ThemePreviewControllerNode.swift | 2 +- .../ChatSearchResultsContollerNode.swift | 2 +- 11 files changed, 115 insertions(+), 54 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 508a43dfe2f..85fc943e006 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1098,10 +1098,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if !didDisplayTip { - #if DEBUG - #else +// #if DEBUG +// #else let _ = ApplicationSpecificNotice.setDisplayChatListArchiveTooltip(accountManager: self.context.sharedContext.accountManager).start() - #endif +// #endif self.push(ArchiveInfoScreen(context: self.context, settings: settings)) } @@ -4648,6 +4648,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if updatedValue { state.hiddenItemShouldBeTemporaryRevealed = false } + state.archiveParams.updateVisibility(isRevealed: state.hiddenItemShouldBeTemporaryRevealed, + isHiddenByDefault: updatedValue) state.peerIdWithRevealedOptions = nil return state } @@ -5117,6 +5119,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.chatListDisplayNode.effectiveContainerNode.updateState { state in var state = state state.hiddenItemShouldBeTemporaryRevealed = false + state.archiveParams.updateVisibility(isRevealed: false) return state } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 9d13279c924..a057da7b836 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -242,7 +242,7 @@ private final class ChatListShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -2574,7 +2574,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight, - finalizeAnimation: false + finalizeAnimation: false, + isHiddenByDefault: currentParams.isHiddenByDefault )) } } @@ -2588,7 +2589,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight, - finalizeAnimation: false + finalizeAnimation: false, + isHiddenByDefault: currentParams.isHiddenByDefault )) } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 77519096eec..8256c39c33c 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -850,7 +850,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) } - )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -3591,7 +3591,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 826fb7bf63e..e38e47fcfdb 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -12,31 +12,59 @@ public struct ArchiveAnimationParams: Equatable { public let scrollOffset: CGFloat public let storiesFraction: CGFloat public private(set)var expandedHeight: CGFloat + public private(set)var isRevealed: Bool = false public let finalizeAnimation: Bool + public private(set)var isHiddenByDefault: Bool - public static var empty: ArchiveAnimationParams { - return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false) + public static var emptyVisibleParams: ArchiveAnimationParams { + return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false, isHiddenByDefault: false) } + public static var emptyDefaultHiddenParams: ArchiveAnimationParams { + return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false, isHiddenByDefault: true) + } + +// public init(scrollOffset: CGFloat, storiesFraction: CGFloat, expandedHeight: CGFloat, finalizeAnimation: Bool, isHiddenByDefault: Bool) { +// +// } + public func withUpdatedFinalizeAnimation(_ finalizeAnimation: Bool) -> ArchiveAnimationParams { var newParams = ArchiveAnimationParams( scrollOffset: self.scrollOffset, storiesFraction: self.storiesFraction, expandedHeight: self.expandedHeight, - finalizeAnimation: finalizeAnimation + finalizeAnimation: finalizeAnimation, + isHiddenByDefault: self.isHiddenByDefault ) if finalizeAnimation { if newParams.isArchiveGroupVisible { newParams.expandedHeight /= 1.2 + newParams.isRevealed = true } else { - newParams = ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: finalizeAnimation) + newParams = ArchiveAnimationParams( + scrollOffset: .zero, + storiesFraction: .zero, + expandedHeight: .zero, + finalizeAnimation: finalizeAnimation, + isHiddenByDefault: self.isHiddenByDefault + ) + newParams.isRevealed = false } } return newParams } + public mutating func updateVisibility(isRevealed: Bool? = nil, isHiddenByDefault: Bool? = nil) { + if let isRevealed { + self.isRevealed = isRevealed + } + if let isHiddenByDefault { + self.isHiddenByDefault = isHiddenByDefault + } + } + var isArchiveGroupVisible: Bool { - return storiesFraction >= 0.85 && finalizeAnimation + return (storiesFraction >= 0.85 && finalizeAnimation) || !isHiddenByDefault } } @@ -65,7 +93,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.backgroundNode.backgroundColor = .clear self.backgroundNode.isLayerBacked = true - self.animation = .init(state: .swipeDownInit, params: .empty) + self.animation = .init(state: .swipeDownInit, params: .emptyDefaultHiddenParams) self.titleNode = ASTextNode() self.titleNode.isLayerBacked = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 52e1ebf9502..e87f3e7df71 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2865,7 +2865,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.avatarNode.updateSize(size: avatarFrame.size) strongSelf.updateVideoVisibility() - if case let .groupReference(data) = item.content, data.groupId == .archive { + if case let .groupReference(data) = item.content, data.groupId == .archive, !item.params.isArchiveGroupVisible { transition.updatePosition(node: strongSelf.archiveTransitionNode, position: contextContainerFrame.center) transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: contextContainerFrame) transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 89615fae177..3cacb773acc 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -425,7 +425,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, - params: .empty, + params: .emptyVisibleParams, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -635,7 +635,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { - print("insert group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed)") + print("insert group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) params: \(groupReferenceEntry.archiveParams)") return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, context: context, @@ -791,7 +791,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, - params: .empty, + params: .emptyVisibleParams, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -955,7 +955,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { -// print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") + print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, @@ -1277,7 +1277,7 @@ public final class ChatListNode: ListView { isSelecting = true } - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, archiveParams: .empty, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, archiveParams: .emptyVisibleParams, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme @@ -1657,6 +1657,18 @@ public final class ChatListNode: ListView { return settings.isHiddenByDefault } |> distinctUntilChanged + |> afterNext { [weak self] value in + Queue.mainQueue().async { + self?.updateState { state in + var state = state + var updatedParams = state.archiveParams + updatedParams.updateVisibility(isHiddenByDefault: value) + state.archiveParams = updatedParams + return state + } + } + } + let displayArchiveIntro: Signal if case .chatList(.archive) = location { @@ -2466,7 +2478,11 @@ public final class ChatListNode: ListView { strongSelf.updateState { state in var state = state state.hiddenItemShouldBeTemporaryRevealed = false - state.archiveParams = .empty + state.archiveParams = .init(scrollOffset: .zero, + storiesFraction: .zero, + expandedHeight: .zero, + finalizeAnimation: false, + isHiddenByDefault: state.archiveParams.isHiddenByDefault) return state } } @@ -2942,34 +2958,34 @@ public final class ChatListNode: ListView { return isHiddenItemVisible } - func revealScrollHiddenItem() { - var isHiddenItemVisible = false - self.forEachItemNode({ itemNode in - if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { - if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { - if threadInfo.isHidden { - isHiddenItemVisible = true - } - } - if case let .groupReference(groupReference) = item.content { - if groupReference.hiddenByDefault { - isHiddenItemVisible = true - } - } - } - }) - if isHiddenItemVisible && !self.currentState.hiddenItemShouldBeTemporaryRevealed { - if self.hapticFeedback == nil { - self.hapticFeedback = HapticFeedback() - } - self.hapticFeedback?.impact(.medium) - self.updateState { state in - var state = state - state.hiddenItemShouldBeTemporaryRevealed = true - return state - } - } - } +// func revealScrollHiddenItem() { +// var isHiddenItemVisible = false +// self.forEachItemNode({ itemNode in +// if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { +// if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { +// if threadInfo.isHidden { +// isHiddenItemVisible = true +// } +// } +// if case let .groupReference(groupReference) = item.content { +// if groupReference.hiddenByDefault { +// isHiddenItemVisible = true +// } +// } +// } +// }) +// if isHiddenItemVisible && !self.currentState.hiddenItemShouldBeTemporaryRevealed { +// if self.hapticFeedback == nil { +// self.hapticFeedback = HapticFeedback() +// } +// self.hapticFeedback?.impact(.medium) +// self.updateState { state in +// var state = state +// state.hiddenItemShouldBeTemporaryRevealed = true +// return state +// } +// } +// } func updateArchiveParams(params: ArchiveAnimationParams) { var isHiddenItemVisible = false @@ -2985,6 +3001,10 @@ public final class ChatListNode: ListView { if groupReference.hiddenByDefault { isHiddenItemVisible = true } + + if groupReference.groupId == .archive && !item.params.isArchiveGroupVisible { + isHiddenItemVisible = true + } } } }) @@ -2997,8 +3017,11 @@ public final class ChatListNode: ListView { if hiddenItemShouldBeTemporaryRevealed != self.currentState.hiddenItemShouldBeTemporaryRevealed { print("hiddenItemShouldBeTemporaryRevealed: \(hiddenItemShouldBeTemporaryRevealed)") } + self.updateState { state in var state = state + var params = params + params.updateVisibility(isRevealed: hiddenItemShouldBeTemporaryRevealed) state.archiveParams = params state.hiddenItemShouldBeTemporaryRevealed = hiddenItemShouldBeTemporaryRevealed return state diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 4ea6091a3ee..18143bc90aa 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -854,6 +854,11 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, if let archiveStoryState = state.archiveStoryState { mappedStoryState = archiveStoryState } + var params = state.archiveParams + if params.isHiddenByDefault != hideArchivedFolderByDefault { + params.updateVisibility(isRevealed: state.hiddenItemShouldBeTemporaryRevealed, isHiddenByDefault: hideArchivedFolderByDefault) + } + result.append(.GroupReferenceEntry(ChatListNodeEntry.GroupReferenceEntryData( index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: messageIndex)), presentationData: state.presentationData, @@ -864,7 +869,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, unreadCount: groupReference.unreadCount, revealed: state.hiddenItemShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault, - archiveParams: state.archiveParams, + archiveParams: params, storyState: mappedStoryState ))) if pinningIndex != 0 { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 86e7f8f0ad1..d108e15a091 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -298,7 +298,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView header: nil, enableContextActions: false, hiddenOffset: false, - params: .empty, + params: .emptyVisibleParams, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 250a4b2232d..ddf7f00a5a7 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -931,7 +931,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate header: nil, enableContextActions: false, hiddenOffset: false, - params: .empty, + params: .emptyVisibleParams, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 4aabe9f7ab2..90ad3276ee9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -444,7 +444,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { header: nil, enableContextActions: false, hiddenOffset: false, - params: .empty, + params: .emptyVisibleParams, interaction: interaction ) } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 74304418d46..09fd25d2279 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -111,7 +111,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { header: nil, enableContextActions: false, hiddenOffset: false, - params: .empty, + params: .emptyVisibleParams, interaction: interaction ) } From 43090d26877feb24aeee645af718893a1106e5a2 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sat, 2 Sep 2023 05:20:31 -0300 Subject: [PATCH 29/34] WIP chat preview transition --- .../Sources/ChatController.swift | 9 + .../Sources/ChatListController.swift | 43 +- .../Sources/ChatListPreviewController.swift | 509 ++++++++++++++++++ .../TelegramUI/Sources/ChatController.swift | 11 + 4 files changed, 558 insertions(+), 14 deletions(-) create mode 100644 submodules/ChatListUI/Sources/ChatListPreviewController.swift diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index d7f0ccc244f..45f6fb561e7 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -559,6 +559,15 @@ public enum ChatControllerPresentationMode: Equatable { case inline(NavigationController?) } +public final class ChatListPreviewPresentationData { + + public let sourceAndRect: (() -> (ASDisplayNode, CGRect)?) + + public init(sourceNodeAndRect: @escaping (() -> (ASDisplayNode, CGRect)?)) { + self.sourceAndRect = sourceNodeAndRect + } +} + public enum ChatPresentationInputQueryResult: Equatable { case stickers([FoundStickerItem]) case hashtags([String]) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 85fc943e006..232894bc435 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1289,6 +1289,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } + switch item.content { case let .groupReference(groupReference): let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .chatList(groupId: groupReference.groupId), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) @@ -1304,15 +1305,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController case .chatList: if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) { if let threadId = threadId { - let source: ContextContentSource +// let source: ContextContentSource let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .replyThread(message: ChatReplyThreadMessage( messageId: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false )), subject: nil, botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) - source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) + strongSelf.present(chatController, in: .window(.root), with: ChatListPreviewPresentationData(sourceNodeAndRect: { + return (node, node.frame) + })) + +// source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) - strongSelf.presentInGlobalOverlay(contextController) + +// let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) +// strongSelf.presentInGlobalOverlay(contextController) } else { let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) chatListController.navigationPresentation = .master @@ -1320,17 +1326,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.presentInGlobalOverlay(contextController) } } else { - let source: ContextContentSource - if let location = location { - source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location)) - } else { - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true)) - chatController.canReadHistory.set(false) - source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - } +// let source: ContextContentSource +// if let location = location { +// source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location)) +// } else { +// let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true)) +// chatController.canReadHistory.set(false) +// strongSelf.present(chatController, in: .window(.root), with: ChatListPreviewPresentationData(sourceNodeAndRect: { +// return (node, node.frame) +// })) + + let chatPreviewController = ChatListPreviewController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId)) + strongSelf.present(chatPreviewController, in: .window(.root), with: ChatListPreviewPresentationData(sourceNodeAndRect: { + return (node, node.frame) + })) + +// source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) +// } - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) - strongSelf.presentInGlobalOverlay(contextController) +// let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) +// strongSelf.presentInGlobalOverlay(contextController) } case let .forum(pinnedIndex, _, threadId, _, _): let isPinned: Bool diff --git a/submodules/ChatListUI/Sources/ChatListPreviewController.swift b/submodules/ChatListUI/Sources/ChatListPreviewController.swift new file mode 100644 index 00000000000..082fe53426f --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListPreviewController.swift @@ -0,0 +1,509 @@ +// +// ChatListPreviewController.swift +// ChatListUI +// +// Created by Bogdan Redkin on 02/09/2023. +// + +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import ContextUI +import TelegramPresentationData + +final class ChatListPreviewContentContainerNode: ASDisplayNode { + public var controllerNode: ContextControllerContentNode? + + override public init() { + super.init() + } + + public func updateLayout(size: CGSize, scaledSize: CGSize, transition: ContainedViewLayoutTransition) { + guard let contentNode = self.controllerNode else { return } + transition.updatePosition(node: contentNode, position: CGPoint(x: scaledSize.width / 2.0, y: scaledSize.height / 2.0)) + transition.updateBounds(node: contentNode, bounds: CGRect(origin: CGPoint(), size: size)) + transition.updateTransformScale(node: contentNode, scale: scaledSize.width / size.width) + contentNode.updateLayout(size: size, transition: transition) + contentNode.controller.containerLayoutUpdated( + ContainerViewLayout( + size: size, + metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), + deviceMetrics: .iPhoneX, + intrinsicInsets: UIEdgeInsets(), + safeInsets: UIEdgeInsets(), + additionalInsets: UIEdgeInsets(), + statusBarHeight: nil, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ), + transition: transition + ) + } +} + +func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> CGRect { + let sourceWindowFrame = fromView.convert(frame, to: nil) + var targetWindowFrame = toView.convert(sourceWindowFrame, from: nil) + + if let fromWindow = fromView.window, let toWindow = toView.window { + targetWindowFrame.origin.x += toWindow.bounds.width - fromWindow.bounds.width + } + return targetWindowFrame +} + + +final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { + private let context: AccountContext + private let presentationData: PresentationData + + private var validLayout: ContainerViewLayout? + + private let effectView: UIVisualEffectView + private var propertyAnimator: AnyObject? + private var displayLinkAnimator: DisplayLinkAnimator? + private let dimNode: ASDisplayNode + private let withoutBlurDimNode: ASDisplayNode + private let dismissNode: ASDisplayNode + private let dismissAccessibilityArea: AccessibilityAreaNode + + private let clippingNode: ASDisplayNode + private let scrollNode: ASScrollNode + + private var originalProjectedContentViewFrame: (CGRect, CGRect)? + private var contentAreaInScreenSpace: CGRect? + private var customPosition: CGPoint? + private let contentContainerNode: ChatListPreviewContentContainerNode + private weak var gesture: ContextGesture? + + var dismiss: (() -> Void)? + var cancel: (() -> Void)? + + private var animatedIn = false + private var isAnimatingOut = false + + private var presentationArguments: ChatListPreviewPresentationData? + private var controller: ViewController? + + init(context: AccountContext, gesture: ContextGesture?) { + self.context = context + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.gesture = gesture + + self.effectView = UIVisualEffectView() + if #available(iOS 9.0, *) { + } else { + if presentationData.theme.rootController.keyboardColor == .dark { + self.effectView.effect = UIBlurEffect(style: .dark) + } else { + self.effectView.effect = UIBlurEffect(style: .light) + } + self.effectView.alpha = 0.0 + } + + self.dimNode = ASDisplayNode() + self.dimNode.backgroundColor = presentationData.theme.contextMenu.dimColor + self.dimNode.alpha = 0.0 + + self.withoutBlurDimNode = ASDisplayNode() + self.withoutBlurDimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.4) + self.withoutBlurDimNode.alpha = 0.0 + + self.dismissNode = ASDisplayNode() + self.dismissAccessibilityArea = AccessibilityAreaNode() + self.dismissAccessibilityArea.accessibilityLabel = presentationData.strings.VoiceOver_DismissContextMenu + self.dismissAccessibilityArea.accessibilityTraits = .button + + self.clippingNode = ASDisplayNode() + self.clippingNode.clipsToBounds = true + + self.scrollNode = ASScrollNode() + self.scrollNode.canCancelAllTouchesInViews = true + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.showsVerticalScrollIndicator = false + if #available(iOS 11.0, *) { + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + } + + self.contentContainerNode = ChatListPreviewContentContainerNode() + + super.init() + self.scrollNode.view.delegate = self + + self.view.addSubview(self.effectView) + self.addSubnode(self.dimNode) + self.addSubnode(self.withoutBlurDimNode) + + self.addSubnode(self.clippingNode) + + self.clippingNode.addSubnode(self.scrollNode) + self.scrollNode.addSubnode(self.dismissNode) + self.scrollNode.addSubnode(self.dismissAccessibilityArea) + + self.initializeContent() + + self.dismissAccessibilityArea.activate = { [weak self] in + self?.dimNodeTapped() + return true + } + } + + deinit { + if let propertyAnimator = self.propertyAnimator { + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) + } + } + } + + override func didLoad() { + super.didLoad() + + self.dismissNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTapped))) + } + + @objc private func dimNodeTapped() { + guard self.animatedIn else { + return + } + self.cancel?() + } + + func initializeContent() { + guard let presentationArguments, + let controller, + let (sourceNode, sourceRect) = presentationArguments.sourceAndRect() + else { return } + + let controlleContentrNode = ContextControllerContentNode(sourceView: sourceNode.view, controller: controller, tapped: { + print("tapped") + }) + + self.contentContainerNode.controllerNode = controlleContentrNode + self.scrollNode.addSubnode(self.contentContainerNode) + self.contentContainerNode.clipsToBounds = true + self.contentContainerNode.cornerRadius = 14.0 + self.contentContainerNode.addSubnode(controlleContentrNode) + + let projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) + self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) + } + + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.cancel?() + } + } + + func animateIn() { + self.gesture?.endPressedAppearance() + guard let presentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceAndRect() else { return } + let projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) + self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) + + var updatedContentAreaInScreenSpace = UIScreen.main.bounds + updatedContentAreaInScreenSpace.origin.x = 0.0 + updatedContentAreaInScreenSpace.size.width = self.bounds.width + self.contentAreaInScreenSpace = updatedContentAreaInScreenSpace + + if let validLayout = self.validLayout { + self.updateLayout(validLayout, transition: .immediate) + } + + if !self.dimNode.isHidden { + self.dimNode.alpha = 1.0 + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } else { + self.withoutBlurDimNode.alpha = 1.0 + self.withoutBlurDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + if #available(iOS 10.0, *) { + if let propertyAnimator = self.propertyAnimator { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) + } + self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) + self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * UIView.animationDurationFactor(), curve: .easeInOut, animations: { + }) + } + + if let _ = self.propertyAnimator { + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in + (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value + }, completion: { //[weak self] in +// self?.didCompleteAnimationIn = true + }) + } + } else { + UIView.animate(withDuration: 0.2, animations: { + self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) + }, completion: { _ in //[weak self] _ in +// self?.didCompleteAnimationIn = true +// self?.actionsContainerNode.animateIn() + }) + } + + let springDuration: Double = 0.52 + let springDamping: CGFloat = 110.0 + + self.contentContainerNode.allowsGroupOpacity = true + self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in + self?.contentContainerNode.allowsGroupOpacity = false + }) + + if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { + let localSourceFrame = self.view.convert(CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: CGSize(width: originalProjectedContentViewFrame.1.width, height: originalProjectedContentViewFrame.1.height)), to: self.scrollNode.view) + + self.contentContainerNode.layer.animateSpring(from: min(localSourceFrame.width / self.contentContainerNode.frame.width, localSourceFrame.height / self.contentContainerNode.frame.height) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping) +// if let source = self.contentContainerNode.controllerNode { +// +// } + + let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y) + if let controller = self.contentContainerNode.controllerNode { + let snapshotView: UIView? = nil// controller.sourceNode.view.snapshotContentTree() + if let snapshotView = snapshotView { + controller.sourceView.isHidden = true + + self.view.insertSubview(snapshotView, belowSubview: self.contentContainerNode.view) + snapshotView.layer.animateSpring(from: NSValue(cgPoint: localSourceFrame.center), to: NSValue(cgPoint: CGPoint(x: self.contentContainerNode.frame.midX, y: self.contentContainerNode.frame.minY + localSourceFrame.height / 2.0)), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) + //snapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (self.contentContainerNode.frame.width / localSourceFrame.width) as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { [weak self] _ in + self?.animatedIn = true + }) + } + } + + func updateLayout() { + if let layout = self.validLayout { + self.updateLayout(layout, transition: .immediate) + } + } + + func updateLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + if self.isAnimatingOut { + return + } + + self.validLayout = layout + + let targetFrame = CGRect(origin: CGPoint(), size: layout.size) + transition.updateFrame(view: self.effectView, frame: targetFrame) + transition.updateFrame(node: self.dimNode, frame: targetFrame) + transition.updateFrame(node: self.withoutBlurDimNode, frame: targetFrame) + + switch layout.metrics.widthClass { + case .compact: + if self.effectView.superview == nil { + self.view.insertSubview(self.effectView, at: 0) + if #available(iOS 10.0, *) { + if let propertyAnimator = self.propertyAnimator { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) + } + } + self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) + self.dimNode.alpha = 1.0 + } + self.dimNode.isHidden = false + self.withoutBlurDimNode.isHidden = true + case .regular: + if self.effectView.superview != nil { + self.effectView.removeFromSuperview() + self.withoutBlurDimNode.alpha = 1.0 + } + self.dimNode.isHidden = true + self.withoutBlurDimNode.isHidden = false + } + transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + + if let contentParentNode = contentContainerNode.controllerNode { + var projectedFrame: CGRect = convertFrame(contentParentNode.sourceView.bounds, from: contentParentNode.sourceView, to: self.view) + if let presentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceAndRect() { + projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) + } + self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) + if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { + let constrainedWidth: CGFloat + if layout.size.width < layout.size.height { + constrainedWidth = layout.size.width + } else { + constrainedWidth = floor(layout.size.width / 2.0) + } + var contentUnscaledSize: CGSize + if case .compact = layout.metrics.widthClass { + contentUnscaledSize = CGSize(width: constrainedWidth, height: max(100.0, layout.size.height)) + if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { + contentUnscaledSize = preferredSize + } + } else { + contentUnscaledSize = CGSize(width: min(layout.size.width, 340.0), height: min(568.0, layout.size.height - layout.intrinsicInsets.bottom)) + if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { + contentUnscaledSize = preferredSize + } + } + let contentSize = CGSize(width: floor(contentUnscaledSize.width), height: floor(contentUnscaledSize.height)) + self.contentContainerNode.updateLayout(size: contentUnscaledSize, scaledSize: contentSize, transition: transition) + let scrollContentSize = layout.size + if self.scrollNode.view.contentSize != scrollContentSize { + self.scrollNode.view.contentSize = scrollContentSize + } + + let contentContainerFrame = CGRect(origin: CGPoint(x: floor(originalProjectedContentViewFrame.1.midX - contentSize.width / 2.0), y: floor(originalProjectedContentViewFrame.1.midY - contentSize.height / 2.0)), size: contentSize) + transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) + + } + } + + transition.updateFrame(node: self.dismissNode, frame: CGRect(origin: CGPoint(), size: self.scrollNode.view.contentSize)) + self.dismissAccessibilityArea.frame = CGRect(origin: CGPoint(), size: self.scrollNode.view.contentSize) + } + + + func animateOut(targetNode: ASDisplayNode?, completion: (() -> Void)? = nil) { + var dimCompleted = false + + let internalCompletion: () -> Void = { [weak self] in + if let strongSelf = self, dimCompleted { + strongSelf.dismiss?() + } + completion?() + } + + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + dimCompleted = true + internalCompletion() + }) + } + + func updatePresentationArguments(_ arguments: ChatListPreviewPresentationData?, controller: ViewController?) { + self.presentationArguments = arguments + self.controller = controller + } + + +} + + +public final class ChatListPreviewController: ViewController { + private weak var recognizer: TapLongTapOrDoubleTapGestureRecognizer? + private weak var gesture: ContextGesture? + + private var animatedDidAppear = false + private var wasDismissed = false + + private var controllerNode: ChatListPreviewControllerNode { + return self.displayNode as! ChatListPreviewControllerNode + } + + private var animatedIn = false + + private let context: AccountContext + private var chatLocation: ChatLocation + private var chatPrevewController: ChatController + + public init(context: AccountContext, chatLocation: ChatLocation, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil) { + self.context = context + self.chatLocation = chatLocation + self.chatPrevewController = context.sharedContext.makeChatController(context: context, chatLocation: chatLocation, subject: nil, botStart: nil, mode: .standard(previewing: true)) + self.recognizer = recognizer + self.gesture = gesture + super.init(navigationBarPresentationData: nil) + self.statusBar.statusBarStyle = .Hide + self.lockOrientation = true + self.blocksBackgroundWhenInOverlay = true + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func loadDisplayNode() { + self.displayNode = ChatListPreviewControllerNode(context: context, gesture: gesture) + self.controllerNode.dismiss = { [weak self] in + self?.presentingViewController?.dismiss(animated: false, completion: nil) + } + self.controllerNode.cancel = { [weak self] in + self?.dismiss() + } + self.displayNodeDidLoad() + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if let _ = self.presentationArguments as? ChatListPreviewPresentationData { + self.updateChatLocation(self.chatLocation) + self.ready.set(.single(true)) + } + } + + override public func viewDidAppear(_ animated: Bool) { + if self.ignoreAppearanceMethodInvocations() { + return + } + super.viewDidAppear(animated) + let _ = (self.ready.get() |> deliverOnMainQueue).start(next: { value in + guard value else { return } + + self.controllerNode.initializeContent() + + print("content is ready: \(value)") + if !self.wasDismissed && !self.animatedDidAppear { + self.animatedDidAppear = true + self.controllerNode.animateIn() + } + }) + +// super.viewDidAppear(animated) +// guard +// let arguments = self.presentationArguments as? ChatListPreviewPresentationData, +// let (sourceNode, _) = arguments.sourceAndRect() +// else { return } +// +// if !self.animatedIn { +// self.animatedIn = true +// self.controllerNode.animateIn(sourceNode: sourceNode) +// } + } + + override public func dismiss(completion: (() -> Void)? = nil) { + guard + let arguments = self.presentationArguments as? ChatListPreviewPresentationData, + let (sourceNode, _) = arguments.sourceAndRect() + else { return } + + self.controllerNode.animateOut(targetNode: sourceNode, completion: completion) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + self.controllerNode.updateLayout(layout, transition: transition) + + } + + public func updateChatLocation(_ chatLocation: ChatLocation) { + self.chatLocation = chatLocation + let chatController = context.sharedContext.makeChatController(context: self.context, chatLocation: chatLocation, subject: nil, botStart: nil, mode: .standard(previewing: true)) + self.chatPrevewController = chatController + self.controllerNode.updatePresentationArguments(self.presentationArguments as? ChatListPreviewPresentationData, + controller: chatController) + +// if self.chatLocation != chatLocation { +// } + } +} diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 9ec50462755..52f0f531ce0 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -11271,6 +11271,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } + if let arguments = self.presentationArguments as? ChatListPreviewPresentationData, + let (sourceNode, sourceRect) = arguments.sourceAndRect() + { + let containerNode = ASDisplayNode() + containerNode.backgroundColor = .white.withAlphaComponent(0.5) + + sourceNode.backgroundColor = .red + self.chatDisplayNode.bounds = sourceRect + self.chatDisplayNode.position = sourceRect.center + } + if !self.didSetup3dTouch { self.didSetup3dTouch = true if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { From 778902e2d15de9d94159386fed07f90bb5ce2432 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sat, 2 Sep 2023 05:41:23 -0300 Subject: [PATCH 30/34] Revert "fixed hidden archive by default" This reverts commit 9bfec967b2f37dc58df245b918d062921cbc8eed. --- .../Sources/ChatListController.swift | 9 +- .../Sources/ChatListControllerNode.swift | 8 +- .../Sources/ChatListSearchListPaneNode.swift | 4 +- .../Node/ChatListArchiveTransitionItem.swift | 40 ++------ .../Sources/Node/ChatListItem.swift | 2 +- .../Sources/Node/ChatListNode.swift | 91 +++++++------------ .../Sources/Node/ChatListNodeEntries.swift | 7 +- .../TextSizeSelectionController.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 2 +- .../Themes/ThemePreviewControllerNode.swift | 2 +- .../ChatSearchResultsContollerNode.swift | 2 +- 11 files changed, 54 insertions(+), 115 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 232894bc435..e787d745b90 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1098,10 +1098,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if !didDisplayTip { -// #if DEBUG -// #else + #if DEBUG + #else let _ = ApplicationSpecificNotice.setDisplayChatListArchiveTooltip(accountManager: self.context.sharedContext.accountManager).start() -// #endif + #endif self.push(ArchiveInfoScreen(context: self.context, settings: settings)) } @@ -4663,8 +4663,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if updatedValue { state.hiddenItemShouldBeTemporaryRevealed = false } - state.archiveParams.updateVisibility(isRevealed: state.hiddenItemShouldBeTemporaryRevealed, - isHiddenByDefault: updatedValue) state.peerIdWithRevealedOptions = nil return state } @@ -5134,7 +5132,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.chatListDisplayNode.effectiveContainerNode.updateState { state in var state = state state.hiddenItemShouldBeTemporaryRevealed = false - state.archiveParams.updateVisibility(isRevealed: false) return state } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index a057da7b836..9d13279c924 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -242,7 +242,7 @@ private final class ChatListShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -2574,8 +2574,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight, - finalizeAnimation: false, - isHiddenByDefault: currentParams.isHiddenByDefault + finalizeAnimation: false )) } } @@ -2589,8 +2588,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight, - finalizeAnimation: false, - isHiddenByDefault: currentParams.isHiddenByDefault + finalizeAnimation: false )) } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 8256c39c33c..77519096eec 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -850,7 +850,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) } - )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -3591,7 +3591,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index e38e47fcfdb..826fb7bf63e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -12,59 +12,31 @@ public struct ArchiveAnimationParams: Equatable { public let scrollOffset: CGFloat public let storiesFraction: CGFloat public private(set)var expandedHeight: CGFloat - public private(set)var isRevealed: Bool = false public let finalizeAnimation: Bool - public private(set)var isHiddenByDefault: Bool - public static var emptyVisibleParams: ArchiveAnimationParams { - return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false, isHiddenByDefault: false) + public static var empty: ArchiveAnimationParams { + return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false) } - public static var emptyDefaultHiddenParams: ArchiveAnimationParams { - return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false, isHiddenByDefault: true) - } - -// public init(scrollOffset: CGFloat, storiesFraction: CGFloat, expandedHeight: CGFloat, finalizeAnimation: Bool, isHiddenByDefault: Bool) { -// -// } - public func withUpdatedFinalizeAnimation(_ finalizeAnimation: Bool) -> ArchiveAnimationParams { var newParams = ArchiveAnimationParams( scrollOffset: self.scrollOffset, storiesFraction: self.storiesFraction, expandedHeight: self.expandedHeight, - finalizeAnimation: finalizeAnimation, - isHiddenByDefault: self.isHiddenByDefault + finalizeAnimation: finalizeAnimation ) if finalizeAnimation { if newParams.isArchiveGroupVisible { newParams.expandedHeight /= 1.2 - newParams.isRevealed = true } else { - newParams = ArchiveAnimationParams( - scrollOffset: .zero, - storiesFraction: .zero, - expandedHeight: .zero, - finalizeAnimation: finalizeAnimation, - isHiddenByDefault: self.isHiddenByDefault - ) - newParams.isRevealed = false + newParams = ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: finalizeAnimation) } } return newParams } - public mutating func updateVisibility(isRevealed: Bool? = nil, isHiddenByDefault: Bool? = nil) { - if let isRevealed { - self.isRevealed = isRevealed - } - if let isHiddenByDefault { - self.isHiddenByDefault = isHiddenByDefault - } - } - var isArchiveGroupVisible: Bool { - return (storiesFraction >= 0.85 && finalizeAnimation) || !isHiddenByDefault + return storiesFraction >= 0.85 && finalizeAnimation } } @@ -93,7 +65,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.backgroundNode.backgroundColor = .clear self.backgroundNode.isLayerBacked = true - self.animation = .init(state: .swipeDownInit, params: .emptyDefaultHiddenParams) + self.animation = .init(state: .swipeDownInit, params: .empty) self.titleNode = ASTextNode() self.titleNode.isLayerBacked = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index e87f3e7df71..52e1ebf9502 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2865,7 +2865,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.avatarNode.updateSize(size: avatarFrame.size) strongSelf.updateVideoVisibility() - if case let .groupReference(data) = item.content, data.groupId == .archive, !item.params.isArchiveGroupVisible { + if case let .groupReference(data) = item.content, data.groupId == .archive { transition.updatePosition(node: strongSelf.archiveTransitionNode, position: contextContainerFrame.center) transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: contextContainerFrame) transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 3cacb773acc..89615fae177 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -425,7 +425,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, - params: .emptyVisibleParams, + params: .empty, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -635,7 +635,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { - print("insert group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) params: \(groupReferenceEntry.archiveParams)") + print("insert group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed)") return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, context: context, @@ -791,7 +791,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, - params: .emptyVisibleParams, + params: .empty, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -955,7 +955,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { - print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") +// print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, @@ -1277,7 +1277,7 @@ public final class ChatListNode: ListView { isSelecting = true } - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, archiveParams: .emptyVisibleParams, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, archiveParams: .empty, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme @@ -1657,18 +1657,6 @@ public final class ChatListNode: ListView { return settings.isHiddenByDefault } |> distinctUntilChanged - |> afterNext { [weak self] value in - Queue.mainQueue().async { - self?.updateState { state in - var state = state - var updatedParams = state.archiveParams - updatedParams.updateVisibility(isHiddenByDefault: value) - state.archiveParams = updatedParams - return state - } - } - } - let displayArchiveIntro: Signal if case .chatList(.archive) = location { @@ -2478,11 +2466,7 @@ public final class ChatListNode: ListView { strongSelf.updateState { state in var state = state state.hiddenItemShouldBeTemporaryRevealed = false - state.archiveParams = .init(scrollOffset: .zero, - storiesFraction: .zero, - expandedHeight: .zero, - finalizeAnimation: false, - isHiddenByDefault: state.archiveParams.isHiddenByDefault) + state.archiveParams = .empty return state } } @@ -2958,34 +2942,34 @@ public final class ChatListNode: ListView { return isHiddenItemVisible } -// func revealScrollHiddenItem() { -// var isHiddenItemVisible = false -// self.forEachItemNode({ itemNode in -// if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { -// if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { -// if threadInfo.isHidden { -// isHiddenItemVisible = true -// } -// } -// if case let .groupReference(groupReference) = item.content { -// if groupReference.hiddenByDefault { -// isHiddenItemVisible = true -// } -// } -// } -// }) -// if isHiddenItemVisible && !self.currentState.hiddenItemShouldBeTemporaryRevealed { -// if self.hapticFeedback == nil { -// self.hapticFeedback = HapticFeedback() -// } -// self.hapticFeedback?.impact(.medium) -// self.updateState { state in -// var state = state -// state.hiddenItemShouldBeTemporaryRevealed = true -// return state -// } -// } -// } + func revealScrollHiddenItem() { + var isHiddenItemVisible = false + self.forEachItemNode({ itemNode in + if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { + if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { + if threadInfo.isHidden { + isHiddenItemVisible = true + } + } + if case let .groupReference(groupReference) = item.content { + if groupReference.hiddenByDefault { + isHiddenItemVisible = true + } + } + } + }) + if isHiddenItemVisible && !self.currentState.hiddenItemShouldBeTemporaryRevealed { + if self.hapticFeedback == nil { + self.hapticFeedback = HapticFeedback() + } + self.hapticFeedback?.impact(.medium) + self.updateState { state in + var state = state + state.hiddenItemShouldBeTemporaryRevealed = true + return state + } + } + } func updateArchiveParams(params: ArchiveAnimationParams) { var isHiddenItemVisible = false @@ -3001,10 +2985,6 @@ public final class ChatListNode: ListView { if groupReference.hiddenByDefault { isHiddenItemVisible = true } - - if groupReference.groupId == .archive && !item.params.isArchiveGroupVisible { - isHiddenItemVisible = true - } } } }) @@ -3017,11 +2997,8 @@ public final class ChatListNode: ListView { if hiddenItemShouldBeTemporaryRevealed != self.currentState.hiddenItemShouldBeTemporaryRevealed { print("hiddenItemShouldBeTemporaryRevealed: \(hiddenItemShouldBeTemporaryRevealed)") } - self.updateState { state in var state = state - var params = params - params.updateVisibility(isRevealed: hiddenItemShouldBeTemporaryRevealed) state.archiveParams = params state.hiddenItemShouldBeTemporaryRevealed = hiddenItemShouldBeTemporaryRevealed return state diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 18143bc90aa..4ea6091a3ee 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -854,11 +854,6 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, if let archiveStoryState = state.archiveStoryState { mappedStoryState = archiveStoryState } - var params = state.archiveParams - if params.isHiddenByDefault != hideArchivedFolderByDefault { - params.updateVisibility(isRevealed: state.hiddenItemShouldBeTemporaryRevealed, isHiddenByDefault: hideArchivedFolderByDefault) - } - result.append(.GroupReferenceEntry(ChatListNodeEntry.GroupReferenceEntryData( index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: messageIndex)), presentationData: state.presentationData, @@ -869,7 +864,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, unreadCount: groupReference.unreadCount, revealed: state.hiddenItemShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault, - archiveParams: params, + archiveParams: state.archiveParams, storyState: mappedStoryState ))) if pinningIndex != 0 { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index d108e15a091..86e7f8f0ad1 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -298,7 +298,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView header: nil, enableContextActions: false, hiddenOffset: false, - params: .emptyVisibleParams, + params: .empty, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index ddf7f00a5a7..250a4b2232d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -931,7 +931,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate header: nil, enableContextActions: false, hiddenOffset: false, - params: .emptyVisibleParams, + params: .empty, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 90ad3276ee9..4aabe9f7ab2 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -444,7 +444,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { header: nil, enableContextActions: false, hiddenOffset: false, - params: .emptyVisibleParams, + params: .empty, interaction: interaction ) } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 09fd25d2279..74304418d46 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -111,7 +111,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { header: nil, enableContextActions: false, hiddenOffset: false, - params: .emptyVisibleParams, + params: .empty, interaction: interaction ) } From d66985030e8082bfebf3f3136c61c297b780e96e Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sat, 2 Sep 2023 06:03:41 -0300 Subject: [PATCH 31/34] Revert "Revert "fixed hidden archive by default"" This reverts commit 778902e2d15de9d94159386fed07f90bb5ce2432. --- .../Sources/ChatListController.swift | 9 +- .../Sources/ChatListControllerNode.swift | 8 +- .../Sources/ChatListSearchListPaneNode.swift | 4 +- .../Node/ChatListArchiveTransitionItem.swift | 40 ++++++-- .../Sources/Node/ChatListItem.swift | 2 +- .../Sources/Node/ChatListNode.swift | 91 ++++++++++++------- .../Sources/Node/ChatListNodeEntries.swift | 7 +- .../TextSizeSelectionController.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 2 +- .../Themes/ThemePreviewControllerNode.swift | 2 +- .../ChatSearchResultsContollerNode.swift | 2 +- 11 files changed, 115 insertions(+), 54 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index e787d745b90..232894bc435 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1098,10 +1098,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if !didDisplayTip { - #if DEBUG - #else +// #if DEBUG +// #else let _ = ApplicationSpecificNotice.setDisplayChatListArchiveTooltip(accountManager: self.context.sharedContext.accountManager).start() - #endif +// #endif self.push(ArchiveInfoScreen(context: self.context, settings: settings)) } @@ -4663,6 +4663,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if updatedValue { state.hiddenItemShouldBeTemporaryRevealed = false } + state.archiveParams.updateVisibility(isRevealed: state.hiddenItemShouldBeTemporaryRevealed, + isHiddenByDefault: updatedValue) state.peerIdWithRevealedOptions = nil return state } @@ -5132,6 +5134,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.chatListDisplayNode.effectiveContainerNode.updateState { state in var state = state state.hiddenItemShouldBeTemporaryRevealed = false + state.archiveParams.updateVisibility(isRevealed: false) return state } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 9d13279c924..a057da7b836 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -242,7 +242,7 @@ private final class ChatListShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -2574,7 +2574,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight, - finalizeAnimation: false + finalizeAnimation: false, + isHiddenByDefault: currentParams.isHiddenByDefault )) } } @@ -2588,7 +2589,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { scrollOffset: scrollOffset.rounded(), storiesFraction: archiveFraction, expandedHeight: expandedHeight, - finalizeAnimation: false + finalizeAnimation: false, + isHiddenByDefault: currentParams.isHiddenByDefault )) } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 77519096eec..8256c39c33c 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -850,7 +850,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) } - )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -3591,7 +3591,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .empty, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, params: .emptyVisibleParams, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift index 826fb7bf63e..e38e47fcfdb 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveTransitionItem.swift @@ -12,31 +12,59 @@ public struct ArchiveAnimationParams: Equatable { public let scrollOffset: CGFloat public let storiesFraction: CGFloat public private(set)var expandedHeight: CGFloat + public private(set)var isRevealed: Bool = false public let finalizeAnimation: Bool + public private(set)var isHiddenByDefault: Bool - public static var empty: ArchiveAnimationParams { - return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false) + public static var emptyVisibleParams: ArchiveAnimationParams { + return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false, isHiddenByDefault: false) } + public static var emptyDefaultHiddenParams: ArchiveAnimationParams { + return ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: false, isHiddenByDefault: true) + } + +// public init(scrollOffset: CGFloat, storiesFraction: CGFloat, expandedHeight: CGFloat, finalizeAnimation: Bool, isHiddenByDefault: Bool) { +// +// } + public func withUpdatedFinalizeAnimation(_ finalizeAnimation: Bool) -> ArchiveAnimationParams { var newParams = ArchiveAnimationParams( scrollOffset: self.scrollOffset, storiesFraction: self.storiesFraction, expandedHeight: self.expandedHeight, - finalizeAnimation: finalizeAnimation + finalizeAnimation: finalizeAnimation, + isHiddenByDefault: self.isHiddenByDefault ) if finalizeAnimation { if newParams.isArchiveGroupVisible { newParams.expandedHeight /= 1.2 + newParams.isRevealed = true } else { - newParams = ArchiveAnimationParams(scrollOffset: .zero, storiesFraction: .zero, expandedHeight: .zero, finalizeAnimation: finalizeAnimation) + newParams = ArchiveAnimationParams( + scrollOffset: .zero, + storiesFraction: .zero, + expandedHeight: .zero, + finalizeAnimation: finalizeAnimation, + isHiddenByDefault: self.isHiddenByDefault + ) + newParams.isRevealed = false } } return newParams } + public mutating func updateVisibility(isRevealed: Bool? = nil, isHiddenByDefault: Bool? = nil) { + if let isRevealed { + self.isRevealed = isRevealed + } + if let isHiddenByDefault { + self.isHiddenByDefault = isHiddenByDefault + } + } + var isArchiveGroupVisible: Bool { - return storiesFraction >= 0.85 && finalizeAnimation + return (storiesFraction >= 0.85 && finalizeAnimation) || !isHiddenByDefault } } @@ -65,7 +93,7 @@ class ChatListArchiveTransitionNode: ASDisplayNode { self.backgroundNode.backgroundColor = .clear self.backgroundNode.isLayerBacked = true - self.animation = .init(state: .swipeDownInit, params: .empty) + self.animation = .init(state: .swipeDownInit, params: .emptyDefaultHiddenParams) self.titleNode = ASTextNode() self.titleNode.isLayerBacked = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 52e1ebf9502..e8b416bcc8f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2865,7 +2865,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.avatarNode.updateSize(size: avatarFrame.size) strongSelf.updateVideoVisibility() - if case let .groupReference(data) = item.content, data.groupId == .archive { + if case let .groupReference(data) = item.content, data.groupId == .archive, item.params.isHiddenByDefault { transition.updatePosition(node: strongSelf.archiveTransitionNode, position: contextContainerFrame.center) transition.updateBounds(node: strongSelf.archiveTransitionNode, bounds: contextContainerFrame) transition.updateAlpha(node: strongSelf.archiveTransitionNode, alpha: 1.0) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 89615fae177..3cacb773acc 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -425,7 +425,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, - params: .empty, + params: .emptyVisibleParams, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -635,7 +635,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { - print("insert group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed)") + print("insert group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) params: \(groupReferenceEntry.archiveParams)") return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, context: context, @@ -791,7 +791,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL header: nil, enableContextActions: true, hiddenOffset: threadInfo?.isHidden == true && !revealed, - params: .empty, + params: .emptyVisibleParams, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout, displayPresence): @@ -955,7 +955,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL // return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveTransitionItem(theme: groupReferenceEntry.presentationData.theme), // directionHint: entry.directionHint) // } else { -// print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") + print("update group entry which hiddenByDefault: \(groupReferenceEntry.hiddenByDefault) revealed: \(groupReferenceEntry.revealed) top offset: \(groupReferenceEntry.archiveParams)") return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: groupReferenceEntry.presentationData, @@ -1277,7 +1277,7 @@ public final class ChatListNode: ListView { isSelecting = true } - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, archiveParams: .empty, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, archiveParams: .emptyVisibleParams, hiddenPsaPeerId: nil, selectedThreadIds: Set(), archiveStoryState: nil) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme @@ -1657,6 +1657,18 @@ public final class ChatListNode: ListView { return settings.isHiddenByDefault } |> distinctUntilChanged + |> afterNext { [weak self] value in + Queue.mainQueue().async { + self?.updateState { state in + var state = state + var updatedParams = state.archiveParams + updatedParams.updateVisibility(isHiddenByDefault: value) + state.archiveParams = updatedParams + return state + } + } + } + let displayArchiveIntro: Signal if case .chatList(.archive) = location { @@ -2466,7 +2478,11 @@ public final class ChatListNode: ListView { strongSelf.updateState { state in var state = state state.hiddenItemShouldBeTemporaryRevealed = false - state.archiveParams = .empty + state.archiveParams = .init(scrollOffset: .zero, + storiesFraction: .zero, + expandedHeight: .zero, + finalizeAnimation: false, + isHiddenByDefault: state.archiveParams.isHiddenByDefault) return state } } @@ -2942,34 +2958,34 @@ public final class ChatListNode: ListView { return isHiddenItemVisible } - func revealScrollHiddenItem() { - var isHiddenItemVisible = false - self.forEachItemNode({ itemNode in - if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { - if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { - if threadInfo.isHidden { - isHiddenItemVisible = true - } - } - if case let .groupReference(groupReference) = item.content { - if groupReference.hiddenByDefault { - isHiddenItemVisible = true - } - } - } - }) - if isHiddenItemVisible && !self.currentState.hiddenItemShouldBeTemporaryRevealed { - if self.hapticFeedback == nil { - self.hapticFeedback = HapticFeedback() - } - self.hapticFeedback?.impact(.medium) - self.updateState { state in - var state = state - state.hiddenItemShouldBeTemporaryRevealed = true - return state - } - } - } +// func revealScrollHiddenItem() { +// var isHiddenItemVisible = false +// self.forEachItemNode({ itemNode in +// if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { +// if case let .peer(peerData) = item.content, let threadInfo = peerData.threadInfo { +// if threadInfo.isHidden { +// isHiddenItemVisible = true +// } +// } +// if case let .groupReference(groupReference) = item.content { +// if groupReference.hiddenByDefault { +// isHiddenItemVisible = true +// } +// } +// } +// }) +// if isHiddenItemVisible && !self.currentState.hiddenItemShouldBeTemporaryRevealed { +// if self.hapticFeedback == nil { +// self.hapticFeedback = HapticFeedback() +// } +// self.hapticFeedback?.impact(.medium) +// self.updateState { state in +// var state = state +// state.hiddenItemShouldBeTemporaryRevealed = true +// return state +// } +// } +// } func updateArchiveParams(params: ArchiveAnimationParams) { var isHiddenItemVisible = false @@ -2985,6 +3001,10 @@ public final class ChatListNode: ListView { if groupReference.hiddenByDefault { isHiddenItemVisible = true } + + if groupReference.groupId == .archive && !item.params.isArchiveGroupVisible { + isHiddenItemVisible = true + } } } }) @@ -2997,8 +3017,11 @@ public final class ChatListNode: ListView { if hiddenItemShouldBeTemporaryRevealed != self.currentState.hiddenItemShouldBeTemporaryRevealed { print("hiddenItemShouldBeTemporaryRevealed: \(hiddenItemShouldBeTemporaryRevealed)") } + self.updateState { state in var state = state + var params = params + params.updateVisibility(isRevealed: hiddenItemShouldBeTemporaryRevealed) state.archiveParams = params state.hiddenItemShouldBeTemporaryRevealed = hiddenItemShouldBeTemporaryRevealed return state diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 4ea6091a3ee..18143bc90aa 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -854,6 +854,11 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, if let archiveStoryState = state.archiveStoryState { mappedStoryState = archiveStoryState } + var params = state.archiveParams + if params.isHiddenByDefault != hideArchivedFolderByDefault { + params.updateVisibility(isRevealed: state.hiddenItemShouldBeTemporaryRevealed, isHiddenByDefault: hideArchivedFolderByDefault) + } + result.append(.GroupReferenceEntry(ChatListNodeEntry.GroupReferenceEntryData( index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: messageIndex)), presentationData: state.presentationData, @@ -864,7 +869,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, unreadCount: groupReference.unreadCount, revealed: state.hiddenItemShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault, - archiveParams: state.archiveParams, + archiveParams: params, storyState: mappedStoryState ))) if pinningIndex != 0 { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 86e7f8f0ad1..d108e15a091 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -298,7 +298,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView header: nil, enableContextActions: false, hiddenOffset: false, - params: .empty, + params: .emptyVisibleParams, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 250a4b2232d..ddf7f00a5a7 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -931,7 +931,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate header: nil, enableContextActions: false, hiddenOffset: false, - params: .empty, + params: .emptyVisibleParams, interaction: interaction ) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 4aabe9f7ab2..90ad3276ee9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -444,7 +444,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { header: nil, enableContextActions: false, hiddenOffset: false, - params: .empty, + params: .emptyVisibleParams, interaction: interaction ) } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 74304418d46..09fd25d2279 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -111,7 +111,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { header: nil, enableContextActions: false, hiddenOffset: false, - params: .empty, + params: .emptyVisibleParams, interaction: interaction ) } From 9838bc6f1addef968b816522d5d03e49072c99f4 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sat, 2 Sep 2023 16:00:22 -0300 Subject: [PATCH 32/34] [WIP] Chat preview animation --- .../Sources/ChatController.swift | 8 +- submodules/ChatListUI/BUILD | 1 + .../Sources/ChatListController.swift | 19 ++ .../Sources/ChatListPreviewController.swift | 298 ++++++++++++++---- .../TelegramUI/Sources/ChatController.swift | 2 +- 5 files changed, 271 insertions(+), 57 deletions(-) diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 45f6fb561e7..803c402f84c 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -561,10 +561,12 @@ public enum ChatControllerPresentationMode: Equatable { public final class ChatListPreviewPresentationData { - public let sourceAndRect: (() -> (ASDisplayNode, CGRect)?) + public let sourceNodeAndRect: (() -> (ASDisplayNode, CGRect)?) + public let contentArea: (() -> (CGRect)) - public init(sourceNodeAndRect: @escaping (() -> (ASDisplayNode, CGRect)?)) { - self.sourceAndRect = sourceNodeAndRect + public init(sourceNodeAndRect: @escaping (() -> (ASDisplayNode, CGRect)?), contentArea: @escaping (() -> (CGRect))) { + self.sourceNodeAndRect = sourceNodeAndRect + self.contentArea = contentArea } } diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index a8c8cf10284..797280e7e0b 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -99,6 +99,7 @@ swift_library( "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen", "//submodules/TelegramUI/Components/Settings/ArchiveInfoScreen", + "//submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 232894bc435..4a1f11781be 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1310,8 +1310,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController messageId: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false )), subject: nil, botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) + + + strongSelf.present(chatController, in: .window(.root), with: ChatListPreviewPresentationData(sourceNodeAndRect: { return (node, node.frame) + }, contentArea: { + let baseContentFrame = strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.frame + if case let .known(topOffset) = strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.visibleContentOffset(), + case let .known(bottomOffset) = strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.visibleBottomContentOffset() { + return baseContentFrame.inset(by: .init(top: topOffset, left: .zero, bottom: bottomOffset, right: .zero)) + } else { + return baseContentFrame.inset(by: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.headerInsets) + } })) // source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) @@ -1339,6 +1350,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let chatPreviewController = ChatListPreviewController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId)) strongSelf.present(chatPreviewController, in: .window(.root), with: ChatListPreviewPresentationData(sourceNodeAndRect: { return (node, node.frame) + }, contentArea: { + let baseContentFrame = strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.frame + if case let .known(topOffset) = strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.visibleContentOffset(), + case let .known(bottomOffset) = strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.visibleBottomContentOffset() { + return baseContentFrame.inset(by: .init(top: topOffset, left: .zero, bottom: bottomOffset, right: .zero)) + } else { + return baseContentFrame.inset(by: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.headerInsets) + } })) // source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) diff --git a/submodules/ChatListUI/Sources/ChatListPreviewController.swift b/submodules/ChatListUI/Sources/ChatListPreviewController.swift index 082fe53426f..803db7416a0 100644 --- a/submodules/ChatListUI/Sources/ChatListPreviewController.swift +++ b/submodules/ChatListUI/Sources/ChatListPreviewController.swift @@ -14,6 +14,8 @@ import TelegramCore import SwiftSignalKit import AccountContext import ContextUI +import ChatAvatarNavigationNode +import ChatTitleView import TelegramPresentationData final class ChatListPreviewContentContainerNode: ASDisplayNode { @@ -59,6 +61,7 @@ func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { + private let context: AccountContext private let presentationData: PresentationData @@ -86,10 +89,34 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi private var animatedIn = false private var isAnimatingOut = false + private var didCompleteAnimationIn = false + + var presentationArguments: ChatListPreviewPresentationData? + var controller: ViewController? + var transitionParams: TransitionParams? - private var presentationArguments: ChatListPreviewPresentationData? - private var controller: ViewController? - + struct TransitionParams { + var contentArea: CGRect + + var sourceMessageFrame: CGRect + var sourceChatItemSnapshot: CALayer + var sourceTitleSnapshot: CALayer + var sourceTitleFrame: CGRect + var sourceAvatarSnapshot: CALayer + var sourceAvatarStartFrame: CGRect + var sourceAvatarFinalFrame: CGRect + + var targetTitleView: ChatTitleView + var targetTitleViewFrame: CGRect + var targetAvatarNode: NavigationButtonNode + var targetAvatarFrameStart: CGRect + var targetAvatarFrameEnd: CGRect + var targetBackgroundLayer: CALayer + var targetBackgroundMaskLayer: CALayer + var targetBackgroundStartFrame: CGRect + var targetBackgroundEndFrame: CGRect + } + init(context: AccountContext, gesture: ContextGesture?) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -178,7 +205,7 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi func initializeContent() { guard let presentationArguments, let controller, - let (sourceNode, sourceRect) = presentationArguments.sourceAndRect() + let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() else { return } let controlleContentrNode = ContextControllerContentNode(sourceView: sourceNode.view, controller: controller, tapped: { @@ -203,11 +230,16 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi func animateIn() { self.gesture?.endPressedAppearance() - guard let presentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceAndRect() else { return } - let projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) - self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) + guard + let transitionParams + else { return } + let sourceTitleSnapshot = transitionParams.sourceTitleSnapshot +// let projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) + let sourceChatItemSnapshot = transitionParams.sourceChatItemSnapshot + let sourceAvatarSnapshot = transitionParams.sourceAvatarSnapshot + var updatedContentAreaInScreenSpace = transitionParams.contentArea + self.originalProjectedContentViewFrame = (transitionParams.sourceMessageFrame, transitionParams.sourceMessageFrame) - var updatedContentAreaInScreenSpace = UIScreen.main.bounds updatedContentAreaInScreenSpace.origin.x = 0.0 updatedContentAreaInScreenSpace.size.width = self.bounds.width self.contentAreaInScreenSpace = updatedContentAreaInScreenSpace @@ -218,36 +250,32 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi if !self.dimNode.isHidden { self.dimNode.alpha = 1.0 - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } else { self.withoutBlurDimNode.alpha = 1.0 - self.withoutBlurDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.withoutBlurDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } - if #available(iOS 10.0, *) { - if let propertyAnimator = self.propertyAnimator { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } - self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) - self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * UIView.animationDurationFactor(), curve: .easeInOut, animations: { - }) + if let propertyAnimator = self.propertyAnimator { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) } + self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) + self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.propertyAnimator = UIViewPropertyAnimator(duration: 0.3 * UIView.animationDurationFactor(), curve: .easeInOut, animations: { + }) if let _ = self.propertyAnimator { - if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in - (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value - }, completion: { //[weak self] in -// self?.didCompleteAnimationIn = true - }) - } + self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.3 * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in + (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value + }, completion: { [weak self] in + self?.didCompleteAnimationIn = true + }) } else { - UIView.animate(withDuration: 0.2, animations: { + UIView.animate(withDuration: 0.3, animations: { self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) - }, completion: { _ in //[weak self] _ in -// self?.didCompleteAnimationIn = true + }, completion: { [weak self] _ in + self?.didCompleteAnimationIn = true // self?.actionsContainerNode.animateIn() }) } @@ -256,14 +284,26 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi let springDamping: CGFloat = 110.0 self.contentContainerNode.allowsGroupOpacity = true - self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in + self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 1.15, completion: { [weak self] _ in self?.contentContainerNode.allowsGroupOpacity = false }) + + sourceAvatarSnapshot.animateAlpha(from: 1.0, to: 0.0, duration: 1.15) + sourceChatItemSnapshot.animateAlpha(from: 1.0, to: 0.0, duration: 1.15) + + if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { - let localSourceFrame = self.view.convert(CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: CGSize(width: originalProjectedContentViewFrame.1.width, height: originalProjectedContentViewFrame.1.height)), to: self.scrollNode.view) - + let localSourceFrame = self.view.convert( + CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, + y: originalProjectedContentViewFrame.1.minY), + size: CGSize(width: originalProjectedContentViewFrame.1.width, + height: originalProjectedContentViewFrame.1.height)), + to: self.scrollNode.view + ) + self.contentContainerNode.layer.animateSpring(from: min(localSourceFrame.width / self.contentContainerNode.frame.width, localSourceFrame.height / self.contentContainerNode.frame.height) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping) + // if let source = self.contentContainerNode.controllerNode { // // } @@ -276,12 +316,16 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi self.view.insertSubview(snapshotView, belowSubview: self.contentContainerNode.view) snapshotView.layer.animateSpring(from: NSValue(cgPoint: localSourceFrame.center), to: NSValue(cgPoint: CGPoint(x: self.contentContainerNode.frame.midX, y: self.contentContainerNode.frame.minY + localSourceFrame.height / 2.0)), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) - //snapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (self.contentContainerNode.frame.width / localSourceFrame.width) as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) + snapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (self.contentContainerNode.frame.width / localSourceFrame.width) as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() }) } } + + let targetPosition = CGPoint(x: localSourceFrame.midY, y: localSourceFrame.minX + 20) + sourceTitleSnapshot.animateSpring(from: NSValue(cgPoint: sourceTitleSnapshot.position), to: NSValue(cgPoint: targetPosition), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping) + self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { [weak self] _ in self?.animatedIn = true }) @@ -310,11 +354,9 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi case .compact: if self.effectView.superview == nil { self.view.insertSubview(self.effectView, at: 0) - if #available(iOS 10.0, *) { - if let propertyAnimator = self.propertyAnimator { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } + if let propertyAnimator = self.propertyAnimator { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) } self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) self.dimNode.alpha = 1.0 @@ -332,40 +374,118 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + let actionsSideInset: CGFloat = layout.safeInsets.left + 12.0 + let contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0) + + let actionsBottomInset: CGFloat = 11.0 + if let contentParentNode = contentContainerNode.controllerNode { var projectedFrame: CGRect = convertFrame(contentParentNode.sourceView.bounds, from: contentParentNode.sourceView, to: self.view) - if let presentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceAndRect() { + if let presentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() { projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) } self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { + let topEdge = max(contentTopInset, presentationArguments!.contentArea().minY) + let constrainedWidth: CGFloat if layout.size.width < layout.size.height { constrainedWidth = layout.size.width } else { constrainedWidth = floor(layout.size.width / 2.0) } + let contentScale = (constrainedWidth - actionsSideInset * 2.0) / constrainedWidth + var contentUnscaledSize: CGSize if case .compact = layout.metrics.widthClass { - contentUnscaledSize = CGSize(width: constrainedWidth, height: max(100.0, layout.size.height)) + let proposedContentHeight: CGFloat + if layout.size.width < layout.size.height { + proposedContentHeight = layout.size.height - topEdge - actionsSideInset - layout.intrinsicInsets.bottom - actionsBottomInset + } else { + proposedContentHeight = layout.size.height - topEdge - topEdge + } + + contentUnscaledSize = CGSize(width: constrainedWidth, height: max(100.0, proposedContentHeight)) if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { contentUnscaledSize = preferredSize } } else { - contentUnscaledSize = CGSize(width: min(layout.size.width, 340.0), height: min(568.0, layout.size.height - layout.intrinsicInsets.bottom)) + let proposedContentHeight = layout.size.height - topEdge - actionsSideInset - layout.intrinsicInsets.bottom + + contentUnscaledSize = CGSize(width: min(layout.size.width, 340.0), height: min(568.0, proposedContentHeight)) if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { contentUnscaledSize = preferredSize } } - let contentSize = CGSize(width: floor(contentUnscaledSize.width), height: floor(contentUnscaledSize.height)) + let contentSize = CGSize(width: floor(contentUnscaledSize.width * contentScale), height: floor(contentUnscaledSize.height * contentScale)) self.contentContainerNode.updateLayout(size: contentUnscaledSize, scaledSize: contentSize, transition: transition) - let scrollContentSize = layout.size + + let contentActionsSpacing: CGFloat = .zero + let actionsSize: CGSize = .zero + + let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height) + var originalActionsFrame: CGRect + var originalContentFrame: CGRect + var contentHeight: CGFloat + + if case .compact = layout.metrics.widthClass { + if layout.size.width < layout.size.height { + let sideInset = floor((layout.size.width - contentSize.width) / 2.0) + originalActionsFrame = CGRect(origin: CGPoint(x: sideInset, y: min(maximumActionsFrameOrigin, floor((layout.size.height - actionsSideInset - contentSize.height) / 2.0) + contentSize.height)), size: actionsSize) + originalContentFrame = CGRect(origin: CGPoint(x: sideInset, y: originalActionsFrame.minY - contentActionsSpacing - contentSize.height), size: contentSize) + if originalContentFrame.minY < topEdge { + let requiredOffset = topEdge - originalContentFrame.minY + let availableOffset = max(0.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - originalActionsFrame.maxY) + let offset = min(requiredOffset, availableOffset) + originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset) + originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset) + } + contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset) + } else { + originalContentFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width - actionsSideInset - actionsSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize) + originalActionsFrame = CGRect(origin: CGPoint(x: originalContentFrame.maxX + actionsSideInset, y: max(topEdge, originalContentFrame.minY)), size: actionsSize) + contentHeight = max(layout.size.height, max(originalContentFrame.maxY, originalActionsFrame.maxY)) + } + } else { + originalContentFrame = CGRect(origin: CGPoint(x: floor(originalProjectedContentViewFrame.1.midX - contentSize.width / 2.0), y: floor(originalProjectedContentViewFrame.1.midY - contentSize.height / 2.0)), size: contentSize) + originalContentFrame.origin.x = min(originalContentFrame.origin.x, layout.size.width - actionsSideInset - contentSize.width) + originalContentFrame.origin.x = max(originalContentFrame.origin.x, actionsSideInset) + originalContentFrame.origin.y = min(originalContentFrame.origin.y, layout.size.height - layout.intrinsicInsets.bottom - actionsSideInset - contentSize.height) + originalContentFrame.origin.y = max(originalContentFrame.origin.y, contentTopInset) + if originalContentFrame.maxX <= layout.size.width - actionsSideInset - actionsSize.width - contentActionsSpacing { + originalActionsFrame = CGRect(origin: CGPoint(x: originalContentFrame.maxX + contentActionsSpacing, y: originalContentFrame.minY), size: actionsSize) + if originalActionsFrame.maxX > layout.size.width - actionsSideInset { + let offset = originalActionsFrame.maxX - (layout.size.width - actionsSideInset) + originalActionsFrame.origin.x -= offset + originalContentFrame.origin.x -= offset + } + } else { + originalActionsFrame = CGRect(origin: CGPoint(x: originalContentFrame.minX - contentActionsSpacing - actionsSize.width, y: originalContentFrame.minY), size: actionsSize) + if originalActionsFrame.minX < actionsSideInset { + let offset = actionsSideInset - originalActionsFrame.minX + originalActionsFrame.origin.x += offset + originalContentFrame.origin.x += offset + } + } + contentHeight = layout.size.height + contentHeight = max(contentHeight, originalActionsFrame.maxY + actionsBottomInset) + contentHeight = max(contentHeight, originalContentFrame.maxY + actionsBottomInset) + } + + let scrollContentSize = CGSize(width: layout.size.width, height: contentSize.height) if self.scrollNode.view.contentSize != scrollContentSize { self.scrollNode.view.contentSize = scrollContentSize } - let contentContainerFrame = CGRect(origin: CGPoint(x: floor(originalProjectedContentViewFrame.1.midX - contentSize.width / 2.0), y: floor(originalProjectedContentViewFrame.1.midY - contentSize.height / 2.0)), size: contentSize) - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) + let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset) + + let contentContainerFrame = originalContentFrame + transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame.offsetBy(dx: 0.0, dy: -overflowOffset)) + + + +// let contentContainerFrame = CGRect(origin: CGPoint(x: floor(originalProjectedContentViewFrame.1.midX - contentSize.width / 2.0), y: floor(originalProjectedContentViewFrame.1.midY - contentSize.height / 2.0)), size: contentSize) +// transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) } } @@ -394,11 +514,80 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi func updatePresentationArguments(_ arguments: ChatListPreviewPresentationData?, controller: ViewController?) { self.presentationArguments = arguments self.controller = controller - } - + guard + let (sourceNode, sourceFrame) = arguments?.sourceNodeAndRect(), + let contentArea = arguments?.contentArea(), + let sourceChatItem = sourceNode.supernode as? ChatListItemNode, + let controller = controller + else { return } + + self.gesture?.endPressedAppearance() + + let titleNode = sourceChatItem.titleNode + let titleSnapshot = titleNode.layer.snapshotContentTree() + let titleSourceframe = sourceChatItem.convert(sourceChatItem.titleNode.frame, to: nil) -} + let avatarNode = sourceChatItem.avatarContainerNode + let avatarSnapshot = avatarNode.layer.snapshotContentTree() + let avatarSourceFrame = sourceChatItem.convert(avatarNode.frame, to: nil) + + titleNode.isHidden = true + avatarNode.isHidden = true + + guard + let chatSnapshot = sourceChatItem.layer.snapshotContentTree(), + let titleSnapshot, + let avatarSnapshot, + let navigationBar = controller.navigationBar, + let avatarNode = navigationBar.subnodes?.compactMap({ $0.subnodes }).flatMap({ $0 }).first(where: { $0 is NavigationButtonNode }) as? NavigationButtonNode, + let titleView = controller.navigationItem.titleView as? ChatTitleView, + let navigationBackgroundSnapshot = navigationBar.layer.snapshotContentTree() + else { return } + + let finalFrame = contentArea + let titleFinalFrame = titleView.frame //update according to current content area + let targetAvatarSize = avatarNode.bounds.size + let avatarFinalFrame = CGRect(x: titleFinalFrame.minX / 2 - targetAvatarSize.width / 2, y: avatarNode.frame.minY, width: targetAvatarSize.width, height: targetAvatarSize.height) + let targetAvatarStart = CGRect(origin: CGPoint(x: avatarNode.frame.minX, y: sourceFrame.midY - 12), size: CGSize(width: 24, height: 24)) + let targtetAvatarEnd = titleView.frame + + let targetBackgroundMaskLayer = CAShapeLayer() + targetBackgroundMaskLayer.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: sourceFrame.size), cornerRadius: .zero).cgPath + navigationBackgroundSnapshot.mask = targetBackgroundMaskLayer + + chatSnapshot.bounds = sourceChatItem.convert(sourceFrame, to: nil) + chatSnapshot.position = sourceChatItem.convert(sourceFrame.center, to: nil) + self.layer.addSublayer(chatSnapshot) + + titleSnapshot.bounds = sourceChatItem.convert(titleSourceframe, to: nil) + titleSnapshot.position = sourceChatItem.convert(titleSourceframe.center, to: nil) + self.layer.addSublayer(titleSnapshot) + avatarSnapshot.bounds = sourceChatItem.convert(avatarSourceFrame, to: nil) + avatarSnapshot.position = sourceChatItem.convert(avatarSourceFrame.center, to: nil) + self.layer.addSublayer(avatarSnapshot) + + self.transitionParams = TransitionParams(contentArea: contentArea, + sourceMessageFrame: sourceFrame, + sourceChatItemSnapshot: chatSnapshot, + sourceTitleSnapshot: titleSnapshot, + sourceTitleFrame: titleSourceframe, + sourceAvatarSnapshot: avatarSnapshot, + sourceAvatarStartFrame: avatarSourceFrame, + sourceAvatarFinalFrame: avatarFinalFrame, + targetTitleView: titleView, + targetTitleViewFrame: titleFinalFrame, + targetAvatarNode: avatarNode, + targetAvatarFrameStart: targetAvatarStart, + targetAvatarFrameEnd: targtetAvatarEnd, + targetBackgroundLayer: navigationBackgroundSnapshot, + targetBackgroundMaskLayer: targetBackgroundMaskLayer, + targetBackgroundStartFrame: sourceFrame, + targetBackgroundEndFrame: finalFrame) + + print("calculated transition params: \(self.transitionParams!) controller layout size: \(String(describing: controller.currentlyAppliedLayout)) contentArea: \(contentArea)") + } +} public final class ChatListPreviewController: ViewController { private weak var recognizer: TapLongTapOrDoubleTapGestureRecognizer? @@ -484,24 +673,27 @@ public final class ChatListPreviewController: ViewController { override public func dismiss(completion: (() -> Void)? = nil) { guard let arguments = self.presentationArguments as? ChatListPreviewPresentationData, - let (sourceNode, _) = arguments.sourceAndRect() + let (sourceNode, _) = arguments.sourceNodeAndRect() else { return } - + self.controllerNode.animateOut(targetNode: sourceNode, completion: completion) } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) + self.chatPrevewController.updateNavigationBarLayout(layout, transition: transition) self.controllerNode.updateLayout(layout, transition: transition) - } public func updateChatLocation(_ chatLocation: ChatLocation) { self.chatLocation = chatLocation let chatController = context.sharedContext.makeChatController(context: self.context, chatLocation: chatLocation, subject: nil, botStart: nil, mode: .standard(previewing: true)) self.chatPrevewController = chatController - self.controllerNode.updatePresentationArguments(self.presentationArguments as? ChatListPreviewPresentationData, - controller: chatController) + if let layout = self.currentlyAppliedLayout { + chatController.updateNavigationBarLayout(layout, transition: .immediate) + self.controllerNode.updatePresentationArguments(self.presentationArguments as? ChatListPreviewPresentationData, + controller: chatController) + } // if self.chatLocation != chatLocation { // } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 52f0f531ce0..d236f0d5980 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -11272,7 +11272,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let arguments = self.presentationArguments as? ChatListPreviewPresentationData, - let (sourceNode, sourceRect) = arguments.sourceAndRect() + let (sourceNode, sourceRect) = arguments.sourceNodeAndRect() { let containerNode = ASDisplayNode() containerNode.backgroundColor = .white.withAlphaComponent(0.5) From 8571f2e6893a65d736a5dabf14d6a08ffb96d8dd Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sun, 3 Sep 2023 09:48:21 -0300 Subject: [PATCH 33/34] WIP chat preview transition animation updates --- .../Sources/ChatListPreviewController.swift | 692 +++++++++++------- 1 file changed, 418 insertions(+), 274 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListPreviewController.swift b/submodules/ChatListUI/Sources/ChatListPreviewController.swift index 803db7416a0..2be067f878f 100644 --- a/submodules/ChatListUI/Sources/ChatListPreviewController.swift +++ b/submodules/ChatListUI/Sources/ChatListPreviewController.swift @@ -5,26 +5,26 @@ // Created by Bogdan Redkin on 02/09/2023. // -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import Postbox -import TelegramCore -import SwiftSignalKit import AccountContext -import ContextUI +import AsyncDisplayKit import ChatAvatarNavigationNode import ChatTitleView +import ContextUI +import Display +import Foundation +import Postbox +import SwiftSignalKit +import TelegramCore import TelegramPresentationData +import UIKit final class ChatListPreviewContentContainerNode: ASDisplayNode { public var controllerNode: ContextControllerContentNode? - + override public init() { super.init() } - + public func updateLayout(size: CGSize, scaledSize: CGSize, transition: ContainedViewLayoutTransition) { guard let contentNode = self.controllerNode else { return } transition.updatePosition(node: contentNode, position: CGPoint(x: scaledSize.width / 2.0, y: scaledSize.height / 2.0)) @@ -52,21 +52,19 @@ final class ChatListPreviewContentContainerNode: ASDisplayNode { func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> CGRect { let sourceWindowFrame = fromView.convert(frame, to: nil) var targetWindowFrame = toView.convert(sourceWindowFrame, from: nil) - + if let fromWindow = fromView.window, let toWindow = toView.window { targetWindowFrame.origin.x += toWindow.bounds.width - fromWindow.bounds.width } return targetWindowFrame } - final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { - private let context: AccountContext private let presentationData: PresentationData - + private var validLayout: ContainerViewLayout? - + private let effectView: UIVisualEffectView private var propertyAnimator: AnyObject? private var displayLinkAnimator: DisplayLinkAnimator? @@ -74,48 +72,24 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi private let withoutBlurDimNode: ASDisplayNode private let dismissNode: ASDisplayNode private let dismissAccessibilityArea: AccessibilityAreaNode - private let clippingNode: ASDisplayNode private let scrollNode: ASScrollNode private var originalProjectedContentViewFrame: (CGRect, CGRect)? - private var contentAreaInScreenSpace: CGRect? - private var customPosition: CGPoint? +// private var contentAreaInScreenSpace: CGRect? +// private var customPosition: CGPoint? private let contentContainerNode: ChatListPreviewContentContainerNode private weak var gesture: ContextGesture? - + var dismiss: (() -> Void)? var cancel: (() -> Void)? - + private var animatedIn = false private var isAnimatingOut = false private var didCompleteAnimationIn = false var presentationArguments: ChatListPreviewPresentationData? var controller: ViewController? - var transitionParams: TransitionParams? - - struct TransitionParams { - var contentArea: CGRect - - var sourceMessageFrame: CGRect - var sourceChatItemSnapshot: CALayer - var sourceTitleSnapshot: CALayer - var sourceTitleFrame: CGRect - var sourceAvatarSnapshot: CALayer - var sourceAvatarStartFrame: CGRect - var sourceAvatarFinalFrame: CGRect - - var targetTitleView: ChatTitleView - var targetTitleViewFrame: CGRect - var targetAvatarNode: NavigationButtonNode - var targetAvatarFrameStart: CGRect - var targetAvatarFrameEnd: CGRect - var targetBackgroundLayer: CALayer - var targetBackgroundMaskLayer: CALayer - var targetBackgroundStartFrame: CGRect - var targetBackgroundEndFrame: CGRect - } init(context: AccountContext, gesture: ContextGesture?) { self.context = context @@ -132,23 +106,23 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi } self.effectView.alpha = 0.0 } - + self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = presentationData.theme.contextMenu.dimColor self.dimNode.alpha = 0.0 - + self.withoutBlurDimNode = ASDisplayNode() self.withoutBlurDimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.4) self.withoutBlurDimNode.alpha = 0.0 - + self.dismissNode = ASDisplayNode() self.dismissAccessibilityArea = AccessibilityAreaNode() self.dismissAccessibilityArea.accessibilityLabel = presentationData.strings.VoiceOver_DismissContextMenu self.dismissAccessibilityArea.accessibilityTraits = .button - + self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true - + self.scrollNode = ASScrollNode() self.scrollNode.canCancelAllTouchesInViews = true self.scrollNode.view.delaysContentTouches = false @@ -156,18 +130,18 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi if #available(iOS 11.0, *) { self.scrollNode.view.contentInsetAdjustmentBehavior = .never } - + self.contentContainerNode = ChatListPreviewContentContainerNode() - + super.init() self.scrollNode.view.delegate = self - + self.view.addSubview(self.effectView) self.addSubnode(self.dimNode) self.addSubnode(self.withoutBlurDimNode) self.addSubnode(self.clippingNode) - + self.clippingNode.addSubnode(self.scrollNode) self.scrollNode.addSubnode(self.dismissNode) self.scrollNode.addSubnode(self.dismissAccessibilityArea) @@ -179,7 +153,7 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi return true } } - + deinit { if let propertyAnimator = self.propertyAnimator { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { @@ -191,63 +165,57 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi override func didLoad() { super.didLoad() - + self.dismissNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTapped))) } - + @objc private func dimNodeTapped() { guard self.animatedIn else { return } self.cancel?() } - + func initializeContent() { - guard let presentationArguments, - let controller, - let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() + guard + let presentationArguments, + let controller, + let (sourceNode, sourceNodeRect) = presentationArguments.sourceNodeAndRect() else { return } - + let controlleContentrNode = ContextControllerContentNode(sourceView: sourceNode.view, controller: controller, tapped: { print("tapped") }) - + self.contentContainerNode.controllerNode = controlleContentrNode self.scrollNode.addSubnode(self.contentContainerNode) self.contentContainerNode.clipsToBounds = true self.contentContainerNode.cornerRadius = 14.0 self.contentContainerNode.addSubnode(controlleContentrNode) - - let projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) + + let projectedFrame = convertFrame(sourceNodeRect, from: sourceNode.view, to: self.view) self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) } - + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.cancel?() } } - + func animateIn() { self.gesture?.endPressedAppearance() - guard - let transitionParams - else { return } - let sourceTitleSnapshot = transitionParams.sourceTitleSnapshot -// let projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) - let sourceChatItemSnapshot = transitionParams.sourceChatItemSnapshot - let sourceAvatarSnapshot = transitionParams.sourceAvatarSnapshot - var updatedContentAreaInScreenSpace = transitionParams.contentArea - self.originalProjectedContentViewFrame = (transitionParams.sourceMessageFrame, transitionParams.sourceMessageFrame) - - updatedContentAreaInScreenSpace.origin.x = 0.0 - updatedContentAreaInScreenSpace.size.width = self.bounds.width - self.contentAreaInScreenSpace = updatedContentAreaInScreenSpace +// guard +// let tp = transitionParams +// else { return } +// let sourceTitleSnapshot = transitionParams.sourceTitleSnapshot +// let sourceChatItemSnapshot = tp.sourceChatItemSnapshot +// let sourceAvatarSnapshot = transitionParams.sourceAvatarSnapshot if let validLayout = self.validLayout { self.updateLayout(validLayout, transition: .immediate) } - + if !self.dimNode.isHidden { self.dimNode.alpha = 1.0 self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) @@ -255,101 +223,226 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi self.withoutBlurDimNode.alpha = 1.0 self.withoutBlurDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } - - if let propertyAnimator = self.propertyAnimator { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } - self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) - self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.propertyAnimator = UIViewPropertyAnimator(duration: 0.3 * UIView.animationDurationFactor(), curve: .easeInOut, animations: { - }) +// +// if let propertyAnimator = self.propertyAnimator { +// let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator +// propertyAnimator?.stopAnimation(true) +// } +// self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) +// self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) +// self.propertyAnimator = UIViewPropertyAnimator(duration: 0.3 * UIView.animationDurationFactor(), curve: .easeInOut, animations: { +// }) +// - if let _ = self.propertyAnimator { - self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.3 * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in - (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value - }, completion: { [weak self] in - self?.didCompleteAnimationIn = true - }) - } else { - UIView.animate(withDuration: 0.3, animations: { - self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) - }, completion: { [weak self] _ in - self?.didCompleteAnimationIn = true -// self?.actionsContainerNode.animateIn() - }) - } +// let springDuration: Double = 0.52 +// let springDamping: CGFloat = 110.0 + +// self.contentContainerNode.allowsGroupOpacity = true +// self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 1.15, completion: { [weak self] _ in +// self?.contentContainerNode.allowsGroupOpacity = false +// }) + +// sourceAvatarSnapshot.animateAlpha(from: 1.0, to: 0.0, duration: 1.15) +// sourceChatItemSnapshot.animateAlpha(from: 1.0, to: 0.0, duration: 1.15) - let springDuration: Double = 0.52 - let springDamping: CGFloat = 110.0 - - self.contentContainerNode.allowsGroupOpacity = true - self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 1.15, completion: { [weak self] _ in - self?.contentContainerNode.allowsGroupOpacity = false - }) - - - sourceAvatarSnapshot.animateAlpha(from: 1.0, to: 0.0, duration: 1.15) - sourceChatItemSnapshot.animateAlpha(from: 1.0, to: 0.0, duration: 1.15) - - if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { let localSourceFrame = self.view.convert( - CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, - y: originalProjectedContentViewFrame.1.minY), - size: CGSize(width: originalProjectedContentViewFrame.1.width, - height: originalProjectedContentViewFrame.1.height)), + CGRect( + origin: CGPoint( + x: originalProjectedContentViewFrame.1.minX, + y: originalProjectedContentViewFrame.1.minY + ), + size: CGSize( + width: originalProjectedContentViewFrame.1.width, + height: originalProjectedContentViewFrame.1.height + ) + ), to: self.scrollNode.view ) - self.contentContainerNode.layer.animateSpring(from: min(localSourceFrame.width / self.contentContainerNode.frame.width, localSourceFrame.height / self.contentContainerNode.frame.height) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping) - -// if let source = self.contentContainerNode.controllerNode { -// -// } - - let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y) - if let controller = self.contentContainerNode.controllerNode { - let snapshotView: UIView? = nil// controller.sourceNode.view.snapshotContentTree() - if let snapshotView = snapshotView { - controller.sourceView.isHidden = true - - self.view.insertSubview(snapshotView, belowSubview: self.contentContainerNode.view) - snapshotView.layer.animateSpring(from: NSValue(cgPoint: localSourceFrame.center), to: NSValue(cgPoint: CGPoint(x: self.contentContainerNode.frame.midX, y: self.contentContainerNode.frame.minY + localSourceFrame.height / 2.0)), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) - snapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (self.contentContainerNode.frame.width / localSourceFrame.width) as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } + print("locate source frame: \(localSourceFrame) originalProjectedContentViewFrame: \(originalProjectedContentViewFrame)") + + CATransaction.begin() + CATransaction.setCompletionBlock { + print("animation finsihed") + } + CATransaction.setAnimationDuration(0.5) + + if let _ = self.propertyAnimator { + self.displayLinkAnimator = DisplayLinkAnimator(duration: 1.15 * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in + (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value + }, completion: { [weak self] in + self?.didCompleteAnimationIn = true + }) + } else { + UIView.animate(withDuration: 1.15, animations: { + self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) + }, completion: { [weak self] _ in + self?.didCompleteAnimationIn = true + // self?.actionsContainerNode.animateIn() + }) + } + CATransaction.commit() + //prepare variables to transition snapshots + guard + let controller = self.contentContainerNode.controllerNode, + let (sourceNode, sourceFrame) = self.presentationArguments?.sourceNodeAndRect(), + let contentArea = self.presentationArguments?.contentArea(), + let sourceChatItem = sourceNode.supernode as? ChatListItemNode else { return } + + let titleNode = sourceChatItem.titleNode + let titleSnapshot = titleNode.view.snapshotContentTree() + let titleSourceframe = self.view.convert(titleNode.view.frame, from: titleNode.view) + + let avatarNode = sourceChatItem.avatarContainerNode + let avatarSnapshot = avatarNode.view.snapshotContentTree() + let avatarSourceFrame = self.view.convert(avatarNode.view.frame, from: avatarNode.view) + + titleNode.isHidden = true + avatarNode.isHidden = true + + guard + let chatSnapshot = sourceChatItem.view.snapshotContentTree(), + let titleView = controller.controller.navigationItem.titleView as? ChatTitleView, + let navigationBackgroundSnapshot = controller.controller.navigationBar?.layer.snapshotContentTree(), + let titleSnapshot, + let avatarSnapshot + else { + titleNode.isHidden = false + avatarNode.isHidden = false + return } + + titleNode.isHidden = false + avatarNode.isHidden = false - let targetPosition = CGPoint(x: localSourceFrame.midY, y: localSourceFrame.minX + 20) - sourceTitleSnapshot.animateSpring(from: NSValue(cgPoint: sourceTitleSnapshot.position), to: NSValue(cgPoint: targetPosition), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping) - self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { [weak self] _ in - self?.animatedIn = true - }) + // snapshot transition and resize + CATransaction.begin() + CATransaction.setAnimationDuration(0.35) + + let targeBgPosition = CGPoint(x: self.contentContainerNode.frame.midX, y: self.contentContainerNode.frame.minY + localSourceFrame.height / 2.0) + _ = self.contentContainerNode.frame.size + + let sourceMessageFrameStart = self.view.convert(sourceFrame, from: titleView) + + let navigationFrameStart = sourceMessageFrameStart.insetBy(dx: .zero, dy: -15) + // let contentViewFrame = originalProjectedContentViewFrame.1 + let navigationFrameEnd = self.view.convert(controller.controller.navigationBar!.frame, from: controller.controller.navigationBar!.view) + + let convertedFrame = self.view + .convert(titleView.frame, from: titleView) // CGRect(x: (contentViewFrame.width - titleSourceframe.width) / 2, y: contentViewFrame.minY + titleSourceframe.height / 2, width: titleSourceframe.width, height: titleSourceframe.height) + let titleFinalFrame = CGRect( + x: (originalProjectedContentViewFrame.1.width - convertedFrame.width) / 2, + y: convertedFrame.minY - (titleView.frame.height - convertedFrame.height.rounded()) * 2 - 5, + width: convertedFrame.width, + height: titleView.frame.height + ) + + // let heightScale = titleSourceframe.height / titleFinalFrame.height + + let targetAvatarSize = CGSize(width: 36, height: 36) + let avatarFinalFrame = CGRect( + x: titleFinalFrame.minX - targetAvatarSize.width / 2, + y: titleFinalFrame.midY - targetAvatarSize.height / 2, + width: targetAvatarSize.width, + height: targetAvatarSize.height + ) + + let smallAvatarHeight = CGFloat(20) + + let sourceMessageFrameFinal = CGRect( + x: avatarFinalFrame.minX, + y: avatarFinalFrame.midY - sourceMessageFrameStart.height / 2, + width: sourceMessageFrameStart.width, + height: sourceMessageFrameStart.height + ) + + let startedBackgrounMaskPath = UIBezierPath(roundedRect: sourceMessageFrameStart, cornerRadius: .zero) + let targtePath = UIBezierPath(roundedRect: contentArea, cornerRadius: 40) + + let targetBackgroundMaskLayer = CAShapeLayer() + targetBackgroundMaskLayer.frame = clippingNode.layer.bounds + targetBackgroundMaskLayer.position = targeBgPosition + navigationBackgroundSnapshot.mask = targetBackgroundMaskLayer + navigationBackgroundSnapshot.masksToBounds = true + + targetBackgroundMaskLayer.path = targtePath.cgPath + targetBackgroundMaskLayer.path = targtePath.cgPath + clippingNode.layer.mask = targetBackgroundMaskLayer + let animation = clippingNode.layer.makeAnimation(from: startedBackgrounMaskPath.cgPath, to: targtePath.cgPath, keyPath: "path", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3) { _ in + print("mask path updated") + } + animation.fillMode = .forwards + targetBackgroundMaskLayer.add(animation, forKey: "path") + + print("titleSourceframe: \(titleSourceframe) titleFinalFrame: \(titleFinalFrame) navigationFrameStar: \(navigationFrameStart)") + + avatarSnapshot.layer.animateScale(from: 1.0, to: smallAvatarHeight / avatarFinalFrame.height, duration: 1.0) + + chatSnapshot.frame = sourceMessageFrameStart + chatSnapshot.layer.addSublayer(navigationBackgroundSnapshot) + self.view.addSubview(chatSnapshot) + chatSnapshot.layer.animatePosition(from: sourceMessageFrameStart.center, to: sourceMessageFrameFinal.center, duration: 0.3, removeOnCompletion: true) { _ in + print("title snapshot animation finished") + // chatSnapshot.removeFromSuperview() + } + chatSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false) + + titleSnapshot.frame = titleSourceframe + self.view.addSubview(titleSnapshot) + titleSnapshot.layer.animatePosition(from: titleSourceframe.center, to: titleFinalFrame.center, duration: 0.3, removeOnCompletion: true) { _ in + print("title snapshot animation finished") + chatSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 1.5, removeOnCompletion: false) + // titleSnapshot.removeFromSuperview() + } + + + avatarSnapshot.frame = avatarSourceFrame + self.view.addSubview(avatarSnapshot) + avatarSnapshot.layer.animatePosition(from: avatarSourceFrame.center, to: avatarFinalFrame.center, duration: 0.3, removeOnCompletion: true) { _ in + print("avatarSnapshot snapshot animation finished") + avatarSnapshot.removeFromSuperview() + } + avatarSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false) + + + let navigationSnapshotView = UIView() + navigationSnapshotView.layer.addSublayer(navigationBackgroundSnapshot) + navigationSnapshotView.frame = navigationFrameStart + navigationSnapshotView.layer.animatePosition(from: navigationFrameStart.center, to: navigationFrameEnd.center, duration: 0.3, removeOnCompletion: true) { _ in + print("navigationSnapshotView snapshot animation finished") + navigationSnapshotView.removeFromSuperview() + } + +// let pf = originalProjectedContentViewFrame.1 + + CATransaction.commit() + +// self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: targetBackgroundMaskLay cgPath), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { [weak self] _ in +// self?.animatedIn = true +// }) } } - + func updateLayout() { if let layout = self.validLayout { self.updateLayout(layout, transition: .immediate) } } - + func updateLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { if self.isAnimatingOut { return } self.validLayout = layout - + let targetFrame = CGRect(origin: CGPoint(), size: layout.size) transition.updateFrame(view: self.effectView, frame: targetFrame) transition.updateFrame(node: self.dimNode, frame: targetFrame) transition.updateFrame(node: self.withoutBlurDimNode, frame: targetFrame) - + switch layout.metrics.widthClass { case .compact: if self.effectView.superview == nil { @@ -373,21 +466,21 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi } transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let actionsSideInset: CGFloat = layout.safeInsets.left + 12.0 + + let actionsSideInset: CGFloat = layout.safeInsets.left + 11 let contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0) let actionsBottomInset: CGFloat = 11.0 - + if let contentParentNode = contentContainerNode.controllerNode { var projectedFrame: CGRect = convertFrame(contentParentNode.sourceView.bounds, from: contentParentNode.sourceView, to: self.view) if let presentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() { projectedFrame = convertFrame(sourceRect, from: sourceNode.view, to: self.view) } self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) - if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { - let topEdge = max(contentTopInset, presentationArguments!.contentArea().minY) - + if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentArea = self.presentationArguments?.contentArea() { + let topEdge = max(contentTopInset, contentArea.minY) + let constrainedWidth: CGFloat if layout.size.width < layout.size.height { constrainedWidth = layout.size.width @@ -395,7 +488,7 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi constrainedWidth = floor(layout.size.width / 2.0) } let contentScale = (constrainedWidth - actionsSideInset * 2.0) / constrainedWidth - + var contentUnscaledSize: CGSize if case .compact = layout.metrics.widthClass { let proposedContentHeight: CGFloat @@ -406,28 +499,52 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi } contentUnscaledSize = CGSize(width: constrainedWidth, height: max(100.0, proposedContentHeight)) - if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { + if + let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout( + size: contentUnscaledSize, + metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), + deviceMetrics: layout.deviceMetrics, + intrinsicInsets: UIEdgeInsets(), + safeInsets: UIEdgeInsets(), + additionalInsets: UIEdgeInsets(), + statusBarHeight: nil, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + )) { contentUnscaledSize = preferredSize } } else { let proposedContentHeight = layout.size.height - topEdge - actionsSideInset - layout.intrinsicInsets.bottom - contentUnscaledSize = CGSize(width: min(layout.size.width, 340.0), height: min(568.0, proposedContentHeight)) - if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { + contentUnscaledSize = CGSize(width: min(layout.size.width, 340.0), height: min(400.0, proposedContentHeight)) + if + let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout( + size: contentUnscaledSize, + metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), + deviceMetrics: layout.deviceMetrics, + intrinsicInsets: UIEdgeInsets(), + safeInsets: UIEdgeInsets(), + additionalInsets: UIEdgeInsets(), + statusBarHeight: nil, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + )) { contentUnscaledSize = preferredSize } } let contentSize = CGSize(width: floor(contentUnscaledSize.width * contentScale), height: floor(contentUnscaledSize.height * contentScale)) self.contentContainerNode.updateLayout(size: contentUnscaledSize, scaledSize: contentSize, transition: transition) - + let contentActionsSpacing: CGFloat = .zero let actionsSize: CGSize = .zero - + let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height) var originalActionsFrame: CGRect var originalContentFrame: CGRect var contentHeight: CGFloat - + if case .compact = layout.metrics.widthClass { if layout.size.width < layout.size.height { let sideInset = floor((layout.size.width - contentSize.width) / 2.0) @@ -471,121 +588,157 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi contentHeight = max(contentHeight, originalActionsFrame.maxY + actionsBottomInset) contentHeight = max(contentHeight, originalContentFrame.maxY + actionsBottomInset) } - + let scrollContentSize = CGSize(width: layout.size.width, height: contentSize.height) + print("originalContentFrame: \(originalContentFrame) contentTopInset: \(contentTopInset) scrollContentSize: \(scrollContentSize)") + if self.scrollNode.view.contentSize != scrollContentSize { self.scrollNode.view.contentSize = scrollContentSize } - + let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset) - + let contentContainerFrame = originalContentFrame transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame.offsetBy(dx: 0.0, dy: -overflowOffset)) - - - + + if let maskLayer = self.clippingNode.layer.mask as? CAShapeLayer { + let newPath = UIBezierPath(roundedRect: originalContentFrame, cornerRadius: 40) + transition.updatePath(layer: maskLayer, path: newPath.cgPath) + } + // let contentContainerFrame = CGRect(origin: CGPoint(x: floor(originalProjectedContentViewFrame.1.midX - contentSize.width / 2.0), y: floor(originalProjectedContentViewFrame.1.midY - contentSize.height / 2.0)), size: contentSize) // transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) - } } - + transition.updateFrame(node: self.dismissNode, frame: CGRect(origin: CGPoint(), size: self.scrollNode.view.contentSize)) self.dismissAccessibilityArea.frame = CGRect(origin: CGPoint(), size: self.scrollNode.view.contentSize) } - + private func animateBlurBackground(isHidden: Bool) {} + func animateOut(targetNode: ASDisplayNode?, completion: (() -> Void)? = nil) { var dimCompleted = false - + let internalCompletion: () -> Void = { [weak self] in if let strongSelf = self, dimCompleted { strongSelf.dismiss?() } completion?() } - + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in dimCompleted = true internalCompletion() }) } - + func updatePresentationArguments(_ arguments: ChatListPreviewPresentationData?, controller: ViewController?) { self.presentationArguments = arguments self.controller = controller - guard - let (sourceNode, sourceFrame) = arguments?.sourceNodeAndRect(), - let contentArea = arguments?.contentArea(), - let sourceChatItem = sourceNode.supernode as? ChatListItemNode, - let controller = controller - else { return } - - self.gesture?.endPressedAppearance() - - let titleNode = sourceChatItem.titleNode - let titleSnapshot = titleNode.layer.snapshotContentTree() - let titleSourceframe = sourceChatItem.convert(sourceChatItem.titleNode.frame, to: nil) - - let avatarNode = sourceChatItem.avatarContainerNode - let avatarSnapshot = avatarNode.layer.snapshotContentTree() - let avatarSourceFrame = sourceChatItem.convert(avatarNode.frame, to: nil) - - titleNode.isHidden = true - avatarNode.isHidden = true +// guard +// let (sourceNode, sourceFrame) = arguments?.sourceNodeAndRect(), +// let contentArea = arguments?.contentArea(), +// let sourceChatItem = sourceNode.supernode as? ChatListItemNode, +// let controller = controller +// else { return } +// +// self.gesture?.endPressedAppearance() +// +// let titleNode = sourceChatItem.titleNode +// let titleSnapshot = titleNode.layer.snapshotContentTree() +// let titleSourceframe = titleNode.convert(titleNode.frame, to: nil) +// +// let avatarNode = sourceChatItem.avatarContainerNode +// let avatarSnapshot = avatarNode.layer.snapshotContentTree() +// let avatarSourceFrame = avatarNode.convert(avatarNode.frame, to: nil) +// +// titleNode.isHidden = true +// avatarNode.isHidden = true +// +// guard +// let chatSnapshot = sourceChatItem.layer.snapshotContentTree(), +// let sourceMessageNode, +// let titleSnapshot, +// let avatarSnapshot, +// let navigationBar = controller.navigationBar, +// let avatarNode = navigationBar.subnodes?.compactMap({ $0.subnodes }).flatMap({ $0 }).first(where: { $0 is NavigationButtonNode }) as? NavigationButtonNode, +// let titleView = controller.navigationItem.titleView as? ChatTitleView, +// let navigationBackgroundSnapshot = navigationBar.layer.snapshotContentTree() +// else { return } +// +// let sourceMessageFrameStart = sourceNode.convert(sourceFrame, to: nil)//.align(in: finalFrame) +// +// let finalFrame = previewFrame(from: contentArea) +// let navigationFrameStart = sourceMessageFrameStart.insetBy(dx: .zero, dy: -15) +// let navigationFrameEnd = CGRect(origin: finalFrame.origin, size: CGSize(width: finalFrame.width, height: 43.0)) +// +// let titleFinalFrame = CGRect(x: (finalFrame.width - titleSourceframe.width) / 2, y: finalFrame.minX + titleSourceframe.height / 2, width: titleSourceframe.width, height: titleSourceframe.height) +// let targetAvatarSize = CGSize(width: 36, height: 36) +// let avatarFinalFrame = CGRect(x: (titleFinalFrame.minX - targetAvatarSize.width) / 2 + finalFrame.minX, +// y: (navigationFrameEnd.height - targetAvatarSize.height) / 2, width: targetAvatarSize.width, height: targetAvatarSize.height) +// +// let targetAvatarStart = CGRect(origin: CGPoint(x: finalFrame.maxX - 5 - (targetAvatarSize.width / 2), +// y: (sourceMessageFrameStart.minY - (targetAvatarSize.height / 2))), +// size: CGSize(width: targetAvatarSize.width / 2, height: targetAvatarSize.height / 2)) +// +// let contentScale = self.view.contentScaleFactor +// +// chatSnapshot.bounds = sourceMessageFrameStart +// chatSnapshot.contentsScale = contentScale +// chatSnapshot.contentsGravity = .resizeAspect +// chatSnapshot.position = sourceMessageFrameStart.center +// sourceMessageNode.layer.addSublayer(chatSnapshot) +// +// let targetBackgroundMaskLayer = CAShapeLayer() +// targetBackgroundMaskLayer.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: navigationFrameStart.size), cornerRadius: .zero).cgPath +// targetBackgroundMaskLayer.bounds = sourceMessageFrameStart +// targetBackgroundMaskLayer.position = sourceMessageFrameStart.center +// navigationBackgroundSnapshot.mask = targetBackgroundMaskLayer +// sourceMessageNode.layer.addSublayer(navigationBackgroundSnapshot) +// +// titleSnapshot.bounds = titleSourceframe +// titleSnapshot.position = titleSourceframe.center +// titleSnapshot.contentsScale = contentScale +// titleSnapshot.contentsGravity = .resizeAspect +// sourceMessageNode.layer.addSublayer(titleSnapshot) +// +// self.sourceAvatarNode?.bounds = avatarSourceFrame +// self.sourceAvatarNode?.position = avatarSourceFrame.center +// self.sourceAvatarNode?.layer.addSublayer(avatarSnapshot) +// avatarSnapshot.contentsScale = contentScale +// avatarSnapshot.contentsGravity = .resizeAspect +// avatarSnapshot.frame = self.sourceAvatarNode!.layer.bounds +// +// let sourceMessageFrameEnd = CGRect(x: finalFrame.minX + 60, y: navigationFrameEnd.minY, width: sourceMessageFrameStart.width, height: sourceMessageFrameStart.height) +// let targtetAvatarEnd = CGRect(origin: CGPoint(x: finalFrame.maxX - 5 - targetAvatarSize.width, y: finalFrame.minY + 5), size: targetAvatarSize) +// +// +// self.transitionParams = TransitionParams(contentArea: finalFrame, +// sourceMessageFrameStart: sourceMessageFrameStart, +// sourceMessageFrameEnd: sourceMessageFrameEnd, +// sourceChatItemSnapshot: chatSnapshot, +// sourceTitleSnapshot: titleSnapshot, +// sourceTitleFrame: titleSourceframe, + //// sourceAvatarSnapshot: avatarSnapshot, +// sourceAvatarStartFrame: avatarSourceFrame, +// sourceAvatarFinalFrame: avatarFinalFrame, +// targetTitleView: titleView, +// targetTitleViewFrame: titleFinalFrame, +// targetAvatarNode: avatarNode, +// targetAvatarFrameStart: targetAvatarStart, +// targetAvatarFrameEnd: targtetAvatarEnd, +// targetBackgroundLayer: navigationBackgroundSnapshot, +// targetBackgroundMaskLayer: targetBackgroundMaskLayer, +// targetBackgroundStartFrame: navigationFrameStart, +// targetBackgroundEndFrame: navigationFrameEnd) +// +// print("calculated transition params: \(self.transitionParams!)") + } - guard - let chatSnapshot = sourceChatItem.layer.snapshotContentTree(), - let titleSnapshot, - let avatarSnapshot, - let navigationBar = controller.navigationBar, - let avatarNode = navigationBar.subnodes?.compactMap({ $0.subnodes }).flatMap({ $0 }).first(where: { $0 is NavigationButtonNode }) as? NavigationButtonNode, - let titleView = controller.navigationItem.titleView as? ChatTitleView, - let navigationBackgroundSnapshot = navigationBar.layer.snapshotContentTree() - else { return } - - let finalFrame = contentArea - let titleFinalFrame = titleView.frame //update according to current content area - let targetAvatarSize = avatarNode.bounds.size - let avatarFinalFrame = CGRect(x: titleFinalFrame.minX / 2 - targetAvatarSize.width / 2, y: avatarNode.frame.minY, width: targetAvatarSize.width, height: targetAvatarSize.height) - let targetAvatarStart = CGRect(origin: CGPoint(x: avatarNode.frame.minX, y: sourceFrame.midY - 12), size: CGSize(width: 24, height: 24)) - let targtetAvatarEnd = titleView.frame - - let targetBackgroundMaskLayer = CAShapeLayer() - targetBackgroundMaskLayer.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: sourceFrame.size), cornerRadius: .zero).cgPath - navigationBackgroundSnapshot.mask = targetBackgroundMaskLayer - - chatSnapshot.bounds = sourceChatItem.convert(sourceFrame, to: nil) - chatSnapshot.position = sourceChatItem.convert(sourceFrame.center, to: nil) - self.layer.addSublayer(chatSnapshot) - - titleSnapshot.bounds = sourceChatItem.convert(titleSourceframe, to: nil) - titleSnapshot.position = sourceChatItem.convert(titleSourceframe.center, to: nil) - self.layer.addSublayer(titleSnapshot) - - avatarSnapshot.bounds = sourceChatItem.convert(avatarSourceFrame, to: nil) - avatarSnapshot.position = sourceChatItem.convert(avatarSourceFrame.center, to: nil) - self.layer.addSublayer(avatarSnapshot) - - self.transitionParams = TransitionParams(contentArea: contentArea, - sourceMessageFrame: sourceFrame, - sourceChatItemSnapshot: chatSnapshot, - sourceTitleSnapshot: titleSnapshot, - sourceTitleFrame: titleSourceframe, - sourceAvatarSnapshot: avatarSnapshot, - sourceAvatarStartFrame: avatarSourceFrame, - sourceAvatarFinalFrame: avatarFinalFrame, - targetTitleView: titleView, - targetTitleViewFrame: titleFinalFrame, - targetAvatarNode: avatarNode, - targetAvatarFrameStart: targetAvatarStart, - targetAvatarFrameEnd: targtetAvatarEnd, - targetBackgroundLayer: navigationBackgroundSnapshot, - targetBackgroundMaskLayer: targetBackgroundMaskLayer, - targetBackgroundStartFrame: sourceFrame, - targetBackgroundEndFrame: finalFrame) - - print("calculated transition params: \(self.transitionParams!) controller layout size: \(String(describing: controller.currentlyAppliedLayout)) contentArea: \(contentArea)") + private func previewFrame(from contentArea: CGRect) -> CGRect { + let size = CGSize(width: min(self.bounds.width - 22, contentArea.width), height: min(400, contentArea.height)) + return CGRect(x: (self.bounds.width - size.width) / 2, y: (self.bounds.height - size.height) / 2, width: size.width, height: size.height) } } @@ -595,17 +748,17 @@ public final class ChatListPreviewController: ViewController { private var animatedDidAppear = false private var wasDismissed = false - + private var controllerNode: ChatListPreviewControllerNode { return self.displayNode as! ChatListPreviewControllerNode } - + private var animatedIn = false - + private let context: AccountContext private var chatLocation: ChatLocation private var chatPrevewController: ChatController - + public init(context: AccountContext, chatLocation: ChatLocation, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil) { self.context = context self.chatLocation = chatLocation @@ -617,11 +770,11 @@ public final class ChatListPreviewController: ViewController { self.lockOrientation = true self.blocksBackgroundWhenInOverlay = true } - - required public init(coder aDecoder: NSCoder) { + + @available(*, unavailable) public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override public func loadDisplayNode() { self.displayNode = ChatListPreviewControllerNode(context: context, gesture: gesture) self.controllerNode.dismiss = { [weak self] in @@ -632,67 +785,58 @@ public final class ChatListPreviewController: ViewController { } self.displayNodeDidLoad() } - - public override func viewWillAppear(_ animated: Bool) { + + override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if let _ = self.presentationArguments as? ChatListPreviewPresentationData { self.updateChatLocation(self.chatLocation) self.ready.set(.single(true)) } } - + override public func viewDidAppear(_ animated: Bool) { if self.ignoreAppearanceMethodInvocations() { return } super.viewDidAppear(animated) - let _ = (self.ready.get() |> deliverOnMainQueue).start(next: { value in + _ = (self.ready.get() |> deliverOnMainQueue).start(next: { value in guard value else { return } - + self.controllerNode.initializeContent() - + print("content is ready: \(value)") - if !self.wasDismissed && !self.animatedDidAppear { + if !self.wasDismissed, !self.animatedDidAppear { self.animatedDidAppear = true self.controllerNode.animateIn() } }) - -// super.viewDidAppear(animated) -// guard -// let arguments = self.presentationArguments as? ChatListPreviewPresentationData, -// let (sourceNode, _) = arguments.sourceAndRect() -// else { return } -// -// if !self.animatedIn { -// self.animatedIn = true -// self.controllerNode.animateIn(sourceNode: sourceNode) -// } } - + override public func dismiss(completion: (() -> Void)? = nil) { guard let arguments = self.presentationArguments as? ChatListPreviewPresentationData, let (sourceNode, _) = arguments.sourceNodeAndRect() else { return } - + self.controllerNode.animateOut(targetNode: sourceNode, completion: completion) } - + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) self.chatPrevewController.updateNavigationBarLayout(layout, transition: transition) self.controllerNode.updateLayout(layout, transition: transition) } - + public func updateChatLocation(_ chatLocation: ChatLocation) { self.chatLocation = chatLocation let chatController = context.sharedContext.makeChatController(context: self.context, chatLocation: chatLocation, subject: nil, botStart: nil, mode: .standard(previewing: true)) self.chatPrevewController = chatController if let layout = self.currentlyAppliedLayout { chatController.updateNavigationBarLayout(layout, transition: .immediate) - self.controllerNode.updatePresentationArguments(self.presentationArguments as? ChatListPreviewPresentationData, - controller: chatController) + self.controllerNode.updatePresentationArguments( + self.presentationArguments as? ChatListPreviewPresentationData, + controller: chatController + ) } // if self.chatLocation != chatLocation { From 287858a5910d52d0c81058504e594aad542b8941 Mon Sep 17 00:00:00 2001 From: aldammit <7161890+aldammit@users.noreply.github.com> Date: Sun, 3 Sep 2023 17:48:39 -0300 Subject: [PATCH 34/34] revert temp changes --- .../ChatListUI/Sources/ChatListPreviewController.swift | 8 ++++---- versions.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListPreviewController.swift b/submodules/ChatListUI/Sources/ChatListPreviewController.swift index 2be067f878f..efc2faac7fd 100644 --- a/submodules/ChatListUI/Sources/ChatListPreviewController.swift +++ b/submodules/ChatListUI/Sources/ChatListPreviewController.swift @@ -268,7 +268,7 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi } CATransaction.setAnimationDuration(0.5) - if let _ = self.propertyAnimator { + if let _ = self.propertyAnimator {` self.displayLinkAnimator = DisplayLinkAnimator(duration: 1.15 * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value }, completion: { [weak self] in @@ -385,7 +385,7 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi self.view.addSubview(chatSnapshot) chatSnapshot.layer.animatePosition(from: sourceMessageFrameStart.center, to: sourceMessageFrameFinal.center, duration: 0.3, removeOnCompletion: true) { _ in print("title snapshot animation finished") - // chatSnapshot.removeFromSuperview() + chatSnapshot.removeFromSuperview() } chatSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false) @@ -394,7 +394,7 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi titleSnapshot.layer.animatePosition(from: titleSourceframe.center, to: titleFinalFrame.center, duration: 0.3, removeOnCompletion: true) { _ in print("title snapshot animation finished") chatSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 1.5, removeOnCompletion: false) - // titleSnapshot.removeFromSuperview() + titleSnapshot.removeFromSuperview() } @@ -415,7 +415,7 @@ final class ChatListPreviewControllerNode: ViewControllerTracingNode, UIScrollVi navigationSnapshotView.removeFromSuperview() } -// let pf = originalProjectedContentViewFrame.1 + let pf = originalProjectedContentViewFrame.1 CATransaction.commit() diff --git a/versions.json b/versions.json index b1f7750f937..2c71d357afa 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { "app": "10.0.1", "bazel": "6.1.1", - "xcode": "14.3.1" + "xcode": "14.2" }