mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
9876ba7d4d
@ -2,19 +2,25 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import HierarchyTrackingLayer
|
||||
|
||||
final class MediaNavigationStripComponent: Component {
|
||||
final class EnvironmentType: Equatable {
|
||||
let currentProgress: Double
|
||||
let currentIsBuffering: Bool
|
||||
|
||||
init(currentProgress: Double) {
|
||||
init(currentProgress: Double, currentIsBuffering: Bool) {
|
||||
self.currentProgress = currentProgress
|
||||
self.currentIsBuffering = currentIsBuffering
|
||||
}
|
||||
|
||||
static func ==(lhs: EnvironmentType, rhs: EnvironmentType) -> Bool {
|
||||
if lhs.currentProgress != rhs.currentProgress {
|
||||
return false
|
||||
}
|
||||
if lhs.currentIsBuffering != rhs.currentIsBuffering {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -39,6 +45,8 @@ final class MediaNavigationStripComponent: Component {
|
||||
|
||||
private final class ItemLayer: SimpleLayer {
|
||||
let foregroundLayer: SimpleLayer
|
||||
var bufferingGradientLayer: SimpleGradientLayer?
|
||||
var bufferingLayer: HierarchyTrackingLayer?
|
||||
|
||||
override init() {
|
||||
self.foregroundLayer = SimpleLayer()
|
||||
@ -60,6 +68,67 @@ final class MediaNavigationStripComponent: Component {
|
||||
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
private func updateProgressAnimation(size: CGSize) {
|
||||
guard let bufferingGradientLayer = self.bufferingGradientLayer else {
|
||||
return
|
||||
}
|
||||
|
||||
let gradientWidth: CGFloat = floor(max(10.0, size.width * 0.6))
|
||||
|
||||
bufferingGradientLayer.frame = CGRect(origin: CGPoint(x: -gradientWidth, y: 0.0), size: CGSize(width: gradientWidth, height: 3.0))
|
||||
|
||||
let animation = bufferingGradientLayer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + gradientWidth * 2.0) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.6, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
bufferingGradientLayer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
|
||||
func updateIsBuffering(size: CGSize, isBuffering: Bool) {
|
||||
if isBuffering {
|
||||
let bufferingLayer: HierarchyTrackingLayer
|
||||
if let current = self.bufferingLayer {
|
||||
bufferingLayer = current
|
||||
} else {
|
||||
bufferingLayer = HierarchyTrackingLayer()
|
||||
bufferingLayer.cornerRadius = 1.5
|
||||
bufferingLayer.masksToBounds = true
|
||||
self.bufferingLayer = bufferingLayer
|
||||
self.addSublayer(bufferingLayer)
|
||||
bufferingLayer.didEnterHierarchy = { [weak self] in
|
||||
guard let self, !self.bounds.isEmpty else {
|
||||
return
|
||||
}
|
||||
self.updateProgressAnimation(size: self.bounds.size)
|
||||
}
|
||||
}
|
||||
|
||||
let bufferingGradientLayer: SimpleGradientLayer
|
||||
if let current = self.bufferingGradientLayer {
|
||||
bufferingGradientLayer = current
|
||||
} else {
|
||||
bufferingGradientLayer = SimpleGradientLayer()
|
||||
self.bufferingGradientLayer = bufferingGradientLayer
|
||||
bufferingLayer.addSublayer(bufferingGradientLayer)
|
||||
|
||||
let colorCount = 7
|
||||
bufferingGradientLayer.colors = (0 ..< colorCount).map { index -> CGColor in
|
||||
let t = CGFloat(index) / CGFloat(colorCount - 1)
|
||||
let fraction: CGFloat = 1.0 - max(0.0, min(1.0, abs(0.5 - t) / 0.5))
|
||||
return UIColor(white: 1.0, alpha: 0.3 * sin(fraction)).cgColor
|
||||
}
|
||||
}
|
||||
|
||||
bufferingLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.updateProgressAnimation(size: size)
|
||||
} else if let bufferingLayer = self.bufferingLayer {
|
||||
self.bufferingLayer = nil
|
||||
self.bufferingGradientLayer = nil
|
||||
bufferingLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak bufferingLayer] _ in
|
||||
bufferingLayer?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
@ -78,7 +147,7 @@ final class MediaNavigationStripComponent: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateCurrentItemProgress(value: CGFloat, transition: Transition) {
|
||||
func updateCurrentItemProgress(value: CGFloat, isBuffering: Bool, transition: Transition) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -88,11 +157,14 @@ final class MediaNavigationStripComponent: Component {
|
||||
|
||||
let itemFrame = itemLayer.bounds
|
||||
transition.setFrame(layer: itemLayer.foregroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value * itemFrame.width, height: itemFrame.height)))
|
||||
itemLayer.updateIsBuffering(size: itemFrame.size, isBuffering: isBuffering)
|
||||
}
|
||||
|
||||
func update(component: MediaNavigationStripComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let environment = environment[EnvironmentType.self].value
|
||||
|
||||
let spacing: CGFloat = 3.0
|
||||
let itemHeight: CGFloat = 2.0
|
||||
let minItemWidth: CGFloat = 2.0
|
||||
@ -156,15 +228,18 @@ final class MediaNavigationStripComponent: Component {
|
||||
itemLayer.foregroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 1.0).cgColor
|
||||
|
||||
let itemProgress: CGFloat
|
||||
var itemIsBuffering = false
|
||||
if i < component.index {
|
||||
itemProgress = 1.0
|
||||
} else if i == component.index {
|
||||
itemProgress = max(0.0, min(1.0, environment[EnvironmentType.self].value.currentProgress))
|
||||
itemProgress = max(0.0, min(1.0, environment.currentProgress))
|
||||
itemIsBuffering = environment.currentIsBuffering
|
||||
} else {
|
||||
itemProgress = 0.0
|
||||
}
|
||||
|
||||
transition.setFrame(layer: itemLayer.foregroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: itemProgress * itemFrame.width, height: itemFrame.height)))
|
||||
itemLayer.updateIsBuffering(size: itemFrame.size, isBuffering: itemIsBuffering)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,14 +46,14 @@ public final class StoryContentItem: Equatable {
|
||||
public let externalState: ExternalState
|
||||
public let sharedState: SharedState
|
||||
public let theme: PresentationTheme
|
||||
public let presentationProgressUpdated: (Double, Bool) -> Void
|
||||
public let presentationProgressUpdated: (Double, Bool, Bool) -> Void
|
||||
public let markAsSeen: (StoryId) -> Void
|
||||
|
||||
public init(
|
||||
externalState: ExternalState,
|
||||
sharedState: SharedState,
|
||||
theme: PresentationTheme,
|
||||
presentationProgressUpdated: @escaping (Double, Bool) -> Void,
|
||||
presentationProgressUpdated: @escaping (Double, Bool, Bool) -> Void,
|
||||
markAsSeen: @escaping (StoryId) -> Void
|
||||
) {
|
||||
self.externalState = externalState
|
||||
|
@ -167,7 +167,7 @@ final class StoryItemContentComponent: Component {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.environment?.presentationProgressUpdated(1.0, true)
|
||||
self.environment?.presentationProgressUpdated(1.0, false, true)
|
||||
}
|
||||
videoNode.ownsContentNodeUpdated = { [weak self] value in
|
||||
guard let self, let component = self.component else {
|
||||
@ -296,7 +296,7 @@ final class StoryItemContentComponent: Component {
|
||||
currentProgressTimerValue = max(0.0, min(currentProgressTimerLimit, currentProgressTimerValue))
|
||||
self.currentProgressTimerValue = currentProgressTimerValue
|
||||
|
||||
self.environment?.presentationProgressUpdated(currentProgressTimerValue / currentProgressTimerLimit, true)
|
||||
self.environment?.presentationProgressUpdated(currentProgressTimerValue / currentProgressTimerLimit, false, true)
|
||||
}
|
||||
}, queue: .mainQueue()
|
||||
)
|
||||
@ -332,6 +332,11 @@ final class StoryItemContentComponent: Component {
|
||||
effectiveDuration = 1.0
|
||||
}
|
||||
|
||||
var isBuffering = false
|
||||
if case .buffering(false, true, _, _) = videoPlaybackStatus.status {
|
||||
isBuffering = true
|
||||
}
|
||||
|
||||
if case .buffering(true, _, _, _) = videoPlaybackStatus.status {
|
||||
timestampAndDuration = (nil, effectiveDuration)
|
||||
} else if effectiveDuration > 0.0 {
|
||||
@ -355,6 +360,10 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
progress = min(1.0, progress)
|
||||
|
||||
if actualTimestamp < 0.1 {
|
||||
isBuffering = false
|
||||
}
|
||||
|
||||
currentProgress = progress
|
||||
|
||||
if isPlaying {
|
||||
@ -373,7 +382,7 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
|
||||
let clippedProgress = max(0.0, min(1.0, currentProgress))
|
||||
self.environment?.presentationProgressUpdated(clippedProgress, false)
|
||||
self.environment?.presentationProgressUpdated(clippedProgress, isBuffering, false)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
|
@ -245,6 +245,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let contentContainerView: UIView
|
||||
let view = ComponentView<StoryContentItem.Environment>()
|
||||
var currentProgress: Double = 0.0
|
||||
var isBuffering: Bool = false
|
||||
var requestedNext: Bool = false
|
||||
|
||||
init() {
|
||||
@ -1027,22 +1028,25 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
externalState: visibleItem.externalState,
|
||||
sharedState: component.storyItemSharedState,
|
||||
theme: component.theme,
|
||||
presentationProgressUpdated: { [weak self, weak visibleItem] progress, canSwitch in
|
||||
presentationProgressUpdated: { [weak self, weak visibleItem] progress, isBuffering, canSwitch in
|
||||
guard let self = self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard let visibleItem else {
|
||||
return
|
||||
}
|
||||
visibleItem.currentProgress = progress
|
||||
|
||||
if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View {
|
||||
navigationStripView.updateCurrentItemProgress(value: progress, transition: .immediate)
|
||||
}
|
||||
if progress >= 1.0 && canSwitch && !visibleItem.requestedNext {
|
||||
visibleItem.requestedNext = true
|
||||
if visibleItem.currentProgress != progress || visibleItem.isBuffering != isBuffering {
|
||||
visibleItem.currentProgress = progress
|
||||
visibleItem.isBuffering = isBuffering
|
||||
|
||||
component.navigate(.next)
|
||||
if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View {
|
||||
navigationStripView.updateCurrentItemProgress(value: progress, isBuffering: isBuffering, transition: .immediate)
|
||||
}
|
||||
if progress >= 1.0 && canSwitch && !visibleItem.requestedNext {
|
||||
visibleItem.requestedNext = true
|
||||
|
||||
component.navigate(.next)
|
||||
}
|
||||
}
|
||||
},
|
||||
markAsSeen: { [weak self] id in
|
||||
@ -2908,7 +2912,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
)),
|
||||
environment: {
|
||||
MediaNavigationStripComponent.EnvironmentType(
|
||||
currentProgress: visibleItem.currentProgress
|
||||
currentProgress: visibleItem.currentProgress,
|
||||
currentIsBuffering: visibleItem.isBuffering
|
||||
)
|
||||
},
|
||||
containerSize: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user