mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
Shared media improvements
This commit is contained in:
@@ -26,9 +26,14 @@ public protocol SparseItemGridDisplayItem: AnyObject {
|
||||
var view: SparseItemGridView? { get }
|
||||
}
|
||||
|
||||
public protocol SparseItemGridShimmerLayer: CALayer {
|
||||
func update(size: CGSize)
|
||||
}
|
||||
|
||||
public protocol SparseItemGridBinding: AnyObject {
|
||||
func createLayer() -> SparseItemGridLayer?
|
||||
func createView() -> SparseItemGridView?
|
||||
func createShimmerLayer() -> SparseItemGridShimmerLayer?
|
||||
func bindLayers(items: [SparseItemGrid.Item], layers: [SparseItemGridDisplayItem], synchronous: Bool)
|
||||
func unbindLayer(layer: SparseItemGridLayer)
|
||||
func scrollerTextForTag(tag: Int32) -> String?
|
||||
@@ -141,10 +146,13 @@ private final class Shimmer {
|
||||
}
|
||||
}
|
||||
|
||||
final class Layer: CALayer {
|
||||
final class Layer: CALayer, SparseItemGridShimmerLayer {
|
||||
override func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +431,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
var layout: Layout?
|
||||
var items: Items?
|
||||
var visibleItems: [AnyHashable: VisibleItem] = [:]
|
||||
var visiblePlaceholders: [Shimmer.Layer] = []
|
||||
var visiblePlaceholders: [SparseItemGridShimmerLayer] = []
|
||||
|
||||
private var scrollingArea: SparseItemGridScrollingArea?
|
||||
private var currentScrollingTag: Int32?
|
||||
@@ -435,9 +443,12 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
private var previousScrollOffset: CGFloat = 0.0
|
||||
var coveringInsetOffset: CGFloat = 0.0
|
||||
|
||||
init(zoomLevel: ZoomLevel, maybeLoadHoleAnchor: @escaping (HoleAnchor, HoleLocation) -> Void) {
|
||||
let coveringOffsetUpdated: (Viewport, ContainedViewLayoutTransition) -> Void
|
||||
|
||||
init(zoomLevel: ZoomLevel, maybeLoadHoleAnchor: @escaping (HoleAnchor, HoleLocation) -> Void, coveringOffsetUpdated: @escaping (Viewport, ContainedViewLayoutTransition) -> Void) {
|
||||
self.zoomLevel = zoomLevel
|
||||
self.maybeLoadHoleAnchor = maybeLoadHoleAnchor
|
||||
self.coveringOffsetUpdated = coveringOffsetUpdated
|
||||
|
||||
self.scrollView = UIScrollView()
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
@@ -476,7 +487,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateVisibleItems(resetScrolling: false, synchronous: true, restoreScrollPosition: nil)
|
||||
|
||||
if let layout = self.layout, let items = self.items {
|
||||
if let layout = self.layout, let _ = self.items {
|
||||
let offset = scrollView.contentOffset.y
|
||||
let delta = offset - self.previousScrollOffset
|
||||
self.previousScrollOffset = offset
|
||||
@@ -495,7 +506,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
if coveringInsetOffset < self.coveringInsetOffset {
|
||||
self.coveringInsetOffset = coveringInsetOffset
|
||||
items.itemBinding.coveringInsetOffsetUpdated(transition: .immediate)
|
||||
self.coveringOffsetUpdated(self, .immediate)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -511,7 +522,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
if coveringInsetOffset != self.coveringInsetOffset {
|
||||
self.coveringInsetOffset = coveringInsetOffset
|
||||
items.itemBinding.coveringInsetOffsetUpdated(transition: .immediate)
|
||||
self.coveringOffsetUpdated(self, .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -539,7 +550,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
|
||||
private func snapCoveringInsetOffset() {
|
||||
if let layout = self.layout, let items = self.items {
|
||||
if let layout = self.layout, let _ = self.items {
|
||||
let offset = self.scrollView.contentOffset.y
|
||||
if offset < layout.containerLayout.insets.top {
|
||||
if offset <= layout.containerLayout.insets.top / 2.0 {
|
||||
@@ -560,7 +571,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
if coveringInsetOffset != self.coveringInsetOffset {
|
||||
self.coveringInsetOffset = coveringInsetOffset
|
||||
items.itemBinding.coveringInsetOffsetUpdated(transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
self.coveringOffsetUpdated(self, .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -680,6 +691,10 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.scrollView.setContentOffset(self.scrollView.contentOffset, animated: false)
|
||||
}
|
||||
|
||||
func updateShimmerLayers() {
|
||||
self.updateVisibleItems(resetScrolling: false, synchronous: false, restoreScrollPosition: nil)
|
||||
}
|
||||
|
||||
private func updateVisibleItems(resetScrolling: Bool, synchronous: Bool, restoreScrollPosition: (y: CGFloat, index: Int)?) {
|
||||
guard let layout = self.layout, let items = self.items else {
|
||||
return
|
||||
@@ -753,21 +768,39 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
if itemLayer.needsShimmer {
|
||||
let placeholderLayer: SparseItemGridShimmerLayer
|
||||
if self.visiblePlaceholders.count > usedPlaceholderCount {
|
||||
placeholderLayer = self.visiblePlaceholders[usedPlaceholderCount]
|
||||
} else {
|
||||
placeholderLayer = items.itemBinding.createShimmerLayer() ?? Shimmer.Layer()
|
||||
self.scrollView.layer.insertSublayer(placeholderLayer, at: 0)
|
||||
self.visiblePlaceholders.append(placeholderLayer)
|
||||
}
|
||||
|
||||
let itemFrame = layout.frame(at: index)
|
||||
placeholderLayer.frame = itemFrame
|
||||
self.shimmer.update(colors: shimmerColors, layer: placeholderLayer, containerSize: layout.containerLayout.size, frame: itemFrame.offsetBy(dx: 0.0, dy: -visibleBounds.minY))
|
||||
placeholderLayer.update(size: itemFrame.size)
|
||||
usedPlaceholderCount += 1
|
||||
}
|
||||
|
||||
validIds.insert(item.id)
|
||||
|
||||
itemLayer.frame = layout.frame(at: index)
|
||||
} else if layout.containerLayout.fixedItemHeight == nil {
|
||||
let placeholderLayer: Shimmer.Layer
|
||||
} else {
|
||||
let placeholderLayer: SparseItemGridShimmerLayer
|
||||
if self.visiblePlaceholders.count > usedPlaceholderCount {
|
||||
placeholderLayer = self.visiblePlaceholders[usedPlaceholderCount]
|
||||
} else {
|
||||
placeholderLayer = Shimmer.Layer()
|
||||
placeholderLayer = items.itemBinding.createShimmerLayer() ?? Shimmer.Layer()
|
||||
self.scrollView.layer.addSublayer(placeholderLayer)
|
||||
self.visiblePlaceholders.append(placeholderLayer)
|
||||
}
|
||||
let itemFrame = layout.frame(at: index)
|
||||
placeholderLayer.frame = itemFrame
|
||||
self.shimmer.update(colors: shimmerColors, layer: placeholderLayer, containerSize: layout.containerLayout.size, frame: itemFrame.offsetBy(dx: 0.0, dy: -visibleBounds.minY))
|
||||
placeholderLayer.update(size: itemFrame.size)
|
||||
usedPlaceholderCount += 1
|
||||
}
|
||||
}
|
||||
@@ -783,11 +816,6 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
} else if let view = item.view {
|
||||
view.update(size: layer.frame.size)
|
||||
}
|
||||
|
||||
if item.needsShimmer {
|
||||
let itemFrame = layer.frame
|
||||
self.shimmer.update(colors: shimmerColors, layer: item.displayLayer, containerSize: layout.containerLayout.size, frame: itemFrame.offsetBy(dx: 0.0, dy: -visibleBounds.minY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -923,12 +951,19 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
var currentProgress: CGFloat = 0.0
|
||||
|
||||
init(interactiveState: InteractiveState?, layout: ContainerLayout, anchorItemIndex: Int, from fromViewport: Viewport, to toViewport: Viewport) {
|
||||
var coveringInsetOffset: CGFloat {
|
||||
return self.fromViewport.coveringInsetOffset * (1.0 - self.currentProgress) + self.toViewport.coveringInsetOffset * self.currentProgress
|
||||
}
|
||||
|
||||
let coveringOffsetUpdated: (ContainedViewLayoutTransition) -> Void
|
||||
|
||||
init(interactiveState: InteractiveState?, layout: ContainerLayout, anchorItemIndex: Int, from fromViewport: Viewport, to toViewport: Viewport, coveringOffsetUpdated: @escaping (ContainedViewLayoutTransition) -> Void) {
|
||||
self.interactiveState = interactiveState
|
||||
self.layout = layout
|
||||
self.anchorItemIndex = anchorItemIndex
|
||||
self.fromViewport = fromViewport
|
||||
self.toViewport = toViewport
|
||||
self.coveringOffsetUpdated = coveringOffsetUpdated
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -1006,6 +1041,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
} else {
|
||||
transition.updateAlpha(node: self.fromViewport, alpha: 1.0 - fromAlphaProgress)
|
||||
}
|
||||
|
||||
self.coveringOffsetUpdated(transition)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1033,12 +1070,17 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
private let loadingHoleDisposable = MetaDisposable()
|
||||
|
||||
public var coveringInsetOffset: CGFloat {
|
||||
if let currentViewport = self.currentViewport {
|
||||
if let currentViewportTransition = self.currentViewportTransition {
|
||||
return currentViewportTransition.coveringInsetOffset
|
||||
} else if let currentViewport = self.currentViewport {
|
||||
return currentViewport.coveringInsetOffset
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
public var cancelExternalContentGestures: (() -> Void)?
|
||||
|
||||
override public init() {
|
||||
self.scrollingArea = SparseItemGridScrollingArea()
|
||||
|
||||
@@ -1085,7 +1127,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
break
|
||||
self.cancelExternalContentGestures?()
|
||||
case .changed:
|
||||
let scale = recognizer.scale
|
||||
if let currentViewportTransition = self.currentViewportTransition, let interactiveState = currentViewportTransition.interactiveState {
|
||||
@@ -1138,6 +1180,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
strongSelf.maybeLoadHoleAnchor(holeAnchor: holeAnchor, location: location)
|
||||
}, coveringOffsetUpdated: { [weak self] viewport, transition in
|
||||
self?.coveringOffsetUpdated(viewport: viewport, transition: transition)
|
||||
})
|
||||
|
||||
nextViewport.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
@@ -1146,7 +1190,9 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.currentViewportTransition?.removeFromSupernode()
|
||||
|
||||
let nextInteractiveState = ViewportTransition.InteractiveState(anchorLocation: anchorLocation, initialScale: startScale, targetScale: nextScale)
|
||||
let currentViewportTransition = ViewportTransition(interactiveState: nextInteractiveState, layout: containerLayout, anchorItemIndex: currentViewportTransition.anchorItemIndex, from: boundaryViewport, to: nextViewport)
|
||||
let currentViewportTransition = ViewportTransition(interactiveState: nextInteractiveState, layout: containerLayout, anchorItemIndex: currentViewportTransition.anchorItemIndex, from: boundaryViewport, to: nextViewport, coveringOffsetUpdated: { [weak self] transition in
|
||||
self?.transitionCoveringOffsetUpdated(transition: transition)
|
||||
})
|
||||
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
self.insertSubnode(currentViewportTransition, belowSubnode: self.scrollingArea)
|
||||
self.currentViewportTransition = currentViewportTransition
|
||||
@@ -1184,12 +1230,16 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
strongSelf.maybeLoadHoleAnchor(holeAnchor: holeAnchor, location: location)
|
||||
}, coveringOffsetUpdated: { [weak self] viewport, transition in
|
||||
self?.coveringOffsetUpdated(viewport: viewport, transition: transition)
|
||||
})
|
||||
|
||||
nextViewport.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
nextViewport.update(containerLayout: containerLayout, items: items, restoreScrollPosition: restoreScrollPosition)
|
||||
|
||||
let currentViewportTransition = ViewportTransition(interactiveState: interactiveState, layout: containerLayout, anchorItemIndex: anchorItemIndex, from: previousViewport, to: nextViewport)
|
||||
let currentViewportTransition = ViewportTransition(interactiveState: interactiveState, layout: containerLayout, anchorItemIndex: anchorItemIndex, from: previousViewport, to: nextViewport, coveringOffsetUpdated: { [weak self] transition in
|
||||
self?.transitionCoveringOffsetUpdated(transition: transition)
|
||||
})
|
||||
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
self.insertSubnode(currentViewportTransition, belowSubnode: self.scrollingArea)
|
||||
self.currentViewportTransition = currentViewportTransition
|
||||
@@ -1249,6 +1299,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
strongSelf.maybeLoadHoleAnchor(holeAnchor: holeAnchor, location: location)
|
||||
}, coveringOffsetUpdated: { [weak self] viewport, transition in
|
||||
self?.coveringOffsetUpdated(viewport: viewport, transition: transition)
|
||||
})
|
||||
self.currentViewport = currentViewport
|
||||
self.insertSubnode(currentViewport, belowSubnode: self.scrollingArea)
|
||||
@@ -1272,6 +1324,12 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
|
||||
/*#if DEBUG
|
||||
if "".isEmpty {
|
||||
return
|
||||
}
|
||||
#endif*/
|
||||
|
||||
self.isLoadingHole = true
|
||||
self.loadingHoleDisposable.set((items.itemBinding.loadHole(anchor: holeAnchor, at: location)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
@@ -1323,6 +1381,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
strongSelf.maybeLoadHoleAnchor(holeAnchor: holeAnchor, location: location)
|
||||
}, coveringOffsetUpdated: { [weak self] viewport, transition in
|
||||
self?.coveringOffsetUpdated(viewport: viewport, transition: transition)
|
||||
})
|
||||
self.currentViewport = currentViewport
|
||||
self.insertSubnode(currentViewport, belowSubnode: self.scrollingArea)
|
||||
@@ -1337,7 +1397,9 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
currentViewport.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
currentViewport.update(containerLayout: containerLayout, items: items, restoreScrollPosition: restoreScrollPosition)
|
||||
|
||||
let currentViewportTransition = ViewportTransition(interactiveState: nil, layout: containerLayout, anchorItemIndex: anchorItemIndex, from: previousViewport, to: currentViewport)
|
||||
let currentViewportTransition = ViewportTransition(interactiveState: nil, layout: containerLayout, anchorItemIndex: anchorItemIndex, from: previousViewport, to: currentViewport, coveringOffsetUpdated: { [weak self] transition in
|
||||
self?.transitionCoveringOffsetUpdated(transition: transition)
|
||||
})
|
||||
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
self.insertSubnode(currentViewportTransition, belowSubnode: self.scrollingArea)
|
||||
self.currentViewportTransition = currentViewportTransition
|
||||
@@ -1365,6 +1427,23 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private func coveringOffsetUpdated(viewport: Viewport, transition: ContainedViewLayoutTransition) {
|
||||
guard let items = self.items else {
|
||||
return
|
||||
}
|
||||
if self.currentViewportTransition != nil {
|
||||
return
|
||||
}
|
||||
items.itemBinding.coveringInsetOffsetUpdated(transition: transition)
|
||||
}
|
||||
|
||||
private func transitionCoveringOffsetUpdated(transition: ContainedViewLayoutTransition) {
|
||||
guard let items = self.items else {
|
||||
return
|
||||
}
|
||||
items.itemBinding.coveringInsetOffsetUpdated(transition: transition)
|
||||
}
|
||||
|
||||
public func forEachVisibleItem(_ f: (SparseItemGridDisplayItem) -> Void) {
|
||||
guard let currentViewport = self.currentViewport else {
|
||||
return
|
||||
@@ -1420,4 +1499,12 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
self.scrollingArea.hideScroller()
|
||||
}
|
||||
|
||||
public func updateShimmerLayers() {
|
||||
self.currentViewport?.updateShimmerLayers()
|
||||
if let currentViewportTransition = self.currentViewportTransition {
|
||||
currentViewportTransition.fromViewport.updateShimmerLayers()
|
||||
currentViewportTransition.toViewport.updateShimmerLayers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user