Various improvements

This commit is contained in:
Ilya Laktyushin 2024-10-15 04:47:03 +04:00
parent 4f255424e5
commit cc6f7882f1
4 changed files with 175 additions and 34 deletions

View File

@ -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,

View File

@ -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<Empty>()
private var visibleItems: [String: ComponentView<Empty>] = [:]
private var separatorViews: [String: UIView] = [:]
private var visibleItems: [AnyHashable: ComponentView<Empty>] = [:]
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<String>()
var validIds = Set<AnyHashable>()
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<Empty>
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()
})
}
}

View File

@ -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<StarsTransactionsPanelEnvironment>] = [:]
private var actualVisibleIds = Set<AnyHashable>()
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<StarsTransactionsPanelContainerEnvironment>, 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.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.setCornerRadius(layer: self.topPanelClippingView.layer, cornerRadius: component.insets.left > 0.0 ? 11.0 : 0.0)
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.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: 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))
@ -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) {

View File

@ -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<CGPoint>) {
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 {