diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index a6d2dfe764..d0cf354fd6 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -24,8 +24,9 @@ public final class ChatMessageItemAssociatedData: Equatable { public let channelDiscussionGroup: ChannelDiscussionGroupStatus public let animatedEmojiStickers: [String: [StickerPackItem]] public let forcedResourceStatus: FileMediaResourceStatus? + public let currentlyPlayingMessageId: MessageIndex? - public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) { + public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: MessageIndex? = nil) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadNetworkType = automaticDownloadNetworkType self.isRecentActions = isRecentActions @@ -34,6 +35,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.channelDiscussionGroup = channelDiscussionGroup self.animatedEmojiStickers = animatedEmojiStickers self.forcedResourceStatus = forcedResourceStatus + self.currentlyPlayingMessageId = currentlyPlayingMessageId } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index b9c754ec2f..7f5ef063b0 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -119,11 +119,11 @@ public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayer public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { if isGlobalSearch { - return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id)) + return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)) } else if isRecentActions { - return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) + return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)) } else { - return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) + return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)) } } diff --git a/submodules/AccountContext/Sources/SharedMediaPlayer.swift b/submodules/AccountContext/Sources/SharedMediaPlayer.swift index 4140e3345d..e71f6843b1 100644 --- a/submodules/AccountContext/Sources/SharedMediaPlayer.swift +++ b/submodules/AccountContext/Sources/SharedMediaPlayer.swift @@ -135,14 +135,16 @@ public func areSharedMediaPlaylistItemIdsEqual(_ lhs: SharedMediaPlaylistItemId? public struct PeerMessagesMediaPlaylistItemId: SharedMediaPlaylistItemId { public let messageId: MessageId + public let messageIndex: MessageIndex - public init(messageId: MessageId) { + public init(messageId: MessageId, messageIndex: MessageIndex) { self.messageId = messageId + self.messageIndex = messageIndex } public func isEqual(to: SharedMediaPlaylistItemId) -> Bool { if let to = to as? PeerMessagesMediaPlaylistItemId { - if self.messageId != to.messageId { + if self.messageId != to.messageId || self.messageIndex != to.messageIndex { return false } return true diff --git a/submodules/Display/Source/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift index 2e59bccfd9..0322282174 100644 --- a/submodules/Display/Source/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -234,6 +234,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { } var apparentHeight: CGFloat = 0.0 + public private(set) var apparentHeightTransition: (CGFloat, CGFloat)? private var _bounds: CGRect = CGRect() private var _position: CGPoint = CGPoint() @@ -478,6 +479,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { } public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { + self.apparentHeightTransition = (self.apparentHeight, value) let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] progress, currentValue in if let strongSelf = self { strongSelf.apparentHeight = currentValue diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 5f17313390..65b20a3dd2 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -1414,7 +1414,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, return 64; case TGMediaVideoConversionPresetVideoMessage: - return 32; + return 64; case TGMediaVideoConversionPresetAnimation: case TGMediaVideoConversionPresetProfile: diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index bfd6117b7d..f3b799b26e 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -114,6 +114,18 @@ public enum PeerInfoAvatarListItem: Equatable { } } + var representations: [ImageRepresentationWithReference] { + switch self { + case .custom: + return [] + case let .topImage(representations, _, _): + return representations + case let .image(_, representations, _, _): + return representations + } + } + + var videoRepresentations: [VideoRepresentationWithReference] { switch self { case .custom: @@ -1117,6 +1129,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { if self.currentIndex >= 0 && self.currentIndex < self.items.count { let preloadSpan: Int = 2 for i in max(0, self.currentIndex - preloadSpan) ... min(self.currentIndex + preloadSpan, self.items.count - 1) { + if self.items[i].representations.isEmpty { + continue + } validIds.append(self.items[i].id) var itemNode: PeerInfoAvatarListItemNode? var wasAdded = false diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index 8dc1fed2ed..6f569eac70 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -148,12 +148,18 @@ public func combineLatest(queue: Que }, initialValues: [:], queue: queue) } -public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), E> { +public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), E> { return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11)], combine: { values in return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11) }, initialValues: [:], queue: queue) } +public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12) + }, initialValues: [:], queue: queue) +} + public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { return single([T](), E.self) diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index c5b8d24f32..ce3e450ffc 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -77,6 +77,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { public var tempVoicePlaylistEnded: (() -> Void)? public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? + public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem? public var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? @@ -150,6 +151,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { updatedVoiceItem = playlistStateAndType.1.item } + strongSelf.tempVoicePlaylistCurrentItem = updatedVoiceItem strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem) if let playlistStateAndType = playlistStateAndType { strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index e44f2868b6..6de22efbc3 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -663,7 +663,7 @@ public struct PresentationResourcesChat { return theme.image(PresentationResourceKey.chatInstantMessageInfoBackgroundImage.rawValue, { theme in return generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.chat.message.mediaDateAndStatusFillColor.cgColor) + context.setFillColor(theme.chat.message.mediaDateAndStatusFillColor.withAlphaComponent(0.3).cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) })?.stretchableImage(withLeftCapWidth: 12, topCapHeight: 12) }) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 8f5ca4753c..b77f6af949 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -566,6 +566,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var openMessageByAction: Bool = false for media in message.media { + if let file = media as? TelegramMediaFile, file.isInstantVideo { + if strongSelf.chatDisplayNode.isInputViewFocused { + strongSelf.returnInputViewFocus = true + strongSelf.chatDisplayNode.dismissInput() + } + } if let action = media as? TelegramMediaAction { switch action.action { case .pinnedMessageUpdated: @@ -4192,6 +4198,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G override public func loadDisplayNode() { self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, backgroundNode: self.chatBackgroundNode, controller: self) + if let currentItem = self.tempVoicePlaylistCurrentItem { + self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem) + } + self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode in guard let strongSelf = self else { return @@ -7032,6 +7042,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + private var returnInputViewFocus = false + override public func viewDidAppear(_ animated: Bool) { #if DEBUG if #available(iOSApplicationExtension 12.0, iOS 12.0, *) { @@ -7110,16 +7122,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.voicePlaylistDidEndTimestamp = CACurrentMediaTime() raiseToListen.activateBasedOnProximity(delay: 0.0) } + + if strongSelf.returnInputViewFocus { + strongSelf.returnInputViewFocus = false + strongSelf.chatDisplayNode.ensureInputViewFocused() + } } self.tempVoicePlaylistItemChanged = { [weak self] previousItem, currentItem in guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else { return } - if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId, let previousItem = previousItem?.id as? PeerMessagesMediaPlaylistItemId, previousItem.messageId.peerId == peerId, currentItem.messageId.peerId == peerId, currentItem.messageId != previousItem.messageId { - if strongSelf.chatDisplayNode.historyNode.isMessageVisibleOnScreen(currentItem.messageId) { - strongSelf.navigateToMessage(from: nil, to: .id(currentItem.messageId, nil), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil) - } - } + + strongSelf.chatDisplayNode.historyNode.voicePlaylistItemChanged(previousItem, currentItem) +// if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId { +// self.controllerInteraction?.currentlyPlayingMessageId = currentItem.messageId +// if let previousItem = previousItem?.id as? PeerMessagesMediaPlaylistItemId, previousItem.messageId.peerId == peerId, currentItem.messageId.peerId == peerId, currentItem.messageId != previousItem.messageId { +// if strongSelf.chatDisplayNode.historyNode.isMessageVisibleOnScreen(currentItem.messageId) { +// strongSelf.navigateToMessage(from: nil, to: .id(currentItem.messageId, nil), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil) +// } +// } +// } } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 26c42c8961..99eaf5d0e5 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -99,7 +99,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } else { selection = .none } - groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id]))) + groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false))) } else { let selection: ChatHistoryMessageSelection if let selectedMessages = selectedMessages { @@ -107,7 +107,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } else { selection = .none } - entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id]))) + entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId))) } } else { let selection: ChatHistoryMessageSelection @@ -116,7 +116,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } else { selection = .none } - entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id]))) + entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId))) } } @@ -167,11 +167,11 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, if messages.count > 1, let groupInfo = messages[0].groupInfo { var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = [] for message in messages { - groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id]))) + groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false))) } entries.insert(.MessageGroupEntry(groupInfo, groupMessages, presentationData), at: 0) } else { - entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id])), at: 0) + entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id], isPlaying: false)), at: 0) } let replyCount = view.entries.isEmpty ? 0 : 1 diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift index a3ffe24b0a..95ae7fa8f8 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift @@ -17,12 +17,14 @@ public struct ChatMessageEntryAttributes: Equatable { let isContact: Bool let contentTypeHint: ChatMessageEntryContentType let updatingMedia: ChatUpdatingMessageMedia? + let isPlaying: Bool - init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?) { + init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?, isPlaying: Bool) { self.rank = rank self.isContact = isContact self.contentTypeHint = contentTypeHint self.updatingMedia = updatingMedia + self.isPlaying = isPlaying } public init() { @@ -30,6 +32,7 @@ public struct ChatMessageEntryAttributes: Equatable { self.isContact = false self.contentTypeHint = .generic self.updatingMedia = nil + self.isPlaying = false } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 06d835e155..74be953d83 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -311,7 +311,7 @@ private final class ChatHistoryTransactionOpaqueState { } } -private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], subject: ChatControllerSubject?) -> ChatMessageItemAssociatedData { +private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?) -> ChatMessageItemAssociatedData { var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel var contactsPeerIds: Set = Set() var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown @@ -360,8 +360,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist } } - let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers) - return associatedData + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId) } private extension ChatHistoryLocationInput { @@ -528,7 +527,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } } - + + private let currentlyPlayingMessageIdPromise = ValuePromise(nil) + private var appliedPlayingMessageId: MessageIndex? = nil + private(set) var isScrollAtBottomPosition = false public var isScrollAtBottomPositionUpdated: (() -> Void)? @@ -832,8 +834,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.pendingRemovedMessagesPromise.get(), animatedEmojiStickers, customChannelDiscussionReadState, - customThreadOutgoingReadState - ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState in + customThreadOutgoingReadState, + self.currentlyPlayingMessageIdPromise.get() + ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId in func applyHole() { Queue.mainQueue().async { if let strongSelf = self { @@ -916,7 +919,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { reverse = reverseValue } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, subject: subject) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId) let filteredEntries = chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, pendingRemovedMessages: pendingRemovedMessages, associatedData: associatedData, updatingMedia: updatingMedia, customChannelDiscussionReadState: customChannelDiscussionReadState, customThreadOutgoingReadState: customThreadOutgoingReadState) let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0 @@ -931,7 +934,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } assert(update.1 >= previousVersion) } - + if scrollPosition == nil, let originalScrollPosition = originalScrollPosition { switch originalScrollPosition { case let .index(index, position, _, _, highlight): @@ -970,12 +973,22 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } } - let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, initialData: initialData?.initialData, keyboardButtonsMessage: view.topTaggedMessages.first, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode()) + + var scrollAnimationCurve: ListViewAnimationCurve? = nil + if let strongSelf = self, strongSelf.appliedPlayingMessageId != currentlyPlayingMessageId, let currentlyPlayingMessageId = currentlyPlayingMessageId { + updatedScrollPosition = .index(index: .message(currentlyPlayingMessageId), position: .center(.bottom), directionHint: .Down, animated: true, highlight: true) + scrollAnimationCurve = .Spring(duration: 0.4) + } + + let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: view.topTaggedMessages.first, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode()) let mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, transition: rawTransition) Queue.mainQueue().async { guard let strongSelf = self else { return } + if strongSelf.appliedPlayingMessageId != currentlyPlayingMessageId { + strongSelf.appliedPlayingMessageId = currentlyPlayingMessageId + } strongSelf.enqueueHistoryViewTransition(mappedTransition) } } @@ -2290,6 +2303,15 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.selectionScrollDisplayLink?.isPaused = false } + + func voicePlaylistItemChanged(_ previousItem: SharedMediaPlaylistItem?, _ currentItem: SharedMediaPlaylistItem?) -> Void { + if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId { + self.currentlyPlayingMessageIdPromise.set(currentItem.messageIndex) + } else { + self.currentlyPlayingMessageIdPromise.set(nil) + } + } + private var currentSendAnimationCorrelationId: Int64? func setCurrentSendAnimationCorrelationId(_ value: Int64?) { self.currentSendAnimationCorrelationId = value diff --git a/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift b/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift index 263a553cbc..0387adf15b 100644 --- a/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift +++ b/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift @@ -5,7 +5,7 @@ import SwiftSignalKit import Display import UniversalMediaPlayer -private let textFont = Font.regular(11.0) +private let textFont = Font.with(size: 11.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]) private struct ChatInstantVideoMessageDurationNodeState: Equatable { let hours: Int32? diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index e5ca7a3c71..d01b40ed2e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -520,8 +520,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else if file.isInstantVideo { + let displaySize = CGSize(width: 212.0, height: 212.0) let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) - let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes, isItemPinned: message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 212.0, height: 212.0), .bubble, automaticDownload) + let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes, isItemPinned: message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, displaySize, displaySize, 0.0, .bubble, automaticDownload) initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight contentInstantVideoSizeAndApply = (videoLayout, apply) } else if file.isVideo { diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 473d47fe08..9dc3b1afb5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -31,8 +31,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? + private var appliedParams: ListViewItemLayoutParams? private var appliedItem: ChatMessageItem? private var appliedForwardInfo: (Peer?, String?)? + private var appliedHasAvatar = false + private var appliedCurrentlyPlaying = false + private var appliedAutomaticDownload = false private var forwardInfoNode: ChatMessageForwardInfoNode? private var forwardBackgroundNode: ASImageNode? @@ -217,6 +221,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let currentItem = self.appliedItem let currentForwardInfo = self.appliedForwardInfo + let currentPlaying = self.appliedCurrentlyPlaying return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil) @@ -318,7 +323,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { deliveryFailedInset += 24.0 } - let displaySize = layoutConstants.instantVideo.dimensions + var isPlaying = false + var displaySize = layoutConstants.instantVideo.dimensions + let maximumDisplaySize = CGSize(width: params.width - 20.0, height: params.width - 20.0) + if item.associatedData.currentlyPlayingMessageId == item.message.index { + isPlaying = true + displaySize = maximumDisplaySize + } var automaticDownload = true for media in item.message.media { @@ -332,7 +343,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { isReplyThread = true } - let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, automaticDownload) + let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, isPlaying ? 1.0 : 0.0, .free, automaticDownload) let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize) @@ -495,30 +506,40 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _ in if let strongSelf = self { - strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize) - strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize) - strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize) - strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize) - - strongSelf.appliedItem = item - strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature) - - strongSelf.updateAccessibilityData(accessibilityData) - let transition: ContainedViewLayoutTransition if animation.isAnimated { transition = .animated(duration: 0.2, curve: .spring) } else { transition = .immediate } - strongSelf.interactiveVideoNode.frame = videoFrame + + strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize) + strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize) + strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize) + strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize) + + strongSelf.appliedParams = params + strongSelf.appliedItem = item + strongSelf.appliedHasAvatar = hasAvatar + strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature) + strongSelf.appliedCurrentlyPlaying = isPlaying + + strongSelf.updateAccessibilityData(accessibilityData) + + + let videoLayoutData: ChatMessageInstantVideoItemLayoutData if incoming { videoLayoutData = .constrained(left: 0.0, right: max(0.0, availableContentWidth - videoFrame.width)) } else { videoLayoutData = .constrained(left: max(0.0, availableContentWidth - videoFrame.width), right: 0.0) } - videoApply(videoLayoutData, transition) + + if currentItem != nil && currentPlaying != isPlaying { + } else { + strongSelf.interactiveVideoNode.frame = videoFrame + videoApply(videoLayoutData, transition) + } strongSelf.contextSourceNode.contentRect = videoFrame strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect @@ -1024,4 +1045,75 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) } + + override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { + super.animateFrameTransition(progress, currentValue) + + guard let item = self.appliedItem, let params = self.appliedParams, progress > 0.0, let (initialHeight, targetHeight) = self.apparentHeightTransition else { + return + } + + let layoutConstants = chatMessageItemLayoutConstants(self.layoutConstants, params: params, presentationData: item.presentationData) + let incoming = item.message.effectivelyIncoming(item.context.account.peerId) + + var isReplyThread = false + if case .replyThread = item.chatLocation { + isReplyThread = true + } + + var isPlaying = false + var displaySize = layoutConstants.instantVideo.dimensions + let maximumDisplaySize = CGSize(width: params.width - 20.0, height: params.width - 20.0) + if item.associatedData.currentlyPlayingMessageId == item.message.index { + isPlaying = true + } + + let avatarInset: CGFloat + if self.appliedHasAvatar { + avatarInset = layoutConstants.avatarDiameter + } else { + avatarInset = 0.0 + } + + let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) + var deliveryFailedInset: CGFloat = 0.0 + if isFailed { + deliveryFailedInset += 24.0 + } + + let makeVideoLayout = self.interactiveVideoNode.asyncLayout() + + let initialSize: CGSize + let targetSize: CGSize + let animationProgress: CGFloat = (currentValue - initialHeight) / (targetHeight - initialHeight) + let scaleProgress: CGFloat + if currentValue < targetHeight { + initialSize = displaySize + targetSize = maximumDisplaySize + scaleProgress = animationProgress + } else if currentValue > targetHeight { + initialSize = maximumDisplaySize + targetSize = displaySize + scaleProgress = 1.0 - animationProgress + } else { + initialSize = isPlaying ? maximumDisplaySize : displaySize + targetSize = initialSize + scaleProgress = isPlaying ? 1.0 : 0.0 + } + displaySize = CGSize(width: initialSize.width + (targetSize.width - initialSize.width) * animationProgress, height: initialSize.height + (targetSize.height - initialSize.height) * animationProgress) + + let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, scaleProgress, .free, self.appliedAutomaticDownload) + + let availableContentWidth = params.width - params.leftInset - params.rightInset - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left + let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize) + self.interactiveVideoNode.frame = videoFrame + + let videoLayoutData: ChatMessageInstantVideoItemLayoutData + if incoming { + videoLayoutData = .constrained(left: 0.0, right: max(0.0, availableContentWidth - videoFrame.width)) + } else { + videoLayoutData = .constrained(left: max(0.0, availableContentWidth - videoFrame.width), right: 0.0) + } + videoApply(videoLayoutData, .immediate) + } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 43a8dbb043..b263ed4117 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -83,6 +83,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } + private var animating = false + override init() { self.secretVideoPlaceholderBackground = ASImageNode() self.secretVideoPlaceholderBackground.isLayerBacked = true @@ -130,7 +132,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.view.addGestureRecognizer(recognizer) } - func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) { + func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) { let previousFile = self.media let currentItem = self.item @@ -138,7 +140,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let makeDateAndStatusLayout = self.dateAndStatusNode.asyncLayout() - return { item, width, displaySize, statusDisplayType, automaticDownload in + return { item, width, displaySize, maximumDisplaySize, scaleProgress, statusDisplayType, automaticDownload in var secretVideoPlaceholderBackgroundImage: UIImage? var updatedInfoBackgroundImage: UIImage? var updatedMuteIconImage: UIImage? @@ -163,7 +165,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { secretVideoPlaceholderBackgroundImage = PresentationResourcesChat.chatInstantVideoBackgroundImage(theme.theme, wallpaper: !theme.wallpaper.isEmpty) } - let imageSize = displaySize + let imageSize = maximumDisplaySize + let imageScale = displaySize.width / maximumDisplaySize.width let updatedMessageId = item.message.id != currentItem?.message.id @@ -294,20 +297,24 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring) - var contentSize = imageSize + var displayVideoFrame = videoFrame + displayVideoFrame.size.width *= imageScale + displayVideoFrame.size.height *= imageScale + + var contentSize = displayVideoFrame.size var dateAndStatusOverflow = false - if case .bubble = statusDisplayType, videoFrame.maxX + dateAndStatusSize.width > width { + if case .bubble = statusDisplayType, displayVideoFrame.maxX + dateAndStatusSize.width > width { contentSize.height += dateAndStatusSize.height + 2.0 contentSize.width = max(contentSize.width, dateAndStatusSize.width) dateAndStatusOverflow = true } - let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floor(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width))) + let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floorToScreenPixels(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width))) return (result, { [weak self] layoutData, transition in if let strongSelf = self { strongSelf.item = item - strongSelf.videoFrame = videoFrame + strongSelf.videoFrame = displayVideoFrame strongSelf.secretProgressIcon = secretProgressIcon strongSelf.automaticDownload = automaticDownload @@ -327,10 +334,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let infoBackgroundImage = strongSelf.infoBackgroundNode.image, let muteImage = strongSelf.muteIconNode.image { let infoWidth = muteImage.size.width - let infoBackgroundFrame = CGRect(origin: CGPoint(x: floor(videoFrame.minX + (videoFrame.size.width - infoWidth) / 2.0), y: videoFrame.maxY - infoBackgroundImage.size.height - 8.0), size: CGSize(width: infoWidth, height: infoBackgroundImage.size.height)) - transition.updateFrame(node: strongSelf.infoBackgroundNode, frame: infoBackgroundFrame) + let infoBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(displayVideoFrame.minX + (displayVideoFrame.size.width - infoWidth) / 2.0), y: displayVideoFrame.maxY - infoBackgroundImage.size.height - 8.0), size: CGSize(width: infoWidth, height: infoBackgroundImage.size.height)) + strongSelf.infoBackgroundNode.frame = infoBackgroundFrame let muteIconFrame = CGRect(origin: CGPoint(x: infoBackgroundFrame.width - muteImage.size.width, y: 0.0), size: muteImage.size) - transition.updateFrame(node: strongSelf.muteIconNode, frame: muteIconFrame) + strongSelf.muteIconNode.frame = muteIconFrame } if let updatedFile = updatedFile, updatedMedia { @@ -340,21 +347,21 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { strongSelf.fetchedThumbnailDisposable.set(nil) } } - + dateAndStatusApply(false) switch layoutData { case let .unconstrained(width): let dateAndStatusOrigin: CGPoint if dateAndStatusOverflow { - dateAndStatusOrigin = CGPoint(x: videoFrame.minX - 4.0, y: videoFrame.maxY + 2.0) + dateAndStatusOrigin = CGPoint(x: displayVideoFrame.minX - 4.0, y: displayVideoFrame.maxY + 2.0) } else { - dateAndStatusOrigin = CGPoint(x: min(floor(videoFrame.midX) + 55.0, width - dateAndStatusSize.width - 4.0), y: videoFrame.height - dateAndStatusSize.height) + dateAndStatusOrigin = CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, width - dateAndStatusSize.width - 4.0), y: displayVideoFrame.height - dateAndStatusSize.height) } strongSelf.dateAndStatusNode.frame = CGRect(origin: dateAndStatusOrigin, size: dateAndStatusSize) case let .constrained(_, right): - strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floor(videoFrame.midX) + 55.0, videoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: videoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize) + strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, displayVideoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: displayVideoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize) } - + var updatedPlayerStatusSignal: Signal? if let telegramFile = updatedFile { if updatedMedia { @@ -471,7 +478,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } if let durationNode = strongSelf.durationNode { - durationNode.frame = CGRect(origin: CGPoint(x: videoFrame.midX - 56.0, y: videoFrame.maxY - 18.0), size: CGSize(width: 1.0, height: 1.0)) + durationNode.frame = CGRect(origin: CGPoint(x: displayVideoFrame.midX - 56.0 - 25.0 * scaleProgress, y: displayVideoFrame.maxY - 18.0), size: CGSize(width: 1.0, height: 1.0)) durationNode.isSeen = !notConsumed let size = durationNode.size if let durationBackgroundNode = strongSelf.durationBackgroundNode, size.width > 1.0 { @@ -481,12 +488,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } if let videoNode = strongSelf.videoNode { - videoNode.frame = videoFrame + videoNode.bounds = CGRect(origin: CGPoint(), size: videoFrame.size) + videoNode.transform = CATransform3DMakeScale(imageScale, imageScale, 1.0) + videoNode.position = displayVideoFrame.center videoNode.updateLayout(size: arguments.boundingSize, transition: .immediate) } - strongSelf.secretVideoPlaceholderBackground.frame = videoFrame + strongSelf.secretVideoPlaceholderBackground.frame = displayVideoFrame - let placeholderFrame = videoFrame.insetBy(dx: 2.0, dy: 2.0) + let placeholderFrame = displayVideoFrame.insetBy(dx: 2.0, dy: 2.0) strongSelf.secretVideoPlaceholder.frame = placeholderFrame let makeSecretPlaceholderLayout = strongSelf.secretVideoPlaceholder.asyncLayout() let arguments = TransformImageArguments(corners: ImageCorners(radius: placeholderFrame.size.width / 2.0), imageSize: placeholderFrame.size, boundingSize: placeholderFrame.size, intrinsicInsets: UIEdgeInsets()) @@ -599,7 +608,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if self.statusNode == nil { let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.message.mediaOverlayControlColors.fillColor) self.isUserInteractionEnabled = false - statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floor((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floor((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) + statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floorToScreenPixels((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floorToScreenPixels((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) self.statusNode = statusNode self.addSubnode(statusNode) } @@ -671,7 +680,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let current = self.playbackStatusNode { playbackStatusNode = current } else { - playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.8)) + playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.6)) self.addSubnode(playbackStatusNode) self.playbackStatusNode = playbackStatusNode } @@ -800,16 +809,16 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { return nil } - static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode) { + static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode) { let makeLayout = node?.asyncLayout() - return { item, width, displaySize, statusType, automaticDownload in + return { item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload in var createdNode: ChatMessageInteractiveInstantVideoNode? let sizeAndApplyLayout: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) if let makeLayout = makeLayout { - sizeAndApplyLayout = makeLayout(item, width, displaySize, statusType, automaticDownload) + sizeAndApplyLayout = makeLayout(item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload) } else { let node = ChatMessageInteractiveInstantVideoNode() - sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, statusType, automaticDownload) + sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload) createdNode = node } return (sizeAndApplyLayout.0, { [weak node] layoutData, transition in diff --git a/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift b/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift index fdf26c0233..ff02ac1547 100644 --- a/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift +++ b/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift @@ -93,7 +93,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode { let lineWidth: CGFloat = 4.0 - let pathDiameter = bounds.size.width - lineWidth + let pathDiameter = bounds.size.width - lineWidth - 8.0 let path = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise:true) path.lineWidth = lineWidth diff --git a/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift b/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift index fb02894766..1cab57392c 100644 --- a/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift +++ b/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift @@ -37,7 +37,7 @@ final class OverlayInstantVideoDecoration: UniversalVideoDecoration { self.contentContainerNode.clipsToBounds = true self.foregroundContainerNode = ASDisplayNode() - self.progressNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.8)) + self.progressNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.6)) self.foregroundContainerNode.addSubnode(self.progressNode) self.foregroundNode = self.foregroundContainerNode } diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index f6aade2b53..631e63ce85 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -59,7 +59,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem { let message: Message init(message: Message) { - self.id = PeerMessagesMediaPlaylistItemId(messageId: message.id) + self.id = PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index) self.message = message } diff --git a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift index cf107d2836..386b553b7f 100644 --- a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift +++ b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift @@ -7,7 +7,7 @@ import Display import MergeLists import AccountContext -func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNode?) -> ChatHistoryViewTransition { +func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, scrollAnimationCurve: ListViewAnimationCurve?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNode?) -> ChatHistoryViewTransition { var mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) let allUpdated = fromView?.associatedData != toView.associatedData if reverse { @@ -137,13 +137,15 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var scrolledToIndex: MessageHistoryAnchorIndex? var scrolledToSomeIndex = false + let curve: ListViewAnimationCurve = scrollAnimationCurve ?? .Default(duration: nil) + if let scrollPosition = scrollPosition { switch scrollPosition { case let .unread(unreadIndex): var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { if case .UnreadEntry = entry { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: curve, directionHint: .Down) break } index -= 1 @@ -153,7 +155,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { if entry.index >= unreadIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: curve, directionHint: .Down) break } index -= 1 @@ -164,7 +166,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = 0 for entry in toView.filteredEntries.reversed() { if entry.index < unreadIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: curve, directionHint: .Down) break } index += 1 @@ -174,7 +176,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { if entry.index >= scrollIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .top(relativeOffset), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .top(relativeOffset), animated: false, curve: curve, directionHint: .Down) break } index -= 1 @@ -184,7 +186,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = 0 for entry in toView.filteredEntries.reversed() { if entry.index < scrollIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: curve, directionHint: .Down) break } index += 1 @@ -197,7 +199,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { if scrollIndex.isLessOrEqual(to: entry.index) { - scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) + scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: directionHint) break } index -= 1 @@ -208,7 +210,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie for entry in toView.filteredEntries.reversed() { if !scrollIndex.isLess(than: entry.index) { scrolledToSomeIndex = true - scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) + scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: directionHint) break } index += 1