mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
98263431a4
commit
218a09bf73
@ -117,7 +117,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
|
||||
}
|
||||
|
||||
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true)
|
||||
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true)
|
||||
|> mapError { error -> UploadPartError in
|
||||
if error.errorCode == 400 {
|
||||
return .invalidMedia
|
||||
|
@ -453,6 +453,7 @@ private final class FetchImpl {
|
||||
let reuploadSignal = self.network.multiplexedRequestManager.request(
|
||||
to: .main(state.cdnData.sourceDatacenterId),
|
||||
consumerId: self.consumerId,
|
||||
resourceId: self.resource.id.stringRepresentation,
|
||||
data: Api.functions.upload.reuploadCdnFile(
|
||||
fileToken: Buffer(data: state.cdnData.fileToken),
|
||||
requestToken: Buffer(data: state.refreshToken)
|
||||
@ -565,6 +566,7 @@ private final class FetchImpl {
|
||||
filePartRequest = self.network.multiplexedRequestManager.request(
|
||||
to: .cdn(cdnData.id),
|
||||
consumerId: self.consumerId,
|
||||
resourceId: self.resource.id.stringRepresentation,
|
||||
data: Api.functions.upload.getCdnFile(
|
||||
fileToken: Buffer(data: cdnData.fileToken),
|
||||
offset: requestedOffset,
|
||||
@ -608,6 +610,7 @@ private final class FetchImpl {
|
||||
filePartRequest = self.network.multiplexedRequestManager.request(
|
||||
to: .main(sourceDatacenterId),
|
||||
consumerId: self.consumerId,
|
||||
resourceId: self.resource.id.stringRepresentation,
|
||||
data: Api.functions.upload.getFile(
|
||||
flags: 0,
|
||||
location: inputLocation,
|
||||
|
@ -81,11 +81,28 @@ private enum MultipartFetchMasterLocation {
|
||||
|
||||
private struct DownloadWrapper {
|
||||
let consumerId: Int64
|
||||
let resourceId: String?
|
||||
let datacenterId: Int32
|
||||
let isCdn: Bool
|
||||
let network: Network
|
||||
let useMainConnection: Bool
|
||||
|
||||
init(
|
||||
consumerId: Int64,
|
||||
resourceId: String?,
|
||||
datacenterId: Int32,
|
||||
isCdn: Bool,
|
||||
network: Network,
|
||||
useMainConnection: Bool
|
||||
) {
|
||||
self.consumerId = consumerId
|
||||
self.resourceId = resourceId
|
||||
self.datacenterId = datacenterId
|
||||
self.isCdn = isCdn
|
||||
self.network = network
|
||||
self.useMainConnection = useMainConnection
|
||||
}
|
||||
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool) -> Signal<(T, NetworkResponseInfo), MTRpcError> {
|
||||
let target: MultiplexedRequestTarget
|
||||
if self.isCdn {
|
||||
@ -93,7 +110,7 @@ private struct DownloadWrapper {
|
||||
} else {
|
||||
target = .main(Int(self.datacenterId))
|
||||
}
|
||||
return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, data: data, tag: tag, continueInBackground: continueInBackground)
|
||||
return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground)
|
||||
|> mapError { error, _ -> MTRpcError in
|
||||
return error
|
||||
}
|
||||
@ -581,7 +598,7 @@ private final class MultipartFetchManager {
|
||||
self.network = network
|
||||
self.networkStatsContext = networkStatsContext
|
||||
self.revalidationContext = revalidationContext
|
||||
self.source = .master(location: location, download: DownloadWrapper(consumerId: self.consumerId, datacenterId: location.datacenterId, isCdn: false, network: network, useMainConnection: self.useMainConnection))
|
||||
self.source = .master(location: location, download: DownloadWrapper(consumerId: self.consumerId, resourceId: self.resource.id.stringRepresentation, datacenterId: location.datacenterId, isCdn: false, network: network, useMainConnection: self.useMainConnection))
|
||||
self.partReady = partReady
|
||||
self.reportCompleteSize = reportCompleteSize
|
||||
self.finishWithError = finishWithError
|
||||
@ -884,7 +901,7 @@ private final class MultipartFetchManager {
|
||||
switch strongSelf.source {
|
||||
case let .master(location, download):
|
||||
strongSelf.partAlignment = dataHashLength
|
||||
strongSelf.source = .cdn(masterDatacenterId: location.datacenterId, cdnDatacenterId: id, fileToken: token, key: key, iv: iv, download: DownloadWrapper(consumerId: strongSelf.consumerId, datacenterId: id, isCdn: true, network: strongSelf.network, useMainConnection: strongSelf.useMainConnection), masterDownload: download, hashSource: MultipartCdnHashSource(queue: strongSelf.queue, fileToken: token, hashes: partHashes, masterDownload: download, continueInBackground: strongSelf.continueInBackground))
|
||||
strongSelf.source = .cdn(masterDatacenterId: location.datacenterId, cdnDatacenterId: id, fileToken: token, key: key, iv: iv, download: DownloadWrapper(consumerId: strongSelf.consumerId, resourceId: strongSelf.resource.id.stringRepresentation, datacenterId: id, isCdn: true, network: strongSelf.network, useMainConnection: strongSelf.useMainConnection), masterDownload: download, hashSource: MultipartCdnHashSource(queue: strongSelf.queue, fileToken: token, hashes: partHashes, masterDownload: download, continueInBackground: strongSelf.continueInBackground))
|
||||
strongSelf.checkState()
|
||||
case .cdn, .none:
|
||||
break
|
||||
|
@ -26,6 +26,7 @@ private struct MultiplexedRequestTargetKey: Equatable, Hashable {
|
||||
private final class RequestData {
|
||||
let id: Int32
|
||||
let consumerId: Int64
|
||||
let resourceId: String?
|
||||
let target: MultiplexedRequestTarget
|
||||
let functionDescription: FunctionDescription
|
||||
let payload: Buffer
|
||||
@ -36,9 +37,10 @@ private final class RequestData {
|
||||
let completed: (Any, NetworkResponseInfo) -> Void
|
||||
let error: (MTRpcError, Double) -> Void
|
||||
|
||||
init(id: Int32, consumerId: Int64, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
|
||||
init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
|
||||
self.id = id
|
||||
self.consumerId = consumerId
|
||||
self.resourceId = resourceId
|
||||
self.target = target
|
||||
self.functionDescription = functionDescription
|
||||
self.tag = tag
|
||||
@ -86,11 +88,11 @@ struct NetworkResponseInfo {
|
||||
var networkDuration: Double
|
||||
}
|
||||
|
||||
final class RequestManagerPriorityContext {
|
||||
|
||||
}
|
||||
|
||||
private final class MultiplexedRequestManagerContext {
|
||||
final class RequestManagerPriorityContext {
|
||||
var resourceCounters: [String: Int] = [:]
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private let takeWorker: (MultiplexedRequestTarget, MediaResourceFetchTag?, Bool) -> Download?
|
||||
|
||||
@ -119,12 +121,29 @@ private final class MultiplexedRequestManagerContext {
|
||||
}
|
||||
}
|
||||
|
||||
func request(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
|
||||
func pushPriority(resourceId: String) -> Disposable {
|
||||
let queue = self.queue
|
||||
|
||||
let value = self.priorityContext.resourceCounters[resourceId] ?? 0
|
||||
self.priorityContext.resourceCounters[resourceId] = value + 1
|
||||
|
||||
return ActionDisposable { [weak self] in
|
||||
queue.async {
|
||||
guard let `self` = self else {
|
||||
return
|
||||
}
|
||||
let value = self.priorityContext.resourceCounters[resourceId] ?? 0
|
||||
self.priorityContext.resourceCounters[resourceId] = max(0, value - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
|
||||
let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground)
|
||||
|
||||
let requestId = self.nextId
|
||||
self.nextId += 1
|
||||
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, deserializeResponse: { buffer in
|
||||
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, deserializeResponse: { buffer in
|
||||
return data.2(buffer)
|
||||
}, completed: { result, info in
|
||||
completed(result, info)
|
||||
@ -169,6 +188,29 @@ private final class MultiplexedRequestManagerContext {
|
||||
let maxWorkersPerTarget = 4
|
||||
|
||||
for request in self.queuedRequests.sorted(by: { lhs, rhs in
|
||||
let lhsPriority = lhs.resourceId.flatMap { id in
|
||||
if let counters = self.priorityContext.resourceCounters[id], counters > 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} ?? false
|
||||
let rhsPriority = rhs.resourceId.flatMap { id in
|
||||
if let counters = self.priorityContext.resourceCounters[id], counters > 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} ?? false
|
||||
|
||||
if lhsPriority != rhsPriority {
|
||||
if lhsPriority {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return lhs.id < rhs.id
|
||||
}) {
|
||||
let targetKey = MultiplexedRequestTargetKey(target: request.target, continueInBackground: request.continueInBackground)
|
||||
@ -291,11 +333,19 @@ final class MultiplexedRequestManager {
|
||||
})
|
||||
}
|
||||
|
||||
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> {
|
||||
func pushPriority(resourceId: String) -> Disposable {
|
||||
let disposable = MetaDisposable()
|
||||
self.context.with { context in
|
||||
disposable.set(context.pushPriority(resourceId: resourceId))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
|
||||
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.context.with { context in
|
||||
disposable.set(context.request(to: target, consumerId: consumerId, data: (data.0, data.1, { buffer in
|
||||
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
||||
return data.2.parse(buffer)
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, completed: { result, _ in
|
||||
if let result = result as? T {
|
||||
@ -312,11 +362,11 @@ final class MultiplexedRequestManager {
|
||||
}
|
||||
}
|
||||
|
||||
func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.context.with { context in
|
||||
disposable.set(context.request(to: target, consumerId: consumerId, data: (data.0, data.1, { buffer in
|
||||
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
||||
return data.2.parse(buffer)
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, completed: { result, info in
|
||||
if let result = result as? T {
|
||||
|
@ -398,5 +398,9 @@ public extension TelegramEngine {
|
||||
public func cancelAllFetches(id: String) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
public func pushPriorityDownload(resourceId: String) -> Disposable {
|
||||
return self.account.network.multiplexedRequestManager.pushPriority(resourceId: resourceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1153,13 +1153,15 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
||||
let item = state.items[focusedIndex]
|
||||
self.focusedId = item.id
|
||||
|
||||
let allItems = state.items.map { stateItem -> StoryContentItem in
|
||||
return StoryContentItem(
|
||||
position: nil,
|
||||
var allItems: [StoryContentItem] = []
|
||||
for i in 0 ..< state.items.count {
|
||||
let stateItem = state.items[i]
|
||||
allItems.append(StoryContentItem(
|
||||
position: i,
|
||||
peerId: peer.id,
|
||||
storyItem: stateItem,
|
||||
entityFiles: extractItemEntityFiles(item: stateItem, allEntityFiles: state.allEntityFiles)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
stateValue = StoryContentContextState(
|
||||
@ -1167,7 +1169,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
||||
peer: peer,
|
||||
additionalPeerData: additionalPeerData,
|
||||
item: StoryContentItem(
|
||||
position: nil,
|
||||
position: focusedIndex,
|
||||
peerId: peer.id,
|
||||
storyItem: item,
|
||||
entityFiles: extractItemEntityFiles(item: item, allEntityFiles: state.allEntityFiles)
|
||||
|
@ -58,6 +58,7 @@ final class StoryItemContentComponent: Component {
|
||||
|
||||
private var currentMessageMedia: EngineMedia?
|
||||
private var fetchDisposable: Disposable?
|
||||
private var priorityDisposable: Disposable?
|
||||
|
||||
private var component: StoryItemContentComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
@ -105,6 +106,7 @@ final class StoryItemContentComponent: Component {
|
||||
|
||||
deinit {
|
||||
self.fetchDisposable?.dispose()
|
||||
self.priorityDisposable?.dispose()
|
||||
self.currentProgressTimer?.invalidate()
|
||||
self.videoProgressDisposable?.dispose()
|
||||
}
|
||||
@ -433,11 +435,18 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
|
||||
if reloadMedia, let messageMedia, let peerReference {
|
||||
self.priorityDisposable?.dispose()
|
||||
self.priorityDisposable = nil
|
||||
|
||||
var fetchSignal: Signal<Never, NoError>?
|
||||
switch messageMedia {
|
||||
case .image:
|
||||
break
|
||||
case let .image(image):
|
||||
if let representation = largestImageRepresentation(image.representations) {
|
||||
self.priorityDisposable = component.context.engine.resources.pushPriorityDownload(resourceId: representation.resource.id.stringRepresentation)
|
||||
}
|
||||
case let .file(file):
|
||||
self.priorityDisposable = component.context.engine.resources.pushPriorityDownload(resourceId: file.resource.id.stringRepresentation)
|
||||
|
||||
fetchSignal = fetchedMediaResource(
|
||||
mediaBox: component.context.account.postbox.mediaBox,
|
||||
userLocation: .other,
|
||||
|
@ -42,12 +42,20 @@ final class StoryItemImageView: UIView {
|
||||
|
||||
func update(context: AccountContext, peer: EnginePeer, storyId: Int32, media: EngineMedia, size: CGSize, isCaptureProtected: Bool, attemptSynchronous: Bool, transition: Transition) {
|
||||
var dimensions: CGSize?
|
||||
|
||||
let isMediaUpdated: Bool
|
||||
if let currentMedia = self.currentMedia {
|
||||
isMediaUpdated = !currentMedia._asMedia().isSemanticallyEqual(to: media._asMedia())
|
||||
} else {
|
||||
isMediaUpdated = true
|
||||
}
|
||||
|
||||
switch media {
|
||||
case let .image(image):
|
||||
if let representation = largestImageRepresentation(image.representations) {
|
||||
dimensions = representation.dimensions.cgSize
|
||||
|
||||
if self.currentMedia != media {
|
||||
if isMediaUpdated {
|
||||
if attemptSynchronous, let path = context.account.postbox.mediaBox.completedResourcePath(id: representation.resource.id, pathExtension: nil) {
|
||||
if #available(iOS 15.0, *) {
|
||||
if let image = UIImage(contentsOfFile: path)?.preparingForDisplay() {
|
||||
@ -104,7 +112,7 @@ final class StoryItemImageView: UIView {
|
||||
case let .file(file):
|
||||
dimensions = file.dimensions?.cgSize
|
||||
|
||||
if self.currentMedia != media {
|
||||
if isMediaUpdated {
|
||||
let cachedPath = context.account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedVideoFirstFrameRepresentation())
|
||||
|
||||
if attemptSynchronous, FileManager.default.fileExists(atPath: cachedPath) {
|
||||
|
@ -227,16 +227,33 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
struct ItemLayout {
|
||||
var containerSize: CGSize
|
||||
var contentFrame: CGRect
|
||||
var contentVisualScale: CGFloat
|
||||
var contentMinScale: CGFloat
|
||||
var contentScaleFraction: CGFloat
|
||||
|
||||
var itemSpacing: CGFloat
|
||||
var centralVisibleItemWidth: CGFloat
|
||||
var sideVisibleItemWidth: CGFloat
|
||||
var fullItemScrollDistance: CGFloat
|
||||
var sideVisibleItemScale: CGFloat
|
||||
var halfItemScrollDistance: CGFloat
|
||||
|
||||
init(
|
||||
containerSize: CGSize,
|
||||
contentFrame: CGRect,
|
||||
contentVisualScale: CGFloat
|
||||
contentMinScale: CGFloat,
|
||||
contentScaleFraction: CGFloat
|
||||
) {
|
||||
self.containerSize = containerSize
|
||||
self.contentFrame = contentFrame
|
||||
self.contentVisualScale = contentVisualScale
|
||||
self.contentMinScale = contentMinScale
|
||||
self.contentScaleFraction = contentScaleFraction
|
||||
|
||||
self.itemSpacing = 12.0
|
||||
self.centralVisibleItemWidth = self.contentFrame.width * self.contentMinScale
|
||||
self.sideVisibleItemWidth = self.centralVisibleItemWidth - 30.0
|
||||
self.fullItemScrollDistance = self.centralVisibleItemWidth * 0.5 + self.itemSpacing + self.sideVisibleItemWidth * 0.5
|
||||
self.sideVisibleItemScale = self.contentMinScale * (self.sideVisibleItemWidth / self.centralVisibleItemWidth)
|
||||
self.halfItemScrollDistance = self.sideVisibleItemWidth * 0.5 + self.itemSpacing + self.sideVisibleItemWidth * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,8 +368,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
var visibleItems: [Int32: VisibleItem] = [:]
|
||||
var trulyValidIds: [Int32] = []
|
||||
var scrollingOffsetX: CGFloat = 0.0
|
||||
var scrollingCenterX: CGFloat = 0.0
|
||||
|
||||
var reactionContextNode: ReactionContextNode?
|
||||
weak var disappearingReactionContextNode: ReactionContextNode?
|
||||
@ -392,7 +407,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.scroller.alwaysBounceHorizontal = true
|
||||
self.scroller.showsVerticalScrollIndicator = false
|
||||
self.scroller.showsHorizontalScrollIndicator = false
|
||||
self.scroller.decelerationRate = .fast
|
||||
self.scroller.decelerationRate = .normal
|
||||
self.scroller.delaysContentTouches = false
|
||||
|
||||
self.controlsContainerView = SparseContainerView()
|
||||
@ -798,14 +813,33 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.scrollingOffsetX = scrollView.contentOffset.x
|
||||
|
||||
self.adjustScroller()
|
||||
self.updateScrolling(transition: .immediate)
|
||||
|
||||
if let component = self.component, let itemLayout = self.itemLayout, itemLayout.contentScaleFraction >= 1.0 - 0.0001 {
|
||||
var index = Int(round(scrollView.contentOffset.x / itemLayout.fullItemScrollDistance))
|
||||
index = max(0, min(index, component.slice.allItems.count - 1))
|
||||
|
||||
if let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
|
||||
if index != currentIndex {
|
||||
let nextId = component.slice.allItems[index].storyItem.id
|
||||
self.awaitingSwitchToId = (component.slice.allItems[currentIndex].storyItem.id, nextId)
|
||||
component.navigate(.id(nextId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
guard let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
if targetContentOffset.pointee.x <= 0.0 || targetContentOffset.pointee.x >= scrollView.contentSize.width - scrollView.bounds.width {
|
||||
return
|
||||
}
|
||||
|
||||
let closestIndex = Int(round(targetContentOffset.pointee.x / itemLayout.fullItemScrollDistance))
|
||||
targetContentOffset.pointee.x = CGFloat(closestIndex) * itemLayout.fullItemScrollDistance
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
@ -819,86 +853,16 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
private func snapScrolling() {
|
||||
self.scroller.setContentOffset(CGPoint(x: self.scrollingCenterX, y: 0.0), animated: true)
|
||||
}
|
||||
|
||||
private func adjustScroller() {
|
||||
guard let component = self.component, let itemLayout = self.itemLayout else {
|
||||
guard let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
let contentOffset = self.scroller.contentOffset
|
||||
if contentOffset.x <= 0.0 || contentOffset.x >= self.scroller.contentSize.width - self.scroller.bounds.width {
|
||||
return
|
||||
}
|
||||
|
||||
self.ignoreScrolling = true
|
||||
|
||||
self.scroller.isScrollEnabled = self.displayViewList
|
||||
|
||||
let itemSpacing: CGFloat = 12.0
|
||||
let centralVisibleItemWidth = itemLayout.contentFrame.width * itemLayout.contentVisualScale
|
||||
let sideVisibleItemWidth = centralVisibleItemWidth - 30.0
|
||||
let fullItemScrollDistance = centralVisibleItemWidth * 0.5 + itemSpacing + sideVisibleItemWidth * 0.5
|
||||
|
||||
var additionalInitializationDistance: CGFloat = 0.0
|
||||
if let (switchFromId, switchToId) = self.awaitingSwitchToId {
|
||||
if component.slice.item.storyItem.id == switchToId {
|
||||
self.awaitingSwitchToId = nil
|
||||
|
||||
if let previousIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == switchFromId }), let centralIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == switchToId }) {
|
||||
let fractionDistance = CGFloat(previousIndex - centralIndex)
|
||||
|
||||
let currentOffset = self.scrollingCenterX - self.scrollingOffsetX
|
||||
additionalInitializationDistance = -(currentOffset - fractionDistance * fullItemScrollDistance)
|
||||
}
|
||||
|
||||
self.initializedOffset = false
|
||||
} else {
|
||||
self.ignoreScrolling = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let centralIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
|
||||
var leftWidth: CGFloat = 0.0
|
||||
var rightWidth: CGFloat = 0.0
|
||||
if centralIndex != 0 {
|
||||
leftWidth = 600.0
|
||||
}
|
||||
if centralIndex != component.slice.allItems.count - 1 {
|
||||
rightWidth = 600.0
|
||||
}
|
||||
|
||||
self.scrollingCenterX = leftWidth
|
||||
self.scroller.contentSize = CGSize(width: leftWidth + itemLayout.containerSize.width + rightWidth, height: 1.0)
|
||||
|
||||
if !self.initializedOffset {
|
||||
self.initializedOffset = true
|
||||
self.scrollingOffsetX = leftWidth + additionalInitializationDistance
|
||||
self.scroller.contentOffset = CGPoint(x: self.scrollingOffsetX, y: 0.0)
|
||||
}
|
||||
|
||||
var lowestFraction: (Int, CGFloat)?
|
||||
|
||||
for index in 0 ..< component.slice.allItems.count {
|
||||
let offsetFraction: CGFloat = (self.scrollingCenterX - self.scrollingOffsetX) / fullItemScrollDistance
|
||||
let centerFraction: CGFloat = CGFloat(index - centralIndex)
|
||||
|
||||
let combinedFraction = abs(offsetFraction + centerFraction)
|
||||
|
||||
if let (_, lowestValue) = lowestFraction {
|
||||
if combinedFraction < lowestValue {
|
||||
lowestFraction = (index, combinedFraction)
|
||||
}
|
||||
} else {
|
||||
lowestFraction = (index, combinedFraction)
|
||||
}
|
||||
}
|
||||
|
||||
if let (index, _) = lowestFraction, index != centralIndex {
|
||||
let fixedId = component.slice.allItems[index].storyItem.id
|
||||
component.navigate(.id(fixedId))
|
||||
self.awaitingSwitchToId = (component.slice.item.storyItem.id, fixedId)
|
||||
}
|
||||
}
|
||||
|
||||
self.ignoreScrolling = false
|
||||
let closestIndex = Int(round(contentOffset.x / itemLayout.fullItemScrollDistance))
|
||||
self.scroller.setContentOffset(CGPoint(x: CGFloat(closestIndex) * itemLayout.fullItemScrollDistance, y: 0.0), animated: true)
|
||||
}
|
||||
|
||||
private func isProgressPaused() -> Bool {
|
||||
@ -952,43 +916,50 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
var validIds: [Int32] = []
|
||||
var trulyValidIds: [Int32] = []
|
||||
|
||||
let centralItemFrame = itemLayout.contentFrame.center.offsetBy(dx: 0.0, dy: 0.0)
|
||||
let centralItemX = itemLayout.contentFrame.center.x
|
||||
|
||||
let centralVisibleItemWidth = itemLayout.contentFrame.width * itemLayout.contentVisualScale
|
||||
let sideVisibleItemWidth = centralVisibleItemWidth - 30.0
|
||||
let sideVisibleItemScale = itemLayout.contentVisualScale * (sideVisibleItemWidth / centralVisibleItemWidth)
|
||||
|
||||
let itemSpacing: CGFloat = 12.0
|
||||
|
||||
let fullItemScrollDistance = centralVisibleItemWidth * 0.5 + itemSpacing + sideVisibleItemWidth * 0.5
|
||||
let halfItemScrollDistance = sideVisibleItemWidth * 0.5 + itemSpacing + sideVisibleItemWidth * 0.5
|
||||
let currentContentScale = itemLayout.contentMinScale * itemLayout.contentScaleFraction + 1.0 * (1.0 - itemLayout.contentScaleFraction)
|
||||
let scaledCentralVisibleItemWidth = itemLayout.contentFrame.width * currentContentScale
|
||||
let scaledSideVisibleItemWidth = scaledCentralVisibleItemWidth - 30.0 * itemLayout.contentScaleFraction
|
||||
let scaledFullItemScrollDistance = scaledCentralVisibleItemWidth * 0.5 + itemLayout.itemSpacing + scaledSideVisibleItemWidth * 0.5
|
||||
let scaledHalfItemScrollDistance = scaledSideVisibleItemWidth * 0.5 + itemLayout.itemSpacing + scaledSideVisibleItemWidth * 0.5
|
||||
|
||||
if let centralIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
|
||||
let centralItemOffset: CGFloat = itemLayout.fullItemScrollDistance * CGFloat(centralIndex)
|
||||
let effectiveScrollingOffsetX = self.scroller.contentOffset.x * itemLayout.contentScaleFraction + centralItemOffset * (1.0 - itemLayout.contentScaleFraction)
|
||||
|
||||
for index in 0 ..< component.slice.allItems.count {
|
||||
let item = component.slice.allItems[index]
|
||||
|
||||
var offsetFraction: CGFloat = (self.scrollingCenterX - self.scrollingOffsetX) / fullItemScrollDistance
|
||||
var offsetFraction: CGFloat = -effectiveScrollingOffsetX / itemLayout.fullItemScrollDistance
|
||||
if let viewListPanState = self.viewListPanState {
|
||||
offsetFraction += viewListPanState.fraction
|
||||
}
|
||||
|
||||
let centerIndexOffset = index - centralIndex
|
||||
let centerFraction: CGFloat = CGFloat(centerIndexOffset)
|
||||
let zeroIndexOffset = index
|
||||
let centerFraction: CGFloat = CGFloat(zeroIndexOffset)
|
||||
|
||||
let combinedFraction = offsetFraction + centerFraction
|
||||
let combinedFractionSign: CGFloat = combinedFraction < 0.0 ? -1.0 : 1.0
|
||||
|
||||
let fractionDistanceToCenter: CGFloat = min(1.0, abs(combinedFraction))
|
||||
|
||||
var itemPosition = centralItemFrame
|
||||
itemPosition.x += min(1.0, abs(combinedFraction)) * combinedFractionSign * fullItemScrollDistance
|
||||
itemPosition.x += max(0.0, abs(combinedFraction) - 1.0) * combinedFractionSign * halfItemScrollDistance
|
||||
var itemPositionX = centralItemX
|
||||
itemPositionX += min(1.0, abs(combinedFraction)) * combinedFractionSign * scaledFullItemScrollDistance
|
||||
itemPositionX += max(0.0, abs(combinedFraction) - 1.0) * combinedFractionSign * scaledHalfItemScrollDistance
|
||||
|
||||
var itemVisible = true
|
||||
if abs(centerIndexOffset) > 2 {
|
||||
itemVisible = false
|
||||
var logicalItemPositionX = centralItemX
|
||||
logicalItemPositionX += min(1.0, abs(combinedFraction)) * combinedFractionSign * itemLayout.fullItemScrollDistance
|
||||
logicalItemPositionX += max(0.0, abs(combinedFraction) - 1.0) * combinedFractionSign * itemLayout.halfItemScrollDistance
|
||||
|
||||
var itemVisible = false
|
||||
let itemLeftEdge = logicalItemPositionX - itemLayout.fullItemScrollDistance * 0.5
|
||||
let itemRightEdge = logicalItemPositionX + itemLayout.fullItemScrollDistance * 0.5
|
||||
if itemRightEdge >= -itemLayout.containerSize.width && itemLeftEdge < itemLayout.containerSize.width * 2.0 {
|
||||
itemVisible = true
|
||||
}
|
||||
if itemLayout.contentVisualScale >= 1.0 - 0.001 && !self.preparingToDisplayViewList {
|
||||
|
||||
if itemLayout.contentScaleFraction <= 0.0001 && !self.preparingToDisplayViewList {
|
||||
if index != centralIndex {
|
||||
itemVisible = false
|
||||
}
|
||||
@ -1007,7 +978,9 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
let scaleFraction: CGFloat = abs(max(-1.0, min(1.0, combinedFraction)))
|
||||
let itemScale = itemLayout.contentVisualScale * (1.0 - scaleFraction) + sideVisibleItemScale * scaleFraction
|
||||
|
||||
let minItemScale = itemLayout.contentMinScale * (1.0 - scaleFraction) + itemLayout.sideVisibleItemScale * scaleFraction
|
||||
let itemScale: CGFloat = itemLayout.contentScaleFraction * minItemScale + (1.0 - itemLayout.contentScaleFraction) * 1.0
|
||||
|
||||
validIds.append(item.storyItem.id)
|
||||
if itemVisible {
|
||||
@ -1058,7 +1031,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
)
|
||||
let _ = visibleItem.view.update(
|
||||
transition: itemTransition.withUserData(StoryItemContentComponent.Hint(
|
||||
synchronousLoad: index == centralIndex
|
||||
synchronousLoad: index == centralIndex && itemLayout.contentScaleFraction <= 0.0001
|
||||
)),
|
||||
component: AnyComponent(StoryItemContentComponent(
|
||||
context: component.context,
|
||||
@ -1081,7 +1054,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
itemTransition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size))
|
||||
|
||||
let itemId = item.storyItem.id
|
||||
itemTransition.setPosition(view: visibleItem.contentContainerView, position: itemPosition, completion: { [weak self] _ in
|
||||
itemTransition.setPosition(view: visibleItem.contentContainerView, position: CGPoint(x: itemPositionX, y: itemLayout.contentFrame.center.y), completion: { [weak self] _ in
|
||||
guard reevaluateVisibilityOnCompletion, let self else {
|
||||
return
|
||||
}
|
||||
@ -1613,10 +1586,12 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.initializedOffset = false
|
||||
}
|
||||
var itemsTransition = transition
|
||||
var resetScrollingOffsetWithItemTransition = false
|
||||
if let animateNextNavigationId = self.animateNextNavigationId, animateNextNavigationId == component.slice.item.storyItem.id {
|
||||
self.animateNextNavigationId = nil
|
||||
self.viewListPanState = nil
|
||||
itemsTransition = transition.withAnimation(.curve(duration: 0.3, curve: .spring))
|
||||
resetScrollingOffsetWithItemTransition = true
|
||||
}
|
||||
|
||||
if self.topContentGradientLayer.colors == nil {
|
||||
@ -1907,6 +1882,10 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
inputPanelIsOverlay = true
|
||||
}
|
||||
|
||||
var minimizedBottomContentHeight: CGFloat = 0.0
|
||||
var maximizedBottomContentHeight: CGFloat = 0.0
|
||||
var minimizedBottomContentFraction: CGFloat = 0.0
|
||||
|
||||
var validViewListIds: [Int32] = []
|
||||
if component.slice.peer.id == component.context.account.peerId, let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
|
||||
var visibleViewListIds: [Int32] = [component.slice.item.storyItem.id]
|
||||
@ -1980,6 +1959,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
peerId: component.slice.peer.id,
|
||||
safeInsets: component.safeInsets,
|
||||
storyItem: item.storyItem,
|
||||
minimizedContentHeight: 325.0,
|
||||
outerExpansionFraction: outerExpansionFraction,
|
||||
outerExpansionDirection: outerExpansionDirection,
|
||||
close: { [weak self] in
|
||||
@ -2079,8 +2059,11 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
}
|
||||
if id == component.slice.item.storyItem.id {
|
||||
viewListInset = viewList.externalState.effectiveHeight
|
||||
viewListInset = viewList.externalState.minimizedHeight * viewList.externalState.minimizationFraction + viewList.externalState.defaultHeight * (1.0 - viewList.externalState.minimizationFraction)
|
||||
inputPanelBottomInset = viewListInset
|
||||
minimizedBottomContentHeight = viewList.externalState.minimizedHeight
|
||||
maximizedBottomContentHeight = viewList.externalState.defaultHeight
|
||||
minimizedBottomContentFraction = viewList.externalState.minimizationFraction
|
||||
}
|
||||
}
|
||||
|
||||
@ -2118,22 +2101,41 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
let itemSize = CGSize(width: availableSize.width, height: ceil(availableSize.width * 1.77778))
|
||||
let contentDefaultBottomInset: CGFloat = bottomContentInset
|
||||
let contentSize = itemSize
|
||||
|
||||
let contentVisualBottomInset: CGFloat = max(contentDefaultBottomInset, viewListInset)
|
||||
let contentVisualMaxBottomInset: CGFloat = max(contentDefaultBottomInset, maximizedBottomContentHeight)
|
||||
let contentVisualMinBottomInset: CGFloat = max(contentDefaultBottomInset, minimizedBottomContentHeight)
|
||||
|
||||
let contentVisualMaxHeight = min(itemSize.height, availableSize.height - component.containerInsets.top - contentVisualMaxBottomInset)
|
||||
let contentSize = CGSize(width: itemSize.width, height: contentVisualMaxHeight)
|
||||
let contentVisualMinHeight = min(contentSize.height, availableSize.height - component.containerInsets.top - contentVisualMinBottomInset)
|
||||
|
||||
let contentVisualHeight = min(contentSize.height, availableSize.height - component.containerInsets.top - contentVisualBottomInset)
|
||||
|
||||
//contentScaleFraction = 1.0 -> contentVisualScale = contentMinScale
|
||||
//contentScaleFraction = 0.0 -> contentVisualScale = 1.0
|
||||
|
||||
var contentVisualHeight = min(contentSize.height, availableSize.height - component.containerInsets.top - contentVisualBottomInset)
|
||||
if contentVisualHeight < contentSize.height && contentVisualHeight >= contentSize.height - 5 {
|
||||
contentVisualHeight = contentSize.height
|
||||
}
|
||||
let contentVisualScale = min(1.0, contentVisualHeight / contentSize.height)
|
||||
|
||||
let contentMaxScale = min(1.0, contentVisualMaxHeight / contentSize.height)
|
||||
let contentMinScale = min(1.0, contentVisualMinHeight / contentSize.height)
|
||||
|
||||
let contentScaleFraction: CGFloat
|
||||
if abs(contentMaxScale - contentMinScale) < CGFloat.ulpOfOne {
|
||||
contentScaleFraction = 0.0
|
||||
} else {
|
||||
contentScaleFraction = 1.0 - (contentVisualScale - contentMinScale) / (contentMaxScale - contentMinScale)
|
||||
}
|
||||
let _ = minimizedBottomContentFraction
|
||||
//print("contentScaleFraction: \(contentScaleFraction), minimizedBottomContentFraction: \(minimizedBottomContentFraction)")
|
||||
|
||||
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: component.containerInsets.top - (contentSize.height - contentVisualHeight) * 0.5), size: contentSize)
|
||||
|
||||
let itemLayout = ItemLayout(
|
||||
containerSize: availableSize,
|
||||
contentFrame: contentFrame,
|
||||
contentVisualScale: contentVisualScale
|
||||
contentMinScale: contentMinScale,
|
||||
contentScaleFraction: contentScaleFraction
|
||||
)
|
||||
self.itemLayout = itemLayout
|
||||
|
||||
@ -2894,10 +2896,28 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
self.ignoreScrolling = true
|
||||
|
||||
transition.setFrame(view: self.scroller, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
self.scroller.contentSize = CGSize(width: itemLayout.fullItemScrollDistance * CGFloat(max(0, component.slice.allItems.count - 1)) + availableSize.width, height: availableSize.height)
|
||||
self.scroller.isScrollEnabled = itemLayout.contentScaleFraction >= 1.0 - 0.0001
|
||||
|
||||
if let centralIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
|
||||
let centralX = itemLayout.fullItemScrollDistance * CGFloat(centralIndex)
|
||||
if itemLayout.contentScaleFraction <= 0.0001 {
|
||||
if abs(self.scroller.contentOffset.x - centralX) > CGFloat.ulpOfOne {
|
||||
self.scroller.contentOffset = CGPoint(x: centralX, y: 0.0)
|
||||
}
|
||||
} else if resetScrollingOffsetWithItemTransition {
|
||||
let deltaX = centralX - self.scroller.contentOffset.x
|
||||
if abs(deltaX) > CGFloat.ulpOfOne {
|
||||
self.scroller.contentOffset = CGPoint(x: centralX, y: 0.0)
|
||||
//itemsTransition.animateBoundsOrigin(view: self.itemsContainerView, from: CGPoint(x: deltaX, y: 0.0), to: CGPoint(), additive: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.ignoreScrolling = false
|
||||
|
||||
self.adjustScroller()
|
||||
self.updateScrolling(transition: itemsTransition)
|
||||
|
||||
if let focusedItem, let visibleItem = self.visibleItems[focusedItem.storyItem.id], let index = focusedItem.position {
|
||||
|
@ -26,7 +26,8 @@ final class StoryItemSetViewListComponent: Component {
|
||||
|
||||
final class ExternalState {
|
||||
fileprivate(set) var minimizedHeight: CGFloat = 0.0
|
||||
fileprivate(set) var effectiveHeight: CGFloat = 0.0
|
||||
fileprivate(set) var defaultHeight: CGFloat = 0.0
|
||||
fileprivate(set) var minimizationFraction: CGFloat = 0.0
|
||||
|
||||
init() {
|
||||
}
|
||||
@ -47,6 +48,7 @@ final class StoryItemSetViewListComponent: Component {
|
||||
let peerId: EnginePeer.Id
|
||||
let safeInsets: UIEdgeInsets
|
||||
let storyItem: EngineStoryItem
|
||||
let minimizedContentHeight: CGFloat
|
||||
let outerExpansionFraction: CGFloat
|
||||
let outerExpansionDirection: Bool
|
||||
let close: () -> Void
|
||||
@ -64,6 +66,7 @@ final class StoryItemSetViewListComponent: Component {
|
||||
peerId: EnginePeer.Id,
|
||||
safeInsets: UIEdgeInsets,
|
||||
storyItem: EngineStoryItem,
|
||||
minimizedContentHeight: CGFloat,
|
||||
outerExpansionFraction: CGFloat,
|
||||
outerExpansionDirection: Bool,
|
||||
close: @escaping () -> Void,
|
||||
@ -80,6 +83,7 @@ final class StoryItemSetViewListComponent: Component {
|
||||
self.peerId = peerId
|
||||
self.safeInsets = safeInsets
|
||||
self.storyItem = storyItem
|
||||
self.minimizedContentHeight = minimizedContentHeight
|
||||
self.outerExpansionFraction = outerExpansionFraction
|
||||
self.outerExpansionDirection = outerExpansionDirection
|
||||
self.close = close
|
||||
@ -105,6 +109,9 @@ final class StoryItemSetViewListComponent: Component {
|
||||
if lhs.storyItem != rhs.storyItem {
|
||||
return false
|
||||
}
|
||||
if lhs.minimizedContentHeight != rhs.minimizedContentHeight {
|
||||
return false
|
||||
}
|
||||
if lhs.outerExpansionFraction != rhs.outerExpansionFraction {
|
||||
return false
|
||||
}
|
||||
@ -559,7 +566,7 @@ final class StoryItemSetViewListComponent: Component {
|
||||
synchronous = animationHint.synchronous
|
||||
}
|
||||
|
||||
let minimizedHeight = max(100.0, availableSize.height - (325.0 + 12.0))
|
||||
let minimizedHeight = max(100.0, availableSize.height - (component.minimizedContentHeight + 12.0))
|
||||
|
||||
if themeUpdated {
|
||||
self.backgroundView.backgroundColor = component.theme.rootController.navigationBar.blurredBackgroundColor
|
||||
@ -655,11 +662,17 @@ final class StoryItemSetViewListComponent: Component {
|
||||
|
||||
let dismissFraction: CGFloat = 1.0 - max(0.0, min(1.0, -dismissOffsetY / expansionOffset))
|
||||
|
||||
var externalViews: EngineStoryItem.Views? = component.storyItem.views
|
||||
if let viewListState = self.viewListState, !viewListState.items.isEmpty {
|
||||
externalViews = EngineStoryItem.Views(seenCount: viewListState.totalCount, seenPeers: viewListState.items.prefix(3).map(\.peer))
|
||||
}
|
||||
|
||||
let navigationPanelSize = self.navigationPanel.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(StoryFooterPanelComponent(
|
||||
context: component.context,
|
||||
storyItem: component.storyItem,
|
||||
externalViews: externalViews,
|
||||
expandFraction: dismissFraction,
|
||||
expandViewStats: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
@ -853,9 +866,11 @@ final class StoryItemSetViewListComponent: Component {
|
||||
transition.setBoundsOrigin(view: self, origin: CGPoint(x: 0.0, y: dismissOffsetY))
|
||||
|
||||
component.externalState.minimizedHeight = minimizedHeight
|
||||
component.externalState.defaultHeight = 60.0 + component.safeInsets.bottom + 1.0
|
||||
|
||||
let effectiveHeight: CGFloat = minimizedHeight * dismissFraction + (1.0 - dismissFraction) * (60.0 + component.safeInsets.bottom + 1.0)
|
||||
component.externalState.effectiveHeight = min(minimizedHeight, max(0.0, effectiveHeight))
|
||||
//let effectiveHeight: CGFloat = minimizedHeight * dismissFraction + (1.0 - dismissFraction) * (60.0 + component.safeInsets.bottom + 1.0)
|
||||
//component.externalState.effectiveHeight = min(minimizedHeight, max(0.0, effectiveHeight))
|
||||
component.externalState.minimizationFraction = dismissFraction
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import SwiftSignalKit
|
||||
public final class StoryFooterPanelComponent: Component {
|
||||
public let context: AccountContext
|
||||
public let storyItem: EngineStoryItem?
|
||||
public let externalViews: EngineStoryItem.Views?
|
||||
public let expandFraction: CGFloat
|
||||
public let expandViewStats: () -> Void
|
||||
public let deleteAction: () -> Void
|
||||
@ -22,6 +23,7 @@ public final class StoryFooterPanelComponent: Component {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
storyItem: EngineStoryItem?,
|
||||
externalViews: EngineStoryItem.Views?,
|
||||
expandFraction: CGFloat,
|
||||
expandViewStats: @escaping () -> Void,
|
||||
deleteAction: @escaping () -> Void,
|
||||
@ -29,6 +31,7 @@ public final class StoryFooterPanelComponent: Component {
|
||||
) {
|
||||
self.context = context
|
||||
self.storyItem = storyItem
|
||||
self.externalViews = externalViews
|
||||
self.expandViewStats = expandViewStats
|
||||
self.expandFraction = expandFraction
|
||||
self.deleteAction = deleteAction
|
||||
@ -42,6 +45,9 @@ public final class StoryFooterPanelComponent: Component {
|
||||
if lhs.storyItem != rhs.storyItem {
|
||||
return false
|
||||
}
|
||||
if lhs.externalViews != rhs.externalViews {
|
||||
return false
|
||||
}
|
||||
if lhs.expandFraction != rhs.expandFraction {
|
||||
return false
|
||||
}
|
||||
@ -233,7 +239,7 @@ public final class StoryFooterPanelComponent: Component {
|
||||
}
|
||||
|
||||
var peers: [EnginePeer] = []
|
||||
if let seenPeers = component.storyItem?.views?.seenPeers {
|
||||
if let seenPeers = component.externalViews?.seenPeers ?? component.storyItem?.views?.seenPeers {
|
||||
peers = Array(seenPeers.prefix(3))
|
||||
}
|
||||
let avatarsContent = self.avatarsContext.update(peers: peers, animated: false)
|
||||
@ -248,7 +254,7 @@ public final class StoryFooterPanelComponent: Component {
|
||||
}
|
||||
|
||||
var viewCount = 0
|
||||
if let views = component.storyItem?.views, views.seenCount != 0 {
|
||||
if let views = component.externalViews ?? component.storyItem?.views, views.seenCount != 0 {
|
||||
viewCount = views.seenCount
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user