diff --git a/submodules/AccountContext/Sources/IsMediaStreamable.swift b/submodules/AccountContext/Sources/IsMediaStreamable.swift index 0c93c9ceb8..a562f0a32c 100644 --- a/submodules/AccountContext/Sources/IsMediaStreamable.swift +++ b/submodules/AccountContext/Sources/IsMediaStreamable.swift @@ -18,7 +18,7 @@ public func isMediaStreamable(message: Message, media: TelegramMediaFile) -> Boo return false } for attribute in media.attributes { - if case let .Video(_, _, flags) = attribute { + if case let .Video(_, _, flags, _) = attribute { if flags.contains(.supportsStreaming) { return true } @@ -41,7 +41,7 @@ public func isMediaStreamable(media: TelegramMediaFile) -> Bool { return false } for attribute in media.attributes { - if case let .Video(_, _, flags) = attribute { + if case let .Video(_, _, flags, _) = attribute { if flags.contains(.supportsStreaming) { return true } diff --git a/submodules/AnimatedAvatarSetNode/Sources/AnimatedAvatarSetNode.swift b/submodules/AnimatedAvatarSetNode/Sources/AnimatedAvatarSetNode.swift index 8b582a1484..4ad78d07e6 100644 --- a/submodules/AnimatedAvatarSetNode/Sources/AnimatedAvatarSetNode.swift +++ b/submodules/AnimatedAvatarSetNode/Sources/AnimatedAvatarSetNode.swift @@ -277,10 +277,14 @@ public final class AnimatedAvatarSetNode: ASDisplayNode { guard let itemNode = self.contentNodes.removeValue(forKey: key) else { continue } - itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak itemNode] _ in - itemNode?.removeFromSupernode() - }) - itemNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false) + if animated { + itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + itemNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false) + } else { + itemNode.removeFromSupernode() + } } return CGSize(width: contentWidth, height: contentHeight) diff --git a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift index d0570afad1..a4b1f77650 100644 --- a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift +++ b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift @@ -206,7 +206,7 @@ public final class AvatarVideoNode: ASDisplayNode { self.backgroundNode.image = nil let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value() - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { self.videoNode?.removeFromSupernode() diff --git a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift index eaa0290715..9749970787 100644 --- a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift +++ b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift @@ -459,7 +459,7 @@ public final class ChatImportActivityScreen: ViewController { if let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) - let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) + let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil)]) let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 4b0b2b3faa..7b07957515 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -244,9 +244,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private var powerSavingMonitoringDisposable: Disposable? - public private(set) var storyListContext: StoryListContext? - private var storyListState: StoryListContext.State? - private var storyListStateDisposable: Disposable? + private var storySubscriptions: EngineStorySubscriptions? + + private var storySubscriptionsDisposable: Disposable? + private var preloadStorySubscriptionsDisposable: Disposable? + private var preloadStoryResourceDisposables: [MediaResourceId: Disposable] = [:] private var storyListHeight: CGFloat @@ -802,7 +804,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.joinForumDisposable.dispose() self.actionDisposables.dispose() self.powerSavingMonitoringDisposable?.dispose() - self.storyListStateDisposable?.dispose() + self.storySubscriptionsDisposable?.dispose() + self.preloadStorySubscriptionsDisposable?.dispose() } private func updateNavigationMetadata() { @@ -1379,25 +1382,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } self.chatListDisplayNode.mainContainerNode.openStories = { [weak self] peerId in - guard let self, let storyListContext = self.storyListContext else { + guard let self else { return } - let _ = (StoryChatContent.stories( - context: self.context, - storyList: storyListContext, - focusItem: nil - ) + let storyContent = StoryContentContextImpl(context: self.context, focusedPeerId: peerId) + let _ = (storyContent.state + |> filter { $0.slice != nil } |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] initialContent in + |> deliverOnMainQueue).start(next: { [weak self] _ in guard let self else { return } let storyContainerScreen = StoryContainerScreen( context: self.context, - initialFocusedId: AnyHashable(peerId), - initialContent: initialContent, + content: storyContent, transitionIn: nil, transitionOut: { _, _ in return nil @@ -2123,30 +2123,59 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } }) - let storyListContext = self.context.engine.messages.allStories() - self.storyListContext = storyListContext - self.storyListStateDisposable = (storyListContext.state - |> deliverOnMainQueue).start(next: { [weak self] state in + self.preloadStorySubscriptionsDisposable = (self.context.engine.messages.preloadStorySubscriptions() + |> deliverOnMainQueue).start(next: { [weak self] resources in guard let self else { return } + + var validIds: [MediaResourceId] = [] + for (_, info) in resources.sorted(by: { $0.value.priority < $1.value.priority }) { + let resource = info.resource + validIds.append(resource.resource.id) + if self.preloadStoryResourceDisposables[resource.resource.id] == nil { + var fetchRange: (Range, MediaBoxFetchPriority)? + if let size = info.size { + fetchRange = (0 ..< Int64(size), .default) + } + self.preloadStoryResourceDisposables[resource.resource.id] = fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: resource, range: fetchRange).start() + } + } + + var removeIds: [MediaResourceId] = [] + for (id, disposable) in self.preloadStoryResourceDisposables { + if !validIds.contains(id) { + removeIds.append(id) + disposable.dispose() + } + } + for id in removeIds { + self.preloadStoryResourceDisposables.removeValue(forKey: id) + } + }) + self.storySubscriptionsDisposable = (self.context.engine.messages.storySubscriptions() + |> deliverOnMainQueue).start(next: { [weak self] storySubscriptions in + guard let self else { + return + } + var wasEmpty = true - if let storyListState = self.storyListState, !storyListState.itemSets.isEmpty { + if let storySubscriptions = self.storySubscriptions, !storySubscriptions.items.isEmpty { wasEmpty = false } - self.storyListState = state - let isEmpty = state.itemSets.isEmpty + self.storySubscriptions = storySubscriptions + let isEmpty = storySubscriptions.items.isEmpty self.chatListDisplayNode.mainContainerNode.currentItemNode.updateState { chatListState in var chatListState = chatListState var peersWithNewStories = Set() - for itemSet in state.itemSets { - if itemSet.peerId == self.context.account.peerId { + for item in storySubscriptions.items { + if item.peer.id == self.context.account.peerId { continue } - if itemSet.items.contains(where: { $0.id > itemSet.maxReadId }) { - peersWithNewStories.insert(itemSet.peerId) + if item.hasUnseen { + peersWithNewStories.insert(item.peer.id) } } chatListState.peersWithNewStories = peersWithNewStories @@ -2156,14 +2185,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.storyListHeight = isEmpty ? 0.0 : 94.0 - let tabsIsEmpty: Bool - if let (resolvedItems, displayTabsAtBottom, _) = self.tabContainerData { - tabsIsEmpty = resolvedItems.count <= 1 || displayTabsAtBottom - } else { - tabsIsEmpty = true - } - - self.navigationBar?.secondaryContentHeight = (!tabsIsEmpty ? NavigationBar.defaultSecondaryContentHeight : 0.0) if case .chatList(.root) = self.location { self.searchContentNode?.additionalHeight = 94.0 } @@ -2413,38 +2434,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let searchContentNode = self.searchContentNode, case .chatList(.root) = self.location { if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { componentView.storyPeerAction = { [weak self] peer in - guard let self, let storyListContext = self.storyListContext else { + guard let self else { return } - let _ = (StoryChatContent.stories( - context: self.context, - storyList: storyListContext, - focusItem: nil - ) + let storyContent = StoryContentContextImpl(context: self.context, focusedPeerId: peer?.id) + let _ = (storyContent.state |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] initialContent in + |> deliverOnMainQueue).start(next: { [weak self] storyContentState in guard let self else { return } - - var transitionIn: StoryContainerScreen.TransitionIn? - if let peer, let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { - if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peer.id) { - transitionIn = StoryContainerScreen.TransitionIn( - sourceView: transitionView, - sourceRect: transitionView.bounds, - sourceCornerRadius: transitionView.bounds.height * 0.5 - ) - } - } - - var initialFocusedId: AnyHashable? - if let peer { - initialFocusedId = AnyHashable(peer.id) - } - - if initialFocusedId == AnyHashable(self.context.account.peerId), let firstItem = initialContent.first, firstItem.id == initialFocusedId && firstItem.items.isEmpty { + if let peer, peer.id == self.context.account.peerId, storyContentState.slice == nil { if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { let coordinator = rootController.openStoryCamera(transitionIn: nil, transitionOut: { [weak self] finished in guard let self else { @@ -2467,10 +2468,46 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } + var transitionIn: StoryContainerScreen.TransitionIn? + if let peer, let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { + if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peer.id) { + transitionIn = StoryContainerScreen.TransitionIn( + sourceView: transitionView, + sourceRect: transitionView.bounds, + sourceCornerRadius: transitionView.bounds.height * 0.5 + ) + } + } + + if let peer, peer.id == self.context.account.peerId { + if let stateValue = storyContent.stateValue { + let _ = stateValue + } + + /*if initialFocusedId == AnyHashable(self.context.account.peerId), let firstItem = initialContent.first, firstItem.id == initialFocusedId && firstItem.items.isEmpty { + if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { + rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionOut: { [weak self] _ in + guard let self else { + return nil + } + if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { + if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { + return StoryCameraTransitionOut( + destinationView: transitionView, + destinationRect: transitionView.bounds, + destinationCornerRadius: transitionView.bounds.height * 0.5 + ) + } + } + return nil + }) + } + }*/ + } + let storyContainerScreen = StoryContainerScreen( context: self.context, - initialFocusedId: initialFocusedId, - initialContent: initialContent, + content: storyContent, transitionIn: transitionIn, transitionOut: { [weak self] peerId, _ in guard let self else { @@ -2502,7 +2539,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController visibleProgress = (1.0 / fraction) * visibleProgress visibleProgress = max(0.0, min(1.0, visibleProgress)) - componentView.updateStories(offset: visibleProgress, context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, storyListState: self.storyListState, transition: Transition(transition)) + componentView.updateStories(offset: visibleProgress, context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, storySubscriptions: self.storySubscriptions, transition: Transition(transition)) } } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 47e1f40e48..d1305205f3 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -177,7 +177,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: processed = true break inner } - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { messageText = strings.Message_VideoMessage processed = true diff --git a/submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift b/submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift index 9483811d6a..00fc79847d 100644 --- a/submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift +++ b/submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift @@ -351,11 +351,11 @@ public final class DirectMediaImageCache { return self.getProgressiveSize(mediaReference: MediaReference.message(message: MessageReference(message), media: file).abstract, width: width, representations: file.previewRepresentations) } - private func getResource(peer: PeerReference, story: StoryListContext.Item, image: TelegramMediaImage, width: Int) -> (resource: MediaResourceReference, size: Int64)? { + private func getResource(peer: PeerReference, story: EngineStoryItem, image: TelegramMediaImage, width: Int) -> (resource: MediaResourceReference, size: Int64)? { return self.getProgressiveSize(mediaReference: MediaReference.story(peer: peer, id: story.id, media: image).abstract, width: width, representations: image.representations) } - private func getResource(peer: PeerReference, story: StoryListContext.Item, file: TelegramMediaFile, width: Int) -> (resource: MediaResourceReference, size: Int64)? { + private func getResource(peer: PeerReference, story: EngineStoryItem, file: TelegramMediaFile, width: Int) -> (resource: MediaResourceReference, size: Int64)? { return self.getProgressiveSize(mediaReference: MediaReference.story(peer: peer, id: story.id, media: file).abstract, width: width, representations: file.previewRepresentations) } @@ -445,7 +445,7 @@ public final class DirectMediaImageCache { } } - private func getImageSynchronous(peer: PeerReference, story: StoryListContext.Item, userLocation: MediaResourceUserLocation, media: Media, width: Int, aspectRatio: CGFloat, possibleWidths: [Int], includeBlurred: Bool) -> GetMediaResult? { + private func getImageSynchronous(peer: PeerReference, story: EngineStoryItem, userLocation: MediaResourceUserLocation, media: Media, width: Int, aspectRatio: CGFloat, possibleWidths: [Int], includeBlurred: Bool) -> GetMediaResult? { var immediateThumbnailData: Data? var resource: (resource: MediaResourceReference, size: Int64)? if let image = media as? TelegramMediaImage { @@ -492,7 +492,7 @@ public final class DirectMediaImageCache { return GetMediaResult(image: resultImage, blurredImage: blurredImage, loadSignal: self.getLoadSignal(width: width, aspectRatio: aspectRatio, userLocation: userLocation, userContentType: .image, resource: resource.resource, resourceSizeLimit: resource.size)) } - public func getImage(peer: PeerReference, story: StoryListContext.Item, media: Media, width: Int, aspectRatio: CGFloat, possibleWidths: [Int], includeBlurred: Bool = false, synchronous: Bool) -> GetMediaResult? { + public func getImage(peer: PeerReference, story: EngineStoryItem, media: Media, width: Int, aspectRatio: CGFloat, possibleWidths: [Int], includeBlurred: Bool = false, synchronous: Bool) -> GetMediaResult? { if synchronous { return self.getImageSynchronous(peer: peer, story: story, userLocation: .peer(peer.id), media: media, width: width, aspectRatio: aspectRatio, possibleWidths: possibleWidths, includeBlurred: includeBlurred) } else { diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index 6f346a965b..77ac1d044a 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -127,7 +127,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { self.angleLayer.bounds = CGRect(origin: .zero, size: CGSize(width: 3000.0, height: 6.0)) } - var entities: [DrawingEntity] { + public var entities: [DrawingEntity] { var entities: [DrawingEntity] = [] for case let view as DrawingEntityView in self.subviews { entities.append(view.entity) diff --git a/submodules/FFMpegBinding/Sources/FFMpegAVIOContext.m b/submodules/FFMpegBinding/Sources/FFMpegAVIOContext.m index 8f76b54667..6a0cc03e29 100644 --- a/submodules/FFMpegBinding/Sources/FFMpegAVIOContext.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVIOContext.m @@ -15,11 +15,12 @@ if (self != nil) { void *avIoBuffer = av_malloc(bufferSize); _impl = avio_alloc_context(avIoBuffer, bufferSize, 0, opaqueContext, readPacket, writePacket, seek); - _impl->direct = 1; if (_impl == nil) { av_free(avIoBuffer); return nil; } + _impl->direct = 1; + _impl->seekable = 0; } return self; } diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 892d0d3bb2..cd3fb84aa2 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -646,7 +646,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } else if let media = media as? TelegramMediaFile, !media.isAnimated { for attribute in media.attributes { switch attribute { - case let .Video(_, dimensions, _): + case let .Video(_, dimensions, _, _): isVideo = true if dimensions.height > 0 { if CGFloat(dimensions.width) / CGFloat(dimensions.height) > 1.33 { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 39da60db97..b6a7ea0300 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1222,7 +1222,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } if let file = file { for attribute in file.attributes { - if case let .Video(duration, _, _) = attribute, duration >= 30 { + if case let .Video(duration, _, _, _) = attribute, duration >= 30 { hintSeekable = true break } diff --git a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift index 7a29e48b2d..3f7da6b890 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift @@ -53,7 +53,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { } else { return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false)) } - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false)) } else { @@ -99,7 +99,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { return SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: albumArt, long: false, caption: nil) } - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { return SharedMediaPlaybackDisplayData.instantVideo(author: nil, peer: nil, timestamp: 0) } else { diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index ebb832dd1f..0d69a83ee4 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -348,7 +348,7 @@ public func legacyEnqueueGifMessage(account: Account, data: Data, correlationId: let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation) var fileAttributes: [TelegramMediaFileAttribute] = [] - fileAttributes.append(.Video(duration: Int(0), size: PixelDimensions(finalDimensions), flags: [.supportsStreaming])) + fileAttributes.append(.Video(duration: Int(0), size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil)) fileAttributes.append(.FileName(fileName: fileName)) fileAttributes.append(.Animated) @@ -390,7 +390,7 @@ public func legacyEnqueueVideoMessage(account: Account, data: Data, correlationI let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation) var fileAttributes: [TelegramMediaFileAttribute] = [] - fileAttributes.append(.Video(duration: Int(0), size: PixelDimensions(finalDimensions), flags: [.supportsStreaming])) + fileAttributes.append(.Video(duration: Int(0), size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil)) fileAttributes.append(.FileName(fileName: fileName)) fileAttributes.append(.Animated) @@ -723,7 +723,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A fileAttributes.append(.Animated) } if !asFile { - fileAttributes.append(.Video(duration: Int(finalDuration), size: PixelDimensions(finalDimensions), flags: [.supportsStreaming])) + fileAttributes.append(.Video(duration: Int(finalDuration), size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil)) if let adjustments = adjustments { if adjustments.sendAsGif { fileAttributes.append(.Animated) diff --git a/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift b/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift index 21edfd4cec..a4e303532e 100644 --- a/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift +++ b/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift @@ -95,6 +95,9 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa if readCount == 0 { fetchedData = Data() } else { + #if DEBUG + print("requestRange: \(requestRange)") + #endif if let tempFilePath = context.tempFilePath, let fileData = (try? Data(contentsOf: URL(fileURLWithPath: tempFilePath), options: .mappedRead))?.subdata(in: Int(requestRange.lowerBound) ..< Int(requestRange.upperBound)) { fetchedData = fileData } else { diff --git a/submodules/MediaPlayer/Sources/MediaPlayer.swift b/submodules/MediaPlayer/Sources/MediaPlayer.swift index f5aa6a8fe4..9f0956aef9 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayer.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayer.swift @@ -68,6 +68,7 @@ public enum MediaPlayerStreaming { case none case conservative case earlierStart + case story public var enabled: Bool { if case .none = self { @@ -83,6 +84,8 @@ public enum MediaPlayerStreaming { return (1.0, 2.0, 3.0) case .earlierStart: return (1.0, 1.0, 2.0) + case .story: + return (0.25, 0.5, 1.0) } } } diff --git a/submodules/MediaPlayer/Sources/MediaTrackFrameBuffer.swift b/submodules/MediaPlayer/Sources/MediaTrackFrameBuffer.swift index 49b3764921..f9fbca1118 100644 --- a/submodules/MediaPlayer/Sources/MediaTrackFrameBuffer.swift +++ b/submodules/MediaPlayer/Sources/MediaTrackFrameBuffer.swift @@ -16,7 +16,13 @@ public enum MediaTrackFrameResult { case finished } -private let traceEvents = false +private let traceEvents: Bool = { + #if DEBUG + return true + #else + return false + #endif +}() public final class MediaTrackFrameBuffer { private let stallDuration: Double @@ -89,7 +95,7 @@ public final class MediaTrackFrameBuffer { if let maxUntilTime = maxUntilTime { if traceEvents { - print("added \(frames.count) frames until \(CMTimeGetSeconds(maxUntilTime)), \(self.frames.count) total") + print("\(self.type) added \(frames.count) frames until \(CMTimeGetSeconds(maxUntilTime)), \(self.frames.count) total") } } @@ -123,7 +129,7 @@ public final class MediaTrackFrameBuffer { if bufferedDuration < self.lowWaterDuration { if traceEvents { - print("buffered duration: \(bufferedDuration), requesting until \(timestamp) + \(self.highWaterDuration - bufferedDuration)") + print("\(self.type) buffered duration: \(bufferedDuration), requesting until \(timestamp) + \(self.highWaterDuration - bufferedDuration)") } let delayIncrement = 0.3 var generateUntil = timestamp + delayIncrement @@ -134,7 +140,7 @@ public final class MediaTrackFrameBuffer { if bufferedDuration > self.stallDuration && !self.isWaitingForLowWaterDuration { if traceEvents { - print("buffered1 duration: \(bufferedDuration), wait until \(timestamp) + \(self.highWaterDuration - bufferedDuration)") + print("\(self.type) buffered1 duration: \(bufferedDuration), wait until \(timestamp) + \(self.highWaterDuration - bufferedDuration)") } return .full(until: timestamp + self.highWaterDuration) } else { @@ -144,7 +150,7 @@ public final class MediaTrackFrameBuffer { } else { self.isWaitingForLowWaterDuration = false if traceEvents { - print("buffered2 duration: \(bufferedDuration), wait until \(timestamp) + \(bufferedDuration - self.lowWaterDuration)") + print("\(self.type) buffered2 duration: \(bufferedDuration), wait until \(timestamp) + \(bufferedDuration - self.lowWaterDuration)") } return .full(until: timestamp + max(0.0, bufferedDuration - self.lowWaterDuration)) } diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index 85e5c5f39d..6a74a5da18 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -187,7 +187,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { let subject: ShareControllerSubject var actionCompletionText: String? if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) subject = .media(videoFileReference.abstract) actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved } else { @@ -279,7 +279,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { if video != previousVideoRepresentations?.last { let mediaManager = self.context.sharedContext.mediaManager - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(id, category), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, useLargeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) videoNode.isUserInteractionEnabled = false diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index ad7b767433..d6d11bf2e9 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -511,7 +511,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { self.isReady.set(.single(true)) } } else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 1f765a4334..bc0c3d7cc1 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1255,6 +1255,55 @@ public final class Transaction { assert(!self.disposed) return self.postbox!.removeChatHidden(peerId: peerId, index: index) } + + public func getAllStorySubscriptions() -> (state: CodableEntry?, peerIds: [PeerId]) { + assert(!self.disposed) + return self.postbox!.getAllStorySubscriptions() + } + + public func replaceAllStorySubscriptions(state: CodableEntry?, peerIds: [PeerId]) { + assert(!self.disposed) + self.postbox!.replaceAllStorySubscriptions(state: state, peerIds: peerIds) + } + + public func getSubscriptionsStoriesState() -> CodableEntry? { + assert(!self.disposed) + return self.postbox!.getSubscriptionsStoriesState() + } + + public func setSubscriptionsStoriesState(state: CodableEntry?) { + assert(!self.disposed) + self.postbox!.setSubscriptionsStoriesState(state: state) + } + + public func getLocalStoryState() -> CodableEntry? { + assert(!self.disposed) + return self.postbox!.getLocalStoryState() + } + + public func setLocalStoryState(state: CodableEntry?) { + assert(!self.disposed) + self.postbox!.setLocalStoryState(state: state) + } + + public func getPeerStoryState(peerId: PeerId) -> CodableEntry? { + assert(!self.disposed) + return self.postbox!.getPeerStoryState(peerId: peerId) + } + + public func setPeerStoryState(peerId: PeerId, state: CodableEntry?) { + assert(!self.disposed) + self.postbox!.setPeerStoryState(peerId: peerId, state: state) + } + + public func setStoryItems(peerId: PeerId, items: [StoryItemsTableEntry]) { + assert(!self.disposed) + self.postbox!.setStoryItems(peerId: peerId, items: items) + } + + public func getStoryItems(peerId: PeerId) -> [StoryItemsTableEntry] { + return self.postbox!.getStoryItems(peerId: peerId) + } } public enum PostboxResult { @@ -1492,6 +1541,10 @@ final class PostboxImpl { private var currentHiddenChatIds: [PeerId: Bag] = [:] private var currentUpdatedHiddenPeerIds: Bool = false + private var currentStoryStatesEvents: [StoryStatesTable.Event] = [] + private var currentStorySubscriptionsEvents: [StorySubscriptionsTable.Event] = [] + private var currentStoryItemsEvents: [StoryItemsTable.Event] = [] + var hiddenChatIds: Set { if self.currentHiddenChatIds.isEmpty { return Set() @@ -1610,6 +1663,9 @@ final class PostboxImpl { let messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable let groupMessageStatsTable: GroupMessageStatsTable let peerTimeoutPropertiesTable: PeerTimeoutPropertiesTable + let storyStatesTable: StoryStatesTable + let storySubscriptionsTable: StorySubscriptionsTable + let storyItemsTable: StoryItemsTable //temporary let peerRatingTable: RatingTable @@ -1699,6 +1755,9 @@ final class PostboxImpl { self.noticeTable = NoticeTable(valueBox: self.valueBox, table: NoticeTable.tableSpec(43), useCaches: useCaches) self.deviceContactImportInfoTable = DeviceContactImportInfoTable(valueBox: self.valueBox, table: DeviceContactImportInfoTable.tableSpec(54), useCaches: useCaches) self.groupMessageStatsTable = GroupMessageStatsTable(valueBox: self.valueBox, table: GroupMessageStatsTable.tableSpec(58), useCaches: useCaches) + self.storyStatesTable = StoryStatesTable(valueBox: self.valueBox, table: StoryStatesTable.tableSpec(65), useCaches: useCaches) + self.storySubscriptionsTable = StorySubscriptionsTable(valueBox: self.valueBox, table: StorySubscriptionsTable.tableSpec(66), useCaches: useCaches) + self.storyItemsTable = StoryItemsTable(valueBox: self.valueBox, table: StoryItemsTable.tableSpec(69), useCaches: useCaches) var tables: [Table] = [] tables.append(self.metadataTable) @@ -1766,6 +1825,9 @@ final class PostboxImpl { tables.append(self.messageHistoryHoleIndexTable) tables.append(self.groupMessageStatsTable) tables.append(self.peerTimeoutPropertiesTable) + tables.append(self.storyStatesTable) + tables.append(self.storySubscriptionsTable) + tables.append(self.storyItemsTable) self.tables = tables @@ -2104,6 +2166,50 @@ final class PostboxImpl { self.synchronizeGroupMessageStatsTable.set(groupId: groupId, namespace: namespace, needsValidation: false, operations: &self.currentUpdatedGroupSummarySynchronizeOperations) } + fileprivate func getAllStorySubscriptions() -> (state: CodableEntry?, peerIds: [PeerId]) { + return ( + self.storyStatesTable.get(key: .subscriptions), + self.storySubscriptionsTable.getAll() + ) + } + + fileprivate func replaceAllStorySubscriptions(state: CodableEntry?, peerIds: [PeerId]) { + self.storyStatesTable.set(key: .subscriptions, value: state, events: &self.currentStoryStatesEvents) + self.storySubscriptionsTable.replaceAll(peerIds: peerIds, events: &self.currentStorySubscriptionsEvents) + } + + fileprivate func getLocalStoryState() -> CodableEntry? { + return self.storyStatesTable.get(key: .local) + } + + fileprivate func getSubscriptionsStoriesState() -> CodableEntry? { + return self.storyStatesTable.get(key: .subscriptions) + } + + fileprivate func setSubscriptionsStoriesState(state: CodableEntry?) { + self.storyStatesTable.set(key: .subscriptions, value: state, events: &self.currentStoryStatesEvents) + } + + fileprivate func setLocalStoryState(state: CodableEntry?) { + self.storyStatesTable.set(key: .local, value: state, events: &self.currentStoryStatesEvents) + } + + fileprivate func getPeerStoryState(peerId: PeerId) -> CodableEntry? { + return self.storyStatesTable.get(key: .peer(peerId)) + } + + fileprivate func setPeerStoryState(peerId: PeerId, state: CodableEntry?) { + self.storyStatesTable.set(key: .peer(peerId), value: state, events: &self.currentStoryStatesEvents) + } + + fileprivate func setStoryItems(peerId: PeerId, items: [StoryItemsTableEntry]) { + self.storyItemsTable.replace(peerId: peerId, entries: items, events: &self.currentStoryItemsEvents) + } + + fileprivate func getStoryItems(peerId: PeerId) -> [StoryItemsTableEntry] { + return self.storyItemsTable.get(peerId: peerId) + } + func renderIntermediateMessage(_ message: IntermediateMessage) -> Message { let renderedMessage = self.messageHistoryTable.renderMessage(message, peerTable: self.peerTable, threadIndexTable: self.messageHistoryThreadIndexTable) @@ -2170,7 +2276,7 @@ final class PostboxImpl { let updatedPeerTimeoutAttributes = self.peerTimeoutPropertiesTable.hasUpdates - let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes, updatedMessageThreadPeerIds: updatedMessageThreadPeerIds, updatedPeerThreadCombinedStates: self.currentUpdatedPeerThreadCombinedStates, updatedPeerThreadsSummaries: Set(alteredInitialPeerThreadsSummaries.keys), updatedPinnedThreads: self.currentUpdatedPinnedThreads, updatedHiddenPeerIds: self.currentUpdatedHiddenPeerIds) + let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes, updatedMessageThreadPeerIds: updatedMessageThreadPeerIds, updatedPeerThreadCombinedStates: self.currentUpdatedPeerThreadCombinedStates, updatedPeerThreadsSummaries: Set(alteredInitialPeerThreadsSummaries.keys), updatedPinnedThreads: self.currentUpdatedPinnedThreads, updatedHiddenPeerIds: self.currentUpdatedHiddenPeerIds, storyStatesEvents: self.currentStoryStatesEvents, storySubscriptionsEvents: self.currentStorySubscriptionsEvents, storyItemsEvents: self.currentStoryItemsEvents) var updatedTransactionState: Int64? var updatedMasterClientId: Int64? if !transaction.isEmpty { @@ -2226,6 +2332,9 @@ final class PostboxImpl { self.currentUpdatedPinnedThreads.removeAll() self.currentUpdatedHiddenPeerIds = false self.currentNeedsReindexUnreadCounters = false + self.currentStoryStatesEvents.removeAll() + self.currentStorySubscriptionsEvents.removeAll() + self.currentStoryItemsEvents.removeAll() for table in self.tables { table.beforeCommit() diff --git a/submodules/Postbox/Sources/PostboxTransaction.swift b/submodules/Postbox/Sources/PostboxTransaction.swift index 118532e4dd..9e224c884c 100644 --- a/submodules/Postbox/Sources/PostboxTransaction.swift +++ b/submodules/Postbox/Sources/PostboxTransaction.swift @@ -49,6 +49,9 @@ final class PostboxTransaction { let updatedPeerThreadsSummaries: Set let updatedPinnedThreads: Set let updatedHiddenPeerIds: Bool + let storyStatesEvents: [StoryStatesTable.Event] + let storySubscriptionsEvents: [StorySubscriptionsTable.Event] + let storyItemsEvents: [StoryItemsTable.Event] var isEmpty: Bool { if currentUpdatedState != nil { @@ -195,10 +198,19 @@ final class PostboxTransaction { if self.updatedHiddenPeerIds { return false } + if !self.storyStatesEvents.isEmpty { + return false + } + if !self.storySubscriptionsEvents.isEmpty { + return false + } + if !self.storyItemsEvents.isEmpty { + return false + } return true } - init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: Set, currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState], currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, replacedAdditionalChatListItems: [AdditionalChatListItem]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set, updatedFailedMessageIds: Set, updatedGlobalNotificationSettings: Bool, updatedPeerTimeoutAttributes: Bool, updatedMessageThreadPeerIds: Set, updatedPeerThreadCombinedStates: Set, updatedPeerThreadsSummaries: Set, updatedPinnedThreads: Set, updatedHiddenPeerIds: Bool) { + init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: Set, currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState], currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, replacedAdditionalChatListItems: [AdditionalChatListItem]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set, updatedFailedMessageIds: Set, updatedGlobalNotificationSettings: Bool, updatedPeerTimeoutAttributes: Bool, updatedMessageThreadPeerIds: Set, updatedPeerThreadCombinedStates: Set, updatedPeerThreadsSummaries: Set, updatedPinnedThreads: Set, updatedHiddenPeerIds: Bool, storyStatesEvents: [StoryStatesTable.Event], storySubscriptionsEvents: [StorySubscriptionsTable.Event], storyItemsEvents: [StoryItemsTable.Event]) { self.currentUpdatedState = currentUpdatedState self.currentPeerHoleOperations = currentPeerHoleOperations self.currentOperationsByPeerId = currentOperationsByPeerId @@ -246,5 +258,8 @@ final class PostboxTransaction { self.updatedPeerThreadsSummaries = updatedPeerThreadsSummaries self.updatedPinnedThreads = updatedPinnedThreads self.updatedHiddenPeerIds = updatedHiddenPeerIds + self.storyStatesEvents = storyStatesEvents + self.storySubscriptionsEvents = storySubscriptionsEvents + self.storyItemsEvents = storyItemsEvents } } diff --git a/submodules/Postbox/Sources/StoryItemsTable.swift b/submodules/Postbox/Sources/StoryItemsTable.swift new file mode 100644 index 0000000000..05d1bfabcd --- /dev/null +++ b/submodules/Postbox/Sources/StoryItemsTable.swift @@ -0,0 +1,101 @@ +import Foundation + +public final class StoryItemsTableEntry: Equatable { + public let value: CodableEntry + public let id: Int32 + + public init( + value: CodableEntry, + id: Int32 + ) { + self.value = value + self.id = id + } + + public static func ==(lhs: StoryItemsTableEntry, rhs: StoryItemsTableEntry) -> Bool { + if lhs === rhs { + return true + } + if lhs.id != rhs.id { + return false + } + if lhs.value != rhs.value { + return false + } + return true + } +} + +final class StoryItemsTable: Table { + enum Event { + case replace(peerId: PeerId) + } + + private struct Key: Hashable { + var peerId: PeerId + var id: Int32 + } + + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false) + } + + private let sharedKey = ValueBoxKey(length: 8 + 4) + + private func key(_ key: Key) -> ValueBoxKey { + self.sharedKey.setInt64(0, value: key.peerId.toInt64()) + self.sharedKey.setInt32(8, value: key.id) + return self.sharedKey + } + + private func lowerBound(peerId: PeerId) -> ValueBoxKey { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: peerId.toInt64()) + return key + } + + private func upperBound(peerId: PeerId) -> ValueBoxKey { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: peerId.toInt64()) + return key.successor + } + + public func get(peerId: PeerId) -> [StoryItemsTableEntry] { + var result: [StoryItemsTableEntry] = [] + + self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), values: { key, value in + let id = key.getInt32(8) + + let entry = CodableEntry(data: value.makeData()) + result.append(StoryItemsTableEntry(value: entry, id: id)) + + return true + }, limit: 10000) + + return result + } + + public func replace(peerId: PeerId, entries: [StoryItemsTableEntry], events: inout [Event]) { + var previousKeys: [ValueBoxKey] = [] + self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), keys: { key in + previousKeys.append(key) + + return true + }, limit: 10000) + for key in previousKeys { + self.valueBox.remove(self.table, key: key, secure: true) + } + + for entry in entries { + self.valueBox.set(self.table, key: self.key(Key(peerId: peerId, id: entry.id)), value: MemoryBuffer(data: entry.value.data)) + } + + events.append(.replace(peerId: peerId)) + } + + override func clearMemoryCache() { + } + + override func beforeCommit() { + } +} diff --git a/submodules/Postbox/Sources/StoryItemsView.swift b/submodules/Postbox/Sources/StoryItemsView.swift new file mode 100644 index 0000000000..00066ec43c --- /dev/null +++ b/submodules/Postbox/Sources/StoryItemsView.swift @@ -0,0 +1,53 @@ +import Foundation + +final class MutableStoryItemsView: MutablePostboxView { + let peerId: PeerId + var items: [StoryItemsTableEntry] + + init(postbox: PostboxImpl, peerId: PeerId) { + self.peerId = peerId + self.items = postbox.storyItemsTable.get(peerId: peerId) + } + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { + var updated = false + if !transaction.storyItemsEvents.isEmpty { + loop: for event in transaction.storyItemsEvents { + switch event { + case .replace(peerId): + let items = postbox.storyItemsTable.get(peerId: self.peerId) + if self.items != items { + self.items = items + updated = true + } + break loop + default: + break + } + } + } + + return updated + } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let items = postbox.storyItemsTable.get(peerId: self.peerId) + if self.items != items { + self.items = items + return true + } + return false + } + + func immutableView() -> PostboxView { + return StoryItemsView(self) + } +} + +public final class StoryItemsView: PostboxView { + public let items: [StoryItemsTableEntry] + + init(_ view: MutableStoryItemsView) { + self.items = view.items + } +} diff --git a/submodules/Postbox/Sources/StoryStatesTable.swift b/submodules/Postbox/Sources/StoryStatesTable.swift new file mode 100644 index 0000000000..b34f2ddfab --- /dev/null +++ b/submodules/Postbox/Sources/StoryStatesTable.swift @@ -0,0 +1,70 @@ +import Foundation + +final class StoryStatesTable: Table { + enum Event { + case set(Key) + } + + enum Key: Hashable { + case local + case subscriptions + case peer(PeerId) + + init(key: ValueBoxKey) { + switch key.getUInt8(0) { + case 0: + self = .local + case 1: + self = .subscriptions + case 2: + self = .peer(PeerId(key.getInt64(1))) + default: + assertionFailure() + self = .peer(PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: ._internalFromInt64Value(0))) + } + } + + func asKey() -> ValueBoxKey { + switch self { + case .local: + let key = ValueBoxKey(length: 1) + key.setUInt8(0, value: 0) + return key + case .subscriptions: + let key = ValueBoxKey(length: 1) + key.setUInt8(0, value: 1) + return key + case let .peer(peerId): + let key = ValueBoxKey(length: 1 + 8) + key.setUInt8(0, value: 2) + key.setInt64(1, value: peerId.toInt64()) + return key + } + } + } + + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false) + } + + private let sharedKey = ValueBoxKey(length: 8 + 4) + + func get(key: Key) -> CodableEntry? { + return self.valueBox.get(self.table, key: key.asKey()).flatMap { CodableEntry(data: $0.makeData()) } + } + + func set(key: Key, value: CodableEntry?, events: inout [Event]) { + if let value = value { + self.valueBox.set(self.table, key: key.asKey(), value: MemoryBuffer(data: value.data)) + } else { + self.valueBox.remove(self.table, key: key.asKey(), secure: true) + } + events.append(.set(key)) + } + + override func clearMemoryCache() { + } + + override func beforeCommit() { + } +} diff --git a/submodules/Postbox/Sources/StoryStatesView.swift b/submodules/Postbox/Sources/StoryStatesView.swift new file mode 100644 index 0000000000..6969ba8d37 --- /dev/null +++ b/submodules/Postbox/Sources/StoryStatesView.swift @@ -0,0 +1,84 @@ +import Foundation + +public enum PostboxStoryStatesKey: Hashable { + case local + case subscriptions + case peer(PeerId) +} + +private extension PostboxStoryStatesKey { + init(tableKey: StoryStatesTable.Key) { + switch tableKey { + case .local: + self = .local + case .subscriptions: + self = .subscriptions + case let .peer(peerId): + self = .peer(peerId) + } + } + + var tableKey: StoryStatesTable.Key { + switch self { + case .local: + return .local + case .subscriptions: + return .subscriptions + case let .peer(peerId): + return .peer(peerId) + } + } +} + +final class MutableStoryStatesView: MutablePostboxView { + let key: PostboxStoryStatesKey + var value: CodableEntry? + + init(postbox: PostboxImpl, key: PostboxStoryStatesKey) { + self.key = key + self.value = postbox.storyStatesTable.get(key: key.tableKey) + } + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { + var updated = false + if !transaction.storyStatesEvents.isEmpty { + let tableKey = self.key.tableKey + loop: for event in transaction.storyStatesEvents { + switch event { + case .set(tableKey): + let value = postbox.storyStatesTable.get(key: self.key.tableKey) + if value != self.value { + self.value = value + updated = true + } + break loop + default: + break + } + } + } + + return updated + } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let value = postbox.storyStatesTable.get(key: self.key.tableKey) + if value != self.value { + self.value = value + return true + } + return false + } + + func immutableView() -> PostboxView { + return StoryStatesView(self) + } +} + +public final class StoryStatesView: PostboxView { + public let value: CodableEntry? + + init(_ view: MutableStoryStatesView) { + self.value = view.value + } +} diff --git a/submodules/Postbox/Sources/StorySubscriptionsTable.swift b/submodules/Postbox/Sources/StorySubscriptionsTable.swift new file mode 100644 index 0000000000..89a98762ab --- /dev/null +++ b/submodules/Postbox/Sources/StorySubscriptionsTable.swift @@ -0,0 +1,67 @@ +import Foundation + +final class StorySubscriptionsTable: Table { + enum Event { + case replaceAll + } + + private struct Key: Hashable { + var peerId: PeerId + } + + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false) + } + + private let sharedKey = ValueBoxKey(length: 8) + + private func key(_ key: Key) -> ValueBoxKey { + self.sharedKey.setInt64(0, value: key.peerId.toInt64()) + return self.sharedKey + } + + private func getAllKeys() -> [Key] { + var result: [Key] = [] + + self.valueBox.scan(self.table, keys: { key in + let peerId = PeerId(key.getInt64(0)) + + result.append(Key(peerId: peerId)) + + return true + }) + + return result + } + + public func getAll() -> [PeerId] { + var result: [PeerId] = [] + + self.valueBox.scan(self.table, keys: { key in + let peerId = PeerId(key.getInt64(0)) + result.append(peerId) + + return true + }) + + return result + } + + public func replaceAll(peerIds: [PeerId], events: inout [Event]) { + for key in self.getAllKeys() { + self.valueBox.remove(self.table, key: self.key(key), secure: true) + } + + for peerId in peerIds { + self.valueBox.set(self.table, key: self.key(Key(peerId: peerId)), value: MemoryBuffer()) + } + + events.append(.replaceAll) + } + + override func clearMemoryCache() { + } + + override func beforeCommit() { + } +} diff --git a/submodules/Postbox/Sources/StorySubscriptionsView.swift b/submodules/Postbox/Sources/StorySubscriptionsView.swift new file mode 100644 index 0000000000..34395d6e55 --- /dev/null +++ b/submodules/Postbox/Sources/StorySubscriptionsView.swift @@ -0,0 +1,50 @@ +import Foundation + +final class MutableStorySubscriptionsView: MutablePostboxView { + var peerIds: [PeerId] + + init(postbox: PostboxImpl) { + self.peerIds = postbox.storySubscriptionsTable.getAll() + } + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { + var updated = false + if !transaction.storySubscriptionsEvents.isEmpty { + loop: for event in transaction.storySubscriptionsEvents { + switch event { + case .replaceAll: + let peerIds = postbox.storySubscriptionsTable.getAll() + if self.peerIds != peerIds { + updated = true + self.peerIds = peerIds + } + + break loop + } + } + } + return updated + } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let peerIds = postbox.storySubscriptionsTable.getAll() + if self.peerIds != peerIds { + self.peerIds = peerIds + + return true + } + return false + } + + func immutableView() -> PostboxView { + return StorySubscriptionsView(self) + } +} + +public final class StorySubscriptionsView: PostboxView { + public let peerIds: [PeerId] + + init(_ view: MutableStorySubscriptionsView) { + self.peerIds = view.peerIds + } +} diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index eab363d562..aa604f7fa8 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -40,6 +40,9 @@ public enum PostboxViewKey: Hashable { case peerTimeoutAttributes case messageHistoryThreadIndex(id: PeerId, summaryComponents: ChatListEntrySummaryComponents) case messageHistoryThreadInfo(peerId: PeerId, threadId: Int64) + case storySubscriptions + case storiesState(key: PostboxStoryStatesKey) + case storyItems(peerId: PeerId) public func hash(into hasher: inout Hasher) { switch self { @@ -134,6 +137,12 @@ public enum PostboxViewKey: Hashable { case let .messageHistoryThreadInfo(peerId, threadId): hasher.combine(peerId) hasher.combine(threadId) + case .storySubscriptions: + hasher.combine(18) + case let .storiesState(key): + hasher.combine(key) + case let .storyItems(peerId): + hasher.combine(peerId) } } @@ -373,6 +382,24 @@ public enum PostboxViewKey: Hashable { } else { return false } + case .storySubscriptions: + if case .storySubscriptions = rhs { + return true + } else { + return false + } + case let .storiesState(key): + if case .storiesState(key) = rhs { + return true + } else { + return false + } + case let .storyItems(peerId): + if case .storyItems(peerId) = rhs { + return true + } else { + return false + } } } } @@ -457,5 +484,11 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutableMessageHistoryThreadIndexView(postbox: postbox, peerId: id, summaryComponents: summaryComponents) case let .messageHistoryThreadInfo(peerId, threadId): return MutableMessageHistoryThreadInfoView(postbox: postbox, peerId: peerId, threadId: threadId) + case .storySubscriptions: + return MutableStorySubscriptionsView(postbox: postbox) + case let .storiesState(key): + return MutableStoryStatesView(postbox: postbox, key: key) + case let .storyItems(peerId): + return MutableStoryItemsView(postbox: postbox, peerId: peerId) } } diff --git a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift index 900046237b..dd015ecea0 100644 --- a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift +++ b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift @@ -275,7 +275,7 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte if let account = account, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) - let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) + let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil)]) let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 4f36bdb7ed..e7c2ab9031 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -146,7 +146,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true) let resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: asset.url.path, adjustments: resourceAdjustments) - return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024) + return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags, preloadSize: nil)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024) |> mapError { _ -> PreparedShareItemError in return .generic } @@ -212,7 +212,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri let mimeType: String if converted { mimeType = "video/mp4" - attributes = [.Video(duration: Int(duration), size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)), flags: [.supportsStreaming]), .Animated, .FileName(fileName: "animation.mp4")] + attributes = [.Video(duration: Int(duration), size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)), flags: [.supportsStreaming], preloadSize: nil), .Animated, .FileName(fileName: "animation.mp4")] } else { mimeType = "animation/gif" attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")] diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 877bf9e25e..a7eed89b13 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -382,6 +382,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-380694650] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowChatParticipants($0) } dict[195371015] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowContacts($0) } dict[-1877932953] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowUsers($0) } + dict[-1672247580] = { return Api.InputReplyTo.parse_inputReplyToMessage($0) } + dict[-1139169566] = { return Api.InputReplyTo.parse_inputReplyToStory($0) } dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) } dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) } dict[-618540889] = { return Api.InputSecureValue.parse_inputSecureValue($0) } @@ -535,6 +537,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1328256121] = { return Api.MessageReactions.parse_messageReactions($0) } dict[-2083123262] = { return Api.MessageReplies.parse_messageReplies($0) } dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } + dict[-1667711039] = { return Api.MessageReplyHeader.parse_messageReplyStoryHeader($0) } dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) } dict[975236280] = { return Api.MessagesFilter.parse_inputMessagesFilterChatPhotos($0) } dict[-530392189] = { return Api.MessagesFilter.parse_inputMessagesFilterContacts($0) } @@ -790,7 +793,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1374088783] = { return Api.StoryItem.parse_storyItemDeleted($0) } dict[-1579626609] = { return Api.StoryItem.parse_storyItemSkipped($0) } dict[-1491424062] = { return Api.StoryView.parse_storyView($0) } - dict[1368082392] = { return Api.StoryViews.parse_storyViews($0) } + dict[-748199729] = { return Api.StoryViews.parse_storyViews($0) } dict[1964978502] = { return Api.TextWithEntities.parse_textWithEntities($0) } dict[-1609668650] = { return Api.Theme.parse_theme($0) } dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) } @@ -1155,7 +1158,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[172975040] = { return Api.storage.FileType.parse_filePng($0) } dict[-1432995067] = { return Api.storage.FileType.parse_fileUnknown($0) } dict[276907596] = { return Api.storage.FileType.parse_fileWebp($0) } - dict[1528473228] = { return Api.stories.AllStories.parse_allStories($0) } + dict[-2086796248] = { return Api.stories.AllStories.parse_allStories($0) } dict[1205903486] = { return Api.stories.AllStories.parse_allStoriesNotModified($0) } dict[1340440049] = { return Api.stories.Stories.parse_stories($0) } dict[-560009955] = { return Api.stories.StoryViews.parse_storyViews($0) } @@ -1479,6 +1482,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.InputPrivacyRule: _1.serialize(buffer, boxed) + case let _1 as Api.InputReplyTo: + _1.serialize(buffer, boxed) case let _1 as Api.InputSecureFile: _1.serialize(buffer, boxed) case let _1 as Api.InputSecureValue: diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 22bf87c5cb..a82c2e4600 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -1,3 +1,77 @@ +public extension Api { + indirect enum InputReplyTo: TypeConstructorDescription { + case inputReplyToMessage(flags: Int32, replyToMsgId: Int32, topMsgId: Int32?) + case inputReplyToStory(flags: Int32, userId: Api.InputUser, storyId: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId): + if boxed { + buffer.appendInt32(-1672247580) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(replyToMsgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + break + case .inputReplyToStory(let flags, let userId, let storyId): + if boxed { + buffer.appendInt32(-1139169566) + } + serializeInt32(flags, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeInt32(storyId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId): + return ("inputReplyToMessage", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("topMsgId", topMsgId as Any)]) + case .inputReplyToStory(let flags, let userId, let storyId): + return ("inputReplyToStory", [("flags", flags as Any), ("userId", userId as Any), ("storyId", storyId as Any)]) + } + } + + public static func parse_inputReplyToMessage(_ reader: BufferReader) -> InputReplyTo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputReplyTo.inputReplyToMessage(flags: _1!, replyToMsgId: _2!, topMsgId: _3) + } + else { + return nil + } + } + public static func parse_inputReplyToStory(_ reader: BufferReader) -> InputReplyTo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputUser? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputUser + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputReplyTo.inputReplyToStory(flags: _1!, userId: _2!, storyId: _3!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputSecureFile: TypeConstructorDescription { case inputSecureFile(id: Int64, accessHash: Int64) @@ -932,119 +1006,3 @@ public extension Api { } } -public extension Api { - enum InputWebFileLocation: TypeConstructorDescription { - case inputWebFileAudioAlbumThumbLocation(flags: Int32, document: Api.InputDocument?, title: String?, performer: String?) - case inputWebFileGeoPointLocation(geoPoint: Api.InputGeoPoint, accessHash: Int64, w: Int32, h: Int32, zoom: Int32, scale: Int32) - case inputWebFileLocation(url: String, accessHash: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputWebFileAudioAlbumThumbLocation(let flags, let document, let title, let performer): - if boxed { - buffer.appendInt32(-193992412) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(performer!, buffer: buffer, boxed: false)} - break - case .inputWebFileGeoPointLocation(let geoPoint, let accessHash, let w, let h, let zoom, let scale): - if boxed { - buffer.appendInt32(-1625153079) - } - geoPoint.serialize(buffer, true) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(zoom, buffer: buffer, boxed: false) - serializeInt32(scale, buffer: buffer, boxed: false) - break - case .inputWebFileLocation(let url, let accessHash): - if boxed { - buffer.appendInt32(-1036396922) - } - serializeString(url, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputWebFileAudioAlbumThumbLocation(let flags, let document, let title, let performer): - return ("inputWebFileAudioAlbumThumbLocation", [("flags", flags as Any), ("document", document as Any), ("title", title as Any), ("performer", performer as Any)]) - case .inputWebFileGeoPointLocation(let geoPoint, let accessHash, let w, let h, let zoom, let scale): - return ("inputWebFileGeoPointLocation", [("geoPoint", geoPoint as Any), ("accessHash", accessHash as Any), ("w", w as Any), ("h", h as Any), ("zoom", zoom as Any), ("scale", scale as Any)]) - case .inputWebFileLocation(let url, let accessHash): - return ("inputWebFileLocation", [("url", url as Any), ("accessHash", accessHash as Any)]) - } - } - - public static func parse_inputWebFileAudioAlbumThumbLocation(_ reader: BufferReader) -> InputWebFileLocation? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputDocument? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputDocument - } } - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputWebFileLocation.inputWebFileAudioAlbumThumbLocation(flags: _1!, document: _2, title: _3, performer: _4) - } - else { - return nil - } - } - public static func parse_inputWebFileGeoPointLocation(_ reader: BufferReader) -> InputWebFileLocation? { - var _1: Api.InputGeoPoint? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint - } - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputWebFileLocation.inputWebFileGeoPointLocation(geoPoint: _1!, accessHash: _2!, w: _3!, h: _4!, zoom: _5!, scale: _6!) - } - else { - return nil - } - } - public static func parse_inputWebFileLocation(_ reader: BufferReader) -> InputWebFileLocation? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputWebFileLocation.inputWebFileLocation(url: _1!, accessHash: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 83048a991e..3448343725 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -1,3 +1,119 @@ +public extension Api { + enum InputWebFileLocation: TypeConstructorDescription { + case inputWebFileAudioAlbumThumbLocation(flags: Int32, document: Api.InputDocument?, title: String?, performer: String?) + case inputWebFileGeoPointLocation(geoPoint: Api.InputGeoPoint, accessHash: Int64, w: Int32, h: Int32, zoom: Int32, scale: Int32) + case inputWebFileLocation(url: String, accessHash: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputWebFileAudioAlbumThumbLocation(let flags, let document, let title, let performer): + if boxed { + buffer.appendInt32(-193992412) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(performer!, buffer: buffer, boxed: false)} + break + case .inputWebFileGeoPointLocation(let geoPoint, let accessHash, let w, let h, let zoom, let scale): + if boxed { + buffer.appendInt32(-1625153079) + } + geoPoint.serialize(buffer, true) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(zoom, buffer: buffer, boxed: false) + serializeInt32(scale, buffer: buffer, boxed: false) + break + case .inputWebFileLocation(let url, let accessHash): + if boxed { + buffer.appendInt32(-1036396922) + } + serializeString(url, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputWebFileAudioAlbumThumbLocation(let flags, let document, let title, let performer): + return ("inputWebFileAudioAlbumThumbLocation", [("flags", flags as Any), ("document", document as Any), ("title", title as Any), ("performer", performer as Any)]) + case .inputWebFileGeoPointLocation(let geoPoint, let accessHash, let w, let h, let zoom, let scale): + return ("inputWebFileGeoPointLocation", [("geoPoint", geoPoint as Any), ("accessHash", accessHash as Any), ("w", w as Any), ("h", h as Any), ("zoom", zoom as Any), ("scale", scale as Any)]) + case .inputWebFileLocation(let url, let accessHash): + return ("inputWebFileLocation", [("url", url as Any), ("accessHash", accessHash as Any)]) + } + } + + public static func parse_inputWebFileAudioAlbumThumbLocation(_ reader: BufferReader) -> InputWebFileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputDocument? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputDocument + } } + var _3: String? + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputWebFileLocation.inputWebFileAudioAlbumThumbLocation(flags: _1!, document: _2, title: _3, performer: _4) + } + else { + return nil + } + } + public static func parse_inputWebFileGeoPointLocation(_ reader: BufferReader) -> InputWebFileLocation? { + var _1: Api.InputGeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint + } + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputWebFileLocation.inputWebFileGeoPointLocation(geoPoint: _1!, accessHash: _2!, w: _3!, h: _4!, zoom: _5!, scale: _6!) + } + else { + return nil + } + } + public static func parse_inputWebFileLocation(_ reader: BufferReader) -> InputWebFileLocation? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputWebFileLocation.inputWebFileLocation(url: _1!, accessHash: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum Invoice: TypeConstructorDescription { case invoice(flags: Int32, currency: String, prices: [Api.LabeledPrice], maxTipAmount: Int64?, suggestedTipAmounts: [Int64]?, recurringTermsUrl: String?) @@ -1000,51 +1116,3 @@ public extension Api { } } -public extension Api { - enum MaskCoords: TypeConstructorDescription { - case maskCoords(n: Int32, x: Double, y: Double, zoom: Double) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .maskCoords(let n, let x, let y, let zoom): - if boxed { - buffer.appendInt32(-1361650766) - } - serializeInt32(n, buffer: buffer, boxed: false) - serializeDouble(x, buffer: buffer, boxed: false) - serializeDouble(y, buffer: buffer, boxed: false) - serializeDouble(zoom, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .maskCoords(let n, let x, let y, let zoom): - return ("maskCoords", [("n", n as Any), ("x", x as Any), ("y", y as Any), ("zoom", zoom as Any)]) - } - } - - public static func parse_maskCoords(_ reader: BufferReader) -> MaskCoords? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Double? - _2 = reader.readDouble() - var _3: Double? - _3 = reader.readDouble() - var _4: Double? - _4 = reader.readDouble() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MaskCoords.maskCoords(n: _1!, x: _2!, y: _3!, zoom: _4!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index 55ad86a29f..2aa6b0b28f 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -1,3 +1,51 @@ +public extension Api { + enum MaskCoords: TypeConstructorDescription { + case maskCoords(n: Int32, x: Double, y: Double, zoom: Double) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .maskCoords(let n, let x, let y, let zoom): + if boxed { + buffer.appendInt32(-1361650766) + } + serializeInt32(n, buffer: buffer, boxed: false) + serializeDouble(x, buffer: buffer, boxed: false) + serializeDouble(y, buffer: buffer, boxed: false) + serializeDouble(zoom, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .maskCoords(let n, let x, let y, let zoom): + return ("maskCoords", [("n", n as Any), ("x", x as Any), ("y", y as Any), ("zoom", zoom as Any)]) + } + } + + public static func parse_maskCoords(_ reader: BufferReader) -> MaskCoords? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Double? + _2 = reader.readDouble() + var _3: Double? + _3 = reader.readDouble() + var _4: Double? + _4 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MaskCoords.maskCoords(n: _1!, x: _2!, y: _3!, zoom: _4!) + } + else { + return nil + } + } + + } +} public extension Api { indirect enum Message: TypeConstructorDescription { case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?) diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index 7b2668a632..f1ea5107f5 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -323,6 +323,7 @@ public extension Api { public extension Api { enum MessageReplyHeader: TypeConstructorDescription { case messageReplyHeader(flags: Int32, replyToMsgId: Int32, replyToPeerId: Api.Peer?, replyToTopId: Int32?) + case messageReplyStoryHeader(userId: Int64, storyId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -335,6 +336,13 @@ public extension Api { if Int(flags) & Int(1 << 0) != 0 {replyToPeerId!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(replyToTopId!, buffer: buffer, boxed: false)} break + case .messageReplyStoryHeader(let userId, let storyId): + if boxed { + buffer.appendInt32(-1667711039) + } + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(storyId, buffer: buffer, boxed: false) + break } } @@ -342,6 +350,8 @@ public extension Api { switch self { case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyToTopId): return ("messageReplyHeader", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("replyToTopId", replyToTopId as Any)]) + case .messageReplyStoryHeader(let userId, let storyId): + return ("messageReplyStoryHeader", [("userId", userId as Any), ("storyId", storyId as Any)]) } } @@ -367,6 +377,20 @@ public extension Api { return nil } } + public static func parse_messageReplyStoryHeader(_ reader: BufferReader) -> MessageReplyHeader? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageReplyHeader.messageReplyStoryHeader(userId: _1!, storyId: _2!) + } + else { + return nil + } + } } } @@ -776,103 +800,3 @@ public extension Api { } } -public extension Api { - enum NotifyPeer: TypeConstructorDescription { - case notifyBroadcasts - case notifyChats - case notifyForumTopic(peer: Api.Peer, topMsgId: Int32) - case notifyPeer(peer: Api.Peer) - case notifyUsers - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .notifyBroadcasts: - if boxed { - buffer.appendInt32(-703403793) - } - - break - case .notifyChats: - if boxed { - buffer.appendInt32(-1073230141) - } - - break - case .notifyForumTopic(let peer, let topMsgId): - if boxed { - buffer.appendInt32(577659656) - } - peer.serialize(buffer, true) - serializeInt32(topMsgId, buffer: buffer, boxed: false) - break - case .notifyPeer(let peer): - if boxed { - buffer.appendInt32(-1613493288) - } - peer.serialize(buffer, true) - break - case .notifyUsers: - if boxed { - buffer.appendInt32(-1261946036) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .notifyBroadcasts: - return ("notifyBroadcasts", []) - case .notifyChats: - return ("notifyChats", []) - case .notifyForumTopic(let peer, let topMsgId): - return ("notifyForumTopic", [("peer", peer as Any), ("topMsgId", topMsgId as Any)]) - case .notifyPeer(let peer): - return ("notifyPeer", [("peer", peer as Any)]) - case .notifyUsers: - return ("notifyUsers", []) - } - } - - public static func parse_notifyBroadcasts(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyBroadcasts - } - public static func parse_notifyChats(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyChats - } - public static func parse_notifyForumTopic(_ reader: BufferReader) -> NotifyPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.NotifyPeer.notifyForumTopic(peer: _1!, topMsgId: _2!) - } - else { - return nil - } - } - public static func parse_notifyPeer(_ reader: BufferReader) -> NotifyPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - if _c1 { - return Api.NotifyPeer.notifyPeer(peer: _1!) - } - else { - return nil - } - } - public static func parse_notifyUsers(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyUsers - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index c62dd8720f..d71cc7f9fe 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -1,3 +1,103 @@ +public extension Api { + enum NotifyPeer: TypeConstructorDescription { + case notifyBroadcasts + case notifyChats + case notifyForumTopic(peer: Api.Peer, topMsgId: Int32) + case notifyPeer(peer: Api.Peer) + case notifyUsers + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .notifyBroadcasts: + if boxed { + buffer.appendInt32(-703403793) + } + + break + case .notifyChats: + if boxed { + buffer.appendInt32(-1073230141) + } + + break + case .notifyForumTopic(let peer, let topMsgId): + if boxed { + buffer.appendInt32(577659656) + } + peer.serialize(buffer, true) + serializeInt32(topMsgId, buffer: buffer, boxed: false) + break + case .notifyPeer(let peer): + if boxed { + buffer.appendInt32(-1613493288) + } + peer.serialize(buffer, true) + break + case .notifyUsers: + if boxed { + buffer.appendInt32(-1261946036) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .notifyBroadcasts: + return ("notifyBroadcasts", []) + case .notifyChats: + return ("notifyChats", []) + case .notifyForumTopic(let peer, let topMsgId): + return ("notifyForumTopic", [("peer", peer as Any), ("topMsgId", topMsgId as Any)]) + case .notifyPeer(let peer): + return ("notifyPeer", [("peer", peer as Any)]) + case .notifyUsers: + return ("notifyUsers", []) + } + } + + public static func parse_notifyBroadcasts(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyBroadcasts + } + public static func parse_notifyChats(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyChats + } + public static func parse_notifyForumTopic(_ reader: BufferReader) -> NotifyPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.NotifyPeer.notifyForumTopic(peer: _1!, topMsgId: _2!) + } + else { + return nil + } + } + public static func parse_notifyPeer(_ reader: BufferReader) -> NotifyPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + let _c1 = _1 != nil + if _c1 { + return Api.NotifyPeer.notifyPeer(peer: _1!) + } + else { + return nil + } + } + public static func parse_notifyUsers(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyUsers + } + + } +} public extension Api { enum Page: TypeConstructorDescription { case page(flags: Int32, url: String, blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document], views: Int32?) @@ -890,111 +990,3 @@ public extension Api { } } -public extension Api { - indirect enum PageCaption: TypeConstructorDescription { - case pageCaption(text: Api.RichText, credit: Api.RichText) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pageCaption(let text, let credit): - if boxed { - buffer.appendInt32(1869903447) - } - text.serialize(buffer, true) - credit.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .pageCaption(let text, let credit): - return ("pageCaption", [("text", text as Any), ("credit", credit as Any)]) - } - } - - public static func parse_pageCaption(_ reader: BufferReader) -> PageCaption? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageCaption.pageCaption(text: _1!, credit: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum PageListItem: TypeConstructorDescription { - case pageListItemBlocks(blocks: [Api.PageBlock]) - case pageListItemText(text: Api.RichText) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pageListItemBlocks(let blocks): - if boxed { - buffer.appendInt32(635466748) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(blocks.count)) - for item in blocks { - item.serialize(buffer, true) - } - break - case .pageListItemText(let text): - if boxed { - buffer.appendInt32(-1188055347) - } - text.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .pageListItemBlocks(let blocks): - return ("pageListItemBlocks", [("blocks", blocks as Any)]) - case .pageListItemText(let text): - return ("pageListItemText", [("text", text as Any)]) - } - } - - public static func parse_pageListItemBlocks(_ reader: BufferReader) -> PageListItem? { - var _1: [Api.PageBlock]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PageListItem.pageListItemBlocks(blocks: _1!) - } - else { - return nil - } - } - public static func parse_pageListItemText(_ reader: BufferReader) -> PageListItem? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageListItem.pageListItemText(text: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index 1ad7545048..8981915c7f 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -1,3 +1,111 @@ +public extension Api { + indirect enum PageCaption: TypeConstructorDescription { + case pageCaption(text: Api.RichText, credit: Api.RichText) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pageCaption(let text, let credit): + if boxed { + buffer.appendInt32(1869903447) + } + text.serialize(buffer, true) + credit.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .pageCaption(let text, let credit): + return ("pageCaption", [("text", text as Any), ("credit", credit as Any)]) + } + } + + public static func parse_pageCaption(_ reader: BufferReader) -> PageCaption? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageCaption.pageCaption(text: _1!, credit: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + indirect enum PageListItem: TypeConstructorDescription { + case pageListItemBlocks(blocks: [Api.PageBlock]) + case pageListItemText(text: Api.RichText) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pageListItemBlocks(let blocks): + if boxed { + buffer.appendInt32(635466748) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(blocks.count)) + for item in blocks { + item.serialize(buffer, true) + } + break + case .pageListItemText(let text): + if boxed { + buffer.appendInt32(-1188055347) + } + text.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .pageListItemBlocks(let blocks): + return ("pageListItemBlocks", [("blocks", blocks as Any)]) + case .pageListItemText(let text): + return ("pageListItemText", [("text", text as Any)]) + } + } + + public static func parse_pageListItemBlocks(_ reader: BufferReader) -> PageListItem? { + var _1: [Api.PageBlock]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PageListItem.pageListItemBlocks(blocks: _1!) + } + else { + return nil + } + } + public static func parse_pageListItemText(_ reader: BufferReader) -> PageListItem? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageListItem.pageListItemText(text: _1!) + } + else { + return nil + } + } + + } +} public extension Api { indirect enum PageListOrderedItem: TypeConstructorDescription { case pageListOrderedItemBlocks(num: String, blocks: [Api.PageBlock]) @@ -1114,157 +1222,3 @@ public extension Api { } } -public extension Api { - enum PhoneCallProtocol: TypeConstructorDescription { - case phoneCallProtocol(flags: Int32, minLayer: Int32, maxLayer: Int32, libraryVersions: [String]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions): - if boxed { - buffer.appendInt32(-58224696) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(minLayer, buffer: buffer, boxed: false) - serializeInt32(maxLayer, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(libraryVersions.count)) - for item in libraryVersions { - serializeString(item, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions): - return ("phoneCallProtocol", [("flags", flags as Any), ("minLayer", minLayer as Any), ("maxLayer", maxLayer as Any), ("libraryVersions", libraryVersions as Any)]) - } - } - - public static func parse_phoneCallProtocol(_ reader: BufferReader) -> PhoneCallProtocol? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [String]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhoneCallProtocol.phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum PhoneConnection: TypeConstructorDescription { - case phoneConnection(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, peerTag: Buffer) - case phoneConnectionWebrtc(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, username: String, password: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .phoneConnection(let flags, let id, let ip, let ipv6, let port, let peerTag): - if boxed { - buffer.appendInt32(-1665063993) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeString(ip, buffer: buffer, boxed: false) - serializeString(ipv6, buffer: buffer, boxed: false) - serializeInt32(port, buffer: buffer, boxed: false) - serializeBytes(peerTag, buffer: buffer, boxed: false) - break - case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password): - if boxed { - buffer.appendInt32(1667228533) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeString(ip, buffer: buffer, boxed: false) - serializeString(ipv6, buffer: buffer, boxed: false) - serializeInt32(port, buffer: buffer, boxed: false) - serializeString(username, buffer: buffer, boxed: false) - serializeString(password, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .phoneConnection(let flags, let id, let ip, let ipv6, let port, let peerTag): - return ("phoneConnection", [("flags", flags as Any), ("id", id as Any), ("ip", ip as Any), ("ipv6", ipv6 as Any), ("port", port as Any), ("peerTag", peerTag as Any)]) - case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password): - return ("phoneConnectionWebrtc", [("flags", flags as Any), ("id", id as Any), ("ip", ip as Any), ("ipv6", ipv6 as Any), ("port", port as Any), ("username", username as Any), ("password", password as Any)]) - } - } - - public static func parse_phoneConnection(_ reader: BufferReader) -> PhoneConnection? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Buffer? - _6 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PhoneConnection.phoneConnection(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, peerTag: _6!) - } - else { - return nil - } - } - public static func parse_phoneConnectionWebrtc(_ reader: BufferReader) -> PhoneConnection? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: String? - _6 = parseString(reader) - var _7: String? - _7 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PhoneConnection.phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api17.swift b/submodules/TelegramApi/Sources/Api17.swift index 41f414105a..c50124ce4c 100644 --- a/submodules/TelegramApi/Sources/Api17.swift +++ b/submodules/TelegramApi/Sources/Api17.swift @@ -1,3 +1,157 @@ +public extension Api { + enum PhoneCallProtocol: TypeConstructorDescription { + case phoneCallProtocol(flags: Int32, minLayer: Int32, maxLayer: Int32, libraryVersions: [String]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions): + if boxed { + buffer.appendInt32(-58224696) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(minLayer, buffer: buffer, boxed: false) + serializeInt32(maxLayer, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(libraryVersions.count)) + for item in libraryVersions { + serializeString(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions): + return ("phoneCallProtocol", [("flags", flags as Any), ("minLayer", minLayer as Any), ("maxLayer", maxLayer as Any), ("libraryVersions", libraryVersions as Any)]) + } + } + + public static func parse_phoneCallProtocol(_ reader: BufferReader) -> PhoneCallProtocol? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [String]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhoneCallProtocol.phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum PhoneConnection: TypeConstructorDescription { + case phoneConnection(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, peerTag: Buffer) + case phoneConnectionWebrtc(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, username: String, password: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .phoneConnection(let flags, let id, let ip, let ipv6, let port, let peerTag): + if boxed { + buffer.appendInt32(-1665063993) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeString(ip, buffer: buffer, boxed: false) + serializeString(ipv6, buffer: buffer, boxed: false) + serializeInt32(port, buffer: buffer, boxed: false) + serializeBytes(peerTag, buffer: buffer, boxed: false) + break + case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password): + if boxed { + buffer.appendInt32(1667228533) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeString(ip, buffer: buffer, boxed: false) + serializeString(ipv6, buffer: buffer, boxed: false) + serializeInt32(port, buffer: buffer, boxed: false) + serializeString(username, buffer: buffer, boxed: false) + serializeString(password, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .phoneConnection(let flags, let id, let ip, let ipv6, let port, let peerTag): + return ("phoneConnection", [("flags", flags as Any), ("id", id as Any), ("ip", ip as Any), ("ipv6", ipv6 as Any), ("port", port as Any), ("peerTag", peerTag as Any)]) + case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password): + return ("phoneConnectionWebrtc", [("flags", flags as Any), ("id", id as Any), ("ip", ip as Any), ("ipv6", ipv6 as Any), ("port", port as Any), ("username", username as Any), ("password", password as Any)]) + } + } + + public static func parse_phoneConnection(_ reader: BufferReader) -> PhoneConnection? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PhoneConnection.phoneConnection(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, peerTag: _6!) + } + else { + return nil + } + } + public static func parse_phoneConnectionWebrtc(_ reader: BufferReader) -> PhoneConnection? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: String? + _6 = parseString(reader) + var _7: String? + _7 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PhoneConnection.phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!) + } + else { + return nil + } + } + + } +} public extension Api { enum Photo: TypeConstructorDescription { case photo(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], videoSizes: [Api.VideoSize]?, dcId: Int32) diff --git a/submodules/TelegramApi/Sources/Api21.swift b/submodules/TelegramApi/Sources/Api21.swift index 337477512a..98fa90f06c 100644 --- a/submodules/TelegramApi/Sources/Api21.swift +++ b/submodules/TelegramApi/Sources/Api21.swift @@ -492,42 +492,46 @@ public extension Api { } public extension Api { enum StoryViews: TypeConstructorDescription { - case storyViews(recentViewers: [Int64], viewsCount: Int32) + case storyViews(flags: Int32, viewsCount: Int32, recentViewers: [Int64]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyViews(let recentViewers, let viewsCount): + case .storyViews(let flags, let viewsCount, let recentViewers): if boxed { - buffer.appendInt32(1368082392) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentViewers.count)) - for item in recentViewers { - serializeInt64(item, buffer: buffer, boxed: false) + buffer.appendInt32(-748199729) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(viewsCount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentViewers!.count)) + for item in recentViewers! { + serializeInt64(item, buffer: buffer, boxed: false) + }} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyViews(let recentViewers, let viewsCount): - return ("storyViews", [("recentViewers", recentViewers as Any), ("viewsCount", viewsCount as Any)]) + case .storyViews(let flags, let viewsCount, let recentViewers): + return ("storyViews", [("flags", flags as Any), ("viewsCount", viewsCount as Any), ("recentViewers", recentViewers as Any)]) } } public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() + var _3: [Int64]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryViews.storyViews(recentViewers: _1!, viewsCount: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, recentViewers: _3) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index f816774ffc..542cf81154 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -360,16 +360,17 @@ public extension Api.storage { } public extension Api.stories { enum AllStories: TypeConstructorDescription { - case allStories(flags: Int32, state: String, userStories: [Api.UserStories], users: [Api.User]) + case allStories(flags: Int32, count: Int32, state: String, userStories: [Api.UserStories], users: [Api.User]) case allStoriesNotModified(state: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .allStories(let flags, let state, let userStories, let users): + case .allStories(let flags, let count, let state, let userStories, let users): if boxed { - buffer.appendInt32(1528473228) + buffer.appendInt32(-2086796248) } serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) serializeString(state, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(userStories.count)) @@ -393,8 +394,8 @@ public extension Api.stories { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .allStories(let flags, let state, let userStories, let users): - return ("allStories", [("flags", flags as Any), ("state", state as Any), ("userStories", userStories as Any), ("users", users as Any)]) + case .allStories(let flags, let count, let state, let userStories, let users): + return ("allStories", [("flags", flags as Any), ("count", count as Any), ("state", state as Any), ("userStories", userStories as Any), ("users", users as Any)]) case .allStoriesNotModified(let state): return ("allStoriesNotModified", [("state", state as Any)]) } @@ -403,22 +404,25 @@ public extension Api.stories { public static func parse_allStories(_ reader: BufferReader) -> AllStories? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: [Api.UserStories]? + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: [Api.UserStories]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.UserStories.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.UserStories.self) } - var _4: [Api.User]? + var _5: [Api.User]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.stories.AllStories.allStories(flags: _1!, state: _2!, userStories: _3!, users: _4!) + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.stories.AllStories.allStories(flags: _1!, count: _2!, state: _3!, userStories: _4!, users: _5!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index b74ada1323..acf86c4503 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -5962,17 +5962,16 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func prolongWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyToMsgId: Int32?, topMsgId: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func prolongWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(2146648841) + buffer.appendInt32(-1328014717) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) bot.serialize(buffer, true) serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.prolongWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("queryId", String(describing: queryId)), ("replyToMsgId", String(describing: replyToMsgId)), ("topMsgId", String(describing: topMsgId)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + return (FunctionDescription(name: "messages.prolongWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("queryId", String(describing: queryId)), ("replyTo", String(describing: replyTo)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) var result: Api.Bool? if let signature = reader.readInt32() { @@ -6337,9 +6336,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String, replyToMsgId: Int32?, topMsgId: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(395003915) + buffer.appendInt32(647873217) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) bot.serialize(buffer, true) @@ -6347,10 +6346,9 @@ public extension Api.functions.messages { if Int(flags) & Int(1 << 3) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {themeParams!.serialize(buffer, true)} serializeString(platform, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform)), ("replyToMsgId", String(describing: replyToMsgId)), ("topMsgId", String(describing: topMsgId)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in + return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform)), ("replyTo", String(describing: replyTo)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in let reader = BufferReader(buffer) var result: Api.WebViewResult? if let signature = reader.readInt32() { @@ -6608,19 +6606,18 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, topMsgId: Int32?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-738468661) + buffer.appendInt32(-138647366) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} serializeInt64(randomId, buffer: buffer, boxed: false) serializeInt64(queryId, buffer: buffer, boxed: false) serializeString(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyToMsgId", String(describing: replyToMsgId)), ("topMsgId", String(describing: topMsgId)), ("randomId", String(describing: randomId)), ("queryId", String(describing: queryId)), ("id", String(describing: id)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("randomId", String(describing: randomId)), ("queryId", String(describing: queryId)), ("id", String(describing: id)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -6631,13 +6628,12 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, topMsgId: Int32?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1967638886) + buffer.appendInt32(1926021693) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} media.serialize(buffer, true) serializeString(message, buffer: buffer, boxed: false) serializeInt64(randomId, buffer: buffer, boxed: false) @@ -6649,7 +6645,7 @@ public extension Api.functions.messages { }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyToMsgId", String(describing: replyToMsgId)), ("topMsgId", String(describing: topMsgId)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -6660,13 +6656,12 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendMessage(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, topMsgId: Int32?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(482476935) + buffer.appendInt32(671943023) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} serializeString(message, buffer: buffer, boxed: false) serializeInt64(randomId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} @@ -6677,7 +6672,7 @@ public extension Api.functions.messages { }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyToMsgId", String(describing: replyToMsgId)), ("topMsgId", String(describing: topMsgId)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -6688,13 +6683,12 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, topMsgId: Int32?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1225713124) + buffer.appendInt32(1164872071) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(multiMedia.count)) for item in multiMedia { @@ -6702,7 +6696,7 @@ public extension Api.functions.messages { } if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyToMsgId", String(describing: replyToMsgId)), ("topMsgId", String(describing: topMsgId)), ("multiMedia", String(describing: multiMedia)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("multiMedia", String(describing: multiMedia)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -6755,13 +6749,13 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendScreenshotNotification(peer: Api.InputPeer, replyToMsgId: Int32, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendScreenshotNotification(peer: Api.InputPeer, replyTo: Api.InputReplyTo, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-914493408) + buffer.appendInt32(-1589618665) peer.serialize(buffer, true) - serializeInt32(replyToMsgId, buffer: buffer, boxed: false) + replyTo.serialize(buffer, true) serializeInt64(randomId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendScreenshotNotification", parameters: [("peer", String(describing: peer)), ("replyToMsgId", String(describing: replyToMsgId)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + return (FunctionDescription(name: "messages.sendScreenshotNotification", parameters: [("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -8583,6 +8577,26 @@ public extension Api.functions.stories { }) } } +public extension Api.functions.stories { + static func incrementStoryViews(userId: Api.InputUser, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(571629863) + userId.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.incrementStoryViews", parameters: [("userId", String(describing: userId)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.stories { static func readStories(userId: Api.InputUser, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index ef4ca53c0f..a108f7ffe7 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -973,6 +973,8 @@ public class Account { let networkStatsContext: NetworkStatsContext + public let storySubscriptionsContext: StorySubscriptionsContext? + public init(accountManager: AccountManager, id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, supplementary: Bool) { self.accountManager = accountManager self.id = id @@ -989,6 +991,13 @@ public class Account { self.networkStatsContext = NetworkStatsContext(postbox: postbox) self.peerInputActivityManager = PeerInputActivityManager() + + if !supplementary { + self.storySubscriptionsContext = StorySubscriptionsContext(accountPeerId: peerId, postbox: postbox, network: network) + } else { + self.storySubscriptionsContext = nil + } + self.callSessionManager = CallSessionManager(postbox: postbox, network: network, maxLayer: networkArguments.voipMaxLayer, versions: networkArguments.voipVersions, addUpdates: { [weak self] updates in self?.stateManager?.addUpdates(updates) }) diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 4d452b2534..ccc5d3ee5c 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -50,7 +50,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], var isAnimated = false inner: for attribute in file.attributes { switch attribute { - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { refinedTag = .voiceOrInstantVideo } else { @@ -250,6 +250,8 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere var replyIds = ReferencedReplyMessageIds() replyIds.add(sourceId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), targetId: targetId) return (replyIds, []) + case .messageReplyStoryHeader: + break } } case .messageEmpty: @@ -262,6 +264,8 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere var replyIds = ReferencedReplyMessageIds() replyIds.add(sourceId: MessageId(peerId: chatPeerId.peerId, namespace: Namespaces.Message.Cloud, id: id), targetId: targetId) return (replyIds, []) + case .messageReplyStoryHeader: + break } } } @@ -479,6 +483,8 @@ extension StoreMessage { } } attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId)) + case .messageReplyStoryHeader: + break } } @@ -728,6 +734,8 @@ extension StoreMessage { break } attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId)) + case .messageReplyStoryHeader: + break } } else { switch action { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift index 04a4a91bdd..576a6fc5da 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift @@ -6,7 +6,7 @@ import TelegramApi func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> PixelDimensions? { for attribute in attributes { switch attribute { - case let .Video(_, size, _): + case let .Video(_, size, _, _): return size case let .ImageSize(size): return size @@ -20,7 +20,7 @@ func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> func durationForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> Int32? { for attribute in attributes { switch attribute { - case let .Video(duration, _, _): + case let .Video(duration, _, _, _): return Int32(duration) case let .Audio(_, duration, _, _, _): return Int32(duration) @@ -97,7 +97,7 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt result.append(.ImageSize(size: PixelDimensions(width: w, height: h))) case .documentAttributeAnimated: result.append(.Animated) - case let .documentAttributeVideo(flags, duration, w, h, _): + case let .documentAttributeVideo(flags, duration, w, h, preloadSize): var videoFlags = TelegramMediaVideoFlags() if (flags & (1 << 0)) != 0 { videoFlags.insert(.instantRoundVideo) @@ -105,7 +105,7 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt if (flags & (1 << 1)) != 0 { videoFlags.insert(.supportsStreaming) } - result.append(.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags)) + result.append(.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: preloadSize)) case let .documentAttributeAudio(flags, duration, title, performer, waveform): let isVoice = (flags & (1 << 10)) != 0 let waveformBuffer: Data? = waveform?.makeData() diff --git a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift index 70f51b3807..0d5c2a058c 100644 --- a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift +++ b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift @@ -659,13 +659,13 @@ final class MediaReferenceRevalidationContext { } } - func story(accountPeerId: PeerId, postbox: Postbox, network: Network, background: Bool, peer: PeerReference, id: Int32) -> Signal { + func story(accountPeerId: PeerId, postbox: Postbox, network: Network, background: Bool, peer: PeerReference, id: Int32) -> Signal { return self.genericItem(key: .story(peer: peer, id: id), background: background, request: { next, error in - return (_internal_getStoryById(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, id: id) + return (_internal_getStoriesById(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, ids: [id]) |> castError(RevalidateMediaReferenceError.self) - |> mapToSignal { result -> Signal in - if let result = result { - return .single(result) + |> mapToSignal { result -> Signal in + if let item = result.first { + return .single(item) } else { return .fail(.generic) } @@ -675,8 +675,8 @@ final class MediaReferenceRevalidationContext { error(.generic) }) }) - |> mapToSignal { next -> Signal in - if let next = next as? StoryListContext.Item { + |> mapToSignal { next -> Signal in + if let next = next as? Stories.StoredItem { return .single(next) } else { return .fail(.generic) @@ -834,10 +834,14 @@ func revalidateMediaResourceReference(accountPeerId: PeerId, postbox: Postbox, n case let .story(peer, id, _): return revalidationContext.story(accountPeerId: accountPeerId, postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, peer: peer, id: id) |> mapToSignal { storyItem -> Signal in - if let updatedResource = findUpdatedMediaResource(media: storyItem.media._asMedia(), previousMedia: nil, resource: resource) { - return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) + guard case let .item(item) = storyItem, let media = item.media else { + return .fail(.generic) + } + if let updatedResource = findUpdatedMediaResource(media: media, previousMedia: nil, resource: resource) { + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) + } else { + return .fail(.generic) } - return .fail(.generic) } case let .standalone(media): if let file = media as? TelegramMediaFile { diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 433a97f15a..8b8e33eae6 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -549,7 +549,7 @@ func inputDocumentAttributesFromFileAttributes(_ fileAttributes: [TelegramMediaF attributes.append(.documentAttributeSticker(flags: flags, alt: displayText, stickerset: stickerSet, maskCoords: inputMaskCoords)) case .HasLinkedStickers: attributes.append(.documentAttributeHasStickers) - case let .Video(duration, size, videoFlags): + case let .Video(duration, size, videoFlags, preloadSize): var flags: Int32 = 0 if videoFlags.contains(.instantRoundVideo) { flags |= (1 << 0) @@ -557,8 +557,11 @@ func inputDocumentAttributesFromFileAttributes(_ fileAttributes: [TelegramMediaF if videoFlags.contains(.supportsStreaming) { flags |= (1 << 1) } + if preloadSize != nil { + flags |= (1 << 2) + } - attributes.append(.documentAttributeVideo(flags: flags, duration: Int32(duration), w: Int32(size.width), h: Int32(size.height), preloadPrefixSize: nil)) + attributes.append(.documentAttributeVideo(flags: flags, duration: Int32(duration), w: Int32(size.width), h: Int32(size.height), preloadPrefixSize: preloadSize)) case let .Audio(isVoice, duration, title, performer, waveform): var flags: Int32 = 0 if isVoice { @@ -628,7 +631,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA } else { return .audio } - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(TelegramMediaVideoFlags.instantRoundVideo) { return .voiceMessages } else { diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 3a715fe788..d7b7f5952d 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -104,9 +104,6 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M } } - if let _ = replyMessageId { - flags |= Int32(1 << 0) - } if let _ = messageEntities { flags |= Int32(1 << 3) } @@ -120,12 +117,28 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M let sendMessageRequest: Signal switch content { case let .text(text): - sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, topMsgId: nil, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer)) + var replyTo: Api.InputReplyTo? + if let replyMessageId = replyMessageId { + flags |= 1 << 0 + + let replyFlags: Int32 = 0 + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: nil) + } + + sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer)) |> `catch` { _ -> Signal in return .complete() } case let .media(inputMedia, text): - sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, topMsgId: nil, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer)) + var replyTo: Api.InputReplyTo? + if let replyMessageId = replyMessageId { + flags |= 1 << 0 + + let replyFlags: Int32 = 0 + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: nil) + } + + sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer)) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index c1dba91b10..ddd782e3de 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -4331,10 +4331,53 @@ func replayFinalState( case let .UpdateStories(updateStories): switch updateStories { case let .userStories(_, userId, maxReadId, stories): - let _ = maxReadId let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + + var updatedPeerEntries: [StoryItemsTableEntry] = transaction.getStoryItems(peerId: peerId) + + for story in stories { + if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peerId, transaction: transaction) { + if let currentIndex = updatedPeerEntries.firstIndex(where: { $0.id == storedItem.id }) { + if case .item = storedItem { + if let codedEntry = CodableEntry(storedItem) { + updatedPeerEntries[currentIndex] = StoryItemsTableEntry(value: codedEntry, id: storedItem.id) + } + } + } else { + if let codedEntry = CodableEntry(storedItem) { + updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id)) + } + } + } else { + if case let .storyItemDeleted(id) = story { + if let index = updatedPeerEntries.firstIndex(where: { $0.id == id }) { + updatedPeerEntries.remove(at: index) + } + } + } + } + + var subscriptionsOpaqueState: String? + if let state = transaction.getSubscriptionsStoriesState()?.get(Stories.SubscriptionsState.self) { + subscriptionsOpaqueState = state.opaqueState + } + var appliedMaxReadId = maxReadId + if let currentState = transaction.getPeerStoryState(peerId: peerId)?.get(Stories.PeerState.self) { + if let appliedMaxReadIdValue = appliedMaxReadId { + appliedMaxReadId = max(appliedMaxReadIdValue, currentState.maxReadId) + } else { + appliedMaxReadId = currentState.maxReadId + } + } + + transaction.setStoryItems(peerId: peerId, items: updatedPeerEntries) + transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState( + subscriptionsOpaqueState: subscriptionsOpaqueState, + maxReadId: appliedMaxReadId ?? 0 + ))) + for storyItem in stories { - if let parsedItem = _internal_parseApiStoryItem(transaction: transaction, peerId: peerId, apiStory: storyItem) { + if let parsedItem = Stories.StoredItem(apiStoryItem: storyItem, peerId: peerId, transaction: transaction) { storyUpdates.append(InternalStoryUpdate.added(peerId: peerId, item: parsedItem)) } else { storyUpdates.append(InternalStoryUpdate.deleted(peerId: peerId, id: storyItem.id)) @@ -4342,6 +4385,20 @@ func replayFinalState( } } case let .UpdateReadStories(peerId, maxId): + var appliedMaxReadId = maxId + if let currentState = transaction.getPeerStoryState(peerId: peerId)?.get(Stories.PeerState.self) { + appliedMaxReadId = max(appliedMaxReadId, currentState.maxReadId) + } + + var subscriptionsOpaqueState: String? + if let state = transaction.getSubscriptionsStoriesState()?.get(Stories.SubscriptionsState.self) { + subscriptionsOpaqueState = state.opaqueState + } + transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState( + subscriptionsOpaqueState: subscriptionsOpaqueState, + maxReadId: appliedMaxReadId + ))) + storyUpdates.append(InternalStoryUpdate.read(peerId: peerId, maxId: maxId)) } } diff --git a/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift index 1d8a54e359..6cffff8581 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift @@ -538,7 +538,7 @@ private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute], t result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet)) case let .ImageSize(size): result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) - case let .Video(duration, size, _): + case let .Video(duration, size, _, _): result.append(.documentAttributeVideo(duration: Int32(duration), w: Int32(size.width), h: Int32(size.height))) case let .Audio(isVoice, duration, title, performer, waveform): var flags: Int32 = 0 @@ -597,7 +597,7 @@ private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute], t result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet)) case let .ImageSize(size): result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) - case let .Video(duration, size, videoFlags): + case let .Video(duration, size, videoFlags, _): var flags: Int32 = 0 if videoFlags.contains(.instantRoundVideo) { flags |= 1 << 0 @@ -660,7 +660,7 @@ private func decryptedAttributes101(_ attributes: [TelegramMediaFileAttribute], result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet)) case let .ImageSize(size): result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) - case let .Video(duration, size, videoFlags): + case let .Video(duration, size, videoFlags, _): var flags: Int32 = 0 if videoFlags.contains(.instantRoundVideo) { flags |= 1 << 0 @@ -723,7 +723,7 @@ private func decryptedAttributes144(_ attributes: [TelegramMediaFileAttribute], result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet)) case let .ImageSize(size): result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) - case let .Video(duration, size, videoFlags): + case let .Video(duration, size, videoFlags, _): var flags: Int32 = 0 if videoFlags.contains(.instantRoundVideo) { flags |= 1 << 0 diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index f6d18ad042..a2f41a7539 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -855,9 +855,6 @@ public final class PendingMessageManager { } } else { flags |= (1 << 7) - if let _ = replyMessageId { - flags |= Int32(1 << 0) - } var sendAsInputPeer: Api.InputPeer? if let sendAsPeerId = sendAsPeerId, let sendAsPeer = transaction.getPeer(sendAsPeerId), let inputPeer = apiInputPeerOrSelf(sendAsPeer, accountPeerId: accountPeerId) { @@ -913,7 +910,18 @@ public final class PendingMessageManager { topMsgId = Int32(clamping: threadId) } - sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, topMsgId: topMsgId, multiMedia: singleMedias, scheduleDate: scheduleTime, sendAs: sendAsInputPeer)) + var replyTo: Api.InputReplyTo? + if let replyMessageId = replyMessageId { + flags |= 1 << 0 + + var replyFlags: Int32 = 0 + if topMsgId != nil { + replyFlags |= 1 << 0 + } + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: topMsgId) + } + + sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyTo: replyTo, multiMedia: singleMedias, scheduleDate: scheduleTime, sendAs: sendAsInputPeer)) } return sendMessageRequest @@ -1141,25 +1149,35 @@ public final class PendingMessageManager { flags |= Int32(1 << 15) } - var topMsgId: Int32? - if let threadId = message.threadId { - flags |= Int32(1 << 9) - topMsgId = Int32(clamping: threadId) + var replyTo: Api.InputReplyTo? + if let replyMessageId = replyMessageId { + flags |= 1 << 0 + + var replyFlags: Int32 = 0 + if message.threadId != nil { + replyFlags |= 1 << 0 + } + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:))) } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, topMsgId: topMsgId, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) } - var topMsgId: Int32? - if let threadId = message.threadId { - flags |= Int32(1 << 9) - topMsgId = Int32(clamping: threadId) + var replyTo: Api.InputReplyTo? + if let replyMessageId = replyMessageId { + flags |= 1 << 0 + + var replyFlags: Int32 = 0 + if message.threadId != nil { + replyFlags |= 1 << 0 + } + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:))) } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, topMsgId: topMsgId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): var topMsgId: Int32? @@ -1179,16 +1197,26 @@ public final class PendingMessageManager { flags |= Int32(1 << 11) } - var topMsgId: Int32? - if let threadId = message.threadId { - flags |= Int32(1 << 9) - topMsgId = Int32(clamping: threadId) + var replyTo: Api.InputReplyTo? + if let replyMessageId = replyMessageId { + flags |= 1 << 0 + + var replyFlags: Int32 = 0 + if message.threadId != nil { + replyFlags |= 1 << 0 + } + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:))) } - sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, topMsgId: topMsgId, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer)) + sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer)) |> map(NetworkRequestResult.result) case .messageScreenshot: - sendMessageRequest = network.request(Api.functions.messages.sendScreenshotNotification(peer: inputPeer, replyToMsgId: replyMessageId ?? 0, randomId: uniqueId)) + var replyTo: Api.InputReplyTo + + let replyFlags: Int32 = 0 + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId ?? 0, topMsgId: nil) + + sendMessageRequest = network.request(Api.functions.messages.sendScreenshotNotification(peer: inputPeer, replyTo: replyTo, randomId: uniqueId)) |> map(NetworkRequestResult.result) case .secretMedia: assertionFailure() diff --git a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift index bc3781c3a7..0ff2743d71 100644 --- a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift @@ -610,7 +610,7 @@ extension TelegramMediaFileAttribute { } self = .Sticker(displayText: alt, packReference: packReference, maskData: nil) case let .documentAttributeVideo(duration, w, h): - self = .Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: []) + self = .Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil) } } } @@ -642,7 +642,7 @@ extension TelegramMediaFileAttribute { if (flags & (1 << 0)) != 0 { videoFlags.insert(.instantRoundVideo) } - self = .Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags) + self = .Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil) } } } @@ -674,7 +674,7 @@ extension TelegramMediaFileAttribute { if (flags & (1 << 0)) != 0 { videoFlags.insert(.instantRoundVideo) } - self = .Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags) + self = .Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil) } } } @@ -706,7 +706,7 @@ extension TelegramMediaFileAttribute { if (flags & (1 << 0)) != 0 { videoFlags.insert(.instantRoundVideo) } - self = .Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags) + self = .Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil) } } } @@ -821,7 +821,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: []), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) @@ -1021,7 +1021,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 loop: for attr in parsedAttributes { switch attr { - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { attributes.append(ConsumableContentMessageAttribute(consumed: false)) } @@ -1040,7 +1040,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: []), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) @@ -1300,7 +1300,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 loop: for attr in parsedAttributes { switch attr { - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { attributes.append(ConsumableContentMessageAttribute(consumed: false)) } @@ -1319,7 +1319,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: []), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) @@ -1501,7 +1501,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 loop: for attr in parsedAttributes { switch attr { - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { attributes.append(ConsumableContentMessageAttribute(consumed: false)) } @@ -1520,7 +1520,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: []), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift index 76b96e9e1c..5475c9cdbc 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift @@ -223,7 +223,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable { case Sticker(displayText: String, packReference: StickerPackReference?, maskData: StickerMaskCoords?) case ImageSize(size: PixelDimensions) case Animated - case Video(duration: Int, size: PixelDimensions, flags: TelegramMediaVideoFlags) + case Video(duration: Int, size: PixelDimensions, flags: TelegramMediaVideoFlags, preloadSize: Int32?) case Audio(isVoice: Bool, duration: Int, title: String?, performer: String?, waveform: Data?) case HasLinkedStickers case hintFileIsLarge @@ -243,7 +243,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable { case typeAnimated: self = .Animated case typeVideo: - self = .Video(duration: Int(decoder.decodeInt32ForKey("du", orElse: 0)), size: PixelDimensions(width: decoder.decodeInt32ForKey("w", orElse: 0), height: decoder.decodeInt32ForKey("h", orElse: 0)), flags: TelegramMediaVideoFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))) + self = .Video(duration: Int(decoder.decodeInt32ForKey("du", orElse: 0)), size: PixelDimensions(width: decoder.decodeInt32ForKey("w", orElse: 0), height: decoder.decodeInt32ForKey("h", orElse: 0)), flags: TelegramMediaVideoFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0)), preloadSize: decoder.decodeOptionalInt32ForKey("prs")) case typeAudio: let waveformBuffer = decoder.decodeBytesForKeyNoCopy("wf") var waveform: Data? @@ -290,12 +290,17 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable { encoder.encodeInt32(Int32(size.height), forKey: "h") case .Animated: encoder.encodeInt32(typeAnimated, forKey: "t") - case let .Video(duration, size, flags): + case let .Video(duration, size, flags, preloadSize): encoder.encodeInt32(typeVideo, forKey: "t") encoder.encodeInt32(Int32(duration), forKey: "du") encoder.encodeInt32(Int32(size.width), forKey: "w") encoder.encodeInt32(Int32(size.height), forKey: "h") encoder.encodeInt32(flags.rawValue, forKey: "f") + if let preloadSize = preloadSize { + encoder.encodeInt32(preloadSize, forKey: "prs") + } else { + encoder.encodeNil(forKey: "prs") + } case let .Audio(isVoice, duration, title, performer, waveform): encoder.encodeInt32(typeAudio, forKey: "t") encoder.encodeInt32(isVoice ? 1 : 0, forKey: "iv") @@ -568,13 +573,22 @@ public final class TelegramMediaFile: Media, Equatable, Codable { public var isInstantVideo: Bool { for attribute in self.attributes { - if case .Video(_, _, let flags) = attribute { + if case .Video(_, _, let flags, _) = attribute { return flags.contains(.instantRoundVideo) } } return false } + public var preloadSize: Int32? { + for attribute in self.attributes { + if case .Video(_, _, _, let preloadSize) = attribute { + return preloadSize + } + } + return nil + } + public var isAnimated: Bool { for attribute in self.attributes { if case .Animated = attribute { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift index 4446c36a22..9b180daf84 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift @@ -63,7 +63,15 @@ public enum RequestWebViewError { private func keepWebViewSignal(network: Network, stateManager: AccountStateManager, flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyToMessageId: MessageId?, threadId: Int64?, sendAs: Api.InputPeer?) -> Signal { let signal = Signal { subscriber in let poll = Signal { subscriber in - let signal: Signal = network.request(Api.functions.messages.prolongWebView(flags: flags, peer: peer, bot: bot, queryId: queryId, replyToMsgId: replyToMessageId?.id, topMsgId: threadId.flatMap(Int32.init(clamping:)), sendAs: sendAs)) + var replyTo: Api.InputReplyTo? + if let replyToMessageId = replyToMessageId { + var replyFlags: Int32 = 0 + if threadId != nil { + replyFlags |= 1 << 0 + } + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyToMessageId.id, topMsgId: threadId.flatMap(Int32.init(clamping:))) + } + let signal: Signal = network.request(Api.functions.messages.prolongWebView(flags: flags, peer: peer, bot: bot, queryId: queryId, replyTo: replyTo, sendAs: sendAs)) |> mapError { _ -> KeepWebViewError in return .generic } @@ -120,22 +128,25 @@ func _internal_requestWebView(postbox: Postbox, network: Network, stateManager: if let _ = serializedThemeParams { flags |= (1 << 2) } - var replyToMsgId: Int32? - if let replyToMessageId = replyToMessageId { - flags |= (1 << 0) - replyToMsgId = replyToMessageId.id - } if let _ = payload { flags |= (1 << 3) } if fromMenu { flags |= (1 << 4) } - if threadId != nil { - flags |= (1 << 9) + + var replyTo: Api.InputReplyTo? + if let replyToMessageId = replyToMessageId { + flags |= (1 << 0) + + var replyFlags: Int32 = 0 + if threadId != nil { + replyFlags |= 1 << 0 + } + replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyToMessageId.id, topMsgId: threadId.flatMap(Int32.init(clamping:))) } - return network.request(Api.functions.messages.requestWebView(flags: flags, peer: inputPeer, bot: inputBot, url: url, startParam: payload, themeParams: serializedThemeParams, platform: botWebViewPlatform, replyToMsgId: replyToMsgId, topMsgId: threadId.flatMap(Int32.init(clamping:)), sendAs: nil)) + return network.request(Api.functions.messages.requestWebView(flags: flags, peer: inputPeer, bot: inputBot, url: url, startParam: payload, themeParams: serializedThemeParams, platform: botWebViewPlatform, replyTo: replyTo, sendAs: nil)) |> mapError { _ -> RequestWebViewError in return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift index c3174d50ab..9dcde2905b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift @@ -113,7 +113,7 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: if let dimensions = externalReference.content?.dimensions { fileAttributes.append(.ImageSize(size: dimensions)) if externalReference.type == "gif" { - fileAttributes.append(.Video(duration: Int(Int32(externalReference.content?.duration ?? 0)), size: dimensions, flags: [])) + fileAttributes.append(.Video(duration: Int(Int32(externalReference.content?.duration ?? 0)), size: dimensions, flags: [], preloadSize: nil)) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index ea5a948956..a734865783 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -25,6 +25,437 @@ public struct EngineStoryPrivacy: Equatable { } } +public enum Stories { + public final class Item: Codable, Equatable { + public struct Views: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case seenCount = "seenCount" + case seenPeerIds = "seenPeerIds" + } + + public var seenCount: Int + public var seenPeerIds: [PeerId] + + public init(seenCount: Int, seenPeerIds: [PeerId]) { + self.seenCount = seenCount + self.seenPeerIds = seenPeerIds + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.seenCount = Int(try container.decode(Int32.self, forKey: .seenCount)) + self.seenPeerIds = try container.decode([Int64].self, forKey: .seenPeerIds).map(PeerId.init) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(Int32(clamping: self.seenCount), forKey: .seenCount) + try container.encode(self.seenPeerIds.map { $0.toInt64() }, forKey: .seenPeerIds) + } + } + + public struct Privacy: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case base = "base" + case additionallyIncludePeers = "addPeers" + } + + public enum Base: Int32 { + case everyone = 0 + case contacts = 1 + case closeFriends = 2 + case nobody = 3 + } + + public var base: Base + public var additionallyIncludePeers: [PeerId] + + public init(base: Base, additionallyIncludePeers: [PeerId]) { + self.base = base + self.additionallyIncludePeers = additionallyIncludePeers + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.base = Base(rawValue: try container.decode(Int32.self, forKey: .base)) ?? .nobody + self.additionallyIncludePeers = try container.decode([Int64].self, forKey: .additionallyIncludePeers).map(PeerId.init) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.base.rawValue, forKey: .base) + try container.encode(self.additionallyIncludePeers.map { $0.toInt64() }, forKey: .additionallyIncludePeers) + } + } + + private enum CodingKeys: String, CodingKey { + case id + case timestamp + case media + case text + case entities + case views + case privacy + } + + public let id: Int32 + public let timestamp: Int32 + public let media: Media? + public let text: String + public let entities: [MessageTextEntity] + public let views: Views? + public let privacy: Privacy? + + public init( + id: Int32, + timestamp: Int32, + media: Media?, + text: String, + entities: [MessageTextEntity], + views: Views?, + privacy: Privacy? + ) { + self.id = id + self.timestamp = timestamp + self.media = media + self.text = text + self.entities = entities + self.views = views + self.privacy = privacy + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.id = try container.decode(Int32.self, forKey: .id) + self.timestamp = try container.decode(Int32.self, forKey: .timestamp) + + if let mediaData = try container.decodeIfPresent(Data.self, forKey: .media) { + self.media = PostboxDecoder(buffer: MemoryBuffer(data: mediaData)).decodeRootObject() as? Media + } else { + self.media = nil + } + + self.text = try container.decode(String.self, forKey: .text) + self.entities = try container.decode([MessageTextEntity].self, forKey: .entities) + self.views = try container.decodeIfPresent(Views.self, forKey: .views) + self.privacy = try container.decodeIfPresent(Privacy.self, forKey: .privacy) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.id, forKey: .id) + try container.encode(self.timestamp, forKey: .timestamp) + + if let media = self.media { + let encoder = PostboxEncoder() + encoder.encodeRootObject(media) + let mediaData = encoder.makeData() + try container.encode(mediaData, forKey: .media) + } + + try container.encode(self.text, forKey: .text) + try container.encode(self.entities, forKey: .entities) + try container.encodeIfPresent(self.views, forKey: .views) + try container.encodeIfPresent(self.privacy, forKey: .privacy) + } + + public static func ==(lhs: Item, rhs: Item) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + + if let lhsMedia = lhs.media, let rhsMedia = rhs.media { + if !lhsMedia.isEqual(to: rhsMedia) { + return false + } + } else { + if (lhs.media == nil) != (rhs.media == nil) { + return false + } + } + + if lhs.text != rhs.text { + return false + } + if lhs.entities != rhs.entities { + return false + } + if lhs.views != rhs.views { + return false + } + if lhs.privacy != rhs.privacy { + return false + } + + return true + } + } + + public final class Placeholder: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case id + case timestamp + } + + public let id: Int32 + public let timestamp: Int32 + + public init( + id: Int32, + timestamp: Int32 + ) { + self.id = id + self.timestamp = timestamp + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.id = try container.decode(Int32.self, forKey: .id) + self.timestamp = try container.decode(Int32.self, forKey: .timestamp) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.id, forKey: .id) + try container.encode(self.timestamp, forKey: .timestamp) + } + + public static func ==(lhs: Placeholder, rhs: Placeholder) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + return true + } + } + + public enum StoredItem: Codable, Equatable { + public enum DecodingError: Error { + case generic + } + + private enum CodingKeys: String, CodingKey { + case discriminator = "d" + case item = "i" + case placeholder = "p" + } + + case item(Item) + case placeholder(Placeholder) + + public var id: Int32 { + switch self { + case let .item(item): + return item.id + case let .placeholder(placeholder): + return placeholder.id + } + } + + public var timestamp: Int32 { + switch self { + case let .item(item): + return item.timestamp + case let .placeholder(placeholder): + return placeholder.timestamp + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + switch try container.decode(Int32.self, forKey: .discriminator) { + case 0: + self = .item(try container.decode(Item.self, forKey: .item)) + case 1: + self = .placeholder(try container.decode(Placeholder.self, forKey: .placeholder)) + default: + throw DecodingError.generic + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .item(item): + try container.encode(0 as Int32, forKey: .discriminator) + try container.encode(item, forKey: .item) + case let .placeholder(placeholder): + try container.encode(1 as Int32, forKey: .discriminator) + try container.encode(placeholder, forKey: .placeholder) + } + } + } + + public final class PeerState: Equatable, Codable { + private enum CodingKeys: CodingKey { + case subscriptionsOpaqueState + case maxReadId + } + + public let subscriptionsOpaqueState: String? + public let maxReadId: Int32 + + public init( + subscriptionsOpaqueState: String?, + maxReadId: Int32 + ){ + self.subscriptionsOpaqueState = subscriptionsOpaqueState + self.maxReadId = maxReadId + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.subscriptionsOpaqueState = try container.decodeIfPresent(String.self, forKey: .subscriptionsOpaqueState) + self.maxReadId = try container.decode(Int32.self, forKey: .maxReadId) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encodeIfPresent(self.subscriptionsOpaqueState, forKey: .subscriptionsOpaqueState) + try container.encode(self.maxReadId, forKey: .maxReadId) + } + + public static func ==(lhs: PeerState, rhs: PeerState) -> Bool { + if lhs.subscriptionsOpaqueState != rhs.subscriptionsOpaqueState { + return false + } + if lhs.maxReadId != rhs.maxReadId { + return false + } + return true + } + } + + public final class SubscriptionsState: Equatable, Codable { + private enum CodingKeys: CodingKey { + case opaqueState + case hasMore + case refreshId + } + + public let opaqueState: String + public let refreshId: UInt64 + public let hasMore: Bool + + public init( + opaqueState: String, + refreshId: UInt64, + hasMore: Bool + ) { + self.opaqueState = opaqueState + self.refreshId = refreshId + self.hasMore = hasMore + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.opaqueState = try container.decode(String.self, forKey: .opaqueState) + self.refreshId = UInt64(bitPattern: (try container.decodeIfPresent(Int64.self, forKey: .refreshId)) ?? 0) + self.hasMore = try container.decode(Bool.self, forKey: .hasMore) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.opaqueState, forKey: .opaqueState) + try container.encode(Int64(bitPattern: self.refreshId), forKey: .refreshId) + try container.encode(self.hasMore, forKey: .hasMore) + } + + public static func ==(lhs: SubscriptionsState, rhs: SubscriptionsState) -> Bool { + if lhs.opaqueState != rhs.opaqueState { + return false + } + if lhs.refreshId != rhs.refreshId { + return false + } + if lhs.hasMore != rhs.hasMore { + return false + } + + return true + } + } +} + +public final class EngineStorySubscriptions: Equatable { + public final class Item: Equatable { + public let peer: EnginePeer + public let hasUnseen: Bool + public let storyCount: Int + public let lastTimestamp: Int32 + + public init( + peer: EnginePeer, + hasUnseen: Bool, + storyCount: Int, + lastTimestamp: Int32 + ) { + self.peer = peer + self.hasUnseen = hasUnseen + self.storyCount = storyCount + self.lastTimestamp = lastTimestamp + } + + public static func ==(lhs: Item, rhs: Item) -> Bool { + if lhs.peer != rhs.peer { + return false + } + if lhs.hasUnseen != rhs.hasUnseen { + return false + } + if lhs.storyCount != rhs.storyCount { + return false + } + if lhs.lastTimestamp != rhs.lastTimestamp { + return false + } + return true + } + } + + public let accountItem: Item? + public let items: [Item] + public let hasMoreToken: String? + + public init(accountItem: Item?, items: [Item], hasMoreToken: String?) { + self.accountItem = accountItem + self.items = items + self.hasMoreToken = hasMoreToken + } + + public static func ==(lhs: EngineStorySubscriptions, rhs: EngineStorySubscriptions) -> Bool { + if lhs.accountItem != rhs.accountItem { + return false + } + if lhs.items != rhs.items { + return false + } + if lhs.hasMoreToken != rhs.hasMoreToken { + return false + } + return true + } +} + func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], privacy: EngineStoryPrivacy) -> Signal { let originalMedia: Media let contentToUpload: MessageContentToUpload @@ -71,7 +502,7 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text: mimeType: "video/mp4", size: nil, attributes: [ - TelegramMediaFileAttribute.Video(duration: duration, size: dimensions, flags: .supportsStreaming) + TelegramMediaFileAttribute.Video(duration: duration, size: dimensions, flags: .supportsStreaming, preloadSize: nil) ] ) originalMedia = fileMedia @@ -216,17 +647,33 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text: } func _internal_deleteStory(account: Account, id: Int32) -> Signal { - return account.network.request(Api.functions.stories.deleteStories(id: [id])) - |> `catch` { _ -> Signal<[Int32], NoError> in - return .single([]) + return account.postbox.transaction { transaction -> Void in + var items = transaction.getStoryItems(peerId: account.peerId) + if let index = items.firstIndex(where: { $0.id == id }) { + items.remove(at: index) + transaction.setStoryItems(peerId: account.peerId, items: items) + } } |> mapToSignal { _ -> Signal in - return .complete() + return account.network.request(Api.functions.stories.deleteStories(id: [id])) + |> `catch` { _ -> Signal<[Int32], NoError> in + return .single([]) + } + |> mapToSignal { _ -> Signal in + return .complete() + } } } func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32) -> Signal { return account.postbox.transaction { transaction -> Api.InputUser? in + if let peerStoryState = transaction.getPeerStoryState(peerId: peerId)?.get(Stories.PeerState.self) { + transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState( + subscriptionsOpaqueState: peerStoryState.subscriptionsOpaqueState, + maxReadId: max(peerStoryState.maxReadId, id) + ))) + } + return transaction.getPeer(peerId).flatMap(apiInputUser) } |> mapToSignal { inputUser -> Signal in @@ -236,6 +683,12 @@ func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32) -> S account.stateManager.injectStoryUpdates(updates: [.read(peerId: peerId, maxId: id)]) + #if DEBUG + if "".isEmpty { + //return .complete() + } + #endif + return account.network.request(Api.functions.stories.readStories(userId: inputUser, maxId: id)) |> `catch` { _ -> Signal<[Int32], NoError> in return .single([]) @@ -257,86 +710,93 @@ extension Api.StoryItem { } } -func _internal_parseApiStoryItem(transaction: Transaction, peerId: PeerId, apiStory: Api.StoryItem) -> StoryListContext.Item? { - switch apiStory { - case let .storyItem(flags, id, date, caption, entities, media, privacy, views): - let _ = flags - let (parsedMedia, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId) - if let parsedMedia = parsedMedia { - var parsedPrivacy: EngineStoryPrivacy? - if let privacy = privacy { - var base: EngineStoryPrivacy.Base = .everyone - var additionalPeerIds: [EnginePeer.Id] = [] - for rule in privacy { - switch rule { - case .privacyValueAllowAll: - base = .everyone - case .privacyValueAllowContacts: - base = .contacts - case .privacyValueAllowCloseFriends: - base = .closeFriends - case let .privacyValueAllowUsers(users): - for id in users { - additionalPeerIds.append(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(id))) - } - case let .privacyValueAllowChatParticipants(chats): - for id in chats { - if let peer = transaction.getPeer(EnginePeer.Id(namespace: Namespaces.Peer.CloudGroup, id: EnginePeer.Id.Id._internalFromInt64Value(id))) { - additionalPeerIds.append(peer.id) - } else if let peer = transaction.getPeer(EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(id))) { - additionalPeerIds.append(peer.id) - } - } - default: - break - } - } - parsedPrivacy = EngineStoryPrivacy(base: base, additionallyIncludePeers: additionalPeerIds) +extension Stories.Item.Views { + init(apiViews: Api.StoryViews) { + switch apiViews { + case let .storyViews(_, viewsCount, recentViewers): + var seenPeerIds: [PeerId] = [] + if let recentViewers = recentViewers { + seenPeerIds = recentViewers.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) } } - - let item = StoryListContext.Item( - id: id, - timestamp: date, - media: EngineMedia(parsedMedia), - text: caption ?? "", - entities: entities.flatMap { entities in return messageTextEntitiesFromApiEntities(entities) } ?? [], - views: views.flatMap { _internal_parseApiStoryViews(transaction: transaction, views: $0) }, - privacy: parsedPrivacy - ) - return item - } else { + self.init(seenCount: Int(viewsCount), seenPeerIds: seenPeerIds) + } + } +} + +extension Stories.StoredItem { + init?(apiStoryItem: Api.StoryItem, peerId: PeerId, transaction: Transaction) { + switch apiStoryItem { + case let .storyItem(flags, id, date, caption, entities, media, privacy, views): + let _ = flags + let (parsedMedia, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId) + if let parsedMedia = parsedMedia { + var parsedPrivacy: Stories.Item.Privacy? + if let privacy = privacy { + var base: Stories.Item.Privacy.Base = .everyone + var additionalPeerIds: [PeerId] = [] + for rule in privacy { + switch rule { + case .privacyValueAllowAll: + base = .everyone + case .privacyValueAllowContacts: + base = .contacts + case .privacyValueAllowCloseFriends: + base = .closeFriends + case let .privacyValueAllowUsers(users): + for id in users { + additionalPeerIds.append(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(id))) + } + case let .privacyValueAllowChatParticipants(chats): + for id in chats { + if let peer = transaction.getPeer(EnginePeer.Id(namespace: Namespaces.Peer.CloudGroup, id: EnginePeer.Id.Id._internalFromInt64Value(id))) { + additionalPeerIds.append(peer.id) + } else if let peer = transaction.getPeer(EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(id))) { + additionalPeerIds.append(peer.id) + } + } + default: + break + } + } + parsedPrivacy = Stories.Item.Privacy(base: base, additionallyIncludePeers: additionalPeerIds) + } + + let item = Stories.Item( + id: id, + timestamp: date, + media: parsedMedia, + text: caption ?? "", + entities: entities.flatMap { entities in return messageTextEntitiesFromApiEntities(entities) } ?? [], + views: views.flatMap(Stories.Item.Views.init(apiViews:)), + privacy: parsedPrivacy + ) + self = .item(item) + } else { + return nil + } + case let .storyItemSkipped(id, date): + self = .placeholder(Stories.Placeholder(id: id, timestamp: date)) + case .storyItemDeleted: return nil } - case .storyItemSkipped: - return nil - case .storyItemDeleted: - return nil } } -func _internal_parseApiStoryViews(transaction: Transaction, views: Api.StoryViews) -> StoryListContext.Views { - switch views { - case let .storyViews(recentViewers, viewsCount): - return StoryListContext.Views(seenCount: Int(viewsCount), seenPeers: recentViewers.compactMap { id -> EnginePeer? in - return transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))).flatMap(EnginePeer.init) - }) - } -} - -func _internal_getStoryById(accountPeerId: PeerId, postbox: Postbox, network: Network, peer: PeerReference, id: Int32) -> Signal { +func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: Network, peer: PeerReference, ids: [Int32]) -> Signal<[Stories.StoredItem], NoError> { guard let inputUser = peer.inputUser else { - return .single(nil) + return .single([]) } - return network.request(Api.functions.stories.getStoriesByID(userId: inputUser, id: [id])) + + return network.request(Api.functions.stories.getStoriesByID(userId: inputUser, id: ids)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal<[Stories.StoredItem], NoError> in guard let result = result else { - return .single(nil) + return .single([]) } - return postbox.transaction { transaction -> StoryListContext.Item? in + return postbox.transaction { transaction -> [Stories.StoredItem] in switch result { case let .stories(_, stories, users): var peers: [Peer] = [] @@ -353,7 +813,53 @@ func _internal_getStoryById(accountPeerId: PeerId, postbox: Postbox, network: Ne }) updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) - return stories.first.flatMap { _internal_parseApiStoryItem(transaction: transaction, peerId: peer.id, apiStory: $0) } + return stories.compactMap { apiStoryItem -> Stories.StoredItem? in + return Stories.StoredItem(apiStoryItem: apiStoryItem, peerId: peer.id, transaction: transaction) + } + } + } + } +} + +func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: Network, peerId: PeerId, ids: [Int32]) -> Signal<[Stories.StoredItem], NoError> { + return postbox.transaction { transaction -> Api.InputUser? in + return transaction.getPeer(peerId).flatMap(apiInputUser) + } + |> mapToSignal { inputUser -> Signal<[Stories.StoredItem], NoError> in + guard let inputUser = inputUser else { + return .single([]) + } + + return network.request(Api.functions.stories.getStoriesByID(userId: inputUser, id: ids)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal<[Stories.StoredItem], NoError> in + guard let result = result else { + return .single([]) + } + return postbox.transaction { transaction -> [Stories.StoredItem] in + switch result { + case let .stories(_, stories, users): + var peers: [Peer] = [] + var peerPresences: [PeerId: Api.User] = [:] + + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + peerPresences[telegramUser.id] = user + } + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) + + return stories.compactMap { apiStoryItem -> Stories.StoredItem? in + return Stories.StoredItem(apiStoryItem: apiStoryItem, peerId: peerId, transaction: transaction) + } + } } } } @@ -422,18 +928,18 @@ func _internal_getStoryViewList(account: Account, id: Int32, offsetTimestamp: In } } -func _internal_getStoryViews(account: Account, ids: [Int32]) -> Signal<[Int32: StoryListContext.Views], NoError> { +func _internal_getStoryViews(account: Account, ids: [Int32]) -> Signal<[Int32: Stories.Item.Views], NoError> { return account.network.request(Api.functions.stories.getStoriesViews(id: ids)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - |> mapToSignal { result -> Signal<[Int32: StoryListContext.Views], NoError> in + |> mapToSignal { result -> Signal<[Int32: Stories.Item.Views], NoError> in guard let result = result else { return .single([:]) } - return account.postbox.transaction { transaction -> [Int32: StoryListContext.Views] in - var parsedViews: [Int32: StoryListContext.Views] = [:] + return account.postbox.transaction { transaction -> [Int32: Stories.Item.Views] in + var parsedViews: [Int32: Stories.Item.Views] = [:] switch result { case let .storyViews(views, users): var peers: [Peer] = [] @@ -452,7 +958,7 @@ func _internal_getStoryViews(account: Account, ids: [Int32]) -> Signal<[Int32: S for i in 0 ..< views.count { if i < ids.count { - parsedViews[ids[i]] = _internal_parseApiStoryViews(transaction: transaction, views: views[i]) + parsedViews[ids[i]] = Stories.Item.Views(apiViews: views[i]) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 4593eb9c47..ad4d0d552d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -5,735 +5,329 @@ import SwiftSignalKit enum InternalStoryUpdate { case deleted(peerId: PeerId, id: Int32) - case added(peerId: PeerId, item: StoryListContext.Item) + case added(peerId: PeerId, item: Stories.StoredItem) case read(peerId: PeerId, maxId: Int32) } -public final class StoryListContext { - public enum Scope { - case all - case peer(EnginePeer.Id) - } - - public struct Views: Equatable { - public var seenCount: Int - public var seenPeers: [EnginePeer] +public final class EngineStoryItem: Equatable { + public final class Views: Equatable { + public let seenCount: Int + public let seenPeers: [EnginePeer] public init(seenCount: Int, seenPeers: [EnginePeer]) { self.seenCount = seenCount self.seenPeers = seenPeers } - } - - public final class Item: Equatable { - public let id: Int32 - public let timestamp: Int32 - public let media: EngineMedia - public let text: String - public let entities: [MessageTextEntity] - public let views: Views? - public let privacy: EngineStoryPrivacy? - public init(id: Int32, timestamp: Int32, media: EngineMedia, text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?) { - self.id = id - self.timestamp = timestamp - self.media = media - self.text = text - self.entities = entities - self.views = views - self.privacy = privacy - } - - public static func ==(lhs: Item, rhs: Item) -> Bool { - if lhs.id != rhs.id { + public static func ==(lhs: Views, rhs: Views) -> Bool { + if lhs.seenCount != rhs.seenCount { return false } - if lhs.timestamp != rhs.timestamp { - return false - } - if lhs.media != rhs.media { - return false - } - if lhs.text != rhs.text { - return false - } - if lhs.entities != rhs.entities { - return false - } - if lhs.views != rhs.views { - return false - } - if lhs.privacy != rhs.privacy { + if lhs.seenPeers != rhs.seenPeers { return false } return true } } - public final class PeerItemSet: Equatable { - public let peerId: EnginePeer.Id - public let peer: EnginePeer? - public var maxReadId: Int32 - public fileprivate(set) var items: [Item] - public fileprivate(set) var totalCount: Int? - - public init(peerId: EnginePeer.Id, peer: EnginePeer?, maxReadId: Int32, items: [Item], totalCount: Int?) { - self.peerId = peerId - self.peer = peer - self.maxReadId = maxReadId - self.items = items - self.totalCount = totalCount - } - - public static func ==(lhs: PeerItemSet, rhs: PeerItemSet) -> Bool { - if lhs.peerId != rhs.peerId { - return false - } - if lhs.peer != rhs.peer { - return false - } - if lhs.maxReadId != rhs.maxReadId { - return false - } - if lhs.items != rhs.items { - return false - } - if lhs.totalCount != rhs.totalCount { - return false - } - return true - } + public let id: Int32 + public let timestamp: Int32 + public let media: EngineMedia + public let text: String + public let entities: [MessageTextEntity] + public let views: Views? + public let privacy: EngineStoryPrivacy? + + public init(id: Int32, timestamp: Int32, media: EngineMedia, text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?) { + self.id = id + self.timestamp = timestamp + self.media = media + self.text = text + self.entities = entities + self.views = views + self.privacy = privacy } - public final class LoadMoreToken: Equatable { - fileprivate let value: String? - - init(value: String?) { - self.value = value + public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool { + if lhs.id != rhs.id { + return false } - - public static func ==(lhs: LoadMoreToken, rhs: LoadMoreToken) -> Bool { - if lhs.value != rhs.value { - return false - } - return true + if lhs.timestamp != rhs.timestamp { + return false } + if lhs.media != rhs.media { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.entities != rhs.entities { + return false + } + if lhs.views != rhs.views { + return false + } + if lhs.privacy != rhs.privacy { + return false + } + return true + } +} + +public final class StorySubscriptionsContext { + private enum OpaqueStateMark: Equatable { + case empty + case value(String) } - public struct State: Equatable { - public var itemSets: [PeerItemSet] - public var uploadProgress: CGFloat? - public var loadMoreToken: LoadMoreToken? - - public init(itemSets: [PeerItemSet], uploadProgress: CGFloat?, loadMoreToken: LoadMoreToken?) { - self.itemSets = itemSets - self.uploadProgress = uploadProgress - self.loadMoreToken = loadMoreToken - } - } - - private final class UploadContext { - let disposable = MetaDisposable() - - init() { - } + private struct TaskState { + var isRefreshScheduled: Bool = false + var isLoadMoreScheduled: Bool = false } private final class Impl { + private let accountPeerId: PeerId private let queue: Queue - private let account: Account - private let scope: Scope + private let postbox: Postbox + private let network: Network + private var taskState = TaskState() + + private var isLoading: Bool = false + + private var loadedStateMark: OpaqueStateMark? + private var stateDisposable: Disposable? private let loadMoreDisposable = MetaDisposable() - private var isLoadingMore = false + private let refreshTimerDisposable = MetaDisposable() - private var pollDisposable: Disposable? - private var updatesDisposable: Disposable? - private var peerDisposables: [PeerId: Disposable] = [:] - - private var uploadContexts: [UploadContext] = [] { - didSet { - self.stateValue.uploadProgress = self.uploadContexts.isEmpty ? nil : 0.0 - } - } - - private var stateValue: State { - didSet { - self.state.set(.single(self.stateValue)) - } - } - let state = Promise() - - init(queue: Queue, account: Account, scope: Scope) { + init(queue: Queue, accountPeerId: PeerId, postbox: Postbox, network: Network) { + self.accountPeerId = accountPeerId self.queue = queue - self.account = account - self.scope = scope + self.postbox = postbox + self.network = network - self.stateValue = State(itemSets: [], uploadProgress: nil, loadMoreToken: LoadMoreToken(value: nil)) - self.state.set(.single(self.stateValue)) + self.taskState.isRefreshScheduled = true - if case .all = scope { - let _ = (account.postbox.transaction { transaction -> Peer? in - return transaction.getPeer(account.peerId) - } - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let `self` = self, let peer = peer else { - return - } - self.stateValue = State(itemSets: [ - PeerItemSet(peerId: peer.id, peer: EnginePeer(peer), maxReadId: 0, items: [], totalCount: 0) - ], uploadProgress: nil, loadMoreToken: LoadMoreToken(value: nil)) - }) - } - - self.updatesDisposable = (account.stateManager.storyUpdates - |> deliverOn(queue)).start(next: { [weak self] updates in - if updates.isEmpty { - return - } - - let _ = account.postbox.transaction({ transaction -> [PeerId: Peer] in - var peers: [PeerId: Peer] = [:] - - if let peer = transaction.getPeer(account.peerId) { - peers[peer.id] = peer - } - - for update in updates { - switch update { - case let .added(peerId, _): - if peers[peerId] == nil, let peer = transaction.getPeer(peerId) { - peers[peer.id] = peer - } - case .deleted: - break - case .read: - break - } - } - return peers - }).start(next: { peers in - guard let `self` = self else { - return - } - if self.isLoadingMore { - return - } - - var itemSets: [PeerItemSet] = self.stateValue.itemSets - - for update in updates { - switch update { - case let .deleted(peerId, id): - for i in 0 ..< itemSets.count { - if itemSets[i].peerId == peerId { - if let index = itemSets[i].items.firstIndex(where: { $0.id == id }) { - var items = itemSets[i].items - items.remove(at: index) - itemSets[i] = PeerItemSet( - peerId: itemSets[i].peerId, - peer: itemSets[i].peer, - maxReadId: itemSets[i].maxReadId, - items: items, - totalCount: items.count - ) - } - } - } - case let .added(peerId, item): - var found = false - for i in 0 ..< itemSets.count { - if itemSets[i].peerId == peerId { - found = true - - var items = itemSets[i].items - if let index = items.firstIndex(where: { $0.id == item.id }) { - items.remove(at: index) - } - - items.append(item) - - items.sort(by: { lhsItem, rhsItem in - if lhsItem.timestamp != rhsItem.timestamp { - switch scope { - case .all: - return lhsItem.timestamp < rhsItem.timestamp - case .peer: - return lhsItem.timestamp < rhsItem.timestamp - } - } - return lhsItem.id < rhsItem.id - }) - itemSets[i] = PeerItemSet( - peerId: itemSets[i].peerId, - peer: itemSets[i].peer, - maxReadId: itemSets[i].maxReadId, - items: items, - totalCount: items.count - ) - } - } - if !found, let peer = peers[peerId] { - let matchesScope: Bool - if case .all = scope { - matchesScope = true - } else if case .peer(peerId) = scope { - matchesScope = true - } else { - matchesScope = false - } - if matchesScope { - itemSets.insert(PeerItemSet( - peerId: peerId, - peer: EnginePeer(peer), - maxReadId: 0, - items: [item], - totalCount: 1 - ), at: 0) - } - } - case let .read(peerId, maxId): - for i in 0 ..< itemSets.count { - if itemSets[i].peerId == peerId { - let items = itemSets[i].items - itemSets[i] = PeerItemSet( - peerId: itemSets[i].peerId, - peer: itemSets[i].peer, - maxReadId: max(itemSets[i].maxReadId, maxId), - items: items, - totalCount: items.count - ) - } - } - } - } - - itemSets.sort(by: { lhs, rhs in - guard let lhsItem = lhs.items.last, let rhsItem = rhs.items.last else { - if lhs.items.first != nil { - return false - } else { - return true - } - } - - if lhsItem.timestamp != rhsItem.timestamp { - switch scope { - case .all: - return lhsItem.timestamp > rhsItem.timestamp - case .peer: - return lhsItem.timestamp < rhsItem.timestamp - } - } - return lhsItem.id > rhsItem.id - }) - - if !itemSets.contains(where: { $0.peerId == self.account.peerId }) { - if let peer = peers[self.account.peerId] { - itemSets.insert(PeerItemSet(peerId: peer.id, peer: EnginePeer(peer), maxReadId: 0, items: [], totalCount: 0), at: 0) - } - } - - self.stateValue.itemSets = itemSets - }) - }) - - self.loadMore(refresh: true) + self.updateTasks() } deinit { + self.stateDisposable?.dispose() self.loadMoreDisposable.dispose() - self.pollDisposable?.dispose() - for (_, disposable) in self.peerDisposables { - disposable.dispose() - } + self.refreshTimerDisposable.dispose() } - func loadPeer(id: EnginePeer.Id) { - if self.peerDisposables[id] == nil { - let disposable = MetaDisposable() - self.peerDisposables[id] = disposable - - let account = self.account - let queue = self.queue - - disposable.set((self.account.postbox.transaction { transaction -> Api.InputUser? in - return transaction.getPeer(id).flatMap(apiInputUser) - } - |> mapToSignal { inputPeer -> Signal in - guard let inputPeer = inputPeer else { - return .single(nil) - } - return account.network.request(Api.functions.stories.getUserStories(flags: 0, userId: inputPeer, offsetId: 0, limit: 30)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { stories -> Signal in - guard let stories = stories else { - return .single(nil) - } - return account.postbox.transaction { transaction -> PeerItemSet? in - switch stories { - case let .stories(_, apiStories, users): - var parsedItemSets: [PeerItemSet] = [] - - var peers: [Peer] = [] - var peerPresences: [PeerId: Api.User] = [:] - - for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - peerPresences[telegramUser.id] = user - } - - updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in - return updated - }) - updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) - - let peerId = id - - for apiStory in apiStories { - if let item = _internal_parseApiStoryItem(transaction: transaction, peerId: peerId, apiStory: apiStory) { - if !parsedItemSets.isEmpty && parsedItemSets[parsedItemSets.count - 1].peerId == peerId { - parsedItemSets[parsedItemSets.count - 1].items.append(item) - parsedItemSets[parsedItemSets.count - 1].totalCount = parsedItemSets[parsedItemSets.count - 1].items.count - } else { - parsedItemSets.append(StoryListContext.PeerItemSet(peerId: peerId, peer: transaction.getPeer(peerId).flatMap(EnginePeer.init), maxReadId: 0, items: [item], totalCount: 1)) - } - } - } - - return parsedItemSets.first - } - } - } - } - |> deliverOn(queue)).start(next: { [weak self] itemSet in - guard let `self` = self, let itemSet = itemSet else { - return - } - var itemSets = self.stateValue.itemSets - if let index = itemSets.firstIndex(where: { $0.peerId == id }) { - itemSets[index] = itemSet - } else { - itemSets.append(itemSet) - } - self.stateValue.itemSets = itemSets - })) - } + func loadMore() { + self.taskState.isLoadMoreScheduled = true + self.updateTasks() } - func upload(media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], privacy: EngineStoryPrivacy) { - let uploadContext = UploadContext() - self.uploadContexts.append(uploadContext) - uploadContext.disposable.set((_internal_uploadStory(account: self.account, media: media, text: text, entities: entities, privacy: privacy) - |> deliverOn(self.queue)).start(next: { _ in - }, completed: { [weak self, weak uploadContext] in - guard let `self` = self, let uploadContext = uploadContext else { - return - } - if let index = self.uploadContexts.firstIndex(where: { $0 === uploadContext }) { - self.uploadContexts.remove(at: index) - } - })) - } - - func loadMore(refresh: Bool) { - if self.isLoadingMore { + private func updateTasks() { + if self.isLoading { return } - var effectiveLoadMoreToken: String? - if refresh { - effectiveLoadMoreToken = "" - } else if let loadMoreToken = self.stateValue.loadMoreToken { - effectiveLoadMoreToken = loadMoreToken.value ?? "" - } - guard let loadMoreToken = effectiveLoadMoreToken else { - return - } - let _ = loadMoreToken - - self.isLoadingMore = true - let account = self.account - let scope = self.scope - - self.pollDisposable?.dispose() - self.pollDisposable = nil - - switch scope { - case .all: - self.loadMoreDisposable.set((account.network.request(Api.functions.stories.getAllStories(flags: 0, state: nil)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { result -> Signal<([PeerItemSet], LoadMoreToken?), NoError> in - guard let result = result else { - return .single(([], nil)) - } - return account.postbox.transaction { transaction -> ([PeerItemSet], LoadMoreToken?) in - switch result { - case let .allStories(_, state, userStorySets, users): - let _ = state - var parsedItemSets: [PeerItemSet] = [] - - var peers: [Peer] = [] - var peerPresences: [PeerId: Api.User] = [:] - - for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - peerPresences[telegramUser.id] = user - } - - updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in - return updated - }) - updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) - - for userStories in userStorySets { - let apiUserId: Int64 - let apiStories: [Api.StoryItem] - var apiTotalCount: Int32? - var apiMaxReadId: Int32 = 0 - switch userStories { - case let .userStories(_, userId, maxReadId, stories): - apiUserId = userId - apiStories = stories - apiTotalCount = Int32(stories.count) - apiMaxReadId = maxReadId ?? 0 - } - - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(apiUserId)) - for apiStory in apiStories { - if let item = _internal_parseApiStoryItem(transaction: transaction, peerId: peerId, apiStory: apiStory) { - if !parsedItemSets.isEmpty && parsedItemSets[parsedItemSets.count - 1].peerId == peerId { - parsedItemSets[parsedItemSets.count - 1].items.append(item) - } else { - parsedItemSets.append(StoryListContext.PeerItemSet( - peerId: peerId, - peer: transaction.getPeer(peerId).flatMap(EnginePeer.init), - maxReadId: apiMaxReadId, - items: [item], - totalCount: apiTotalCount.flatMap(Int.init) - )) - } - } - } - } - - if !parsedItemSets.contains(where: { $0.peerId == account.peerId }) { - if let peer = transaction.getPeer(account.peerId) { - parsedItemSets.insert(PeerItemSet(peerId: peer.id, peer: EnginePeer(peer), maxReadId: 0, items: [], totalCount: 0), at: 0) - } - } - - return (parsedItemSets, nil) - case .allStoriesNotModified: - return ([], nil) - } - } - } - |> deliverOn(self.queue)).start(next: { [weak self] result in + if self.taskState.isRefreshScheduled { + self.isLoading = true + + self.stateDisposable = (postbox.combinedView(keys: [PostboxViewKey.storiesState(key: .subscriptions)]) + |> take(1) + |> deliverOn(self.queue)).start(next: { [weak self] views in guard let `self` = self else { return } - self.isLoadingMore = false - - var itemSets = self.stateValue.itemSets - for itemSet in result.0 { - if let index = itemSets.firstIndex(where: { $0.peerId == itemSet.peerId }) { - let currentItemSet = itemSets[index] - - var items = currentItemSet.items - for item in itemSet.items { - if !items.contains(where: { $0.id == item.id }) { - items.append(item) - } - } - - items.sort(by: { lhsItem, rhsItem in - if lhsItem.timestamp != rhsItem.timestamp { - switch scope { - case .all: - return lhsItem.timestamp < rhsItem.timestamp - case .peer: - return lhsItem.timestamp < rhsItem.timestamp - } - } - return lhsItem.id < rhsItem.id - }) - - itemSets[index] = PeerItemSet( - peerId: itemSet.peerId, - peer: itemSet.peer, - maxReadId: itemSet.maxReadId, - items: items, - totalCount: items.count - ) - } else { - itemSet.items.sort(by: { lhsItem, rhsItem in - if lhsItem.timestamp != rhsItem.timestamp { - switch scope { - case .all: - return lhsItem.timestamp < rhsItem.timestamp - case .peer: - return lhsItem.timestamp < rhsItem.timestamp - } - } - return lhsItem.id < rhsItem.id - }) - itemSets.append(itemSet) - } + guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else { + return } - itemSets.sort(by: { lhs, rhs in - guard let lhsItem = lhs.items.last, let rhsItem = rhs.items.last else { - if lhs.items.last != nil { - return false - } else { - return true - } + let stateMark: OpaqueStateMark + if let subscriptionsState = storiesStateView.value?.get(Stories.SubscriptionsState.self) { + stateMark = .value(subscriptionsState.opaqueState) + } else { + stateMark = .empty + } + + self.loadImpl(isRefresh: true, stateMark: stateMark) + }) + } else if self.taskState.isLoadMoreScheduled { + self.isLoading = true + + self.stateDisposable = (postbox.combinedView(keys: [PostboxViewKey.storiesState(key: .subscriptions)]) + |> take(1) + |> deliverOn(self.queue)).start(next: { [weak self] views in + guard let `self` = self else { + return + } + guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else { + return + } + + let hasMore: Bool + let stateMark: OpaqueStateMark + if let subscriptionsState = storiesStateView.value?.get(Stories.SubscriptionsState.self) { + hasMore = subscriptionsState.hasMore + stateMark = .value(subscriptionsState.opaqueState) + } else { + stateMark = .empty + hasMore = true + } + + if hasMore && self.loadedStateMark != stateMark { + self.loadImpl(isRefresh: false, stateMark: stateMark) + } else { + self.isLoading = false + self.taskState.isLoadMoreScheduled = false + self.updateTasks() + } + }) + } + } + + private func loadImpl(isRefresh: Bool, stateMark: OpaqueStateMark) { + var flags: Int32 = 0 + var state: String? + switch stateMark { + case .empty: + break + case let .value(value): + state = value + flags |= 1 << 0 + + if !isRefresh { + flags |= 1 << 1 + } + } + + let accountPeerId = self.accountPeerId + + self.loadMoreDisposable.set((self.network.request(Api.functions.stories.getAllStories(flags: flags, state: state)) + |> deliverOn(self.queue)).start(next: { [weak self] result in + guard let self else { + return + } + + let _ = (self.postbox.transaction { transaction -> Void in + switch result { + case let .allStoriesNotModified(state): + self.loadedStateMark = .value(state) + let (currentStateValue, _) = transaction.getAllStorySubscriptions() + let currentState = currentStateValue.flatMap { $0.get(Stories.SubscriptionsState.self) } + + var hasMore = false + if let currentState = currentState { + hasMore = currentState.hasMore } - if lhsItem.timestamp != rhsItem.timestamp { - switch scope { - case .all: - return lhsItem.timestamp > rhsItem.timestamp - case .peer: - return lhsItem.timestamp < rhsItem.timestamp - } + transaction.setSubscriptionsStoriesState(state: CodableEntry(Stories.SubscriptionsState( + opaqueState: state, + refreshId: currentState?.refreshId ?? UInt64.random(in: 0 ... UInt64.max), + hasMore: hasMore + ))) + case let .allStories(flags, _, state, userStories, users): + //TODO:count + + var peers: [Peer] = [] + var peerPresences: [PeerId: Api.User] = [:] + + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + peerPresences[telegramUser.id] = user } - return lhsItem.id > rhsItem.id - }) - - self.stateValue = State(itemSets: itemSets, uploadProgress: self.stateValue.uploadProgress, loadMoreToken: result.1) - })) - case let .peer(peerId): - let account = self.account - let queue = self.queue - - self.loadMoreDisposable.set((self.account.postbox.transaction { transaction -> Api.InputUser? in - return transaction.getPeer(peerId).flatMap(apiInputUser) - } - |> mapToSignal { inputPeer -> Signal in - guard let inputPeer = inputPeer else { - return .single(nil) - } - return account.network.request(Api.functions.stories.getUserStories(flags: 0, userId: inputPeer, offsetId: 0, limit: 30)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { stories -> Signal in - guard let stories = stories else { - return .single(nil) - } - return account.postbox.transaction { transaction -> PeerItemSet? in - switch stories { - case let .stories(_, apiStories, users): - var parsedItemSets: [PeerItemSet] = [] + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) + + let hasMore: Bool = (flags & (1 << 0)) != 0 + + let (_, currentPeerItems) = transaction.getAllStorySubscriptions() + var peerEntries: [PeerId] = [] + + for userStorySet in userStories { + switch userStorySet { + case let .userStories(_, userId, maxReadId, stories): + let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) - var peers: [Peer] = [] - var peerPresences: [PeerId: Api.User] = [:] + let previousPeerEntries: [StoryItemsTableEntry] = transaction.getStoryItems(peerId: peerId) - for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - peerPresences[telegramUser.id] = user - } - - updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in - return updated - }) - updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) - - for apiStory in apiStories { - if let item = _internal_parseApiStoryItem(transaction: transaction, peerId: peerId, apiStory: apiStory) { - if !parsedItemSets.isEmpty && parsedItemSets[parsedItemSets.count - 1].peerId == peerId { - parsedItemSets[parsedItemSets.count - 1].items.append(item) - parsedItemSets[parsedItemSets.count - 1].totalCount = parsedItemSets[parsedItemSets.count - 1].items.count + var updatedPeerEntries: [StoryItemsTableEntry] = [] + for story in stories { + if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peerId, transaction: transaction) { + if case .placeholder = storedItem, let previousEntry = previousPeerEntries.first(where: { $0.id == storedItem.id }) { + updatedPeerEntries.append(previousEntry) } else { - parsedItemSets.append(StoryListContext.PeerItemSet(peerId: peerId, peer: transaction.getPeer(peerId).flatMap(EnginePeer.init), maxReadId: 0, items: [item], totalCount: 1)) + if let codedEntry = CodableEntry(storedItem) { + updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id)) + } } } } - return parsedItemSets.first + peerEntries.append(peerId) + + transaction.setStoryItems(peerId: peerId, items: updatedPeerEntries) + transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState( + subscriptionsOpaqueState: state, + maxReadId: maxReadId ?? 0 + ))) } } + + if !isRefresh { + let leftPeerIds = currentPeerItems.filter({ !peerEntries.contains($0) }) + if !leftPeerIds.isEmpty { + peerEntries = leftPeerIds + peerEntries + } + } + + transaction.replaceAllStorySubscriptions(state: CodableEntry(Stories.SubscriptionsState( + opaqueState: state, + refreshId: UInt64.random(in: 0 ... UInt64.max), + hasMore: hasMore + )), peerIds: peerEntries) } } - |> deliverOn(queue)).start(next: { [weak self] itemSet in - guard let `self` = self, let itemSet = itemSet else { + |> deliverOn(self.queue)).start(completed: { [weak self] in + guard let `self` = self else { return } - self.isLoadingMore = false - self.stateValue.itemSets = [itemSet] - })) - } - } - - func delete(id: Int32) { - let _ = _internal_deleteStory(account: self.account, id: id).start() - - var itemSets: [PeerItemSet] = self.stateValue.itemSets - for i in (0 ..< itemSets.count).reversed() { - if let index = itemSets[i].items.firstIndex(where: { $0.id == id }) { - var items = itemSets[i].items - items.remove(at: index) - if items.isEmpty { - itemSets.remove(at: i) + + self.isLoading = false + if isRefresh { + self.taskState.isRefreshScheduled = false + self.refreshTimerDisposable.set((Signal.complete() + |> suspendAwareDelay(60.0, queue: self.queue)).start(completed: { [weak self] in + guard let `self` = self else { + return + } + self.taskState.isRefreshScheduled = true + self.updateTasks() + })) } else { - itemSets[i] = PeerItemSet( - peerId: itemSets[i].peerId, - peer: itemSets[i].peer, - maxReadId: itemSets[i].maxReadId, - items: items, - totalCount: items.count - ) + self.taskState.isLoadMoreScheduled = false } - } - } - self.stateValue.itemSets = itemSets + + self.updateTasks() + }) + })) } } - private let queue: Queue + private let queue = Queue(name: "StorySubscriptionsContext") private let impl: QueueLocalObject - public var state: Signal { - return self.impl.signalWith { impl, subscriber in - return impl.state.get().start(next: subscriber.putNext) - } - } - - init(account: Account, scope: Scope) { - let queue = Queue.mainQueue() - self.queue = queue + init(accountPeerId: PeerId, postbox: Postbox, network: Network) { + let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, account: account, scope: scope) + Impl(queue: queue, accountPeerId: accountPeerId, postbox: postbox, network: network) }) } - public func delete(id: Int32) { + public func loadMore() { self.impl.with { impl in - impl.delete(id: id) - } - } - - public func upload(media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], privacy: EngineStoryPrivacy) { - self.impl.with { impl in - impl.upload(media: media, text: text, entities: entities, privacy: privacy) - } - } - - public func loadPeer(id: EnginePeer.Id) { - self.impl.with { impl in - impl.loadPeer(id: id) + impl.loadMore() } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 8073859ad6..53c7eba5e8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -7,6 +7,27 @@ public enum EngineOutgoingMessageContent { case text(String) } +public final class StoryPreloadInfo { + public enum Priority: Comparable { + case top(position: Int) + case next(position: Int) + } + + public let resource: MediaResourceReference + public let size: Int32? + public let priority: Priority + + public init( + resource: MediaResourceReference, + size: Int32?, + priority: Priority + ) { + self.resource = resource + self.size = size + self.priority = priority + } +} + public extension TelegramEngine { final class Messages { private let account: Account @@ -569,12 +590,251 @@ public extension TelegramEngine { }).start() } - public func allStories() -> StoryListContext { - return StoryListContext(account: self.account, scope: .all) + public func storySubscriptions() -> Signal { + let basicPeerKey = PostboxViewKey.basicPeer(self.account.peerId) + return self.account.postbox.combinedView(keys: [ + basicPeerKey, + PostboxViewKey.storySubscriptions, + PostboxViewKey.storiesState(key: .subscriptions) + ]) + |> mapToSignal { views -> Signal in + guard let basicPeerView = views.views[basicPeerKey] as? BasicPeerView, let accountPeer = basicPeerView.peer else { + return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil)) + } + guard let storySubscriptionsView = views.views[PostboxViewKey.storySubscriptions] as? StorySubscriptionsView else { + return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil)) + } + guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else { + return .single(EngineStorySubscriptions(accountItem: nil, items: [], hasMoreToken: nil)) + } + + var additionalDataKeys: [PostboxViewKey] = [] + + additionalDataKeys.append(PostboxViewKey.storyItems(peerId: self.account.peerId)) + additionalDataKeys.append(PostboxViewKey.storiesState(key: .peer(self.account.peerId))) + + let subscriptionPeerIds = storySubscriptionsView.peerIds.filter { $0 != self.account.peerId } + + additionalDataKeys.append(contentsOf: subscriptionPeerIds.map { peerId -> PostboxViewKey in + return PostboxViewKey.storyItems(peerId: peerId) + }) + additionalDataKeys.append(contentsOf: subscriptionPeerIds.map { peerId -> PostboxViewKey in + return PostboxViewKey.storiesState(key: .peer(peerId)) + }) + additionalDataKeys.append(contentsOf: subscriptionPeerIds.map { peerId -> PostboxViewKey in + return PostboxViewKey.basicPeer(peerId) + }) + + return self.account.postbox.combinedView(keys: additionalDataKeys) + |> map { views -> EngineStorySubscriptions in + let _ = accountPeer + + var hasMoreToken: String? + if let subscriptionsState = storiesStateView.value?.get(Stories.SubscriptionsState.self) { + if subscriptionsState.hasMore { + hasMoreToken = subscriptionsState.opaqueState + "_\(subscriptionsState.refreshId)" + } else { + hasMoreToken = nil + } + } else { + hasMoreToken = "" + } + + var accountItem: EngineStorySubscriptions.Item = EngineStorySubscriptions.Item( + peer: EnginePeer(accountPeer), + hasUnseen: false, + storyCount: 0, + lastTimestamp: 0 + ) + + var items: [EngineStorySubscriptions.Item] = [] + + do { + let peerId = self.account.peerId + + if let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView, let stateView = views.views[PostboxViewKey.storiesState(key: .peer(peerId))] as? StoryStatesView { + if let lastEntry = itemsView.items.last?.value.get(Stories.StoredItem.self) { + let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) + var hasUnseen = false + if let peerState = peerState { + hasUnseen = peerState.maxReadId < lastEntry.id + } + + let item = EngineStorySubscriptions.Item( + peer: EnginePeer(accountPeer), + hasUnseen: hasUnseen, + storyCount: itemsView.items.count, + lastTimestamp: lastEntry.timestamp + ) + accountItem = item + } + } + } + + for peerId in subscriptionPeerIds { + guard let peerView = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView else { + continue + } + guard let peer = peerView.peer else { + continue + } + guard let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView else { + continue + } + guard let stateView = views.views[PostboxViewKey.storiesState(key: .peer(peerId))] as? StoryStatesView else { + continue + } + guard let lastEntry = itemsView.items.last?.value.get(Stories.StoredItem.self) else { + continue + } + + let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) + var hasUnseen = false + if let peerState = peerState { + hasUnseen = peerState.maxReadId < lastEntry.id + } + + let item = EngineStorySubscriptions.Item( + peer: EnginePeer(peer), + hasUnseen: hasUnseen, + storyCount: itemsView.items.count, + lastTimestamp: lastEntry.timestamp + ) + + if peerId == accountPeer.id { + accountItem = item + } else { + items.append(item) + } + } + + items.sort(by: { lhs, rhs in + return lhs.lastTimestamp > rhs.lastTimestamp + }) + + return EngineStorySubscriptions(accountItem: accountItem, items: items, hasMoreToken: hasMoreToken) + } + } } - public func peerStories(id: EnginePeer.Id) -> StoryListContext { - return StoryListContext(account: self.account, scope: .peer(id)) + public func preloadStorySubscriptions() -> Signal<[EngineMediaResource.Id: StoryPreloadInfo], NoError> { + let basicPeerKey = PostboxViewKey.basicPeer(self.account.peerId) + return self.account.postbox.combinedView(keys: [ + basicPeerKey, + PostboxViewKey.storySubscriptions, + PostboxViewKey.storiesState(key: .subscriptions) + ]) + |> mapToSignal { views -> Signal<[EngineMediaResource.Id: StoryPreloadInfo], NoError> in + guard let basicPeerView = views.views[basicPeerKey] as? BasicPeerView, let accountPeer = basicPeerView.peer else { + return .single([:]) + } + guard let storySubscriptionsView = views.views[PostboxViewKey.storySubscriptions] as? StorySubscriptionsView else { + return .single([:]) + } + guard let storiesStateView = views.views[PostboxViewKey.storiesState(key: .subscriptions)] as? StoryStatesView else { + return .single([:]) + } + + var additionalDataKeys: [PostboxViewKey] = [] + additionalDataKeys.append(contentsOf: storySubscriptionsView.peerIds.map { peerId -> PostboxViewKey in + return PostboxViewKey.storyItems(peerId: peerId) + }) + additionalDataKeys.append(contentsOf: storySubscriptionsView.peerIds.map { peerId -> PostboxViewKey in + return PostboxViewKey.storiesState(key: .peer(peerId)) + }) + additionalDataKeys.append(contentsOf: storySubscriptionsView.peerIds.map { peerId -> PostboxViewKey in + return PostboxViewKey.basicPeer(peerId) + }) + + return self.account.postbox.combinedView(keys: additionalDataKeys) + |> map { views -> [EngineMediaResource.Id: StoryPreloadInfo] in + let _ = accountPeer + let _ = storySubscriptionsView + let _ = storiesStateView + + var nextPriority: Int = 0 + + var resultResources: [EngineMediaResource.Id: StoryPreloadInfo] = [:] + + for peerId in storySubscriptionsView.peerIds { + guard let peerView = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView else { + continue + } + guard let peer = peerView.peer else { + continue + } + guard let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView else { + continue + } + guard let stateView = views.views[PostboxViewKey.storiesState(key: .peer(peerId))] as? StoryStatesView else { + continue + } + + var nextItem: Stories.StoredItem? = itemsView.items.first?.value.get(Stories.StoredItem.self) + + let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) + if let peerState = peerState { + if let item = itemsView.items.first(where: { $0.id >= peerState.maxReadId }) { + nextItem = item.value.get(Stories.StoredItem.self) + } + } + + if let peerReference = PeerReference(peer) { + if let nextItem = nextItem, case let .item(item) = nextItem, let media = item.media { + if let image = media as? TelegramMediaImage, let resource = image.representations.last?.resource { + let resource = MediaResourceReference.media(media: .story(peer: peerReference, id: item.id, media: media), resource: resource) + resultResources[EngineMediaResource.Id(resource.resource.id)] = StoryPreloadInfo( + resource: resource, + size: nil, + priority: .top(position: nextPriority) + ) + nextPriority += 1 + } else if let file = media as? TelegramMediaFile { + if let preview = file.previewRepresentations.last { + let resource = MediaResourceReference.media(media: .story(peer: peerReference, id: item.id, media: file), resource: preview.resource) + resultResources[EngineMediaResource.Id(resource.resource.id)] = StoryPreloadInfo( + resource: resource, + size: nil, + priority: .top(position: nextPriority) + ) + nextPriority += 1 + } + + let resource = MediaResourceReference.media(media: .story(peer: peerReference, id: item.id, media: file), resource: file.resource) + resultResources[EngineMediaResource.Id(resource.resource.id)] = StoryPreloadInfo( + resource: resource, + size: file.preloadSize, + priority: .top(position: nextPriority) + ) + nextPriority += 1 + } + } + } + } + + return resultResources + } + } + } + + public func refreshStories(peerId: EnginePeer.Id, ids: [Int32]) -> Signal { + return _internal_getStoriesById(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, peerId: peerId, ids: ids) + |> mapToSignal { result -> Signal in + return self.account.postbox.transaction { transaction -> Void in + var currentItems = transaction.getStoryItems(peerId: peerId) + for i in 0 ..< currentItems.count { + if let updatedItem = result.first(where: { $0.id == currentItems[i].id }) { + if case .item = updatedItem { + if let entry = CodableEntry(updatedItem) { + currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id) + } + } + } + } + transaction.setStoryItems(peerId: peerId, items: currentItems) + } + |> ignoreValues + } } public func uploadStory(media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], privacy: EngineStoryPrivacy) -> Signal { diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index e306922d14..741f201b36 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -285,7 +285,7 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil return .file(performer) } } - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if file.isAnimated { result = .animation } else { diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 1c7581b5ea..f514957521 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -235,7 +235,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } else { for attribute in file.attributes { switch attribute { - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { type = .round } else { diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift index 7230dff4cd..456b798e2f 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift @@ -214,7 +214,7 @@ final class ShutterBlobView: MTKView, MTKViewDelegate { self.colorPixelFormat = .bgra8Unorm self.framebufferOnly = true - self.presentsWithTransaction = true + //self.presentsWithTransaction = true self.isPaused = true self.delegate = self @@ -294,25 +294,19 @@ final class ShutterBlobView: MTKView, MTKViewDelegate { private func tick() { self.updateAnimations() - autoreleasepool { - self.draw(in: self) - } + self.draw() } - - override func layoutSubviews() { - super.layoutSubviews() - - self.tick() + + override public func draw(_ rect: CGRect) { + self.redraw(drawable: self.currentDrawable!) } - - func draw(in view: MTKView) { + + private func redraw(drawable: MTLDrawable) { guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { return } - - guard let renderPassDescriptor = self.currentRenderPassDescriptor else { - return - } + + let renderPassDescriptor = self.currentRenderPassDescriptor! renderPassDescriptor.colorAttachments[0].loadAction = .clear renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0.0) @@ -355,13 +349,18 @@ final class ShutterBlobView: MTKView, MTKViewDelegate { renderEncoder.setFragmentBytes(&secondaryParameters, length: MemoryLayout.size, index: 2) renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1) renderEncoder.endEncoding() - - if let currentDrawable = self.currentDrawable { - commandBuffer.present(currentDrawable) - } + + commandBuffer.present(drawable) commandBuffer.commit() - //commandBuffer.waitUntilScheduled() + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.tick() + } + + func draw(in view: MTKView) { - //self.currentDrawable?.present() } } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index aefd014290..152f2497b3 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -2946,7 +2946,7 @@ public func paneGifSearchForQuery(context: AccountContext, query: String, offset )) } } - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]) + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)]) references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result))) } case let .internalReference(internalReference): diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index b97ee5c3e4..76d8769390 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -745,9 +745,9 @@ public final class ChatListHeaderComponent: Component { return defaultResult } - public func updateStories(offset: CGFloat, context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, storyListState: StoryListContext.State?, transition: Transition) { + public func updateStories(offset: CGFloat, context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, storySubscriptions: EngineStorySubscriptions?, transition: Transition) { var storyOffsetFraction: CGFloat = 1.0 - if let storyListState, storyListState.itemSets.count > 1 { + if let storySubscriptions, !storySubscriptions.items.isEmpty { storyOffsetFraction = offset } @@ -770,7 +770,7 @@ public final class ChatListHeaderComponent: Component { context: context, theme: theme, strings: strings, - state: storyListState, + storySubscriptions: storySubscriptions, collapseFraction: 1.0 - offset, peerAction: { [weak self] peer in guard let self else { @@ -795,7 +795,7 @@ public final class ChatListHeaderComponent: Component { storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: storyPeerListPosition), size: CGSize(width: self.bounds.width, height: 94.0))) var storyListAlpha: CGFloat = 1.0 - if let storyListState, storyListState.itemSets.count > 1 { + if let storySubscriptions, !storySubscriptions.items.isEmpty { } else { storyListAlpha = offset } diff --git a/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift b/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift index 5b33cf7cbf..366662da63 100644 --- a/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift +++ b/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift @@ -212,7 +212,7 @@ public func legacyInstantVideoController(theme: PresentationTheme, panelFrame: C } } - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: Int(finalDuration), size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo])]) + let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: Int(finalDuration), size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil)]) var message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let scheduleTime: Int32? = scheduleTimestamp > 0 ? scheduleTimestamp : nil diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 1cdddad3ec..a61e8ad0d7 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -496,7 +496,7 @@ public final class MediaEditor { let trimRange = trimStart ..< trimEnd self.values = self.values.withUpdatedVideoTrimRange(trimRange) } - + public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) { self.values = self.values.withUpdatedDrawingAndEntities(drawing: image, entities: entities) } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index f36fbafd5b..4a3866b39e 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1259,16 +1259,30 @@ public final class MediaEditorScreen: ViewController { view.animateOut(to: .gallery) } let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view) - let destinatinoScale = destinationLocalFrame.width / self.previewContainerView.frame.width + let destinationScale = destinationLocalFrame.width / self.previewContainerView.frame.width let destinationAspectRatio = destinationLocalFrame.height / destinationLocalFrame.width var destinationSnapshotView: UIView? if let destinationNode = destinationView.asyncdisplaykit_node, destinationNode is AvatarNode, let snapshotView = destinationView.snapshotView(afterScreenUpdates: false) { destinationView.isHidden = true + snapshotView.layer.anchorPoint = CGPoint(x: 0.0, y: 0.5) let snapshotScale = self.previewContainerView.bounds.width / snapshotView.frame.width - snapshotView.center = CGPoint(x: self.previewContainerView.bounds.width / 2.0, y: self.previewContainerView.bounds.height / 2.0) - snapshotView.transform = CGAffineTransform(scaleX: snapshotScale, y: snapshotScale) + snapshotView.center = CGPoint(x: 0.0, y: self.previewContainerView.bounds.height / 2.0) + + let snapshotTransform = CATransform3DMakeScale(0.001, snapshotScale, 1.0) + //snapshotTransform.m34 = 1.0 / -500 + //snapshotTransform = CATransform3DRotate(snapshotTransform, -90.0 * .pi / 180.0, 0.0, 1.0, 0.0) + + let targetTransform = CATransform3DMakeScale(snapshotScale, snapshotScale, 1.0) + //snapshotTransform + //targetTransform = CATransform3DRotate(targetTransform, 0.0, 0.0, 1.0, 0.0) + + snapshotView.layer.transform = snapshotTransform + Queue.mainQueue().after(0.15) { + snapshotView.layer.transform = targetTransform + snapshotView.layer.animate(from: NSValue(caTransform3D: snapshotTransform), to: NSValue(caTransform3D: targetTransform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) + } self.previewContainerView.addSubview(snapshotView) destinationSnapshotView = snapshotView @@ -1279,7 +1293,7 @@ public final class MediaEditorScreen: ViewController { destinationSnapshotView?.removeFromSuperview() completion() }) - self.previewContainerView.layer.animateScale(from: 1.0, to: destinatinoScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.previewContainerView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.bounds.height - self.previewContainerView.bounds.width * destinationAspectRatio) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width * destinationAspectRatio)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) let targetCornerRadius: CGFloat @@ -1301,7 +1315,7 @@ public final class MediaEditorScreen: ViewController { if let componentView = self.componentHost.view { componentView.clipsToBounds = true componentView.layer.animatePosition(from: componentView.center, to: destinationLocalFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - componentView.layer.animateScale(from: 1.0, to: destinatinoScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + componentView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) componentView.layer.animateBounds(from: componentView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (componentView.bounds.height - componentView.bounds.width) / 2.0), size: CGSize(width: componentView.bounds.width, height: componentView.bounds.width)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) componentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) componentView.layer.animate( @@ -2032,6 +2046,9 @@ public final class MediaEditorScreen: ViewController { return } + let codableEntities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }.compactMap({ CodableDrawingEntity(entity: $0) }) + mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities) + let tempVideoPath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4" let saveToPhotos: (String, Bool) -> Void = { path, isVideo in PHPhotoLibrary.shared().performChanges({ diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 9fdbe2e22a..646e6008d9 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -659,8 +659,8 @@ public final class MessageInputPanelComponent: Component { transition.setPosition(view: reactionButtonView, position: reactionIconFrame.center) transition.setBounds(view: reactionButtonView, bounds: CGRect(origin: CGPoint(), size: reactionIconFrame.size)) - transition.setAlpha(view: reactionButtonView, alpha: (self.textFieldExternalState.hasText || hasMediaRecording || hasMediaEditing) ? 0.0 : 1.0) - transition.setScale(view: reactionButtonView, scale: (self.textFieldExternalState.hasText || hasMediaRecording || hasMediaEditing) ? 0.1 : 1.0) + transition.setAlpha(view: reactionButtonView, alpha: (self.textFieldExternalState.hasText || hasMediaRecording || hasMediaEditing || self.textFieldExternalState.isEditing) ? 0.0 : 1.0) + transition.setScale(view: reactionButtonView, scale: (self.textFieldExternalState.hasText || hasMediaRecording || hasMediaEditing || self.textFieldExternalState.isEditing) ? 0.1 : 1.0) fieldIconNextX -= reactionButtonSize.width + 2.0 } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 818d89db16..216011c30d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -81,7 +81,7 @@ private final class VisualMediaItem: SparseItemGrid.Item { } let localMonthTimestamp: Int32 let peer: PeerReference - let story: StoryListContext.Item + let story: EngineStoryItem override var id: AnyHashable { return AnyHashable(self.story.id) @@ -95,7 +95,7 @@ private final class VisualMediaItem: SparseItemGrid.Item { return VisualMediaHoleAnchor(index: self.index, storyId: self.story.id, localMonthTimestamp: self.localMonthTimestamp) } - init(index: Int, peer: PeerReference, story: StoryListContext.Item, localMonthTimestamp: Int32) { + init(index: Int, peer: PeerReference, story: EngineStoryItem, localMonthTimestamp: Int32) { self.indexValue = index self.peer = peer self.story = story @@ -738,7 +738,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { } } -public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { +/*public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { public enum ContentType { case photoOrVideo case photo @@ -815,7 +815,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private var animationTimer: SwiftSignalKit.Timer? public private(set) var calendarSource: SparseMessageCalendar? - private var listSource: StoryListContext + private var listSource: StorySubscriptionsContext public var openCurrentDate: (() -> Void)? public var paneDidScroll: (() -> Void)? @@ -848,7 +848,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr captureProtected: captureProtected ) - self.listSource = context.engine.messages.peerStories(id: self.peerId) //self.listSource = context.engine.messages.allStories() self.calendarSource = nil @@ -889,7 +888,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } strongSelf.chatControllerInteraction.toggleMessagesSelection([item.message.id], toggledValue)*/ } else { - let _ = (StoryChatContent.stories( + /*let _ = (StoryChatContent.stories( context: self.context, storyList: self.listSource, focusItem: item.story.id @@ -956,7 +955,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } ) navigationController.pushViewController(storyContainerScreen) - }) + })*/ //TODO:open //let _ = strongSelf.chatControllerInteraction.openMessage(item.message, .default) } @@ -1934,3 +1933,4 @@ private class MediaListSelectionRecognizer: UIPanGestureRecognizer { } } } +*/ diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index a332c19a96..ac84987c48 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -33,21 +33,18 @@ private final class StoryContainerScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext - let initialFocusedId: AnyHashable? - let initialContent: [StoryContentItemSlice] + let content: StoryContentContext let transitionIn: StoryContainerScreen.TransitionIn? let transitionOut: (EnginePeer.Id, AnyHashable) -> StoryContainerScreen.TransitionOut? init( context: AccountContext, - initialFocusedId: AnyHashable?, - initialContent: [StoryContentItemSlice], + content: StoryContentContext, transitionIn: StoryContainerScreen.TransitionIn?, transitionOut: @escaping (EnginePeer.Id, AnyHashable) -> StoryContainerScreen.TransitionOut? ) { self.context = context - self.initialFocusedId = initialFocusedId - self.initialContent = initialContent + self.content = content self.transitionIn = transitionIn self.transitionOut = transitionOut } @@ -56,6 +53,9 @@ private final class StoryContainerScreenComponent: Component { if lhs.context !== rhs.context { return false } + if lhs.content !== rhs.content { + return false + } return true } @@ -117,9 +117,9 @@ private final class StoryContainerScreenComponent: Component { private let backgroundLayer: SimpleLayer - private var focusedItemSet: AnyHashable? - private var itemSets: [StoryContentItemSlice] = [] - private var visibleItemSetViews: [AnyHashable: ItemSetView] = [:] + private var contentUpdatedDisposable: Disposable? + + private var visibleItemSetViews: [EnginePeer.Id: ItemSetView] = [:] private var itemSetPanState: ItemSetPanState? private var dismissPanState: ItemSetPanState? @@ -137,7 +137,7 @@ private final class StoryContainerScreenComponent: Component { self.layer.addSublayer(self.backgroundLayer) let horizontalPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in - guard let self, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else { + guard let self, let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else { return [] } if !itemSetComponentView.isPointInsideContentArea(point: self.convert(point, to: itemSetComponentView)) { @@ -148,7 +148,7 @@ private final class StoryContainerScreenComponent: Component { self.addGestureRecognizer(horizontalPanRecognizer) let verticalPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.dismissPanGesture(_:)), allowedDirections: { [weak self] point in - guard let self, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else { + guard let self, let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else { return [] } if !itemSetComponentView.isPointInsideContentArea(point: self.convert(point, to: itemSetComponentView)) { @@ -169,11 +169,12 @@ private final class StoryContainerScreenComponent: Component { } deinit { + self.contentUpdatedDisposable?.dispose() } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { - guard let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else { - return true + guard let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else { + return false } if !itemSetComponentView.isPointInsideContentArea(point: touch.location(in: itemSetComponentView)) { @@ -183,85 +184,102 @@ private final class StoryContainerScreenComponent: Component { return true } + private func beginHorizontalPan() { + self.layer.removeAnimation(forKey: "panState") + + if let itemSetPanState = self.itemSetPanState, !itemSetPanState.didBegin { + self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) + } else { + self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true) + self.state?.updated(transition: .immediate) + } + } + + private func updateHorizontalPan(translation: CGPoint) { + var translation = translation + + if var itemSetPanState = self.itemSetPanState, self.bounds.width > 0.0, let component = self.component, let stateValue = component.content.stateValue, let _ = stateValue.slice { + func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat { + let bandedOffset = offset - bandingStart + let range: CGFloat = 600.0 + let coefficient: CGFloat = 0.4 + return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range + } + + if translation.x > 0.0 && stateValue.previousSlice == nil { + translation.x = rubberBandingOffset(offset: translation.x, bandingStart: 0.0) + } else if translation.x < 0.0 && stateValue.nextSlice == nil { + translation.x = -rubberBandingOffset(offset: -translation.x, bandingStart: 0.0) + } + + var fraction = translation.x / self.bounds.width + fraction = -max(-1.0, min(1.0, fraction)) + + itemSetPanState.fraction = fraction + self.itemSetPanState = itemSetPanState + + self.state?.updated(transition: .immediate) + } + } + + private func commitHorizontalPan(velocity: CGPoint) { + if var itemSetPanState = self.itemSetPanState { + if let component = self.component, let stateValue = component.content.stateValue, let _ = stateValue.slice { + var direction: StoryContentContextNavigation.Direction? + if abs(velocity.x) > 10.0 { + if velocity.x < 0.0 { + if stateValue.nextSlice != nil { + direction = .next + } + } else { + if stateValue.previousSlice != nil { + direction = .previous + } + } + } + + if let direction { + component.content.navigate(navigation: .peer(direction)) + + if case .previous = direction { + itemSetPanState.fraction = 1.0 + itemSetPanState.fraction + } else { + itemSetPanState.fraction = itemSetPanState.fraction - 1.0 + } + self.itemSetPanState = itemSetPanState + self.state?.updated(transition: .immediate) + } + } + + itemSetPanState.fraction = 0.0 + self.itemSetPanState = itemSetPanState + + let transition = Transition(animation: .curve(duration: 0.4, curve: .spring)) + self.state?.updated(transition: transition) + + transition.attachAnimation(view: self, id: "panState", completion: { [weak self] completed in + guard let self, completed else { + return + } + self.itemSetPanState = nil + self.state?.updated(transition: .immediate) + + if let component = self.component { + component.content.resetSideStates() + } + }) + } + } + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .began: - self.layer.removeAnimation(forKey: "panState") - - if let itemSetPanState = self.itemSetPanState, !itemSetPanState.didBegin { - self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true) - self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) - } else { - self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true) - self.state?.updated(transition: .immediate) - } + self.beginHorizontalPan() case .changed: - if var itemSetPanState = self.itemSetPanState, self.bounds.width > 0.0, let focusedItemSet = self.focusedItemSet, let focusedIndex = self.itemSets.firstIndex(where: { $0.id == focusedItemSet }) { - var translation = recognizer.translation(in: self) - - func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat { - let bandedOffset = offset - bandingStart - let range: CGFloat = 600.0 - let coefficient: CGFloat = 0.4 - return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range - } - - if translation.x > 0.0 && focusedIndex == 0 { - translation.x = rubberBandingOffset(offset: translation.x, bandingStart: 0.0) - } else if translation.x < 0.0 && focusedIndex == self.itemSets.count - 1 { - translation.x = -rubberBandingOffset(offset: -translation.x, bandingStart: 0.0) - } - - var fraction = translation.x / self.bounds.width - fraction = -max(-1.0, min(1.0, fraction)) - - itemSetPanState.fraction = fraction - self.itemSetPanState = itemSetPanState - - self.state?.updated(transition: .immediate) - } + self.updateHorizontalPan(translation: recognizer.translation(in: self)) case .cancelled, .ended: - if var itemSetPanState = self.itemSetPanState { - if let focusedItemSet = self.focusedItemSet, let focusedIndex = self.itemSets.firstIndex(where: { $0.id == focusedItemSet }) { - let velocity = recognizer.velocity(in: self) - - var switchToIndex = focusedIndex - if abs(velocity.x) > 10.0 { - if velocity.x < 0.0 { - switchToIndex += 1 - } else { - switchToIndex -= 1 - } - } - - switchToIndex = max(0, min(switchToIndex, self.itemSets.count - 1)) - if switchToIndex != focusedIndex { - self.focusedItemSet = self.itemSets[switchToIndex].id - - if switchToIndex < focusedIndex { - itemSetPanState.fraction = 1.0 + itemSetPanState.fraction - } else { - itemSetPanState.fraction = itemSetPanState.fraction - 1.0 - } - self.itemSetPanState = itemSetPanState - self.state?.updated(transition: .immediate) - } - } - - itemSetPanState.fraction = 0.0 - self.itemSetPanState = itemSetPanState - - let transition = Transition(animation: .curve(duration: 0.4, curve: .spring)) - self.state?.updated(transition: transition) - - transition.attachAnimation(view: self, id: "panState", completion: { [weak self] completed in - guard let self, completed else { - return - } - self.itemSetPanState = nil - self.state?.updated(transition: .immediate) - }) - } + self.commitHorizontalPan(velocity: recognizer.velocity(in: self)) default: break } @@ -313,9 +331,12 @@ private final class StoryContainerScreenComponent: Component { if !subview.isUserInteractionEnabled || subview.isHidden || subview.alpha == 0.0 { continue } + if subview is ItemSetView { - if let result = subview.hitTest(point, with: event) { - return result + if self.itemSetPanState == nil { + if let result = subview.hitTest(point, with: event) { + return result + } } } else { if let result = subview.hitTest(self.convert(point, to: subview), with: event) { @@ -331,7 +352,7 @@ private final class StoryContainerScreenComponent: Component { if let transitionIn = self.component?.transitionIn, transitionIn.sourceView != nil { self.backgroundLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.28, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) - if let transitionIn = self.component?.transitionIn, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet] { + if let transitionIn = self.component?.transitionIn, let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] { if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { itemSetComponentView.animateIn(transitionIn: transitionIn) } @@ -348,7 +369,7 @@ private final class StoryContainerScreenComponent: Component { self.isAnimatingOut = true self.state?.updated(transition: .immediate) - if let component = self.component, let focusedItemSet = self.focusedItemSet, let peerId = focusedItemSet.base as? EnginePeer.Id, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View, let focusedItemId = itemSetComponentView.focusedItemId, let transitionOut = component.transitionOut(peerId, focusedItemId) { + if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View, let transitionOut = component.transitionOut(slice.peer.id, slice.item.id) { let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut)) transition.setAlpha(layer: self.backgroundLayer, alpha: 0.0) @@ -403,23 +424,27 @@ private final class StoryContainerScreenComponent: Component { return availableSize } - let isFirstTime = self.component == nil - let environment = environment[ViewControllerComponentContainer.Environment.self].value self.environment = environment + if self.component?.content !== component.content { + self.contentUpdatedDisposable?.dispose() + var update = false + self.contentUpdatedDisposable = (component.content.updated + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self else { + return + } + if update { + self.state?.updated(transition: .immediate) + } + }) + update = true + } + self.component = component self.state = state - if isFirstTime { - if let initialFocusedId = component.initialFocusedId, component.initialContent.contains(where: { $0.id == initialFocusedId }) { - self.focusedItemSet = initialFocusedId - } else { - self.focusedItemSet = component.initialContent.first?.id - } - self.itemSets = component.initialContent - } - transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize)) var isProgressPaused = false @@ -447,17 +472,33 @@ private final class StoryContainerScreenComponent: Component { var contentDerivedBottomInset: CGFloat = environment.safeInsets.bottom var validIds: [AnyHashable] = [] - if let focusedItemSet = self.focusedItemSet, let focusedIndex = self.itemSets.firstIndex(where: { $0.id == focusedItemSet }) { - for i in max(0, focusedIndex - 1) ... min(focusedIndex + 1, self.itemSets.count - 1) { + + var currentSlices: [StoryContentContextState.FocusedSlice] = [] + var focusedIndex: Int? + if let component = self.component, let stateValue = component.content.stateValue { + if let previousSlice = stateValue.previousSlice { + currentSlices.append(previousSlice) + } + if let slice = stateValue.slice { + focusedIndex = currentSlices.count + currentSlices.append(slice) + } + if let nextSlice = stateValue.nextSlice { + currentSlices.append(nextSlice) + } + } + + if !currentSlices.isEmpty, let focusedIndex { + for i in max(0, focusedIndex - 1) ... min(focusedIndex + 1, currentSlices.count - 1) { var isItemVisible = false if i == focusedIndex { isItemVisible = true } - let itemSet = self.itemSets[i] + let slice = currentSlices[i] if let itemSetPanState = self.itemSetPanState { - if self.visibleItemSetViews[itemSet.id] != nil { + if self.visibleItemSetViews[slice.peer.id] != nil { isItemVisible = true } if itemSetPanState.fraction < 0.0 && i == focusedIndex - 1 { @@ -469,23 +510,24 @@ private final class StoryContainerScreenComponent: Component { } if isItemVisible { - validIds.append(itemSet.id) + validIds.append(slice.peer.id) let itemSetView: ItemSetView var itemSetTransition = transition - if let current = self.visibleItemSetViews[itemSet.id] { + if let current = self.visibleItemSetViews[slice.peer.id] { itemSetView = current } else { itemSetTransition = .immediate itemSetView = ItemSetView() - self.visibleItemSetViews[itemSet.id] = itemSetView + self.visibleItemSetViews[slice.peer.id] = itemSetView } + let _ = itemSetView.view.update( transition: itemSetTransition, component: AnyComponent(StoryItemSetContainerComponent( context: component.context, externalState: itemSetView.externalState, - initialItemSlice: itemSet, + slice: slice, theme: environment.theme, strings: environment.strings, containerInsets: UIEdgeInsets(top: environment.statusBarHeight + 12.0, left: 0.0, bottom: environment.inputHeight, right: 0.0), @@ -509,52 +551,58 @@ private final class StoryContainerScreenComponent: Component { } environment.controller()?.dismiss() }, - navigateToItemSet: { [weak self] direction in - guard let self, let environment = self.environment else { + navigate: { [weak self] direction in + guard let self, let component = self.component, let environment = self.environment else { return } - if let focusedItemSet = self.focusedItemSet, let focusedIndex = self.itemSets.firstIndex(where: { $0.id == focusedItemSet }) { - var switchToIndex = focusedIndex - switch direction { - case .previous: - switchToIndex -= 1 - case .next: - switchToIndex += 1 - } - - switchToIndex = max(0, min(switchToIndex, self.itemSets.count - 1)) - if switchToIndex != focusedIndex { - var itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true) - - self.focusedItemSet = self.itemSets[switchToIndex].id - - if switchToIndex < focusedIndex { - itemSetPanState.fraction = 1.0 + itemSetPanState.fraction + if let stateValue = component.content.stateValue, let slice = stateValue.slice { + if case .next = direction, slice.nextItemId == nil { + if stateValue.nextSlice == nil { + environment.controller()?.dismiss() } else { - itemSetPanState.fraction = itemSetPanState.fraction - 1.0 + self.beginHorizontalPan() + self.updateHorizontalPan(translation: CGPoint()) + self.commitHorizontalPan(velocity: CGPoint(x: -100.0, y: 0.0)) } - self.itemSetPanState = itemSetPanState - self.state?.updated(transition: .immediate) - - itemSetPanState.fraction = 0.0 - self.itemSetPanState = itemSetPanState - - let transition = Transition(animation: .curve(duration: 0.4, curve: .spring)) - self.state?.updated(transition: transition) - - transition.attachAnimation(view: self, id: "panState", completion: { [weak self] completed in - guard let self, completed else { - return + } else if case .previous = direction, slice.previousItemId == nil { + if stateValue.previousSlice == nil { + if let itemSetView = self.visibleItemSetViews[slice.peer.id] { + if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { + componentView.rewindCurrentItem() + } } - self.itemSetPanState = nil - self.state?.updated(transition: .immediate) - }) - } else if switchToIndex == self.itemSets.count - 1 { + } else { + self.beginHorizontalPan() + self.updateHorizontalPan(translation: CGPoint()) + self.commitHorizontalPan(velocity: CGPoint(x: 100.0, y: 0.0)) + } + } else { + let mappedDirection: StoryContentContextNavigation.Direction + switch direction { + case .previous: + mappedDirection = .previous + case .next: + mappedDirection = .next + } + component.content.navigate(navigation: .item(mappedDirection)) + } + } + }, + delete: { [weak self] in + guard let self else { + return + } + if let stateValue = component.content.stateValue, let slice = stateValue.slice { + if slice.nextItemId != nil { + component.content.navigate(navigation: .item(.next)) + } else if slice.previousItemId != nil { + component.content.navigate(navigation: .item(.previous)) + } else if let environment = self.environment { environment.controller()?.dismiss() } - } else { - environment.controller()?.dismiss() + + let _ = component.context.engine.messages.deleteStory(id: slice.item.storyItem.id).start() } }, controller: { [weak self] in @@ -695,7 +743,7 @@ private final class StoryContainerScreenComponent: Component { } } } - var removedIds: [AnyHashable] = [] + var removedIds: [EnginePeer.Id] = [] for (id, itemSetView) in self.visibleItemSetViews { if !validIds.contains(id) { removedIds.append(id) @@ -779,8 +827,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer { public init( context: AccountContext, - initialFocusedId: AnyHashable?, - initialContent: [StoryContentItemSlice], + content: StoryContentContext, transitionIn: TransitionIn?, transitionOut: @escaping (EnginePeer.Id, AnyHashable) -> TransitionOut? ) { @@ -788,8 +835,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer { super.init(context: context, component: StoryContainerScreenComponent( context: context, - initialFocusedId: initialFocusedId, - initialContent: initialContent, + content: content, transitionIn: transitionIn, transitionOut: transitionOut ), navigationBarAppearance: .none, theme: .dark) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index ebc5c4c493..3118de5307 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -14,6 +14,9 @@ public final class StoryContentItem { open class View: UIView { open func setIsProgressPaused(_ isProgressPaused: Bool) { } + + open func rewind() { + } } public final class Environment: Equatable { @@ -42,7 +45,7 @@ public final class StoryContentItem { public let centerInfoComponent: AnyComponent? public let rightInfoComponent: AnyComponent? public let peerId: EnginePeer.Id? - public let storyItem: StoryListContext.Item? + public let storyItem: EngineStoryItem public let preload: Signal? public let delete: (() -> Void)? public let markAsSeen: (() -> Void)? @@ -56,7 +59,7 @@ public final class StoryContentItem { centerInfoComponent: AnyComponent?, rightInfoComponent: AnyComponent?, peerId: EnginePeer.Id?, - storyItem: StoryListContext.Item?, + storyItem: EngineStoryItem, preload: Signal?, delete: (() -> Void)?, markAsSeen: (() -> Void)?, @@ -83,6 +86,8 @@ public final class StoryContentItemSlice { public let focusedItemId: AnyHashable? public let items: [StoryContentItem] public let totalCount: Int + public let previousItemId: AnyHashable? + public let nextItemId: AnyHashable? public let update: (StoryContentItemSlice, AnyHashable) -> Signal public init( @@ -90,12 +95,92 @@ public final class StoryContentItemSlice { focusedItemId: AnyHashable?, items: [StoryContentItem], totalCount: Int, + previousItemId: AnyHashable?, + nextItemId: AnyHashable?, update: @escaping (StoryContentItemSlice, AnyHashable) -> Signal ) { self.id = id self.focusedItemId = focusedItemId self.items = items self.totalCount = totalCount + self.previousItemId = previousItemId + self.nextItemId = nextItemId self.update = update } } + +public final class StoryContentContextState { + public final class FocusedSlice: Equatable { + public let peer: EnginePeer + public let item: StoryContentItem + public let totalCount: Int + public let previousItemId: Int32? + public let nextItemId: Int32? + + public init( + peer: EnginePeer, + item: StoryContentItem, + totalCount: Int, + previousItemId: Int32?, + nextItemId: Int32? + ) { + self.peer = peer + self.item = item + self.totalCount = totalCount + self.previousItemId = previousItemId + self.nextItemId = nextItemId + } + + public static func ==(lhs: FocusedSlice, rhs: FocusedSlice) -> Bool { + if lhs.peer != rhs.peer { + return false + } + if lhs.item.id != rhs.item.id { + return false + } + if lhs.totalCount != rhs.totalCount { + return false + } + if lhs.previousItemId != rhs.previousItemId { + return false + } + if lhs.nextItemId != rhs.nextItemId { + return false + } + return true + } + } + + public let slice: FocusedSlice? + public let previousSlice: FocusedSlice? + public let nextSlice: FocusedSlice? + + public init( + slice: FocusedSlice?, + previousSlice: FocusedSlice?, + nextSlice: FocusedSlice? + ) { + self.slice = slice + self.previousSlice = previousSlice + self.nextSlice = nextSlice + } +} + +public enum StoryContentContextNavigation { + public enum Direction { + case previous + case next + } + + case item(Direction) + case peer(Direction) +} + +public protocol StoryContentContext: AnyObject { + var stateValue: StoryContentContextState? { get } + var state: Signal { get } + var updated: Signal { get } + + func resetSideStates() + func navigate(navigation: StoryContentContextNavigation) +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 05fadcedb5..0782369527 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -32,7 +32,7 @@ public final class StoryItemSetContainerComponent: Component { public let context: AccountContext public let externalState: ExternalState - public let initialItemSlice: StoryContentItemSlice + public let slice: StoryContentContextState.FocusedSlice public let theme: PresentationTheme public let strings: PresentationStrings public let containerInsets: UIEdgeInsets @@ -42,13 +42,14 @@ public final class StoryItemSetContainerComponent: Component { public let hideUI: Bool public let presentController: (ViewController) -> Void public let close: () -> Void - public let navigateToItemSet: (NavigationDirection) -> Void + public let navigate: (NavigationDirection) -> Void + public let delete: () -> Void public let controller: () -> ViewController? public init( context: AccountContext, externalState: ExternalState, - initialItemSlice: StoryContentItemSlice, + slice: StoryContentContextState.FocusedSlice, theme: PresentationTheme, strings: PresentationStrings, containerInsets: UIEdgeInsets, @@ -58,12 +59,13 @@ public final class StoryItemSetContainerComponent: Component { hideUI: Bool, presentController: @escaping (ViewController) -> Void, close: @escaping () -> Void, - navigateToItemSet: @escaping (NavigationDirection) -> Void, + navigate: @escaping (NavigationDirection) -> Void, + delete: @escaping () -> Void, controller: @escaping () -> ViewController? ) { self.context = context self.externalState = externalState - self.initialItemSlice = initialItemSlice + self.slice = slice self.theme = theme self.strings = strings self.containerInsets = containerInsets @@ -73,7 +75,8 @@ public final class StoryItemSetContainerComponent: Component { self.hideUI = hideUI self.presentController = presentController self.close = close - self.navigateToItemSet = navigateToItemSet + self.navigate = navigate + self.delete = delete self.controller = controller } @@ -81,7 +84,7 @@ public final class StoryItemSetContainerComponent: Component { if lhs.context !== rhs.context { return false } - if lhs.initialItemSlice !== rhs.initialItemSlice { + if lhs.slice != rhs.slice { return false } if lhs.theme !== rhs.theme { @@ -171,10 +174,6 @@ public final class StoryItemSetContainerComponent: Component { var itemLayout: ItemLayout? var ignoreScrolling: Bool = false - var focusedItemId: AnyHashable? - var currentSlice: StoryContentItemSlice? - var currentSliceDisposable: Disposable? - var visibleItems: [AnyHashable: VisibleItem] = [:] var preloadContexts: [AnyHashable: Disposable] = [:] @@ -330,7 +329,6 @@ public final class StoryItemSetContainerComponent: Component { } deinit { - self.currentSliceDisposable?.dispose() self.audioRecorderDisposable?.dispose() self.audioRecorderStatusDisposable?.dispose() self.audioRecorderStatusDisposable?.dispose() @@ -350,6 +348,18 @@ public final class StoryItemSetContainerComponent: Component { return false } + func rewindCurrentItem() { + guard let component = self.component else { + return + } + guard let visibleItem = self.visibleItems[component.slice.item.id] else { + return + } + if let itemView = visibleItem.view.view as? StoryContentItem.View { + itemView.rewind() + } + } + @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool { if otherGestureRecognizer is UIPanGestureRecognizer { return true @@ -358,7 +368,7 @@ public final class StoryItemSetContainerComponent: Component { } @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state, let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let currentIndex = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }), let itemLayout = self.itemLayout { + if case .ended = recognizer.state, let component = self.component, let itemLayout = self.itemLayout { if hasFirstResponder(self) { self.displayReactions = false self.endEditing(true) @@ -368,39 +378,15 @@ public final class StoryItemSetContainerComponent: Component { } else { let point = recognizer.location(in: self) - var nextIndex: Int + var direction: NavigationDirection? if point.x < itemLayout.size.width * 0.25 { - nextIndex = currentIndex - 1 + direction = .previous } else { - nextIndex = currentIndex + 1 + direction = .next } - nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1)) - if nextIndex != currentIndex { - let focusedItemId = currentSlice.items[nextIndex].id - self.focusedItemId = focusedItemId - - currentSlice.items[nextIndex].markAsSeen?() - - self.state?.updated(transition: .immediate) - - self.currentSliceDisposable?.dispose() - self.currentSliceDisposable = (currentSlice.update( - currentSlice, - focusedItemId - ) - |> deliverOnMainQueue).start(next: { [weak self] contentSlice in - guard let self else { - return - } - self.currentSlice = contentSlice - self.state?.updated(transition: .immediate) - }) - } else { - if point.x < itemLayout.size.width * 0.25 { - self.component?.navigateToItemSet(.previous) - } else { - self.component?.navigateToItemSet(.next) - } + + if let direction { + component.navigate(direction) } } } @@ -432,83 +418,57 @@ public final class StoryItemSetContainerComponent: Component { } var validIds: [AnyHashable] = [] - if let focusedItemId = self.focusedItemId, let focusedItem = self.currentSlice?.items.first(where: { $0.id == focusedItemId }) { - validIds.append(focusedItemId) + let focusedItem = component.slice.item + + validIds.append(focusedItem.id) - var itemTransition = transition - let visibleItem: VisibleItem - if let current = self.visibleItems[focusedItemId] { - visibleItem = current - } else { - itemTransition = .immediate - visibleItem = VisibleItem() - self.visibleItems[focusedItemId] = visibleItem - } - - let _ = visibleItem.view.update( - transition: itemTransition, - component: focusedItem.component, - environment: { - StoryContentItem.Environment( - externalState: visibleItem.externalState, - presentationProgressUpdated: { [weak self, weak visibleItem] progress in - guard let self = self 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 && !visibleItem.requestedNext { - visibleItem.requestedNext = true - - if let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let currentIndex = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }) { - var nextIndex = currentIndex + 1 - nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1)) - if nextIndex != currentIndex { - let focusedItemId = currentSlice.items[nextIndex].id - self.focusedItemId = focusedItemId - - currentSlice.items[nextIndex].markAsSeen?() - - self.state?.updated(transition: .immediate) - - self.currentSliceDisposable?.dispose() - self.currentSliceDisposable = (currentSlice.update( - currentSlice, - focusedItemId - ) - |> deliverOnMainQueue).start(next: { [weak self] contentSlice in - guard let self else { - return - } - self.currentSlice = contentSlice - self.state?.updated(transition: .immediate) - }) - } else { - self.component?.navigateToItemSet(.next) - } - } - } + var itemTransition = transition + let visibleItem: VisibleItem + if let current = self.visibleItems[focusedItem.id] { + visibleItem = current + } else { + itemTransition = .immediate + visibleItem = VisibleItem() + self.visibleItems[focusedItem.id] = visibleItem + } + + let _ = visibleItem.view.update( + transition: itemTransition, + component: focusedItem.component, + environment: { + StoryContentItem.Environment( + externalState: visibleItem.externalState, + presentationProgressUpdated: { [weak self, weak visibleItem] progress in + guard let self = self, let component = self.component else { + return } - ) - }, - containerSize: itemLayout.size - ) - if let view = visibleItem.view.view { - if view.superview == nil { - view.isUserInteractionEnabled = false - self.contentContainerView.insertSubview(view, at: 0) - } - itemTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: itemLayout.size)) - - if let view = view as? StoryContentItem.View { - view.setIsProgressPaused(self.inputPanelExternalState.isEditing || component.isProgressPaused || self.displayReactions || self.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil) - } + 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 && !visibleItem.requestedNext { + visibleItem.requestedNext = true + + component.navigate(.next) + } + } + ) + }, + containerSize: itemLayout.size + ) + if let view = visibleItem.view.view { + if view.superview == nil { + view.isUserInteractionEnabled = false + self.contentContainerView.insertSubview(view, at: 0) + } + itemTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: itemLayout.size)) + + if let view = view as? StoryContentItem.View { + view.setIsProgressPaused(self.inputPanelExternalState.isEditing || component.isProgressPaused || self.displayReactions || self.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil) } } @@ -586,7 +546,7 @@ public final class StoryItemSetContainerComponent: Component { duration: 0.3 ) - if let focusedItemId = self.focusedItemId, let visibleItemView = self.visibleItems[focusedItemId]?.view.view { + if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view { let innerScale = innerSourceLocalFrame.width / visibleItemView.bounds.width let innerFromFrame = CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: CGSize(width: innerSourceLocalFrame.width, height: visibleItemView.bounds.height * innerScale)) @@ -657,7 +617,7 @@ public final class StoryItemSetContainerComponent: Component { removeOnCompletion: false ) - if let focusedItemId = self.focusedItemId, let visibleItemView = self.visibleItems[focusedItemId]?.view.view { + if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view { let innerScale = innerSourceLocalFrame.width / visibleItemView.bounds.width let innerFromFrame = CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: CGSize(width: innerSourceLocalFrame.width, height: visibleItemView.bounds.height * innerScale)) @@ -680,24 +640,6 @@ public final class StoryItemSetContainerComponent: Component { let isFirstTime = self.component == nil if self.component == nil { - self.focusedItemId = component.initialItemSlice.focusedItemId ?? component.initialItemSlice.items.first?.id - self.currentSlice = component.initialItemSlice - - self.currentSliceDisposable?.dispose() - if let focusedItemId = self.focusedItemId { - self.currentSliceDisposable = (component.initialItemSlice.update( - component.initialItemSlice, - focusedItemId - ) - |> deliverOnMainQueue).start(next: { [weak self] contentSlice in - guard let self else { - return - } - self.currentSlice = contentSlice - self.state?.updated(transition: .immediate) - }) - } - let _ = (allowedStoryReactions(context: component.context) |> deliverOnMainQueue).start(next: { [weak self] reactionItems in guard let self, let component = self.component else { @@ -718,6 +660,10 @@ public final class StoryItemSetContainerComponent: Component { }) } + if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id { + let _ = component.context.engine.messages.markStoryAsSeen(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id).start() + } + if self.topContentGradientLayer.colors == nil { var locations: [NSNumber] = [] var colors: [CGColor] = [] @@ -759,18 +705,6 @@ public final class StoryItemSetContainerComponent: Component { self.contentDimLayer.backgroundColor = UIColor(white: 0.0, alpha: 0.3).cgColor } - if let focusedItemId = self.focusedItemId { - if let currentSlice = self.currentSlice { - if !currentSlice.items.contains(where: { $0.id == focusedItemId }) { - self.focusedItemId = currentSlice.items.first?.id - - currentSlice.items.first?.markAsSeen?() - } - } else { - self.focusedItemId = nil - } - } - //self.updatePreloads() self.component = component @@ -854,7 +788,7 @@ public final class StoryItemSetContainerComponent: Component { return true } - self.displayReactions = true + self.displayReactions = !self.displayReactions self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) }) }, @@ -874,9 +808,7 @@ public final class StoryItemSetContainerComponent: Component { ) var currentItem: StoryContentItem? - if let focusedItemId = self.focusedItemId, let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == focusedItemId }) { - currentItem = item - } + currentItem = component.slice.item let footerPanelSize = self.footerPanel.update( transition: transition, @@ -884,7 +816,7 @@ public final class StoryItemSetContainerComponent: Component { context: component.context, storyItem: currentItem?.storyItem, deleteAction: { [weak self] in - guard let self, let component = self.component, let focusedItemId = self.focusedItemId else { + guard let self, let component = self.component else { return } @@ -899,8 +831,9 @@ public final class StoryItemSetContainerComponent: Component { guard let self, let component = self.component else { return } + component.delete() - if let currentSlice = self.currentSlice, let index = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }) { + /*if let currentSlice = self.currentSlice, let index = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }) { let item = currentSlice.items[index] if currentSlice.items.count == 1 { @@ -914,36 +847,11 @@ public final class StoryItemSetContainerComponent: Component { currentSlice.items[nextIndex].markAsSeen?() - /*var updatedItems: [StoryContentItem] = [] - for item in currentSlice.items { - if item.id != focusedItemId { - updatedItems.append(StoryContentItem( - id: item.id, - position: updatedItems.count, - component: item.component, - centerInfoComponent: item.centerInfoComponent, - rightInfoComponent: item.rightInfoComponent, - targetMessageId: item.targetMessageId, - preload: item.preload, - delete: item.delete, - hasLike: item.hasLike, - isMy: item.isMy - )) - } - }*/ - - /*self.currentSlice = StoryContentItemSlice( - id: currentSlice.id, - focusedItemId: nil, - items: updatedItems, - totalCount: currentSlice.totalCount - 1, - update: currentSlice.update - )*/ self.state?.updated(transition: .immediate) } item.delete?() - } + }*/ }) ]), ActionSheetItemGroup(items: [ @@ -970,7 +878,9 @@ public final class StoryItemSetContainerComponent: Component { return } - var items: [ContextMenuItem] = [] + let _ = controller + + /*var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: "Who can see", textLayout: .secondLineWithValue("Everyone"), icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) @@ -1029,7 +939,7 @@ public final class StoryItemSetContainerComponent: Component { } self.contextController = contextController self.updateIsProgressPaused() - controller.present(contextController, in: .window(.root)) + controller.present(contextController, in: .window(.root))*/ } )), environment: {}, @@ -1065,15 +975,16 @@ public final class StoryItemSetContainerComponent: Component { transition.setAlpha(view: self.closeButton, alpha: component.hideUI ? 0.0 : 1.0) } - var focusedItem: StoryContentItem? - if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) { + let focusedItem: StoryContentItem? = component.slice.item + let _ = focusedItem + /*if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) { focusedItem = item - } + }*/ var currentRightInfoItem: InfoItem? - if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) { - if let rightInfoComponent = item.rightInfoComponent { - if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == item.rightInfoComponent { + if let focusedItem { + if let rightInfoComponent = focusedItem.rightInfoComponent { + if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == focusedItem.rightInfoComponent { currentRightInfoItem = rightInfoItem } else { currentRightInfoItem = InfoItem(component: rightInfoComponent) @@ -1092,9 +1003,9 @@ public final class StoryItemSetContainerComponent: Component { } var currentCenterInfoItem: InfoItem? - if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) { - if let centerInfoComponent = item.centerInfoComponent { - if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == item.centerInfoComponent { + if let focusedItem { + if let centerInfoComponent = focusedItem.centerInfoComponent { + if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == focusedItem.centerInfoComponent { currentCenterInfoItem = centerInfoItem } else { currentCenterInfoItem = InfoItem(component: centerInfoComponent) @@ -1389,17 +1300,17 @@ public final class StoryItemSetContainerComponent: Component { self.ignoreScrolling = false self.updateScrolling(transition: transition) - if let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let visibleItem = self.visibleItems[focusedItemId] { + if let focusedItem, let visibleItem = self.visibleItems[focusedItem.storyItem.id] { let navigationStripSideInset: CGFloat = 8.0 let navigationStripTopInset: CGFloat = 8.0 - let index = currentSlice.items.first(where: { $0.id == self.focusedItemId })?.position ?? 0 + let index = focusedItem.position let _ = self.navigationStrip.update( transition: transition, component: AnyComponent(MediaNavigationStripComponent( - index: max(0, min(index, currentSlice.totalCount - 1)), - count: currentSlice.totalCount + index: max(0, min(index, component.slice.totalCount - 1)), + count: component.slice.totalCount )), environment: { MediaNavigationStripComponent.EnvironmentType( @@ -1417,53 +1328,45 @@ public final class StoryItemSetContainerComponent: Component { transition.setAlpha(view: navigationStripView, alpha: component.hideUI ? 0.0 : 1.0) } - if let focusedItemId = self.focusedItemId, let focusedItem = self.currentSlice?.items.first(where: { $0.id == focusedItemId }) { - var items: [StoryActionsComponent.Item] = [] - let _ = focusedItem - /*if !focusedItem.isMy { - items.append(StoryActionsComponent.Item( - kind: .like, - isActivated: focusedItem.hasLike - )) - }*/ - items.append(StoryActionsComponent.Item( - kind: .share, - isActivated: false - )) - - let inlineActionsSize = self.inlineActions.update( - transition: transition, - component: AnyComponent(StoryActionsComponent( - items: items, - action: { [weak self] item in - guard let self else { - return - } - self.sendMessageContext.performInlineAction(view: self, item: item) + var items: [StoryActionsComponent.Item] = [] + let _ = focusedItem + items.append(StoryActionsComponent.Item( + kind: .share, + isActivated: false + )) + + let inlineActionsSize = self.inlineActions.update( + transition: transition, + component: AnyComponent(StoryActionsComponent( + items: items, + action: { [weak self] item in + guard let self else { + return } - )), - environment: {}, - containerSize: contentFrame.size - ) - if let inlineActionsView = self.inlineActions.view { - if inlineActionsView.superview == nil { - self.contentContainerView.addSubview(inlineActionsView) + self.sendMessageContext.performInlineAction(view: self, item: item) } - transition.setFrame(view: inlineActionsView, frame: CGRect(origin: CGPoint(x: contentFrame.width - 10.0 - inlineActionsSize.width, y: contentFrame.height - 20.0 - inlineActionsSize.height), size: inlineActionsSize)) - - var inlineActionsAlpha: CGFloat = inputPanelIsOverlay ? 0.0 : 1.0 - if self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil { - inlineActionsAlpha = 0.0 - } - if self.reactionItems != nil { - inlineActionsAlpha = 0.0 - } - if component.hideUI { - inlineActionsAlpha = 0.0 - } - - transition.setAlpha(view: inlineActionsView, alpha: inlineActionsAlpha) + )), + environment: {}, + containerSize: contentFrame.size + ) + if let inlineActionsView = self.inlineActions.view { + if inlineActionsView.superview == nil { + self.contentContainerView.addSubview(inlineActionsView) } + transition.setFrame(view: inlineActionsView, frame: CGRect(origin: CGPoint(x: contentFrame.width - 10.0 - inlineActionsSize.width, y: contentFrame.height - 20.0 - inlineActionsSize.height), size: inlineActionsSize)) + + var inlineActionsAlpha: CGFloat = inputPanelIsOverlay ? 0.0 : 1.0 + if self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil { + inlineActionsAlpha = 0.0 + } + if self.displayReactions { + inlineActionsAlpha = 0.0 + } + if component.hideUI { + inlineActionsAlpha = 0.0 + } + + transition.setAlpha(view: inlineActionsView, alpha: inlineActionsAlpha) } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index cdaecd0af4..18c3194cb8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -59,9 +59,7 @@ final class StoryItemSetContainerSendMessage { guard let component = view.component else { return } - guard let focusedItemId = view.focusedItemId, let focusedItem = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else { - return - } + let focusedItem = component.slice.item guard let peerId = focusedItem.peerId else { return } @@ -117,9 +115,7 @@ final class StoryItemSetContainerSendMessage { guard let component = view.component else { return } - guard let focusedItemId = view.focusedItemId, let focusedItem = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else { - return - } + let focusedItem = component.slice.item guard let peerId = focusedItem.peerId else { return } @@ -335,9 +331,7 @@ final class StoryItemSetContainerSendMessage { guard let component = view.component else { return } - guard let focusedItemId = view.focusedItemId, let focusedItem = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else { - return - } + let focusedItem = component.slice.item guard let peerId = focusedItem.peerId else { return } @@ -1582,10 +1576,6 @@ final class StoryItemSetContainerSendMessage { } private func transformEnqueueMessages(view: StoryItemSetContainerComponent.View, messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] { - guard let focusedItemId = view.focusedItemId, let _ = view.currentSlice?.items.first(where: { $0.id == focusedItemId }) else { - return [] - } - let defaultReplyMessageId: EngineMessage.Id? = nil return messages.map { message in diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD b/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD index f31c881253..de30c2ebad 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD @@ -17,6 +17,7 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AccountContext", "//submodules/TelegramCore", + "//submodules/Postbox", "//submodules/PhotoResources", "//submodules/MediaPlayer:UniversalMediaPlayer", "//submodules/TelegramUniversalVideoContent", diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift index 6b2932292e..b0db8eee8a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift @@ -5,91 +5,531 @@ import ComponentFlow import SwiftSignalKit import AccountContext import TelegramCore +import Postbox import StoryContainerScreen -public enum StoryChatContent { - public static func stories(context: AccountContext, storyList: StoryListContext, focusItem: Int32?) -> Signal<[StoryContentItemSlice], NoError> { - return storyList.state - |> map { state -> [StoryContentItemSlice] in - var itemSlices: [StoryContentItemSlice] = [] +public final class StoryContentContextImpl: StoryContentContext { + private struct StoryKey: Hashable { + var peerId: EnginePeer.Id + var id: Int32 + } + + private final class PeerContext { + private let context: AccountContext + private let peerId: EnginePeer.Id + + private(set) var sliceValue: StoryContentContextState.FocusedSlice? + + let updated = Promise() + + private(set) var isReady: Bool = false + + private var disposable: Disposable? + private var loadDisposable: Disposable? + + private let currentFocusedIdPromise = Promise() + private var storedFocusedId: Int32? + var currentFocusedId: Int32? { + didSet { + if self.currentFocusedId != self.storedFocusedId { + self.storedFocusedId = self.currentFocusedId + self.currentFocusedIdPromise.set(.single(self.currentFocusedId)) + } + } + } + + init(context: AccountContext, peerId: EnginePeer.Id, focusedId initialFocusedId: Int32?, loadIds: @escaping ([StoryKey]) -> Void) { + self.context = context + self.peerId = peerId - for itemSet in state.itemSets { - var items: [StoryContentItem] = [] - - guard let peer = itemSet.peer else { - continue + self.currentFocusedIdPromise.set(.single(initialFocusedId)) + + self.disposable = (combineLatest(queue: .mainQueue(), + self.currentFocusedIdPromise.get(), + context.account.postbox.combinedView( + keys: [ + PostboxViewKey.basicPeer(peerId), + PostboxViewKey.storiesState(key: .peer(peerId)), + PostboxViewKey.storyItems(peerId: peerId) + ] + ) + ) + |> mapToSignal { currentFocusedId, views -> Signal<(Int32?, CombinedView, [PeerId: Peer]), NoError> in + return context.account.postbox.transaction { transaction -> (Int32?, CombinedView, [PeerId: Peer]) in + var peers: [PeerId: Peer] = [:] + if let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView { + for item in itemsView.items { + if let item = item.value.get(Stories.StoredItem.self), case let .item(itemValue) = item { + if let views = itemValue.views { + for peerId in views.seenPeerIds { + if let peer = transaction.getPeer(peerId) { + peers[peer.id] = peer + } + } + } + } + } + } + return (currentFocusedId, views, peers) } - let peerId = itemSet.peerId + } + |> deliverOnMainQueue).start(next: { [weak self] currentFocusedId, views, peers in + guard let self else { + return + } + guard let peerView = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView else { + return + } + guard let stateView = views.views[PostboxViewKey.storiesState(key: .peer(peerId))] as? StoryStatesView else { + return + } + guard let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView else { + return + } + guard let peer = peerView.peer.flatMap(EnginePeer.init) else { + return + } + let state = stateView.value?.get(Stories.PeerState.self) - for item in itemSet.items { - items.append(StoryContentItem( - id: AnyHashable(item.id), - position: items.count, - component: AnyComponent(StoryItemContentComponent( - context: context, + var focusedIndex: Int? + if let currentFocusedId { + focusedIndex = itemsView.items.firstIndex(where: { $0.id == currentFocusedId }) + } + if focusedIndex == nil, let state { + if let storedFocusedId = self.storedFocusedId { + focusedIndex = itemsView.items.firstIndex(where: { $0.id >= storedFocusedId }) + } else { + focusedIndex = itemsView.items.firstIndex(where: { $0.id > state.maxReadId }) + } + } + if focusedIndex == nil { + if !itemsView.items.isEmpty { + focusedIndex = 0 + } + } + + if let focusedIndex { + self.storedFocusedId = itemsView.items[focusedIndex].id + + var previousItemId: Int32? + var nextItemId: Int32? + + if focusedIndex != 0 { + previousItemId = itemsView.items[focusedIndex - 1].id + } + if focusedIndex != itemsView.items.count - 1 { + nextItemId = itemsView.items[focusedIndex + 1].id + } + + var loadKeys: [StoryKey] = [] + for index in (focusedIndex - 2) ... (focusedIndex + 2) { + if index >= 0 && index < itemsView.items.count { + if let item = itemsView.items[focusedIndex].value.get(Stories.StoredItem.self), case .placeholder = item { + loadKeys.append(StoryKey(peerId: peerId, id: item.id)) + } + } + } + + if let item = itemsView.items[focusedIndex].value.get(Stories.StoredItem.self), case let .item(item) = item, let media = item.media { + let mappedItem = EngineStoryItem( + id: item.id, + timestamp: item.timestamp, + media: EngineMedia(media), + text: item.text, + entities: item.entities, + views: item.views.flatMap { views in + return EngineStoryItem.Views( + seenCount: views.seenCount, + seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in + return peers[id].flatMap(EnginePeer.init) + } + ) + }, + privacy: nil + ) + + self.sliceValue = StoryContentContextState.FocusedSlice( peer: peer, - item: item - )), - centerInfoComponent: AnyComponent(StoryAuthorInfoComponent( - context: context, - peer: itemSet.peer, - timestamp: item.timestamp - )), - rightInfoComponent: itemSet.peer.flatMap { author -> AnyComponent in - return AnyComponent(StoryAvatarInfoComponent( - context: context, - peer: author - )) - }, - peerId: itemSet.peerId, - storyItem: item, - preload: nil, - delete: { [weak storyList] in - storyList?.delete(id: item.id) - }, - markAsSeen: { [weak context] in - guard let context else { - return - } - let _ = context.engine.messages.markStoryAsSeen(peerId: peerId, id: item.id).start() - }, - hasLike: false, - isMy: itemSet.peerId == context.account.peerId - )) - } - - var sliceFocusedItemId: AnyHashable? - if let focusItem, items.contains(where: { ($0.id.base as? Int32) == focusItem }) { - sliceFocusedItemId = AnyHashable(focusItem) + item: StoryContentItem( + id: AnyHashable(item.id), + position: focusedIndex, + component: AnyComponent(StoryItemContentComponent( + context: context, + peer: peer, + item: mappedItem + )), + centerInfoComponent: AnyComponent(StoryAuthorInfoComponent( + context: context, + peer: peer, + timestamp: item.timestamp + )), + rightInfoComponent: AnyComponent(StoryAvatarInfoComponent( + context: context, + peer: peer + )), + peerId: peer.id, + storyItem: mappedItem, + preload: nil, + delete: { [weak context] in + guard let context else { + return + } + let _ = context + }, + markAsSeen: { [weak context] in + guard let context else { + return + } + let _ = context.engine.messages.markStoryAsSeen(peerId: peerId, id: item.id).start() + }, + hasLike: false, + isMy: peerId == context.account.peerId + ), + totalCount: itemsView.items.count, + previousItemId: previousItemId, + nextItemId: nextItemId + ) + self.isReady = true + self.updated.set(.single(Void())) + } } else { - if let id = itemSet.items.first(where: { $0.id > itemSet.maxReadId })?.id { - sliceFocusedItemId = AnyHashable(id) - } + self.isReady = true + self.updated.set(.single(Void())) } - - itemSlices.append(StoryContentItemSlice( - id: AnyHashable(itemSet.peerId), - focusedItemId: sliceFocusedItemId, - items: items, - totalCount: items.count, - update: { requestedItemSet, itemId in - var focusItem: Int32? - if let id = itemId.base as? Int32 { - focusItem = id - } - return StoryChatContent.stories(context: context, storyList: storyList, focusItem: focusItem) - |> mapToSignal { result -> Signal in - if let foundItemSet = result.first(where: { $0.id == requestedItemSet.id }) { - return .single(foundItemSet) - } else { - return .never() - } - } + }) + } + + deinit { + self.disposable?.dispose() + self.loadDisposable?.dispose() + } + } + + private final class StateContext { + let centralPeerContext: PeerContext + let previousPeerContext: PeerContext? + let nextPeerContext: PeerContext? + + let updated = Promise() + + var isReady: Bool { + if !self.centralPeerContext.isReady { + return false + } + return true + } + + private var centralDisposable: Disposable? + private var previousDisposable: Disposable? + private var nextDisposable: Disposable? + + init( + centralPeerContext: PeerContext, + previousPeerContext: PeerContext?, + nextPeerContext: PeerContext? + ) { + self.centralPeerContext = centralPeerContext + self.previousPeerContext = previousPeerContext + self.nextPeerContext = nextPeerContext + + self.centralDisposable = (centralPeerContext.updated.get() + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self else { + return + } + self.updated.set(.single(Void())) + }) + + if let previousPeerContext { + self.previousDisposable = (previousPeerContext.updated.get() + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self else { + return } - )) + self.updated.set(.single(Void())) + }) } - return itemSlices + if let nextPeerContext { + self.nextDisposable = (nextPeerContext.updated.get() + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self else { + return + } + self.updated.set(.single(Void())) + }) + } + } + + deinit { + self.centralDisposable?.dispose() + self.previousDisposable?.dispose() + self.nextDisposable?.dispose() + } + + func findPeerContext(id: EnginePeer.Id) -> PeerContext? { + if self.centralPeerContext.sliceValue?.peer.id == id { + return self.centralPeerContext + } + if let previousPeerContext = self.previousPeerContext, previousPeerContext.sliceValue?.peer.id == id { + return previousPeerContext + } + if let nextPeerContext = self.nextPeerContext, nextPeerContext.sliceValue?.peer.id == id { + return nextPeerContext + } + return nil + } + } + + private let context: AccountContext + + public private(set) var stateValue: StoryContentContextState? + public var state: Signal { + return self.statePromise.get() + } + private let statePromise = Promise() + + private let updatedPromise = Promise() + public var updated: Signal { + return self.updatedPromise.get() + } + + private var focusedItem: (peerId: EnginePeer.Id, storyId: Int32?)? + + private var currentState: StateContext? + private var currentStateUpdatedDisposable: Disposable? + + private var pendingState: StateContext? + private var pendingStateReadyDisposable: Disposable? + + private var storySubscriptions: EngineStorySubscriptions? + private var storySubscriptionsDisposable: Disposable? + + private var requestedStoryKeys = Set() + private var requestStoryDisposables = DisposableSet() + + public init( + context: AccountContext, + focusedPeerId: EnginePeer.Id? + ) { + self.context = context + if let focusedPeerId { + self.focusedItem = (focusedPeerId, nil) + } + + self.storySubscriptionsDisposable = (context.engine.messages.storySubscriptions() + |> deliverOnMainQueue).start(next: { [weak self] storySubscriptions in + guard let self else { + return + } + self.storySubscriptions = storySubscriptions + self.updatePeerContexts() + }) + } + + deinit { + self.storySubscriptionsDisposable?.dispose() + self.requestStoryDisposables.dispose() + } + + private func updatePeerContexts() { + if let currentState = self.currentState { + let _ = currentState + } else { + self.switchToFocusedPeerId() + } + } + + private func switchToFocusedPeerId() { + if let storySubscriptions = self.storySubscriptions { + if self.pendingState == nil { + let loadIds: ([StoryKey]) -> Void = { [weak self] keys in + guard let self else { + return + } + let missingKeys = Set(keys).subtracting(self.requestedStoryKeys) + if !missingKeys.isEmpty { + var idsByPeerId: [EnginePeer.Id: [Int32]] = [:] + for key in missingKeys { + if idsByPeerId[key.peerId] == nil { + idsByPeerId[key.peerId] = [key.id] + } else { + idsByPeerId[key.peerId]?.append(key.id) + } + } + for (peerId, ids) in idsByPeerId { + self.requestStoryDisposables.add(self.context.engine.messages.refreshStories(peerId: peerId, ids: ids).start()) + } + } + } + + if let (focusedPeerId, _) = self.focusedItem, focusedPeerId == self.context.account.peerId { + let centralPeerContext = PeerContext(context: self.context, peerId: self.context.account.peerId, focusedId: nil, loadIds: loadIds) + + let pendingState = StateContext( + centralPeerContext: centralPeerContext, + previousPeerContext: nil, + nextPeerContext: nil + ) + self.pendingState = pendingState + self.pendingStateReadyDisposable = (pendingState.updated.get() + |> deliverOnMainQueue).start(next: { [weak self, weak pendingState] _ in + guard let self, let pendingState, self.pendingState === pendingState, pendingState.isReady else { + return + } + self.pendingState = nil + self.pendingStateReadyDisposable?.dispose() + self.pendingStateReadyDisposable = nil + + self.currentState = pendingState + + self.updateState() + + self.currentStateUpdatedDisposable?.dispose() + self.currentStateUpdatedDisposable = (pendingState.updated.get() + |> deliverOnMainQueue).start(next: { [weak self, weak pendingState] _ in + guard let self, let pendingState, self.currentState === pendingState else { + return + } + self.updateState() + }) + }) + } else { + var centralIndex: Int? + if let (focusedPeerId, _) = self.focusedItem { + if let index = storySubscriptions.items.firstIndex(where: { $0.peer.id == focusedPeerId }) { + centralIndex = index + } + } + if centralIndex == nil { + if !storySubscriptions.items.isEmpty { + centralIndex = 0 + } + } + + if let centralIndex { + let centralPeerContext: PeerContext + if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: storySubscriptions.items[centralIndex].peer.id) { + centralPeerContext = existingContext + } else { + centralPeerContext = PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex].peer.id, focusedId: nil, loadIds: loadIds) + } + + var previousPeerContext: PeerContext? + if centralIndex != 0 { + if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: storySubscriptions.items[centralIndex - 1].peer.id) { + previousPeerContext = existingContext + } else { + previousPeerContext = PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex - 1].peer.id, focusedId: nil, loadIds: loadIds) + } + } + + var nextPeerContext: PeerContext? + if centralIndex != storySubscriptions.items.count - 1 { + if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: storySubscriptions.items[centralIndex + 1].peer.id) { + nextPeerContext = existingContext + } else { + nextPeerContext = PeerContext(context: self.context, peerId: storySubscriptions.items[centralIndex + 1].peer.id, focusedId: nil, loadIds: loadIds) + } + } + + let pendingState = StateContext( + centralPeerContext: centralPeerContext, + previousPeerContext: previousPeerContext, + nextPeerContext: nextPeerContext + ) + self.pendingState = pendingState + self.pendingStateReadyDisposable = (pendingState.updated.get() + |> deliverOnMainQueue).start(next: { [weak self, weak pendingState] _ in + guard let self, let pendingState, self.pendingState === pendingState, pendingState.isReady else { + return + } + self.pendingState = nil + self.pendingStateReadyDisposable?.dispose() + self.pendingStateReadyDisposable = nil + + self.currentState = pendingState + + self.updateState() + + self.currentStateUpdatedDisposable?.dispose() + self.currentStateUpdatedDisposable = (pendingState.updated.get() + |> deliverOnMainQueue).start(next: { [weak self, weak pendingState] _ in + guard let self, let pendingState, self.currentState === pendingState else { + return + } + self.updateState() + }) + }) + } + } + } + } + } + + private func updateState() { + guard let currentState = self.currentState else { + return + } + let stateValue = StoryContentContextState( + slice: currentState.centralPeerContext.sliceValue, + previousSlice: currentState.previousPeerContext?.sliceValue, + nextSlice: currentState.nextPeerContext?.sliceValue + ) + self.stateValue = stateValue + self.statePromise.set(.single(stateValue)) + + self.updatedPromise.set(.single(Void())) + } + + public func resetSideStates() { + guard let currentState = self.currentState else { + return + } + if let previousPeerContext = currentState.previousPeerContext { + previousPeerContext.currentFocusedId = nil + } + if let nextPeerContext = currentState.nextPeerContext { + nextPeerContext.currentFocusedId = nil + } + } + + public func navigate(navigation: StoryContentContextNavigation) { + guard let currentState = self.currentState else { + return + } + + switch navigation { + case let .peer(direction): + switch direction { + case .previous: + if let previousPeerContext = currentState.previousPeerContext, let previousSlice = previousPeerContext.sliceValue { + self.pendingStateReadyDisposable?.dispose() + self.pendingState = nil + self.focusedItem = (previousSlice.peer.id, nil) + self.switchToFocusedPeerId() + } + case .next: + if let nextPeerContext = currentState.nextPeerContext, let nextSlice = nextPeerContext.sliceValue { + self.pendingStateReadyDisposable?.dispose() + self.pendingState = nil + self.focusedItem = (nextSlice.peer.id, nil) + self.switchToFocusedPeerId() + } + } + case let .item(direction): + if let slice = currentState.centralPeerContext.sliceValue { + switch direction { + case .previous: + if let previousItemId = slice.previousItemId { + currentState.centralPeerContext.currentFocusedId = previousItemId + } + case .next: + if let nextItemId = slice.nextItemId { + currentState.centralPeerContext.currentFocusedId = nextItemId + } + } + } } } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift index 1fec8e9fa4..78ea5c2d5b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift @@ -17,9 +17,9 @@ final class StoryItemContentComponent: Component { let context: AccountContext let peer: EnginePeer - let item: StoryListContext.Item + let item: EngineStoryItem - init(context: AccountContext, peer: EnginePeer, item: StoryListContext.Item) { + init(context: AccountContext, peer: EnginePeer, item: EngineStoryItem) { self.context = context self.peer = peer self.item = item @@ -156,6 +156,7 @@ final class StoryItemContentComponent: Component { userLocation: .other, fileReference: .story(peer: peerReference, id: component.item.id, media: file), imageReference: nil, + streamVideo: .story, loopVideo: true, enableSound: true, tempFilePath: nil, @@ -192,6 +193,15 @@ final class StoryItemContentComponent: Component { } } + override func rewind() { + self.currentProgressTimerValue = 0.0 + if let videoNode = self.videoNode { + if self.contentLoaded { + videoNode.seek(0.0) + } + } + } + private func updateIsProgressPaused() { if let videoNode = self.videoNode { if !self.isProgressPaused && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy { @@ -229,7 +239,7 @@ final class StoryItemContentComponent: Component { } #if DEBUG// && false - let currentProgressTimerLimit: Double = 5 * 60.0 + let currentProgressTimerLimit: Double = 1 * 60.0 #else let currentProgressTimerLimit: Double = 5.0 #endif diff --git a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift index 948f98aafb..7c96bddc23 100644 --- a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift @@ -11,13 +11,13 @@ import TelegramCore public final class StoryFooterPanelComponent: Component { public let context: AccountContext - public let storyItem: StoryListContext.Item? + public let storyItem: EngineStoryItem? public let deleteAction: () -> Void public let moreAction: (UIView, ContextGesture?) -> Void public init( context: AccountContext, - storyItem: StoryListContext.Item?, + storyItem: EngineStoryItem?, deleteAction: @escaping () -> Void, moreAction: @escaping (UIView, ContextGesture?) -> Void ) { diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index fc8e982513..14f6214730 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -13,7 +13,7 @@ public final class StoryPeerListComponent: Component { public let context: AccountContext public let theme: PresentationTheme public let strings: PresentationStrings - public let state: StoryListContext.State? + public let storySubscriptions: EngineStorySubscriptions? public let collapseFraction: CGFloat public let peerAction: (EnginePeer?) -> Void @@ -21,14 +21,14 @@ public final class StoryPeerListComponent: Component { context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, - state: StoryListContext.State?, + storySubscriptions: EngineStorySubscriptions?, collapseFraction: CGFloat, peerAction: @escaping (EnginePeer?) -> Void ) { self.context = context self.theme = theme self.strings = strings - self.state = state + self.storySubscriptions = storySubscriptions self.collapseFraction = collapseFraction self.peerAction = peerAction } @@ -43,7 +43,7 @@ public final class StoryPeerListComponent: Component { if lhs.strings !== rhs.strings { return false } - if lhs.state != rhs.state { + if lhs.storySubscriptions != rhs.storySubscriptions { return false } if lhs.collapseFraction != rhs.collapseFraction { @@ -102,7 +102,7 @@ public final class StoryPeerListComponent: Component { private var ignoreScrolling: Bool = false private var itemLayout: ItemLayout? - private var sortedItemSets: [StoryListContext.PeerItemSet] = [] + private var sortedItems: [EngineStorySubscriptions.Item] = [] private var visibleItems: [EnginePeer.Id: VisibleItem] = [:] private let title = ComponentView() @@ -110,6 +110,9 @@ public final class StoryPeerListComponent: Component { private var component: StoryPeerListComponent? private weak var state: EmptyComponentState? + private var requestedLoadMoreToken: String? + private let loadMoreDisposable = MetaDisposable() + public override init(frame: CGRect) { self.collapsedButton = HighlightableButton() @@ -153,6 +156,10 @@ public final class StoryPeerListComponent: Component { preconditionFailure() } + deinit { + self.loadMoreDisposable.dispose() + } + @objc private func collapsedButtonPressed() { guard let component = self.component else { return @@ -182,14 +189,15 @@ public final class StoryPeerListComponent: Component { } var hasStories: Bool = false - if let state = component.state, state.itemSets.count > 1 { + var storyCount = 0 + if let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty { hasStories = true + storyCount = storySubscriptions.items.count } let titleSpacing: CGFloat = 8.0 let titleText: String - let storyCount = self.sortedItemSets.count - 1 if storyCount <= 0 { titleText = "No Stories" } else { @@ -211,7 +219,7 @@ public final class StoryPeerListComponent: Component { let collapsedItemWidth: CGFloat = 24.0 let collapsedItemDistance: CGFloat = 14.0 - let collapsedItemCount: CGFloat = CGFloat(min(self.sortedItemSets.count - collapseStartIndex, 3)) + let collapsedItemCount: CGFloat = CGFloat(min(self.sortedItems.count - collapseStartIndex, 3)) var collapsedContentWidth: CGFloat = 0.0 if collapsedItemCount > 0 { collapsedContentWidth = 1.0 * collapsedItemWidth + (collapsedItemDistance) * max(0.0, collapsedItemCount - 1.0) @@ -257,11 +265,9 @@ public final class StoryPeerListComponent: Component { let visibleBounds = self.scrollView.bounds var validIds: [EnginePeer.Id] = [] - for i in 0 ..< self.sortedItemSets.count { - let itemSet = self.sortedItemSets[i] - guard let peer = itemSet.peer else { - continue - } + for i in 0 ..< self.sortedItems.count { + let itemSet = self.sortedItems[i] + let peer = itemSet.peer let regularItemFrame = itemLayout.frame(at: i) if !visibleBounds.intersects(regularItemFrame) { @@ -271,30 +277,32 @@ public final class StoryPeerListComponent: Component { //} } - validIds.append(itemSet.peerId) + validIds.append(itemSet.peer.id) let visibleItem: VisibleItem var itemTransition = transition - if let current = self.visibleItems[itemSet.peerId] { + if let current = self.visibleItems[itemSet.peer.id] { visibleItem = current } else { itemTransition = .immediate visibleItem = VisibleItem() - self.visibleItems[itemSet.peerId] = visibleItem + self.visibleItems[itemSet.peer.id] = visibleItem } var hasUnseen = false - let hasItems = !itemSet.items.isEmpty + hasUnseen = itemSet.hasUnseen + + var hasItems = true var itemProgress: CGFloat? if peer.id == component.context.account.peerId { - itemProgress = component.state?.uploadProgress - //itemProgress = 0.0 - } - - for item in itemSet.items { - if item.id > itemSet.maxReadId { - hasUnseen = true + itemProgress = nil + if let storySubscriptions = component.storySubscriptions, let accountItem = storySubscriptions.accountItem { + hasItems = accountItem.storyCount != 0 + } else { + hasItems = false } + //itemProgress = component.state?.uploadProgress + //itemProgress = 0.0 } let collapsedItemFrame = CGRect(origin: CGPoint(x: collapsedContentOrigin + CGFloat(i - collapseStartIndex) * collapsedItemDistance, y: regularItemFrame.minY + collapsedItemOffsetY), size: CGSize(width: collapsedItemWidth, height: regularItemFrame.height)) @@ -406,24 +414,28 @@ public final class StoryPeerListComponent: Component { self.component = component self.state = state + if let storySubscriptions = component.storySubscriptions, let hasMoreToken = storySubscriptions.hasMoreToken { + if self.requestedLoadMoreToken != hasMoreToken { + self.requestedLoadMoreToken = hasMoreToken + if let storySubscriptionsContext = component.context.account.storySubscriptionsContext { + storySubscriptionsContext.loadMore() + } + } + } + self.collapsedButton.isUserInteractionEnabled = component.collapseFraction >= 1.0 - .ulpOfOne - self.sortedItemSets.removeAll(keepingCapacity: true) - if let state = component.state { - if let myIndex = state.itemSets.firstIndex(where: { $0.peerId == component.context.account.peerId }) { - self.sortedItemSets.append(state.itemSets[myIndex]) + self.sortedItems.removeAll(keepingCapacity: true) + if let storySubscriptions = component.storySubscriptions { + if let accountItem = storySubscriptions.accountItem { + self.sortedItems.append(accountItem) } - for i in 0 ..< 1 { - for itemSet in state.itemSets { - if itemSet.peerId == component.context.account.peerId { - continue - } - if i == 0 { - self.sortedItemSets.append(itemSet) - } else { - self.sortedItemSets.append(StoryListContext.PeerItemSet(peerId: EnginePeer.Id(namespace: itemSet.peerId.namespace, id: EnginePeer.Id.Id._internalFromInt64Value(itemSet.peerId.id._internalGetInt64Value() + Int64(i))), peer: itemSet.peer, maxReadId: itemSet.maxReadId, items: itemSet.items, totalCount: itemSet.totalCount)) - } + + for itemSet in storySubscriptions.items { + if itemSet.peer.id == component.context.account.peerId { + continue } + self.sortedItems.append(itemSet) } } @@ -432,7 +444,7 @@ public final class StoryPeerListComponent: Component { containerInsets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0), itemSize: CGSize(width: 60.0, height: 77.0), itemSpacing: 24.0, - itemCount: self.sortedItemSets.count + itemCount: self.sortedItems.count ) self.itemLayout = itemLayout diff --git a/submodules/TelegramUI/Sources/ChatContextResultPeekContentNode.swift b/submodules/TelegramUI/Sources/ChatContextResultPeekContentNode.swift index 1bbde22823..c1dd40fcea 100644 --- a/submodules/TelegramUI/Sources/ChatContextResultPeekContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatContextResultPeekContentNode.swift @@ -172,7 +172,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont imageDimensions = externalReference.content?.dimensions?.cgSize if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = imageResource , let dimensions = content.dimensions { - videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])])) + videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)])) imageResource = nil } case let .internalReference(internalReference): diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 5235878a23..d4a294eff3 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -338,7 +338,7 @@ func messageMediaEditingOptions(message: Message) -> MessageMediaEditingOptions return [] case .Animated: return [] - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { return [] } else { diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift index 31d1d1060f..dcd36a252d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift @@ -258,7 +258,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.mediaBackgroundNode.image = backgroundImage if let image = image, let video = image.videoRepresentations.last, let id = image.id?.id { - let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 5887353ef3..623b851752 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -585,7 +585,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let messageTheme = arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing let isInstantVideo = arguments.file.isInstantVideo for attribute in arguments.file.attributes { - if case let .Video(videoDuration, _, flags) = attribute, flags.contains(.instantRoundVideo) { + if case let .Video(videoDuration, _, flags, _) = attribute, flags.contains(.instantRoundVideo) { isAudio = true isVoice = true @@ -1439,7 +1439,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { var isVoice = false var audioDuration: Int32? for attribute in file.attributes { - if case let .Video(duration, _, flags) = attribute, flags.contains(.instantRoundVideo) { + if case let .Video(duration, _, flags, _) = attribute, flags.contains(.instantRoundVideo) { isAudio = true isVoice = true audioDuration = Int32(duration) diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index fd012872ba..269f23b620 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -86,7 +86,7 @@ private func mediaMergeableStyle(_ media: Media) -> ChatMessageMerge { switch attribute { case .Sticker: return .semanticallyMerged - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { return .none } @@ -462,7 +462,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { viewClassName = ChatMessageStickerItemNode.self } break loop - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { // viewClassName = ChatMessageInstantVideoItemNode.self viewClassName = ChatMessageBubbleItemNode.self diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index 5555d4e440..aefcf7c7c2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -299,7 +299,7 @@ final class ChatMessageAccessibilityData { text = item.presentationData.strings.VoiceOver_Chat_MusicTitle(title, performer).string text.append(item.presentationData.strings.VoiceOver_Chat_Duration(durationString).string) } - case let .Video(duration, _, flags): + case let .Video(duration, _, flags, _): isSpecialFile = true if isSelected == nil { hint = item.presentationData.strings.VoiceOver_Chat_PlayHint diff --git a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift index cec115a682..fcd0fd106c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift @@ -215,7 +215,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode } if let photo = photo, let video = photo.videoRepresentations.last, let id = photo.id?.id { - let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift index 0346088900..7c245e3530 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift @@ -260,7 +260,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } imageDimensions = externalReference.content?.dimensions?.cgSize if externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let content = externalReference.content, let dimensions = content.dimensions { - videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]) + videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)]) imageResource = nil } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 8d88b5b792..80c6aabc17 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -552,7 +552,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0)) markupNode.updateVisibility(true) } else if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { self.videoNode?.removeFromSupernode() @@ -949,7 +949,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { markupNode.removeFromSupernode() } - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { self.videoNode?.removeFromSupernode() diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift index 71884a94e7..da7240e65a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift @@ -368,7 +368,8 @@ private final class PeerInfoPendingPane { let paneNode: PeerInfoPaneNode switch key { case .stories: - let visualPaneNode = PeerInfoStoryPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected) + //let visualPaneNode = PeerInfoStoryPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected) + let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected) paneNode = visualPaneNode visualPaneNode.openCurrentDate = { openMediaCalendar() diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index 5166e2061e..44d712c54c 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -72,7 +72,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem { } else { return SharedMediaPlaybackData(type: .music, source: source) } - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { return SharedMediaPlaybackData(type: .instantVideo, source: source) } else { @@ -129,7 +129,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem { displayData = SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: albumArt, long: CGFloat(duration) > 10.0 * 60.0, caption: caption) } return displayData - case let .Video(_, _, flags): + case let .Video(_, _, flags, _): if flags.contains(.instantRoundVideo) { return SharedMediaPlaybackDisplayData.instantVideo(author: self.message.effectiveAuthor.flatMap(EnginePeer.init), peer: self.message.peers[self.message.id.peerId].flatMap(EnginePeer.init), timestamp: self.message.timestamp) } else { diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index c5e1f9464f..254e0a6e6e 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -369,13 +369,13 @@ public final class TelegramRootController: NavigationController, TelegramRootCon return } - if let chatListController = self.chatListController as? ChatListControllerImpl, let storyListContext = chatListController.storyListContext { + if let chatListController = self.chatListController as? ChatListControllerImpl { switch mediaResult { case let .image(image, dimensions, caption): if let imageData = compressImageToJPEG(image, quality: 0.6) { switch privacy { case let .story(storyPrivacy, _): - storyListContext.upload(media: .image(dimensions: dimensions, data: imageData), text: caption?.string ?? "", entities: [], privacy: storyPrivacy) + let _ = self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption?.string ?? "", entities: [], privacy: storyPrivacy).start() Queue.mainQueue().after(0.2, { [weak chatListController] in chatListController?.animateStoryUploadRipple() }) @@ -456,7 +456,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments)) } if case let .story(storyPrivacy, _) = privacy { - storyListContext.upload(media: .video(dimensions: dimensions, duration: Int(duration), resource: resource), text: caption?.string ?? "", entities: [], privacy: storyPrivacy) + let _ = self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: Int(duration), resource: resource), text: caption?.string ?? "", entities: [], privacy: storyPrivacy).start() Queue.mainQueue().after(0.2, { [weak chatListController] in chatListController?.animateStoryUploadRipple() }) diff --git a/submodules/WatchBridge/Sources/WatchBridge.swift b/submodules/WatchBridge/Sources/WatchBridge.swift index 953ab45513..f5162f7b77 100644 --- a/submodules/WatchBridge/Sources/WatchBridge.swift +++ b/submodules/WatchBridge/Sources/WatchBridge.swift @@ -172,7 +172,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P for attribute in file.attributes { switch attribute { - case let .Video(duration, size, flags): + case let .Video(duration, size, flags, _): bridgeVideo.duration = Int32(clamping: duration) bridgeVideo.dimensions = size.cgSize bridgeVideo.round = flags.contains(.instantRoundVideo) diff --git a/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift b/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift index 3574de778f..097eb64e41 100644 --- a/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift @@ -37,7 +37,7 @@ struct WebSearchGalleryEntry: Equatable { switch self.result { case let .externalReference(externalReference): if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let dimensions = content.dimensions { - let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])])) + let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)])) return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, index: self.index, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), userLocation: .other, fileReference: fileReference, loopVideo: true, enableSound: false, fetchAutomatically: true, storeAfterDownload: nil), controllerInteraction: controllerInteraction) } case let .internalReference(internalReference): diff --git a/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift b/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift index 64d45a51e4..a17977a4d4 100644 --- a/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift +++ b/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift @@ -22,7 +22,7 @@ public extension WidgetDataPeer.Message { switch attribute { case let .Sticker(altText, _, _): content = .sticker(WidgetDataPeer.Message.Content.Sticker(altText: altText)) - case let .Video(duration, _, flags): + case let .Video(duration, _, flags, _): if flags.contains(.instantRoundVideo) { content = .videoMessage(WidgetDataPeer.Message.Content.VideoMessage(duration: Int32(duration))) } else {