Merge commit '44e9ff08e7d116a38b49ecaee4164d31c395bc0a'

This commit is contained in:
Ali 2021-07-18 01:03:47 +02:00
commit b888551106
21 changed files with 261 additions and 81 deletions

View File

@ -24,8 +24,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
public let channelDiscussionGroup: ChannelDiscussionGroupStatus public let channelDiscussionGroup: ChannelDiscussionGroupStatus
public let animatedEmojiStickers: [String: [StickerPackItem]] public let animatedEmojiStickers: [String: [StickerPackItem]]
public let forcedResourceStatus: FileMediaResourceStatus? public let forcedResourceStatus: FileMediaResourceStatus?
public let currentlyPlayingMessageId: MessageIndex?
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<PeerId> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) { public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<PeerId> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: MessageIndex? = nil) {
self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadNetworkType = automaticDownloadNetworkType self.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions self.isRecentActions = isRecentActions
@ -34,6 +35,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.channelDiscussionGroup = channelDiscussionGroup self.channelDiscussionGroup = channelDiscussionGroup
self.animatedEmojiStickers = animatedEmojiStickers self.animatedEmojiStickers = animatedEmojiStickers
self.forcedResourceStatus = forcedResourceStatus self.forcedResourceStatus = forcedResourceStatus
self.currentlyPlayingMessageId = currentlyPlayingMessageId
} }
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {

View File

@ -119,11 +119,11 @@ public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayer
public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? {
if isGlobalSearch { if isGlobalSearch {
return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id)) return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
} else if isRecentActions { } 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 { } else {
return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
} }
} }

View File

@ -135,14 +135,16 @@ public func areSharedMediaPlaylistItemIdsEqual(_ lhs: SharedMediaPlaylistItemId?
public struct PeerMessagesMediaPlaylistItemId: SharedMediaPlaylistItemId { public struct PeerMessagesMediaPlaylistItemId: SharedMediaPlaylistItemId {
public let messageId: MessageId public let messageId: MessageId
public let messageIndex: MessageIndex
public init(messageId: MessageId) { public init(messageId: MessageId, messageIndex: MessageIndex) {
self.messageId = messageId self.messageId = messageId
self.messageIndex = messageIndex
} }
public func isEqual(to: SharedMediaPlaylistItemId) -> Bool { public func isEqual(to: SharedMediaPlaylistItemId) -> Bool {
if let to = to as? PeerMessagesMediaPlaylistItemId { if let to = to as? PeerMessagesMediaPlaylistItemId {
if self.messageId != to.messageId { if self.messageId != to.messageId || self.messageIndex != to.messageIndex {
return false return false
} }
return true return true

View File

@ -234,6 +234,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
} }
var apparentHeight: CGFloat = 0.0 var apparentHeight: CGFloat = 0.0
public private(set) var apparentHeightTransition: (CGFloat, CGFloat)?
private var _bounds: CGRect = CGRect() private var _bounds: CGRect = CGRect()
private var _position: CGPoint = CGPoint() 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) { 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 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 { if let strongSelf = self {
strongSelf.apparentHeight = currentValue strongSelf.apparentHeight = currentValue

View File

@ -1414,7 +1414,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
return 64; return 64;
case TGMediaVideoConversionPresetVideoMessage: case TGMediaVideoConversionPresetVideoMessage:
return 32; return 64;
case TGMediaVideoConversionPresetAnimation: case TGMediaVideoConversionPresetAnimation:
case TGMediaVideoConversionPresetProfile: case TGMediaVideoConversionPresetProfile:

View File

@ -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] { var videoRepresentations: [VideoRepresentationWithReference] {
switch self { switch self {
case .custom: case .custom:
@ -1128,6 +1140,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if self.currentIndex >= 0 && self.currentIndex < self.items.count { if self.currentIndex >= 0 && self.currentIndex < self.items.count {
let preloadSpan: Int = 2 let preloadSpan: Int = 2
for i in max(0, self.currentIndex - preloadSpan) ... min(self.currentIndex + preloadSpan, self.items.count - 1) { 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) validIds.append(self.items[i].id)
var itemNode: PeerInfoAvatarListItemNode? var itemNode: PeerInfoAvatarListItemNode?
var wasAdded = false var wasAdded = false

View File

@ -148,12 +148,18 @@ public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, E>(queue: Que
}, initialValues: [:], queue: queue) }, initialValues: [:], queue: queue)
} }
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), E> { public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>) -> 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 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) 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) }, initialValues: [:], queue: queue)
} }
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>, _ s12: Signal<T12, E>) -> 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<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> { public func combineLatest<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> {
if signals.count == 0 { if signals.count == 0 {
return single([T](), E.self) return single([T](), E.self)

View File

@ -77,6 +77,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
public var tempVoicePlaylistEnded: (() -> Void)? public var tempVoicePlaylistEnded: (() -> Void)?
public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)?
public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem?
public var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? public var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
@ -150,6 +151,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
updatedVoiceItem = playlistStateAndType.1.item updatedVoiceItem = playlistStateAndType.1.item
} }
strongSelf.tempVoicePlaylistCurrentItem = updatedVoiceItem
strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem) strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem)
if let playlistStateAndType = playlistStateAndType { if let playlistStateAndType = playlistStateAndType {
strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0) strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0)

