diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 97995bad9a..9b2abf30a7 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -666,6 +666,18 @@ final class MutableMessageHistoryView { hasChanges = true } } + if !transaction.storyEvents.isEmpty { + var updatedStories = Set() + for event in transaction.storyEvents { + switch event { + case let .updated(id): + updatedStories.insert(id) + } + } + if loadedState.updateStories(postbox: postbox, updatedStories: updatedStories) { + hasChanges = true + } + } } if hasChanges { diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index 68a8703e82..cec31265a6 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -1457,6 +1457,63 @@ final class HistoryViewLoadedState { return updated } + func updateStories(postbox: PostboxImpl, updatedStories: Set) -> Bool { + var updated = false + for space in self.orderedEntriesBySpace.keys { + let spaceUpdated = self.orderedEntriesBySpace[space]!.mutableScan({ entry in + switch entry { + case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers): + let message = value.message + var rebuild = false + for media in message.media { + for storyId in media.storyIds { + if updatedStories.contains(storyId) { + rebuild = true + break + } + } + } + for attribute in message.attributes { + for storyId in attribute.associatedStoryIds { + if updatedStories.contains(storyId) { + rebuild = true + break + } + } + } + + if rebuild { + let messageMedia: [Media] = message.media + var associatedStories: [StoryId: CodableEntry] = [:] + for media in message.media { + for storyId in media.storyIds { + if associatedStories[storyId] == nil, let value = postbox.storyTable.get(id: storyId) { + associatedStories[storyId] = value + } + } + } + for attribute in message.attributes { + for storyId in attribute.associatedStoryIds { + if associatedStories[storyId] == nil, let value = postbox.storyTable.get(id: storyId) { + associatedStories[storyId] = value + } + } + } + let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds, associatedMedia: message.associatedMedia, associatedThreadInfo: message.associatedThreadInfo, associatedStories: associatedStories) + return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) + } + case .IntermediateMessageEntry: + break + } + return nil + }) + if spaceUpdated { + updated = true + } + } + return updated + } + func add(entry: MutableMessageHistoryEntry) -> Bool { if let ignoreMessagesInTimestampRange = self.ignoreMessagesInTimestampRange { if ignoreMessagesInTimestampRange.contains(entry.index.timestamp) { diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 137ad7238c..baf12fa36c 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -308,6 +308,7 @@ public final class AccountViewTracker { private var updatedUnsupportedMediaMessageIdsAndTimestamps: [MessageId: Int32] = [:] private var refreshSecretChatMediaMessageIdsAndTimestamps: [MessageId: Int32] = [:] + private var refreshStoriesForMessageIdsAndTimestamps: [MessageId: Int32] = [:] private var nextUpdatedUnsupportedMediaDisposableId: Int32 = 0 private var updatedUnsupportedMediaDisposables = DisposableDict() @@ -1229,6 +1230,71 @@ public final class AccountViewTracker { } } + public func refreshStoriesForMessageIds(messageIds: Set) { + self.queue.async { + var addedMessageIds: [MessageId] = [] + let timestamp = Int32(CFAbsoluteTimeGetCurrent()) + for messageId in messageIds { + let messageTimestamp = self.refreshStoriesForMessageIdsAndTimestamps[messageId] + if messageTimestamp == nil { + self.refreshStoriesForMessageIdsAndTimestamps[messageId] = timestamp + addedMessageIds.append(messageId) + } + } + if !addedMessageIds.isEmpty { + for (_, messageIds) in messagesIdsGroupedByPeerId(Set(addedMessageIds)) { + let disposableId = self.nextUpdatedUnsupportedMediaDisposableId + self.nextUpdatedUnsupportedMediaDisposableId += 1 + + if let account = self.account { + let signal = account.postbox.transaction { transaction -> Set in + var result = Set() + for id in messageIds { + if let message = transaction.getMessage(id) { + for media in message.media { + if let storyMedia = media as? TelegramMediaStory { + result.insert(storyMedia.storyId) + } + } + } + } + return result + } + |> mapToSignal { ids -> Signal in + guard !ids.isEmpty else { + return .complete() + } + + var requests: [Signal] = [] + + var idsGroupedByPeerId: [PeerId: Set] = [:] + for id in ids { + if idsGroupedByPeerId[id.peerId] == nil { + idsGroupedByPeerId[id.peerId] = Set([id.id]) + } else { + idsGroupedByPeerId[id.peerId]?.insert(id.id) + } + } + + for (peerId, ids) in idsGroupedByPeerId { + requests.append(_internal_refreshStories(account: account, peerId: peerId, ids: Array(ids))) + } + + return combineLatest(requests) + |> ignoreValues + } + |> afterDisposed { [weak self] in + self?.queue.async { + self?.updatedUnsupportedMediaDisposables.set(nil, forKey: disposableId) + } + } + self.updatedUnsupportedMediaDisposables.set(signal.start(), forKey: disposableId) + } + } + } + } + } + public func updateMarkAllMentionsSeen(peerId: PeerId, threadId: Int64?) { self.queue.async { guard let account = self.account else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 50c57f475b..ff352c23e5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1491,3 +1491,36 @@ func _internal_exportStoryLink(account: Account, peerId: EnginePeer.Id, id: Int3 } } } + +func _internal_refreshStories(account: Account, peerId: PeerId, ids: [Int32]) -> Signal { + return _internal_getStoriesById(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, peerId: peerId, ids: ids) + |> mapToSignal { result -> Signal in + return 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) + + for id in ids { + let current = transaction.getStory(id: StoryId(peerId: peerId, id: id)) + var updated: CodableEntry? + if let updatedItem = result.first(where: { $0.id == id }) { + if let entry = CodableEntry(updatedItem) { + updated = entry + } + } + if current != updated { + transaction.setStory(id: StoryId(peerId: peerId, id: id), value: updated ?? CodableEntry(data: Data())) + } + } + } + |> ignoreValues + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 805b7d9fc0..a3b9667b44 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -862,23 +862,7 @@ public extension TelegramEngine { } 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 - } + return _internal_refreshStories(account: self.account, peerId: peerId, ids: ids) } public func refreshStoryViews(peerId: EnginePeer.Id, ids: [Int32]) -> Signal { diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index b7b71bc3bf..2c21713e0a 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -42,9 +42,21 @@ public final class AvatarStoryIndicatorComponent: Component { self.component = component self.state = state - let lineWidth: CGFloat = 3.0 + let lineWidth: CGFloat + let diameter: CGFloat + let outerInset: CGFloat - self.indicatorView.image = generateImage(CGSize(width: availableSize.width + lineWidth * 2.0, height: availableSize.width + lineWidth * 2.0), rotatedContext: { size, context in + if component.hasUnseen { + lineWidth = 3.0 + outerInset = 3.0 + lineWidth + diameter = availableSize.width + outerInset * 2.0 + } else { + lineWidth = 2.0 + outerInset = 3.0 + lineWidth + diameter = availableSize.width + outerInset * 2.0 + } + + self.indicatorView.image = generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setLineWidth(lineWidth) @@ -65,7 +77,7 @@ public final class AvatarStoryIndicatorComponent: Component { context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) }) - transition.setFrame(view: self.indicatorView, frame: CGRect(origin: CGPoint(), size: availableSize).insetBy(dx: -lineWidth * 2.0, dy: -lineWidth * 2.0)) + transition.setFrame(view: self.indicatorView, frame: CGRect(origin: CGPoint(), size: availableSize).insetBy(dx: -outerInset, dy: -outerInset)) return availableSize } diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift index abe410c220..113aa42e3d 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift @@ -137,6 +137,20 @@ enum ChatHistoryEntry: Identifiable, Comparable { } } } + if lhsMessage.associatedStories.count != rhsMessage.associatedStories.count { + return false + } + if !lhsMessage.associatedStories.isEmpty { + for (id, story) in lhsMessage.associatedStories { + if let otherStory = rhsMessage.associatedStories[id] { + if story != otherStory { + return false + } + } else { + return false + } + } + } if lhsSelection != rhsSelection { return false } @@ -194,6 +208,20 @@ enum ChatHistoryEntry: Identifiable, Comparable { } } } + if lhsMessage.associatedStories.count != rhsMessage.associatedStories.count { + return false + } + if !lhsMessage.associatedStories.isEmpty { + for (id, story) in lhsMessage.associatedStories { + if let otherStory = rhsMessage.associatedStories[id] { + if story != otherStory { + return false + } + } else { + return false + } + } + } if lhsAttributes != rhsAttributes { return false } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 46d8f9a1c6..c932bd4a69 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -523,6 +523,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let unseenReactionsProcessingManager = ChatMessageThrottledProcessingManager(delay: 0.2, submitInterval: 0.0) private let extendedMediaProcessingManager = ChatMessageVisibleThrottledProcessingManager(interval: 5.0) private let translationProcessingManager = ChatMessageThrottledProcessingManager(submitInterval: 1.0) + private let refreshStoriesProcessingManager = ChatMessageThrottledProcessingManager() let prefetchManager: InChatPrefetchManager private var currentEarlierPrefetchMessages: [(Message, Media)] = [] @@ -793,6 +794,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.refreshMediaProcessingManager.process = { [weak context] messageIds in context?.account.viewTracker.refreshSecretMediaMediaForMessageIds(messageIds: messageIds) } + self.refreshStoriesProcessingManager.process = { [weak context] messageIds in + context?.account.viewTracker.refreshStoriesForMessageIds(messageIds: messageIds) + } self.translationProcessingManager.process = { [weak self, weak context] messageIds in if let context = context, let toLang = self?.toLang { let _ = translateMessageIds(context: context, messageIds: Array(messageIds), toLang: toLang).start() @@ -2043,6 +2047,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var messageIdsWithLiveLocation: [MessageId] = [] var messageIdsWithUnsupportedMedia: [MessageId] = [] var messageIdsWithRefreshMedia: [MessageId] = [] + var messageIdsWithRefreshStories: [MessageId] = [] var messageIdsWithUnseenPersonalMention: [MessageId] = [] var messageIdsWithUnseenReactions: [MessageId] = [] var messageIdsWithInactiveExtendedMedia = Set() @@ -2068,6 +2073,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var contentRequiredValidation = false var mediaRequiredValidation = false var hasUnseenReactions = false + var storiesRequiredValidation = false for attribute in message.attributes { if attribute is ViewCountMessageAttribute { if message.id.namespace == Namespaces.Message.Cloud { @@ -2126,6 +2132,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if invoice.version != TelegramMediaInvoice.lastVersion { contentRequiredValidation = true } + } else if let story = media as? TelegramMediaStory { + if message.associatedStories[story.storyId] == nil { + storiesRequiredValidation = true + } } } if contentRequiredValidation { @@ -2134,6 +2144,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if mediaRequiredValidation { messageIdsWithRefreshMedia.append(message.id) } + if storiesRequiredValidation { + messageIdsWithRefreshStories.append(message.id) + } if hasUnconsumedMention && !hasUnconsumedContent { messageIdsWithUnseenPersonalMention.append(message.id) } @@ -2329,6 +2342,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if !messageIdsWithRefreshMedia.isEmpty { self.refreshMediaProcessingManager.add(messageIdsWithRefreshMedia) } + if !messageIdsWithRefreshStories.isEmpty { + self.refreshStoriesProcessingManager.add(messageIdsWithRefreshStories) + } if !messageIdsWithUnseenPersonalMention.isEmpty { self.messageMentionProcessingManager.add(messageIdsWithUnseenPersonalMention) } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index c5f75a54ed..b98c6e659c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -586,6 +586,14 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } else if let media = media as? TelegramMediaImage, let resource = largestImageRepresentation(media.representations)?.resource { messageMediaImageCancelInteractiveFetch(context: context, messageId: message.id, image: media, resource: resource) } + } else if let storyMedia = media as? TelegramMediaStory, let storyItem = message.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) { + if case let .item(item) = storyItem, let media = item.media { + if let media = media as? TelegramMediaFile { + messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: media) + } else if let media = media as? TelegramMediaImage, let resource = largestImageRepresentation(media.representations)?.resource { + messageMediaImageCancelInteractiveFetch(context: context, messageId: message.id, image: media, resource: resource) + } + } } } if let cancel = self.fetchControls.with({ return $0?.cancel }) { @@ -615,6 +623,13 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia { media = fullMedia } + + if let storyMedia = media as? TelegramMediaStory, let storyItem = self.message?.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) { + if case let .item(item) = storyItem, let mediaValue = item.media { + media = mediaValue + } + } + videoContentMatch = self.message?.stableId == stableId && media?.id == mediaId } self.activateLocalContent((self.automaticPlayback ?? false) && videoContentMatch ? .automaticPlayback : .default) @@ -626,6 +641,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } else { if let invoice = self.media as? TelegramMediaInvoice, let _ = invoice.extendedMedia { self.activateLocalContent(.default) + } else if let storyMedia = media as? TelegramMediaStory, let storyItem = self.message?.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) { + if case let .item(item) = storyItem, let mediaValue = item.media { + let _ = mediaValue + self.activateLocalContent(.default) + } } else { self.progressPressed(canActivate: true) } @@ -682,7 +702,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio var maxHeight = layoutConstants.image.maxDimensions.height var unboundSize: CGSize - if let image = media as? TelegramMediaImage, let dimensions = largestImageRepresentation(image.representations)?.dimensions { + if let _ = media as? TelegramMediaStory { + unboundSize = CGSize(width: 1080, height: 1920) + } else if let image = media as? TelegramMediaImage, let dimensions = largestImageRepresentation(image.representations)?.dimensions { unboundSize = CGSize(width: max(10.0, floor(dimensions.cgSize.width * 0.5)), height: max(10.0, floor(dimensions.cgSize.height * 0.5))) } else if let file = media as? TelegramMediaFile, var dimensions = file.dimensions { if let thumbnail = file.previewRepresentations.first { @@ -888,6 +910,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio var mediaUpdated = false if let currentMedia = currentMedia { mediaUpdated = !media.isSemanticallyEqual(to: currentMedia) + + if !mediaUpdated, let media = media as? TelegramMediaStory { + if message.associatedStories[media.storyId] != currentMessage?.associatedStories[media.storyId] { + mediaUpdated = true + } + } } else { mediaUpdated = true } @@ -958,7 +986,134 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } - if let image = media as? TelegramMediaImage { + if let story = media as? TelegramMediaStory { + if hasCurrentVideoNode { + replaceVideoNode = true + } + if hasCurrentAnimatedStickerNode { + replaceAnimatedStickerNode = true + } + + if let storyItem = message.associatedStories[story.storyId]?.get(Stories.StoredItem.self), case let .item(item) = storyItem, let media = item.media { + if let image = media as? TelegramMediaImage { + if hasCurrentVideoNode { + replaceVideoNode = true + } + if hasCurrentAnimatedStickerNode { + replaceAnimatedStickerNode = true + } + if isSecretMedia { + updateImageSignal = { synchronousLoad, _ in + return chatSecretPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image)) + } + } else { + updateImageSignal = { synchronousLoad, highQuality in + return chatMessagePhoto(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image), synchronousLoad: synchronousLoad, highQuality: highQuality) + } + updateBlurredImageSignal = { synchronousLoad, _ in + return chatSecretPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image), ignoreFullSize: true, synchronousLoad: true) + } + } + + updatedFetchControls = FetchControls(fetch: { manual in + if let strongSelf = self { + if let representation = largestRepresentationForPhoto(image) { + strongSelf.fetchDisposable.set(messageMediaImageInteractiveFetched(context: context, message: message, image: image, resource: representation.resource, range: representationFetchRangeForDisplayAtSize(representation: representation, dimension: nil/*isSecretMedia ? nil : 600*/), userInitiated: manual, storeToDownloadsPeerId: storeToDownloadsPeerId).start()) + } + } + }, cancel: { + chatMessagePhotoCancelInteractiveFetch(account: context.account, photoReference: .message(message: MessageReference(message), media: image)) + if let resource = largestRepresentationForPhoto(image)?.resource { + messageMediaImageCancelInteractiveFetch(context: context, messageId: message.id, image: image, resource: resource) + } + }) + } else if let file = media as? TelegramMediaFile { + if isSecretMedia { + updateImageSignal = { synchronousLoad, _ in + return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file)) + } + } else { + if file.isAnimatedSticker { + let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) + updateImageSignal = { synchronousLoad, _ in + return chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))) + } + } else if file.isSticker || file.isVideoSticker { + updateImageSignal = { synchronousLoad, _ in + return chatMessageSticker(account: context.account, userLocation: .peer(message.id.peerId), file: file, small: false) + } + } else { + onlyFullSizeVideoThumbnail = isSendingUpdated + updateImageSignal = { synchronousLoad, _ in + return mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), onlyFullSize: currentMedia?.id?.namespace == Namespaces.Media.LocalFile, autoFetchFullSizeThumbnail: true) + } + updateBlurredImageSignal = { synchronousLoad, _ in + return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: true) + } + } + } + + var uploading = false + if file.resource is VideoLibraryMediaResource { + uploading = true + } + + if file.isVideo && !file.isVideoSticker && !isSecretMedia && automaticPlayback && !uploading { + updateVideoFile = file + if hasCurrentVideoNode { + if let currentFile = currentMedia as? TelegramMediaFile { + if currentFile.resource is EmptyMediaResource { + replaceVideoNode = true + } else if currentFile.fileId.namespace == Namespaces.Media.CloudFile && file.fileId.namespace == Namespaces.Media.CloudFile && currentFile.fileId != file.fileId { + replaceVideoNode = true + } else if currentFile.fileId != file.fileId && file.fileId.namespace == Namespaces.Media.CloudSecretFile { + replaceVideoNode = true + } else if file.isAnimated && currentFile.fileId.namespace == Namespaces.Media.LocalFile && file.fileId.namespace == Namespaces.Media.CloudFile { + replaceVideoNode = true + } + } + } else if !(file.resource is LocalFileVideoMediaResource) { + replaceVideoNode = true + } + } else { + if hasCurrentVideoNode { + replaceVideoNode = false + } + + if file.isAnimatedSticker || file.isVideoSticker { + updateAnimatedStickerFile = file + if hasCurrentAnimatedStickerNode { + if let currentMedia = currentMedia { + if !currentMedia.isSemanticallyEqual(to: file) { + replaceAnimatedStickerNode = true + } + } else { + replaceAnimatedStickerNode = true + } + } else { + replaceAnimatedStickerNode = true + } + } + } + + updatedFetchControls = FetchControls(fetch: { manual in + if let strongSelf = self { + if file.isAnimated { + strongSelf.fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: file), reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes)).start()) + } else { + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: manual, storeToDownloadsPeerId: storeToDownloadsPeerId).start()) + } + } + }, cancel: { + if file.isAnimated { + context.account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) + } else { + messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) + } + }) + } + } + } else if let image = media as? TelegramMediaImage { if hasCurrentVideoNode { replaceVideoNode = true } @@ -1149,6 +1304,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio isExtendedMedia = true media = fullMedia } + if let storyMedia = media as? TelegramMediaStory, let storyItem = message.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) { + if case let .item(item) = storyItem, let mediaValue = item.media { + media = mediaValue + } + } if let image = media as? TelegramMediaImage { if message.flags.isSending { @@ -1426,6 +1586,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia { media = fullMedia } + if let storyMedia = media as? TelegramMediaStory, let storyItem = message.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) { + if case let .item(item) = storyItem, let mediaValue = item.media { + media = mediaValue + } + } if case .full = automaticDownload { if let _ = media as? TelegramMediaImage { @@ -1670,6 +1835,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia { media = fullMedia } + if let storyMedia = media as? TelegramMediaStory, let storyItem = message.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) { + if case let .item(item) = storyItem, let mediaValue = item.media { + media = mediaValue + } + } switch fetchStatus { case let .Fetching(_, progress): diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index ac979bf2a4..4f99945eb8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -92,14 +92,13 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { automaticDownload = .full } } else if let telegramStory = media as? TelegramMediaStory { + selectedMedia = telegramStory if let storyMedia = item.message.associatedStories[telegramStory.storyId], case let .item(storyItem) = storyMedia.get(Stories.StoredItem.self), let media = storyItem.media { if let telegramImage = media as? TelegramMediaImage { - selectedMedia = telegramImage if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) { automaticDownload = .full } } else if let telegramFile = media as? TelegramMediaFile { - selectedMedia = telegramFile if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramFile) { automaticDownload = .full } else if shouldPredownloadMedia(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile) { diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 948ed554ab..888c1da692 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -23,8 +23,35 @@ import UndoUI import WebsiteType import GalleryData import StoryContainerScreen +import StoryContentComponent func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { + for media in params.message.media { + if let media = media as? TelegramMediaStory { + let navigationController = params.navigationController + let context = params.context + let storyContent = SingleStoryContentContextImpl(context: params.context, storyId: media.storyId) + let _ = (storyContent.state + |> take(1) + |> deliverOnMainQueue).start(next: { [weak navigationController] _ in + let transitionIn: StoryContainerScreen.TransitionIn? = nil + + let storyContainerScreen = StoryContainerScreen( + context: context, + content: storyContent, + transitionIn: transitionIn, + transitionOut: { _, _ in + let transitionOut: StoryContainerScreen.TransitionOut? = nil + + return transitionOut + } + ) + navigationController?.pushViewController(storyContainerScreen) + }) + return true + } + } + if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) { switch mediaData { case let .url(url): diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 65b57d9a9d..80d8d560c5 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -3415,6 +3415,11 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateSublayerTransformScale(node: self.avatarListNode.avatarContainerNode, scale: avatarScale) transition.updateSublayerTransformScale(node: self.avatarOverlayNode, scale: avatarScale) } + + if let avatarStoryView = self.avatarListNode.avatarContainerNode.avatarStoryView?.view { + transition.updateAlpha(layer: avatarStoryView.layer, alpha: 1.0 - transitionFraction) + } + let apparentAvatarFrame: CGRect let controlsClippingFrame: CGRect if self.isAvatarExpanded {