From b51dd938f44a0e348bbed299b07e421a1f519378 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 10 Jul 2020 18:36:28 +0300 Subject: [PATCH] Video avatar fixes --- .../Sources/AvatarGalleryController.swift | 41 ++-- .../Sources/PeerAvatarImageGalleryItem.swift | 6 +- .../SyncCore/Sources/CachedGroupData.swift | 39 +++- .../Sources/UpdateCachedPeerData.swift | 3 + .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 209 +++++++++++------- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- .../Sources/GenericEmbedImplementation.swift | 8 +- 7 files changed, 202 insertions(+), 106 deletions(-) diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 31c75ac133..c4ce047bca 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -23,12 +23,12 @@ public enum AvatarGalleryEntryId: Hashable { } public enum AvatarGalleryEntry: Equatable { - case topImage([ImageRepresentationWithReference], GalleryItemIndexData?, Data?, String?) + case topImage([ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], GalleryItemIndexData?, Data?, String?) case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], Peer?, Int32, GalleryItemIndexData?, MessageId?, Data?, String?) public var id: AvatarGalleryEntryId { switch self { - case let .topImage(representations, _, _, _): + case let .topImage(representations, _, _, _, _): if let last = representations.last { return .resource(last.representation.resource.id.uniqueId) } @@ -43,7 +43,7 @@ public enum AvatarGalleryEntry: Equatable { public var representations: [ImageRepresentationWithReference] { switch self { - case let .topImage(representations, _, _, _): + case let .topImage(representations, _, _, _, _): return representations case let .image(_, _, representations, _, _, _, _, _, _, _): return representations @@ -52,8 +52,8 @@ public enum AvatarGalleryEntry: Equatable { public var videoRepresentations: [TelegramMediaImage.VideoRepresentation] { switch self { - case .topImage: - return [] + case let .topImage(_, videoRepresentations, _, _, _): + return videoRepresentations case let .image(_, _, _, videoRepresentations, _, _, _, _, _, _): return videoRepresentations } @@ -61,7 +61,7 @@ public enum AvatarGalleryEntry: Equatable { public var indexData: GalleryItemIndexData? { switch self { - case let .topImage(_, indexData, _, _): + case let .topImage(_, _, indexData, _, _): return indexData case let .image(_, _, _, _, _, _, indexData, _, _, _): return indexData @@ -70,8 +70,8 @@ public enum AvatarGalleryEntry: Equatable { public static func ==(lhs: AvatarGalleryEntry, rhs: AvatarGalleryEntry) -> Bool { switch lhs { - case let .topImage(lhsRepresentations, lhsIndexData, lhsImmediateThumbnailData, lhsCategory): - if case let .topImage(rhsRepresentations, rhsIndexData, rhsImmediateThumbnailData, rhsCategory) = rhs, lhsRepresentations == rhsRepresentations, lhsIndexData == rhsIndexData, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory { + case let .topImage(lhsRepresentations, lhsVideoRepresentations, lhsIndexData, lhsImmediateThumbnailData, lhsCategory): + if case let .topImage(rhsRepresentations, rhsVideoRepresentations, rhsIndexData, rhsImmediateThumbnailData, rhsCategory) = rhs, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, lhsIndexData == rhsIndexData, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory { return true } else { return false @@ -102,8 +102,8 @@ public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryE var index: Int32 = 0 for entry in entries { let indexData = GalleryItemIndexData(position: index, totalCount: count) - if case let .topImage(representations, _, immediateThumbnailData, category) = entry { - updatedEntries.append(.topImage(representations, indexData, immediateThumbnailData, category)) + if case let .topImage(representations, videoRepresentations, _, immediateThumbnailData, category) = entry { + updatedEntries.append(.topImage(representations, videoRepresentations, indexData, immediateThumbnailData, category)) } else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId, immediateThumbnailData, category) = entry { updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId, immediateThumbnailData, category)) } @@ -115,15 +115,24 @@ public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryE public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<[AvatarGalleryEntry], NoError> { var initialEntries: [AvatarGalleryEntry] = [] if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer) { - initialEntries.append(.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), nil, nil, nil)) + initialEntries.append(.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), [], nil, nil, nil)) } - if let peer = peer as? TelegramChannel { + if peer is TelegramChannel || peer is TelegramGroup { return account.postbox.transaction { transaction in return transaction.getPeerCachedData(peerId: peer.id) } |> map { cachedData in - if let cachedData = cachedData as? CachedChannelData, let photo = cachedData.photo { - return [.image(photo.imageId, photo.reference, photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.videoRepresentations, peer, 0, nil, nil, photo.immediateThumbnailData, nil)] + var initialPhoto: TelegramMediaImage? + if let cachedData = cachedData as? CachedGroupData, let photo = cachedData.photo { + initialPhoto = photo + } + else if let cachedData = cachedData as? CachedChannelData, let photo = cachedData.photo { + initialPhoto = photo + } + + if let photo = initialPhoto { + return [.topImage(photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.videoRepresentations, nil, nil, nil)] +// return [.image(photo.imageId, photo.reference, photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.videoRepresentations, peer, 0, nil, nil, photo.immediateThumbnailData, nil)] } else { return initialEntries } @@ -233,7 +242,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private let editDisposable = MetaDisposable () - public init(context: AccountContext, peer: Peer, sourceHasRoundCorners: Bool = true, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { + public init(context: AccountContext, peer: Peer, sourceHasRoundCorners: Bool = true, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { self.context = context self.peer = peer self.sourceHasRoundCorners = sourceHasRoundCorners @@ -256,7 +265,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr remoteEntriesSignal = fetchedAvatarGalleryEntries(account: context.account, peer: peer) } - let entriesSignal: Signal<[AvatarGalleryEntry], NoError> = initialAvatarGalleryEntries(account: context.account, peer: peer) |> then(remoteEntriesSignal) + let entriesSignal: Signal<[AvatarGalleryEntry], NoError> = skipInitial ? remoteEntriesSignal : (initialAvatarGalleryEntries(account: context.account, peer: peer) |> then(remoteEntriesSignal)) let presentationData = self.presentationData diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index 12fe994097..a077c2b3ee 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -104,7 +104,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { func thumbnailItem() -> (Int64, GalleryThumbnailItem)? { let content: [ImageRepresentationWithReference] switch self.entry { - case let .topImage(representations, _, _, _): + case let .topImage(representations, _, _, _, _): content = representations case let .image(_, _, representations, _, _, _, _, _, _, _): content = representations @@ -256,6 +256,8 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if case let .image(image) = entry { id = image.0.id category = image.9 + } else { + id = 1 } if let video = entry.videoRepresentations.last, let id = id { if video != previousVideoRepresentations?.last { @@ -551,7 +553,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { case .Remote: let representations: [ImageRepresentationWithReference] switch entry { - case let .topImage(topRepresentations, _, _, _): + case let .topImage(topRepresentations, _, _, _, _): representations = topRepresentations case let .image(_, _, imageRepresentations, _, _, _, _, _, _, _): representations = imageRepresentations diff --git a/submodules/SyncCore/Sources/CachedGroupData.swift b/submodules/SyncCore/Sources/CachedGroupData.swift index 78a43607d7..695e5511a2 100644 --- a/submodules/SyncCore/Sources/CachedGroupData.swift +++ b/submodules/SyncCore/Sources/CachedGroupData.swift @@ -49,6 +49,7 @@ public final class CachedGroupData: CachedPeerData { public let flags: CachedGroupFlags public let hasScheduledMessages: Bool public let invitedBy: PeerId? + public let photo: TelegramMediaImage? public let peerIds: Set public let messageIds: Set @@ -66,9 +67,10 @@ public final class CachedGroupData: CachedPeerData { self.flags = CachedGroupFlags() self.hasScheduledMessages = false self.invitedBy = nil + self.photo = nil } - public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags, hasScheduledMessages: Bool, invitedBy: PeerId?) { + public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags, hasScheduledMessages: Bool, invitedBy: PeerId?, photo: TelegramMediaImage?) { self.participants = participants self.exportedInvitation = exportedInvitation self.botInfos = botInfos @@ -78,6 +80,7 @@ public final class CachedGroupData: CachedPeerData { self.flags = flags self.hasScheduledMessages = hasScheduledMessages self.invitedBy = invitedBy + self.photo = photo var messageIds = Set() if let pinnedMessageId = self.pinnedMessageId { @@ -123,6 +126,12 @@ public final class CachedGroupData: CachedPeerData { self.invitedBy = decoder.decodeOptionalInt64ForKey("invBy").flatMap(PeerId.init) + if let photo = decoder.decodeObjectForKey("ph", decoder: { TelegramMediaImage(decoder: $0) }) as? TelegramMediaImage { + self.photo = photo + } else { + self.photo = nil + } + var messageIds = Set() if let pinnedMessageId = self.pinnedMessageId { messageIds.insert(pinnedMessageId) @@ -181,6 +190,12 @@ public final class CachedGroupData: CachedPeerData { } else { encoder.encodeNil(forKey: "invBy") } + + if let photo = self.photo { + encoder.encodeObject(photo, forKey: "ph") + } else { + encoder.encodeNil(forKey: "ph") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -192,38 +207,42 @@ public final class CachedGroupData: CachedPeerData { } public func withUpdatedParticipants(_ participants: CachedGroupParticipants?) -> CachedGroupData { - return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy) + return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo) } public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy) + return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo) } public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo) } public func withUpdatedAbout(_ about: String?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo) } public func withUpdatedFlags(_ flags: CachedGroupFlags) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: hasScheduledMessages, invitedBy: self.invitedBy) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo) } public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: invitedBy) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: invitedBy, photo: self.photo) + } + + public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedGroupData { + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: photo) } } diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index 07a5eed43d..55726f9f60 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -250,6 +250,8 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI } } + let photo: TelegramMediaImage? = chatFull.chatPhoto.flatMap(telegramMediaImageFromApiPhoto) + let exportedInvitation = ExportedInvitation(apiExportedInvite: chatFull.exportedInvite) let pinnedMessageId = chatFull.pinnedMsgId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }) @@ -300,6 +302,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI .withUpdatedFlags(flags) .withUpdatedHasScheduledMessages(hasScheduledMessages) .withUpdatedInvitedBy(invitedBy) + .withUpdatedPhoto(photo) }) case .channelFull: break diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 37d4b8da4e..b5bcb810ab 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -158,12 +158,12 @@ final class PeerInfoHeaderNavigationTransition { } enum PeerInfoAvatarListItem: Equatable { - case topImage([ImageRepresentationWithReference], Data?) + case topImage([ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], Data?) case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], Data?) var id: WrappedMediaResourceId { switch self { - case let .topImage(representations, _): + case let .topImage(representations, _, _): let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation return WrappedMediaResourceId(representation.resource.id) case let .image(_, representations, _, _): @@ -171,6 +171,15 @@ enum PeerInfoAvatarListItem: Equatable { return WrappedMediaResourceId(representation.resource.id) } } + + var videoRepresentations: [TelegramMediaImage.VideoRepresentation] { + switch self { + case let .topImage(_, videoRepresentations, _): + return videoRepresentations + case let .image(_, _, videoRepresentations, _): + return videoRepresentations + } + } } final class PeerInfoAvatarListItemNode: ASDisplayNode { @@ -250,15 +259,15 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { let immediateThumbnailData: Data? var id: Int64? switch item { - case let .topImage(topRepresentations, immediateThumbnail): + case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail): representations = topRepresentations - videoRepresentations = [] + videoRepresentations = videoRepresentationsValue immediateThumbnailData = immediateThumbnail + id = 1 case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): representations = imageRepresentations videoRepresentations = videoRepresentationsValue immediateThumbnailData = immediateThumbnail - if case let .cloud(imageId, _, _) = reference { id = imageId } @@ -761,9 +770,9 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode { var entries: [AvatarGalleryEntry] = [] for entry in self.galleryEntries { switch entry { - case let .topImage(representations, _, immediateThumbnailData, _): + case let .topImage(representations, videoRepresentations, _, immediateThumbnailData, _): entries.append(entry) - items.append(.topImage(representations, immediateThumbnailData)) + items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) case let .image(id, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): if image.0 == reference { entries.insert(entry, at: 0) @@ -797,9 +806,9 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode { let previousIndex = self.currentIndex for entry in self.galleryEntries { switch entry { - case let .topImage(representations, _, immediateThumbnailData, _): + case let .topImage(representations, videoRepresentations, _, immediateThumbnailData, _): entries.append(entry) - items.append(.topImage(representations, immediateThumbnailData)) + items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): if image.0 != reference { entries.append(entry) @@ -861,8 +870,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode { var items: [PeerInfoAvatarListItem] = [] for entry in entries { switch entry { - case let .topImage(representations, _, immediateThumbnailData, _): - items.append(.topImage(representations, immediateThumbnailData)) + case let .topImage(representations, videoRepresentations, _, immediateThumbnailData, _): + items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData)) } @@ -1112,53 +1121,78 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize)) self.avatarNode.font = avatarPlaceholderFont(size: floor(avatarSize * 16.0 / 37.0)) - - if let item = item, case let .image(reference, representations, videoRepresentations, immediateThumbnailData) = item, let video = videoRepresentations.last, case let .cloud(imageId, _, _) = reference { - let id = imageId - let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) - let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) - if videoContent.id != self.videoContent?.id { - let mediaManager = self.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) - videoNode.isUserInteractionEnabled = false - videoNode.isHidden = true - - if let startTimestamp = video.startTimestamp { - self.videoStartTimestamp = startTimestamp - self.playbackStatusDisposable.set((videoNode.status - |> map { status -> Bool in - if let status = status, case .playing = status.status { - return true - } else { - return false - } - } - |> filter { playing in - return playing - } - |> take(1) - |> deliverOnMainQueue).start(completed: { [weak self] in - if let strongSelf = self { - Queue.mainQueue().after(0.15) { - strongSelf.videoNode?.isHidden = false + + if let item = item { + let representations: [ImageRepresentationWithReference] + let videoRepresentations: [TelegramMediaImage.VideoRepresentation] + let immediateThumbnailData: Data? + var id: Int64? + switch item { + case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail): + representations = topRepresentations + videoRepresentations = videoRepresentationsValue + immediateThumbnailData = immediateThumbnail + id = 1 + case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): + representations = imageRepresentations + videoRepresentations = videoRepresentationsValue + immediateThumbnailData = immediateThumbnail + if case let .cloud(imageId, _, _) = reference { + id = imageId + } + } + + if let video = videoRepresentations.last, let id = id { + let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) + let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) + if videoContent.id != self.videoContent?.id { + let mediaManager = self.context.sharedContext.mediaManager + let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) + videoNode.isUserInteractionEnabled = false + videoNode.isHidden = true + + if let startTimestamp = video.startTimestamp { + self.videoStartTimestamp = startTimestamp + self.playbackStatusDisposable.set((videoNode.status + |> map { status -> Bool in + if let status = status, case .playing = status.status { + return true + } else { + return false } } - })) - } else { - self.videoStartTimestamp = nil - self.playbackStatusDisposable.set(nil) - videoNode.isHidden = false + |> filter { playing in + return playing + } + |> take(1) + |> deliverOnMainQueue).start(completed: { [weak self] in + if let strongSelf = self { + Queue.mainQueue().after(0.15) { + strongSelf.videoNode?.isHidden = false + } + } + })) + } else { + self.videoStartTimestamp = nil + self.playbackStatusDisposable.set(nil) + videoNode.isHidden = false + } + + self.videoContent = videoContent + self.videoNode = videoNode + + let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size)) + let shape = CAShapeLayer() + shape.path = maskPath.cgPath + videoNode.layer.mask = shape + + self.addSubnode(videoNode) } + } else if let videoNode = self.videoNode { + self.videoContent = nil + self.videoNode = nil - self.videoContent = videoContent - self.videoNode = videoNode - - let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size)) - let shape = CAShapeLayer() - shape.path = maskPath.cgPath - videoNode.layer.mask = shape - - self.addSubnode(videoNode) + videoNode.removeFromSupernode() } } else if let videoNode = self.videoNode { self.videoContent = nil @@ -1283,7 +1317,7 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode { iconHidden = true overlayHidden = true } - Queue.mainQueue().after(0.15) { [weak self] in + Queue.mainQueue().after(0.3) { [weak self] in guard let strongSelf = self else { return } @@ -1351,28 +1385,53 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize)) - if let item = item, case let .image(reference, representations, videoRepresentations, immediateThumbnailData) = item, let video = videoRepresentations.last, case let .cloud(imageId, _, _) = reference { - let id = imageId - let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) - let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) - if videoContent.id != self.videoContent?.id { - let mediaManager = self.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) - videoNode.isUserInteractionEnabled = false - videoNode.ownsContentNodeUpdated = { [weak self] owns in - if let strongSelf = self { - strongSelf.videoNode?.isHidden = !owns - } + if let item = item { + let representations: [ImageRepresentationWithReference] + let videoRepresentations: [TelegramMediaImage.VideoRepresentation] + let immediateThumbnailData: Data? + var id: Int64? + switch item { + case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail): + representations = topRepresentations + videoRepresentations = videoRepresentationsValue + immediateThumbnailData = immediateThumbnail + id = 1 + case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): + representations = imageRepresentations + videoRepresentations = videoRepresentationsValue + immediateThumbnailData = immediateThumbnail + if case let .cloud(imageId, _, _) = reference { + id = imageId } - self.videoContent = videoContent - self.videoNode = videoNode + } + + if let video = videoRepresentations.last, let id = id { + let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) + let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) + if videoContent.id != self.videoContent?.id { + let mediaManager = self.context.sharedContext.mediaManager + let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) + videoNode.isUserInteractionEnabled = false + videoNode.ownsContentNodeUpdated = { [weak self] owns in + if let strongSelf = self { + strongSelf.videoNode?.isHidden = !owns + } + } + self.videoContent = videoContent + self.videoNode = videoNode + + let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size)) + let shape = CAShapeLayer() + shape.path = maskPath.cgPath + videoNode.layer.mask = shape + + self.insertSubnode(videoNode, aboveSubnode: self.avatarNode) + } + } else if let videoNode = self.videoNode { + self.videoContent = nil + self.videoNode = nil - let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size)) - let shape = CAShapeLayer() - shape.path = maskPath.cgPath - videoNode.layer.mask = shape - - self.insertSubnode(videoNode, aboveSubnode: self.avatarNode) + videoNode.removeFromSupernode() } } else if let videoNode = self.videoNode { self.videoContent = nil diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index eb16a54410..11fb1333fe 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -2086,7 +2086,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } let entriesPromise = Promise<[AvatarGalleryEntry]>(entries) - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, sourceHasRoundCorners: !strongSelf.headerNode.isAvatarExpanded, remoteEntries: entriesPromise, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, sourceHasRoundCorners: !strongSelf.headerNode.isAvatarExpanded, remoteEntries: entriesPromise, skipInitial: true, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in }) galleryController.openAvatarSetup = { [weak self] completion in self?.openAvatarForEditing(hasRemove: false, completion: completion) diff --git a/submodules/TelegramUniversalVideoContent/Sources/GenericEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/GenericEmbedImplementation.swift index bcdcb85067..436e4161cf 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/GenericEmbedImplementation.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/GenericEmbedImplementation.swift @@ -43,8 +43,12 @@ final class GenericEmbedImplementation: WebEmbedImplementation { self.onPlaybackStarted = onPlaybackStarted updateStatus(self.status) - let html = String(format: htmlTemplate, self.url) - webView.loadHTMLString(html, baseURL: URL(string: "about:blank")) + if self.url.contains("player.twitch.tv/"), let url = URL(string: self.url) { + webView.load(URLRequest(url: url)) + } else { + let html = String(format: htmlTemplate, self.url) + webView.loadHTMLString(html, baseURL: URL(string: "about:blank")) + } userContentController.addUserScript(WKUserScript(source: userScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false))