View File

@ -663,7 +663,7 @@ public struct PresentationResourcesChat {
return theme.image(PresentationResourceKey.chatInstantMessageInfoBackgroundImage.rawValue, { theme in return theme.image(PresentationResourceKey.chatInstantMessageInfoBackgroundImage.rawValue, { theme in
return generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in return generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) 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)) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})?.stretchableImage(withLeftCapWidth: 12, topCapHeight: 12) })?.stretchableImage(withLeftCapWidth: 12, topCapHeight: 12)
}) })

View File

@ -566,6 +566,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var openMessageByAction: Bool = false var openMessageByAction: Bool = false
for media in message.media { 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 { if let action = media as? TelegramMediaAction {
switch action.action { switch action.action {
case .pinnedMessageUpdated: case .pinnedMessageUpdated:
@ -4192,6 +4198,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
override public func loadDisplayNode() { 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) 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 self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -7032,6 +7042,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
private var returnInputViewFocus = false
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
#if DEBUG #if DEBUG
if #available(iOSApplicationExtension 12.0, iOS 12.0, *) { if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
@ -7110,16 +7122,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.voicePlaylistDidEndTimestamp = CACurrentMediaTime() strongSelf.voicePlaylistDidEndTimestamp = CACurrentMediaTime()
raiseToListen.activateBasedOnProximity(delay: 0.0) raiseToListen.activateBasedOnProximity(delay: 0.0)
} }
if strongSelf.returnInputViewFocus {
strongSelf.returnInputViewFocus = false
strongSelf.chatDisplayNode.ensureInputViewFocused()
}
} }
self.tempVoicePlaylistItemChanged = { [weak self] previousItem, currentItem in self.tempVoicePlaylistItemChanged = { [weak self] previousItem, currentItem in
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else { guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
return 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.chatDisplayNode.historyNode.voicePlaylistItemChanged(previousItem, currentItem)
strongSelf.navigateToMessage(from: nil, to: .id(currentItem.messageId, nil), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil) // 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)
// }
// }
// }
} }
} }

View File

