diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index e22556c410..85a6dd2ab3 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -569,7 +569,7 @@ final class StarsStatisticsScreenComponent: Component { theme: environment.theme, strings: strings, dateTimeFormat: environment.dateTimeFormat, - containerInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right), + containerInsets: .zero, isScrollable: false, isCurrent: true, externalScrollBounds: self.lastScrollBounds ?? .zero, diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index 5806fc42ea..6c4d12c565 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -102,6 +102,12 @@ final class StarsTransactionsListPanelComponent: Component { } private final class ScrollViewImpl: UIScrollView { + var forceDecelerating = false + + override var isDecelerating: Bool { + return self.forceDecelerating || super.isDecelerating + } + override func touchesShouldCancel(in view: UIView) -> Bool { return true } @@ -111,8 +117,9 @@ final class StarsTransactionsListPanelComponent: Component { private let scrollView: ScrollViewImpl private let measureItem = ComponentView() - private var visibleItems: [String: ComponentView] = [:] - private var separatorViews: [String: UIView] = [:] + private var visibleItems: [AnyHashable: ComponentView] = [:] + private var separatorLayers: [AnyHashable: SimpleLayer] = [:] + private var highlightLayer = SimpleLayer() private var ignoreScrolling: Bool = false @@ -145,6 +152,8 @@ final class StarsTransactionsListPanelComponent: Component { self.scrollView.delegate = self self.scrollView.clipsToBounds = true self.addSubview(self.scrollView) + + self.scrollView.layer.addSublayer(self.highlightLayer) } required init?(coder: NSCoder) { @@ -163,6 +172,79 @@ final class StarsTransactionsListPanelComponent: Component { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { cancelContextGestures(view: scrollView) + if let decelerationAnimator = self.decelerationAnimator { + self.scrollView.forceDecelerating = false + self.decelerationAnimator = nil + decelerationAnimator.invalidate() + } + } + + private var decelerationAnimator: ConstantDisplayLinkAnimator? + func transferVelocity(_ velocity: CGFloat) { + if velocity <= 0.0 { + return + } + self.decelerationAnimator?.isPaused = true + let startTime = CACurrentMediaTime() + var currentOffset = self.scrollView.contentOffset + let decelerationRate: CGFloat = 0.998 + self.scrollView.forceDecelerating = true + //self.scrollViewDidEndDragging(self.scrollView, willDecelerate: true) + self.decelerationAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in + guard let strongSelf = self else { + return + } + let t = CACurrentMediaTime() - startTime + var currentVelocity = velocity * 15.0 * CGFloat(pow(Double(decelerationRate), 1000.0 * t)) + currentOffset.y += currentVelocity + let maxOffset = strongSelf.scrollView.contentSize.height - strongSelf.scrollView.bounds.height + if currentOffset.y >= maxOffset { + currentOffset.y = maxOffset + currentVelocity = 0.0 + } + if currentOffset.y < 0.0 { + currentOffset.y = 0.0 + currentVelocity = 0.0 + } + + var didEnd = false + if abs(currentVelocity) < 0.1 { + strongSelf.decelerationAnimator?.isPaused = true + strongSelf.decelerationAnimator = nil + didEnd = true + } + var contentOffset = strongSelf.scrollView.contentOffset + contentOffset.y = floorToScreenPixels(currentOffset.y) + strongSelf.scrollView.setContentOffset(contentOffset, animated: false) + strongSelf.scrollViewDidScroll(strongSelf.scrollView) + if didEnd { + //strongSelf.scrollViewDidEndDecelerating(strongSelf.scrollView) + strongSelf.scrollView.forceDecelerating = false + } + }) + self.decelerationAnimator?.isPaused = false + } + + private var highlightedItemId: AnyHashable? + private func updateHighlightedItem(itemId: AnyHashable?) { + guard let environment = self.environment else { + return + } + if self.highlightedItemId == itemId { + return + } + let previousHighlightedItemId = self.highlightedItemId + self.highlightedItemId = itemId + + if let _ = previousHighlightedItemId, itemId == nil { + ComponentTransition.easeInOut(duration: 0.2).setBackgroundColor(layer: self.highlightLayer, color: .clear) + } + if let itemId, let itemView = self.visibleItems[itemId]?.view { + var highlightFrame = itemView.frame + highlightFrame.size.height += UIScreenPixel + self.highlightLayer.frame = highlightFrame + ComponentTransition.immediate.setBackgroundColor(layer: self.highlightLayer, color: environment.theme.list.itemHighlightedBackgroundColor) + } } private func updateScrolling(transition: ComponentTransition) { @@ -173,34 +255,34 @@ final class StarsTransactionsListPanelComponent: Component { var visibleBounds = environment.externalScrollBounds ?? self.scrollView.bounds visibleBounds = visibleBounds.insetBy(dx: 0.0, dy: -100.0) - var validIds = Set() + var validIds = Set() if let visibleItems = itemLayout.visibleItems(for: visibleBounds) { for index in visibleItems.lowerBound ..< visibleItems.upperBound { if index >= self.items.count { continue } let item = self.items[index] - let id = item.extendedId + let id = AnyHashable(item.extendedId) validIds.insert(id) var itemTransition = transition let itemView: ComponentView - let separatorView: UIView - if let current = self.visibleItems[id], let currentSeparator = self.separatorViews[id] { + let separatorLayer: SimpleLayer + if let current = self.visibleItems[id], let currentSeparator = self.separatorLayers[id] { itemView = current - separatorView = currentSeparator + separatorLayer = currentSeparator } else { itemTransition = .immediate itemView = ComponentView() self.visibleItems[id] = itemView - separatorView = UIView() - self.separatorViews[id] = separatorView - self.scrollView.addSubview(separatorView) + separatorLayer = SimpleLayer() + self.separatorLayers[id] = separatorLayer + self.scrollView.layer.addSublayer(separatorLayer) } - separatorView.backgroundColor = environment.theme.list.itemBlocksSeparatorColor - separatorView.isHidden = index == self.items.count - 1 + separatorLayer.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor + separatorLayer.isHidden = index == self.items.count - 1 let fontBaseDisplaySize = 17.0 @@ -367,12 +449,18 @@ final class StarsTransactionsListPanelComponent: Component { if !item.flags.contains(.isLocal) { component.action(item) } + }, + updateIsHighlighted: { [weak self] _, highlighted in + guard let self else { + return + } + self.updateHighlightedItem(itemId: highlighted ? id : nil) } )), environment: {}, - containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight) + containerSize: CGSize(width: itemLayout.containerWidth - itemLayout.containerInsets.left - itemLayout.containerInsets.right, height: itemLayout.itemHeight) ) - let itemFrame = itemLayout.itemFrame(for: index) + let itemFrame = itemLayout.itemFrame(for: index).offsetBy(dx: itemLayout.containerInsets.left, dy: 0.0) if let itemComponentView = itemView.view { if itemComponentView.superview == nil { if !transition.animation.isImmediate { @@ -383,11 +471,11 @@ final class StarsTransactionsListPanelComponent: Component { itemTransition.setFrame(view: itemComponentView, frame: itemFrame) } let sideInset: CGFloat = 60.0 + environment.containerInsets.left - itemTransition.setFrame(view: separatorView, frame: CGRect(x: sideInset, y: itemFrame.maxY, width: itemFrame.width - sideInset, height: UIScreenPixel)) + itemTransition.setFrame(layer: separatorLayer, frame: CGRect(x: sideInset, y: itemFrame.maxY, width: itemFrame.width - sideInset - environment.containerInsets.right, height: UIScreenPixel)) } } - var removeIds: [String] = [] + var removeIds: [AnyHashable] = [] for (id, itemView) in self.visibleItems { if !validIds.contains(id) { removeIds.append(id) @@ -398,10 +486,10 @@ final class StarsTransactionsListPanelComponent: Component { } } } - for (id, separatorView) in self.separatorViews { + for (id, separatorLayer) in self.separatorLayers { if !validIds.contains(id) { - transition.setAlpha(view: separatorView, alpha: 0.0, completion: { [weak separatorView] _ in - separatorView?.removeFromSuperview() + transition.setAlpha(layer: separatorLayer, alpha: 0.0, completion: { [weak separatorLayer] _ in + separatorLayer?.removeFromSuperlayer() }) } } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift index 2b1e6896cc..c35e7e9619 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift @@ -427,6 +427,7 @@ final class StarsTransactionsPanelContainerComponent: Component { } class View: UIView, UIGestureRecognizerDelegate { + private let topPanelClippingView: UIView private let topPanelBackgroundView: UIView private let topPanelMergedBackgroundView: UIView private let topPanelSeparatorLayer: SimpleLayer @@ -436,6 +437,7 @@ final class StarsTransactionsPanelContainerComponent: Component { private weak var state: EmptyComponentState? private let panelsBackgroundLayer: SimpleLayer + private let clippingView: UIView private var visiblePanels: [AnyHashable: ComponentView] = [:] private var actualVisibleIds = Set() private var currentId: AnyHashable? @@ -443,6 +445,10 @@ final class StarsTransactionsPanelContainerComponent: Component { private var animatingTransition: Bool = false override init(frame: CGRect) { + self.topPanelClippingView = UIView() + self.topPanelClippingView.clipsToBounds = true + self.topPanelClippingView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + self.topPanelBackgroundView = UIView() self.topPanelMergedBackgroundView = UIView() @@ -452,11 +458,16 @@ final class StarsTransactionsPanelContainerComponent: Component { self.panelsBackgroundLayer = SimpleLayer() + self.clippingView = UIView() + self.clippingView.clipsToBounds = true + super.init(frame: frame) self.layer.addSublayer(self.panelsBackgroundLayer) - self.addSubview(self.topPanelBackgroundView) - self.addSubview(self.topPanelMergedBackgroundView) + self.addSubview(self.clippingView) + self.addSubview(self.topPanelClippingView) + self.topPanelClippingView.addSubview(self.topPanelBackgroundView) + self.topPanelClippingView.addSubview(self.topPanelMergedBackgroundView) self.layer.addSublayer(self.topPanelSeparatorLayer) let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in @@ -593,6 +604,12 @@ final class StarsTransactionsPanelContainerComponent: Component { transition.setAlpha(view: self.topPanelBackgroundView, alpha: 1.0 - value) } + func transferVelocity(_ velocity: CGFloat) { + if let currentPanelView = self.currentPanelView as? StarsTransactionsListPanelComponent.View { + currentPanelView.transferVelocity(velocity) + } + } + func update(component: StarsTransactionsPanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let environment = environment[StarsTransactionsPanelContainerEnvironment.self].value @@ -610,13 +627,17 @@ final class StarsTransactionsPanelContainerComponent: Component { let topPanelCoverHeight: CGFloat = 10.0 - let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -topPanelCoverHeight), size: CGSize(width: availableSize.width, height: 44.0)) - transition.setFrame(view: self.topPanelBackgroundView, frame: topPanelFrame) - transition.setFrame(view: self.topPanelMergedBackgroundView, frame: topPanelFrame) + let containerWidth = availableSize.width - component.insets.left - component.insets.right + let topPanelFrame = CGRect(origin: CGPoint(x: component.insets.left, y: -topPanelCoverHeight), size: CGSize(width: containerWidth, height: 44.0)) + transition.setFrame(view: self.topPanelClippingView, frame: topPanelFrame) + transition.setFrame(view: self.topPanelBackgroundView, frame: CGRect(origin: .zero, size: topPanelFrame.size)) + transition.setFrame(view: self.topPanelMergedBackgroundView, frame: CGRect(origin: .zero, size: topPanelFrame.size)) + + transition.setCornerRadius(layer: self.topPanelClippingView.layer, cornerRadius: component.insets.left > 0.0 ? 11.0 : 0.0) - transition.setFrame(layer: self.panelsBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY))) + transition.setFrame(layer: self.panelsBackgroundLayer, frame: CGRect(origin: CGPoint(x: component.insets.left, y: topPanelFrame.maxY), size: CGSize(width: containerWidth, height: availableSize.height - topPanelFrame.maxY))) - transition.setFrame(layer: self.topPanelSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel))) + transition.setFrame(layer: self.topPanelSeparatorLayer, frame: CGRect(origin: CGPoint(x: component.insets.left, y: topPanelFrame.maxY), size: CGSize(width: containerWidth, height: UIScreenPixel))) if let currentIdValue = self.currentId, !component.items.contains(where: { $0.id == currentIdValue }) { self.currentId = nil @@ -641,7 +662,7 @@ final class StarsTransactionsPanelContainerComponent: Component { } } - let sideInset: CGFloat = 16.0 + let sideInset: CGFloat = 16.0 + component.insets.left let condensedPanelWidth: CGFloat = availableSize.width - sideInset * 2.0 let headerSize = self.header.update( transition: transition, @@ -674,7 +695,7 @@ final class StarsTransactionsPanelContainerComponent: Component { if headerView.superview == nil { self.addSubview(headerView) } - transition.setFrame(view: headerView, frame: CGRect(origin: topPanelFrame.origin.offsetBy(dx: sideInset, dy: 0.0), size: headerSize)) + transition.setFrame(view: headerView, frame: CGRect(origin: topPanelFrame.origin.offsetBy(dx: 16.0, dy: 0.0), size: headerSize)) } let centralPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY)) @@ -688,7 +709,7 @@ final class StarsTransactionsPanelContainerComponent: Component { for (id, _) in self.visiblePanels { visibleIds.insert(id) } - + var validIds = Set() if let currentIndex { var anyAnchorOffset: CGFloat = 0.0 @@ -769,7 +790,7 @@ final class StarsTransactionsPanelContainerComponent: Component { ) if let panelView = panel.view { if panelView.superview == nil { - self.insertSubview(panelView, belowSubview: self.topPanelBackgroundView) + self.clippingView.addSubview(panelView) } panelTransition.setFrame(view: panelView, frame: itemFrame, completion: { [weak self] _ in @@ -790,6 +811,11 @@ final class StarsTransactionsPanelContainerComponent: Component { } } + let clippingFrame = CGRect(origin: CGPoint(x: component.insets.left, y: 0.0), size: CGSize(width: availableSize.width - component.insets.left - component.insets.right, height: availableSize.height)) + + transition.setPosition(view: self.clippingView, position: clippingFrame.center) + transition.setBounds(view: self.clippingView, bounds: CGRect(origin: CGPoint(x: component.insets.left, y: 0.0), size: clippingFrame.size)) + var removeIds: [AnyHashable] = [] for (id, panel) in self.visiblePanels { if !validIds.contains(id) { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 52d550b52c..870009686b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -120,6 +120,8 @@ final class StarsTransactionsScreenComponent: Component { private var previousVelocityM1: CGFloat = 0.0 private var previousVelocity: CGFloat = 0.0 + private var listIsExpanded = false + private var ignoreScrolling: Bool = false private var stateDisposable: Disposable? @@ -200,6 +202,19 @@ final class StarsTransactionsScreenComponent: Component { } } + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + guard let navigationMetrics = self.navigationMetrics else { + return + } + + if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { + let paneAreaExpansionFinalPoint: CGFloat = panelContainerView.frame.minY - navigationMetrics.navigationHeight + if abs(scrollView.contentOffset.y - paneAreaExpansionFinalPoint) < .ulpOfOne { + panelContainerView.transferVelocity(self.previousVelocityM1) + } + } + } + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { guard let _ = self.navigationMetrics else { return @@ -273,6 +288,14 @@ final class StarsTransactionsScreenComponent: Component { if let view = self.topBalanceIconView.view { view.alpha = topBalanceAlpha } + + let listIsExpanded = expansionDistanceFactor == 0.0 + if listIsExpanded != self.listIsExpanded { + self.listIsExpanded = listIsExpanded + if !self.isUpdating { + self.state?.updated(transition: .init(animation: .curve(duration: 0.25, curve: .slide))) + } + } } let _ = self.panelContainer.updateEnvironment( @@ -362,7 +385,7 @@ final class StarsTransactionsScreenComponent: Component { var contentHeight: CGFloat = 0.0 - let sideInsets: CGFloat = environment.safeInsets.left + environment.safeInsets.right + 16 * 2.0 + let sideInsets: CGFloat = environment.safeInsets.left + environment.safeInsets.right + 16.0 * 2.0 let bottomInset: CGFloat = environment.safeInsets.bottom contentHeight += environment.statusBarHeight @@ -834,13 +857,16 @@ final class StarsTransactionsScreenComponent: Component { } if !panelItems.isEmpty { + let panelContainerInset: CGFloat = self.listIsExpanded ? 0.0 : 16.0 + let panelContainerCornerRadius: CGFloat = self.listIsExpanded ? 0.0 : 11.0 + let panelContainerSize = self.panelContainer.update( transition: panelTransition, component: AnyComponent(StarsTransactionsPanelContainerComponent( theme: environment.theme, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, - insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: bottomInset, right: environment.safeInsets.right), + insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left + panelContainerInset, bottom: bottomInset, right: environment.safeInsets.right + panelContainerInset), items: panelItems, currentPanelUpdated: { [weak self] id, transition in guard let self else { @@ -859,7 +885,8 @@ final class StarsTransactionsScreenComponent: Component { if panelContainerView.superview == nil { self.scrollContainerView.addSubview(panelContainerView) } - transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: panelContainerSize)) + transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - panelContainerSize.width) / 2.0), y: contentHeight), size: panelContainerSize)) + transition.setCornerRadius(layer: panelContainerView.layer, cornerRadius: panelContainerCornerRadius) } contentHeight += panelContainerSize.height } else {