@ -99,7 +99,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
} else { } else {
selection = .none 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 { } else {
let selection: ChatHistoryMessageSelection let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages { if let selectedMessages = selectedMessages {
@ -107,7 +107,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
} else { } else {
selection = .none 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 { } else {
let selection: ChatHistoryMessageSelection let selection: ChatHistoryMessageSelection
@ -116,7 +116,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
} else { } else {
selection = .none 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 { if messages.count > 1, let groupInfo = messages[0].groupInfo {
var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = [] var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = []
for message in messages { 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) entries.insert(.MessageGroupEntry(groupInfo, groupMessages, presentationData), at: 0)
} else { } 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 let replyCount = view.entries.isEmpty ? 0 : 1

View File

@ -17,12 +17,14 @@ public struct ChatMessageEntryAttributes: Equatable {
let isContact: Bool let isContact: Bool
let contentTypeHint: ChatMessageEntryContentType let contentTypeHint: ChatMessageEntryContentType
let updatingMedia: ChatUpdatingMessageMedia? 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.rank = rank
self.isContact = isContact self.isContact = isContact
self.contentTypeHint = contentTypeHint self.contentTypeHint = contentTypeHint
self.updatingMedia = updatingMedia self.updatingMedia = updatingMedia
self.isPlaying = isPlaying
} }
public init() { public init() {
@ -30,6 +32,7 @@ public struct ChatMessageEntryAttributes: Equatable {
self.isContact = false self.isContact = false
self.contentTypeHint = .generic self.contentTypeHint = .generic
self.updatingMedia = nil self.updatingMedia = nil
self.isPlaying = false
} }
} }

View File

@ -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 automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel
var contactsPeerIds: Set<PeerId> = Set() var contactsPeerIds: Set<PeerId> = Set()
var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown 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 ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId)
return associatedData
} }
private extension ChatHistoryLocationInput { private extension ChatHistoryLocationInput {
@ -528,7 +527,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
} }
} }
private let currentlyPlayingMessageIdPromise = ValuePromise<MessageIndex?>(nil)
private var appliedPlayingMessageId: MessageIndex? = nil
private(set) var isScrollAtBottomPosition = false private(set) var isScrollAtBottomPosition = false
public var isScrollAtBottomPositionUpdated: (() -> Void)? public var isScrollAtBottomPositionUpdated: (() -> Void)?
@ -832,8 +834,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
self.pendingRemovedMessagesPromise.get(), self.pendingRemovedMessagesPromise.get(),
animatedEmojiStickers, animatedEmojiStickers,
customChannelDiscussionReadState, customChannelDiscussionReadState,
customThreadOutgoingReadState customThreadOutgoingReadState,
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState in self.currentlyPlayingMessageIdPromise.get()
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId in
func applyHole() { func applyHole() {
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {
@ -916,7 +919,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
reverse = reverseValue 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 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 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) assert(update.1 >= previousVersion)
} }
if scrollPosition == nil, let originalScrollPosition = originalScrollPosition { if scrollPosition == nil, let originalScrollPosition = originalScrollPosition {
switch originalScrollPosition { switch originalScrollPosition {
case let .index(index, position, _, _, highlight): 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) let mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, transition: rawTransition)
Queue.mainQueue().async { Queue.mainQueue().async {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if strongSelf.appliedPlayingMessageId != currentlyPlayingMessageId {
strongSelf.appliedPlayingMessageId = currentlyPlayingMessageId
}
strongSelf.enqueueHistoryViewTransition(mappedTransition) strongSelf.enqueueHistoryViewTransition(mappedTransition)
} }
} }
@ -2290,6 +2303,15 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
self.selectionScrollDisplayLink?.isPaused = false 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? private var currentSendAnimationCorrelationId: Int64?
func setCurrentSendAnimationCorrelationId(_ value: Int64?) { func setCurrentSendAnimationCorrelationId(_ value: Int64?) {
self.currentSendAnimationCorrelationId = value self.currentSendAnimationCorrelationId = value

View File

@ -5,7 +5,7 @@ import SwiftSignalKit
import Display import Display
import UniversalMediaPlayer 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 { private struct ChatInstantVideoMessageDurationNodeState: Equatable {
let hours: Int32? let hours: Int32?

View File

@ -520,8 +520,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout refineContentImageLayout = refineLayout
} else if file.isInstantVideo { } 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 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 initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight
contentInstantVideoSizeAndApply = (videoLayout, apply) contentInstantVideoSizeAndApply = (videoLayout, apply)
} else if file.isVideo { } else if file.isVideo {

View File

@ -31,8 +31,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
private var swipeToReplyFeedback: HapticFeedback? private var swipeToReplyFeedback: HapticFeedback?
private var appliedParams: ListViewItemLayoutParams?
private var appliedItem: ChatMessageItem? private var appliedItem: ChatMessageItem?
private var appliedForwardInfo: (Peer?, String?)? private var appliedForwardInfo: (Peer?, String?)?
private var appliedHasAvatar = false
private var appliedCurrentlyPlaying = false
private var appliedAutomaticDownload = false
private var forwardInfoNode: ChatMessageForwardInfoNode? private var forwardInfoNode: ChatMessageForwardInfoNode?
private var forwardBackgroundNode: ASImageNode? private var forwardBackgroundNode: ASImageNode?
@ -217,6 +221,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
let currentItem = self.appliedItem let currentItem = self.appliedItem
let currentForwardInfo = self.appliedForwardInfo let currentForwardInfo = self.appliedForwardInfo
let currentPlaying = self.appliedCurrentlyPlaying
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil) let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
@ -318,7 +323,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
deliveryFailedInset += 24.0 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 var automaticDownload = true
for media in item.message.media { for media in item.message.media {
@ -332,7 +343,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
isReplyThread = true 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) 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 return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _ in
if let strongSelf = self { 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 let transition: ContainedViewLayoutTransition
if animation.isAnimated { if animation.isAnimated {
transition = .animated(duration: 0.2, curve: .spring) transition = .animated(duration: 0.2, curve: .spring)
} else { } else {
transition = .immediate 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 let videoLayoutData: ChatMessageInstantVideoItemLayoutData
if incoming { if incoming {
videoLayoutData = .constrained(left: 0.0, right: max(0.0, availableContentWidth - videoFrame.width)) videoLayoutData = .constrained(left: 0.0, right: max(0.0, availableContentWidth - videoFrame.width))
} else { } else {
videoLayoutData = .constrained(left: max(0.0, availableContentWidth - videoFrame.width), right: 0.0) 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.contextSourceNode.contentRect = videoFrame
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
@ -1024,4 +1045,75 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) {
self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) 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)
}
} }

View File

@ -83,6 +83,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
} }
} }
private var animating = false
override init() { override init() {
self.secretVideoPlaceholderBackground = ASImageNode() self.secretVideoPlaceholderBackground = ASImageNode()
self.secretVideoPlaceholderBackground.isLayerBacked = true self.secretVideoPlaceholderBackground.isLayerBacked = true
@ -130,7 +132,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
self.view.addGestureRecognizer(recognizer) 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 previousFile = self.media
let currentItem = self.item let currentItem = self.item
@ -138,7 +140,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let makeDateAndStatusLayout = self.dateAndStatusNode.asyncLayout() 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 secretVideoPlaceholderBackgroundImage: UIImage?
var updatedInfoBackgroundImage: UIImage? var updatedInfoBackgroundImage: UIImage?
var updatedMuteIconImage: UIImage? var updatedMuteIconImage: UIImage?
@ -163,7 +165,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
secretVideoPlaceholderBackgroundImage = PresentationResourcesChat.chatInstantVideoBackgroundImage(theme.theme, wallpaper: !theme.wallpaper.isEmpty) 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 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) 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 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.height += dateAndStatusSize.height + 2.0
contentSize.width = max(contentSize.width, dateAndStatusSize.width) contentSize.width = max(contentSize.width, dateAndStatusSize.width)
dateAndStatusOverflow = true 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 return (result, { [weak self] layoutData, transition in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
strongSelf.videoFrame = videoFrame strongSelf.videoFrame = displayVideoFrame
strongSelf.secretProgressIcon = secretProgressIcon strongSelf.secretProgressIcon = secretProgressIcon
strongSelf.automaticDownload = automaticDownload strongSelf.automaticDownload = automaticDownload
@ -327,10 +334,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if let infoBackgroundImage = strongSelf.infoBackgroundNode.image, let muteImage = strongSelf.muteIconNode.image { if let infoBackgroundImage = strongSelf.infoBackgroundNode.image, let muteImage = strongSelf.muteIconNode.image {
let infoWidth = muteImage.size.width 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)) 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))
transition.updateFrame(node: strongSelf.infoBackgroundNode, frame: infoBackgroundFrame) strongSelf.infoBackgroundNode.frame = infoBackgroundFrame
let muteIconFrame = CGRect(origin: CGPoint(x: infoBackgroundFrame.width - muteImage.size.width, y: 0.0), size: muteImage.size) 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 { if let updatedFile = updatedFile, updatedMedia {
@ -340,21 +347,21 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.fetchedThumbnailDisposable.set(nil) strongSelf.fetchedThumbnailDisposable.set(nil)
} }
} }
dateAndStatusApply(false) dateAndStatusApply(false)
switch layoutData { switch layoutData {
case let .unconstrained(width): case let .unconstrained(width):
let dateAndStatusOrigin: CGPoint let dateAndStatusOrigin: CGPoint
if dateAndStatusOverflow { 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 { } 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) strongSelf.dateAndStatusNode.frame = CGRect(origin: dateAndStatusOrigin, size: dateAndStatusSize)
case let .constrained(_, right): 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<MediaPlayerStatus?, NoError>? var updatedPlayerStatusSignal: Signal<MediaPlayerStatus?, NoError>?
if let telegramFile = updatedFile { if let telegramFile = updatedFile {
if updatedMedia { if updatedMedia {
@ -471,7 +478,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
} }
if let durationNode = strongSelf.durationNode { 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 durationNode.isSeen = !notConsumed
let size = durationNode.size let size = durationNode.size
if let durationBackgroundNode = strongSelf.durationBackgroundNode, size.width > 1.0 { if let durationBackgroundNode = strongSelf.durationBackgroundNode, size.width > 1.0 {
@ -481,12 +488,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
} }
if let videoNode = strongSelf.videoNode { 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) 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 strongSelf.secretVideoPlaceholder.frame = placeholderFrame
let makeSecretPlaceholderLayout = strongSelf.secretVideoPlaceholder.asyncLayout() let makeSecretPlaceholderLayout = strongSelf.secretVideoPlaceholder.asyncLayout()
let arguments = TransformImageArguments(corners: ImageCorners(radius: placeholderFrame.size.width / 2.0), imageSize: placeholderFrame.size, boundingSize: placeholderFrame.size, intrinsicInsets: UIEdgeInsets()) 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 { if self.statusNode == nil {
let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.message.mediaOverlayControlColors.fillColor) let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.message.mediaOverlayControlColors.fillColor)
self.isUserInteractionEnabled = false 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.statusNode = statusNode
self.addSubnode(statusNode) self.addSubnode(statusNode)
} }
@ -671,7 +680,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if let current = self.playbackStatusNode { if let current = self.playbackStatusNode {
playbackStatusNode = current playbackStatusNode = current
} else { } 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.addSubnode(playbackStatusNode)
self.playbackStatusNode = playbackStatusNode self.playbackStatusNode = playbackStatusNode
} }
@ -800,16 +809,16 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
return nil 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() let makeLayout = node?.asyncLayout()
return { item, width, displaySize, statusType, automaticDownload in return { item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload in
var createdNode: ChatMessageInteractiveInstantVideoNode? var createdNode: ChatMessageInteractiveInstantVideoNode?
let sizeAndApplyLayout: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) let sizeAndApplyLayout: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void)
if let makeLayout = makeLayout { if let makeLayout = makeLayout {
sizeAndApplyLayout = makeLayout(item, width, displaySize, statusType, automaticDownload) sizeAndApplyLayout = makeLayout(item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload)
} else { } else {
let node = ChatMessageInteractiveInstantVideoNode() let node = ChatMessageInteractiveInstantVideoNode()
sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, statusType, automaticDownload) sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload)
createdNode = node createdNode = node
} }
return (sizeAndApplyLayout.0, { [weak node] layoutData, transition in return (sizeAndApplyLayout.0, { [weak node] layoutData, transition in

View File

@ -93,7 +93,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode {
let lineWidth: CGFloat = 4.0 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) 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 path.lineWidth = lineWidth

View File

@ -37,7 +37,7 @@ final class OverlayInstantVideoDecoration: UniversalVideoDecoration {
self.contentContainerNode.clipsToBounds = true self.contentContainerNode.clipsToBounds = true
self.foregroundContainerNode = ASDisplayNode() 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.foregroundContainerNode.addSubnode(self.progressNode)
self.foregroundNode = self.foregroundContainerNode self.foregroundNode = self.foregroundContainerNode
} }

View File

@ -59,7 +59,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem {
let message: Message let message: Message
init(message: Message) { init(message: Message) {
self.id = PeerMessagesMediaPlaylistItemId(messageId: message.id) self.id = PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)
self.message = message self.message = message
} }

View File

@ -7,7 +7,7 @@ import Display
import MergeLists import MergeLists
import AccountContext 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)]) var mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)])
let allUpdated = fromView?.associatedData != toView.associatedData let allUpdated = fromView?.associatedData != toView.associatedData
if reverse { if reverse {
@ -137,13 +137,15 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
var scrolledToIndex: MessageHistoryAnchorIndex? var scrolledToIndex: MessageHistoryAnchorIndex?
var scrolledToSomeIndex = false var scrolledToSomeIndex = false
let curve: ListViewAnimationCurve = scrollAnimationCurve ?? .Default(duration: nil)
if let scrollPosition = scrollPosition { if let scrollPosition = scrollPosition {
switch scrollPosition { switch scrollPosition {
case let .unread(unreadIndex): case let .unread(unreadIndex):
var index = toView.filteredEntries.count - 1 var index = toView.filteredEntries.count - 1
for entry in toView.filteredEntries { for entry in toView.filteredEntries {
if case .UnreadEntry = entry { 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 break
} }
index -= 1 index -= 1
@ -153,7 +155,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
var index = toView.filteredEntries.count - 1 var index = toView.filteredEntries.count - 1
for entry in toView.filteredEntries { for entry in toView.filteredEntries {
if entry.index >= unreadIndex { 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 break
} }
index -= 1 index -= 1
@ -164,7 +166,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
var index = 0 var index = 0
for entry in toView.filteredEntries.reversed() { for entry in toView.filteredEntries.reversed() {
if entry.index < unreadIndex { 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 break
} }
index += 1 index += 1
@ -174,7 +176,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
var index = toView.filteredEntries.count - 1 var index = toView.filteredEntries.count - 1
for entry in toView.filteredEntries { for entry in toView.filteredEntries {
if entry.index >= scrollIndex { 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 break
} }
index -= 1 index -= 1
@ -184,7 +186,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
var index = 0 var index = 0
for entry in toView.filteredEntries.reversed() { for entry in toView.filteredEntries.reversed() {
if entry.index < scrollIndex { 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 break
} }
index += 1 index += 1
@ -197,7 +199,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
var index = toView.filteredEntries.count - 1 var index = toView.filteredEntries.count - 1
for entry in toView.filteredEntries { for entry in toView.filteredEntries {
if scrollIndex.isLessOrEqual(to: entry.index) { 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 break
} }
index -= 1 index -= 1
@ -208,7 +210,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
for entry in toView.filteredEntries.reversed() { for entry in toView.filteredEntries.reversed() {
if !scrollIndex.isLess(than: entry.index) { if !scrollIndex.isLess(than: entry.index) {
scrolledToSomeIndex = true 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 break
} }
index += 1 index += 1