Merge commit 'cccc13ac499d5e0c47c690b4360850fc8f21ac56'

This commit is contained in:
Ali 2020-07-15 19:25:45 +04:00
commit 61bb79c25e
26 changed files with 376 additions and 144 deletions

1
bazel-Telegram-iOS-Beta Symbolic link
View File

@ -0,0 +1 @@
/private/var/tmp/_bazel_ilya/b5422c0ab62ebf818a9d47ff552063a2/execroot/__main__

View File

@ -250,10 +250,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
}
override func setVisibilityAlpha(_ alpha: CGFloat) {
override func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
self.visibilityAlpha = alpha
self.contentNode.alpha = alpha
self.scrubberView?.setCollapsed(alpha < 1.0 ? true : false, animated: true)
self.scrubberView?.setCollapsed(alpha < 1.0 ? true : false, animated: animated)
}
init(context: AccountContext, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void = { _, _ in }) {

View File

@ -120,13 +120,32 @@ final class ChatVideoGalleryItemScrubberView: UIView {
self.fetchStatusDisposable.dispose()
}
var collapsed: Bool = false
func setCollapsed(_ collapsed: Bool, animated: Bool) {
let alpha: CGFloat = collapsed ? 0.0 : 1.0
guard self.collapsed != collapsed else {
return
}
self.scrubberNode.setCollapsed(collapsed, animated: animated)
self.collapsed = collapsed
let alpha: CGFloat = collapsed ? 0.0 : 1.0
self.leftTimestampNode.alpha = alpha
self.rightTimestampNode.alpha = alpha
self.infoNode.alpha = alpha
self.updateScrubberVisibility(animated: animated)
}
private func updateScrubberVisibility(animated: Bool) {
var collapsed = self.collapsed
var alpha: CGFloat = 1.0
if let playbackStatus = self.playbackStatus, playbackStatus.duration <= 30.0 {
} else {
alpha = self.collapsed ? 0.0 : 1.0
collapsed = false
}
self.scrubberNode.setCollapsed(collapsed, animated: animated)
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
transition.updateAlpha(node: self.scrubberNode, alpha: alpha)
}
func setStatusSignal(_ status: Signal<MediaPlayerStatus, NoError>?) {
@ -232,7 +251,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
self.containerLayout = (size, leftInset, rightInset)
let scrubberHeight: CGFloat = 14.0
let scrubberInset: CGFloat
var scrubberInset: CGFloat
let leftTimestampOffset: CGFloat
let rightTimestampOffset: CGFloat
let infoOffset: CGFloat
@ -257,7 +276,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
let infoSize = self.infoNode.measure(infoConstrainedSize)
self.infoNode.bounds = CGRect(origin: CGPoint(), size: infoSize)
transition.updatePosition(node: self.infoNode, position: CGPoint(x: size.width / 2.0, y: infoOffset + infoSize.height / 2.0))
self.infoNode.alpha = size.width < size.height ? 1.0 : 0.0
self.infoNode.alpha = size.width < size.height && !self.collapsed ? 1.0 : 0.0
self.scrubberNode.frame = CGRect(origin: CGPoint(x: scrubberInset, y: 6.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberInset * 2.0, height: scrubberHeight))
}

View File

@ -935,6 +935,7 @@ public class GalleryController: ViewController, StandalonePresentableController
self.galleryNode.controlsVisibilityChanged = { [weak self] visible in
self?.prefersOnScreenNavigationHidden = !visible
self?.galleryNode.pager.centralItemNode()?.controlsVisibilityUpdated(isVisible: visible)
}
let baseNavigationController = self.baseNavigationController

View File

@ -253,7 +253,7 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
if displayThumbnailPanel {
thumbnailPanelHeight = 52.0
}
let thumbnailsFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 40.0 - panelHeight + 4.0 - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width, height: panelHeight - 4.0))
let thumbnailsFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 40.0 - panelHeight + 4.0 - layout.intrinsicInsets.bottom + (self.areControlsHidden ? 106.0 : 0.0)), size: CGSize(width: layout.size.width, height: panelHeight - 4.0))
transition.updateFrame(node: currentThumbnailContainerNode, frame: thumbnailsFrame)
currentThumbnailContainerNode.updateLayout(size: thumbnailsFrame.size, transition: transition)
@ -279,6 +279,9 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
}
open func setControlsHidden(_ hidden: Bool, animated: Bool) {
guard self.areControlsHidden != hidden else {
return
}
self.areControlsHidden = hidden
self.controlsVisibilityChanged?(!hidden)
if animated {
@ -286,7 +289,7 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
self.navigationBar?.alpha = alpha
self.statusBar?.updateAlpha(alpha, transition: .animated(duration: 0.3, curve: .easeInOut))
self.footerNode.setVisibilityAlpha(alpha)
self.footerNode.setVisibilityAlpha(alpha, animated: animated)
self.updateThumbnailContainerNodeAlpha(.immediate)
})
@ -297,7 +300,7 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
self.navigationBar?.alpha = alpha
self.statusBar?.updateAlpha(alpha, transition: .immediate)
self.footerNode.setVisibilityAlpha(alpha)
self.footerNode.setVisibilityAlpha(alpha, animated: animated)
self.updateThumbnailContainerNodeAlpha(.immediate)
if let (navigationBarHeight, layout) = self.containerLayout {

View File

@ -21,7 +21,7 @@ open class GalleryFooterContentNode: ASDisplayNode {
public var controllerInteraction: GalleryControllerInteraction?
var visibilityAlpha: CGFloat = 1.0
open func setVisibilityAlpha(_ alpha: CGFloat) {
open func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
self.visibilityAlpha = alpha
self.alpha = alpha
}

View File

@ -24,10 +24,10 @@ public final class GalleryFooterNode: ASDisplayNode {
}
private var visibilityAlpha: CGFloat = 1.0
public func setVisibilityAlpha(_ alpha: CGFloat) {
public func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
self.visibilityAlpha = alpha
self.backgroundNode.alpha = alpha
self.currentFooterContentNode?.setVisibilityAlpha(alpha)
self.currentFooterContentNode?.setVisibilityAlpha(alpha, animated: true)
self.currentOverlayContentNode?.setVisibilityAlpha(alpha)
}
@ -43,7 +43,7 @@ public final class GalleryFooterNode: ASDisplayNode {
}
self.currentFooterContentNode = footerContentNode
if let footerContentNode = footerContentNode {
footerContentNode.setVisibilityAlpha(self.visibilityAlpha)
footerContentNode.setVisibilityAlpha(self.visibilityAlpha, animated: transition.isAnimated)
footerContentNode.controllerInteraction = self.controllerInteraction
footerContentNode.requestLayout = { [weak self] transition in
if let strongSelf = self, let (currentLayout, currentThumbnailPanelHeight, isHidden) = strongSelf.currentLayout {
@ -67,7 +67,7 @@ public final class GalleryFooterNode: ASDisplayNode {
}
var backgroundHeight: CGFloat = 0.0
let verticalOffset: CGFloat = isHidden ? (layout.size.width > layout.size.height ? 44.0 : 54.0) : 0.0
let verticalOffset: CGFloat = isHidden ? (layout.size.width > layout.size.height ? 44.0 : (thumbnailPanelHeight > 0.0 ? 106.0 : 54.0)) : 0.0
if let footerContentNode = self.currentFooterContentNode {
backgroundHeight = footerContentNode.updateLayout(size: layout.size, metrics: layout.metrics, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: thumbnailPanelHeight, transition: transition)
transition.updateFrame(node: footerContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))

View File

@ -82,6 +82,9 @@ open class GalleryItemNode: ASDisplayNode {
open func visibilityUpdated(isVisible: Bool) {
}
open func controlsVisibilityUpdated(isVisible: Bool) {
}
open func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
}

View File

@ -661,7 +661,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
seekable = value.duration >= 30.0
}
if playing && strongSelf.previousPlaying != true {
if strongSelf.isCentral && playing && strongSelf.previousPlaying != true && !disablePlayerControls {
strongSelf.controlsTimer?.invalidate()
let timer = SwiftSignalKit.Timer(timeout: 3.0, repeat: false, completion: { [weak self] in
self?.updateControlsVisibility(false)
self?.controlsTimer = nil
@ -762,6 +764,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.footerContentNode.setup(origin: item.originData, caption: item.caption)
}
override func controlsVisibilityUpdated(isVisible: Bool) {
self.controlsTimer?.invalidate()
self.controlsTimer = nil
}
private func updateDisplayPlaceholder(_ displayPlaceholder: Bool) {
if displayPlaceholder {
if self.pictureInPictureNode == nil {

View File

@ -445,7 +445,12 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
TGMediaAssetImageType screenImageType = refresh ? TGMediaAssetImageTypeLargeThumbnail : TGMediaAssetImageTypeFastLargeThumbnail;
TGMediaAssetImageType imageType = thumbnail ? TGMediaAssetImageTypeAspectRatioThumbnail : screenImageType;
SSignal *assetSignal = [TGMediaAssetImageSignals imageForAsset:asset imageType:imageType size:imageSize];
TGMediaAsset *concreteAsset = asset;
if ([concreteAsset isKindOfClass:[TGCameraCapturedVideo class]]) {
concreteAsset = [(TGCameraCapturedVideo *)asset originalAsset];
}
SSignal *assetSignal = [TGMediaAssetImageSignals imageForAsset:concreteAsset imageType:imageType size:imageSize];
if (_editingContext == nil)
return assetSignal;

View File

@ -81,6 +81,7 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
CGFloat renderWidth = CGFloor(sourceWidth / blockSize) * blockSize;
CGFloat renderHeight = CGFloor(sourceHeight * renderWidth / sourceWidth);
CGSize renderSize = CGSizeMake(renderWidth, renderHeight);
CGSize targetSize = TGFitSizeF(CGSizeMake(renderWidth, renderHeight), CGSizeMake(TGGifConverterMaximumSide, TGGifConverterMaximumSide));
NSDictionary *videoCleanApertureSettings = @
@ -152,11 +153,11 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
if (gifProperties != NULL)
{
CVPixelBufferRef pxBuffer = [self newBufferFrom:imgRef size:targetSize withPixelBufferPool:adaptor.pixelBufferPool andAttributes:adaptor.sourcePixelBufferAttributes];
CVPixelBufferRef pxBuffer = [self newBufferFrom:imgRef size:renderSize withPixelBufferPool:adaptor.pixelBufferPool andAttributes:adaptor.sourcePixelBufferAttributes];
if (pxBuffer != NULL)
{
if (previewImage == nil) {
previewImage = TGScaleImageToPixelSize([[UIImage alloc] initWithCGImage:imgRef], targetSize);
previewImage = TGScaleImageToPixelSize([[UIImage alloc] initWithCGImage:imgRef], renderSize);
}
float frameDuration = 0.1f;
NSNumber *delayTimeUnclampedProp = CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime);

View File

@ -1050,6 +1050,9 @@
case TGMediaAssetGifType:
{
TGCameraCapturedVideo *video = (TGCameraCapturedVideo *)item;
if ([video isKindOfClass:[TGMediaAsset class]]) {
video = [[TGCameraCapturedVideo alloc] initWithAsset:(TGMediaAsset *)video livePhoto:false];
}
TGVideoEditAdjustments *adjustments = (TGVideoEditAdjustments *)[editingContext adjustmentsForItem:video];
NSString *caption = [editingContext captionForItem:video];
@ -1102,7 +1105,7 @@
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
dict[@"type"] = @"cameraVideo";
dict[@"url"] = ((TGCameraCapturedVideo *)item).immediateAVAsset.URL;
dict[@"url"] = video.immediateAVAsset.URL;
dict[@"previewImage"] = image;
dict[@"adjustments"] = adjustments;
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];

View File

@ -23,12 +23,12 @@ public enum AvatarGalleryEntryId: Hashable {
}
public enum AvatarGalleryEntry: Equatable {
case topImage([ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], GalleryItemIndexData?, Data?, String?)
case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], Peer?, Int32, GalleryItemIndexData?, MessageId?, Data?, String?)
case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, GalleryItemIndexData?, Data?, String?)
case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], 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)
}
@ -41,9 +41,18 @@ public enum AvatarGalleryEntry: Equatable {
}
}
public var peer: Peer? {
switch self {
case let .topImage(_, _, peer, _, _, _):
return peer
case let .image(_, _, _, _, peer, _, _, _, _, _):
return peer
}
}
public var representations: [ImageRepresentationWithReference] {
switch self {
case let .topImage(representations, _, _, _, _):
case let .topImage(representations, _, _, _, _, _):
return representations
case let .image(_, _, representations, _, _, _, _, _, _, _):
return representations
@ -52,16 +61,16 @@ public enum AvatarGalleryEntry: Equatable {
public var immediateThumbnailData: Data? {
switch self {
case let .topImage(_, _, _, immediateThumbnailData, _):
case let .topImage(_, _, _, _, immediateThumbnailData, _):
return immediateThumbnailData
case let .image(_, _, _, _, _, _, _, _, immediateThumbnailData, _):
return immediateThumbnailData
}
}
public var videoRepresentations: [TelegramMediaImage.VideoRepresentation] {
public var videoRepresentations: [VideoRepresentationWithReference] {
switch self {
case let .topImage(_, videoRepresentations, _, _, _):
case let .topImage(_, videoRepresentations, _, _, _, _):
return videoRepresentations
case let .image(_, _, _, videoRepresentations, _, _, _, _, _, _):
return videoRepresentations
@ -70,7 +79,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
@ -79,8 +88,8 @@ public enum AvatarGalleryEntry: Equatable {
public static func ==(lhs: AvatarGalleryEntry, rhs: AvatarGalleryEntry) -> Bool {
switch lhs {
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 {
case let .topImage(lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsIndexData, lhsImmediateThumbnailData, lhsCategory):
if case let .topImage(rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsIndexData, rhsImmediateThumbnailData, rhsCategory) = rhs, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsIndexData == rhsIndexData, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory {
return true
} else {
return false
@ -111,8 +120,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, videoRepresentations, _, immediateThumbnailData, category) = entry {
updatedEntries.append(.topImage(representations, videoRepresentations, indexData, immediateThumbnailData, category))
if case let .topImage(representations, videoRepresentations, peer, _, immediateThumbnailData, category) = entry {
updatedEntries.append(.topImage(representations, videoRepresentations, peer, 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))
}
@ -124,7 +133,7 @@ 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)) }), [], peer, nil, nil, nil))
}
if peer is TelegramChannel || peer is TelegramGroup {
@ -139,8 +148,8 @@ public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<
initialPhoto = photo
}
if let photo = initialPhoto, !photo.videoRepresentations.isEmpty {
return [.topImage(photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.videoRepresentations, nil, nil, nil)]
if let photo = initialPhoto, !photo.videoRepresentations.isEmpty, let peerReference = PeerReference(peer) {
return [.topImage(photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil)]
} else {
return initialEntries
}
@ -155,19 +164,19 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<
|> mapToSignal { initialEntries in
return .single(initialEntries)
|> then(
requestPeerPhotos(account: account, peerId: peer.id)
requestPeerPhotos(postbox: account.postbox, network: account.network, peerId: peer.id)
|> map { photos -> [AvatarGalleryEntry] in
var result: [AvatarGalleryEntry] = []
if photos.isEmpty {
result = initialEntries
} else {
} else if let peerReference = PeerReference(peer) {
var index: Int32 = 0
for photo in photos {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first {
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil))
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil))
} else {
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil))
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil))
}
index += 1
}
@ -182,20 +191,20 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer, firstEntry
let initialEntries = [firstEntry]
return Signal<[AvatarGalleryEntry], NoError>.single(initialEntries)
|> then(
requestPeerPhotos(account: account, peerId: peer.id)
requestPeerPhotos(postbox: account.postbox, network: account.network, peerId: peer.id)
|> map { photos -> [AvatarGalleryEntry] in
var result: [AvatarGalleryEntry] = []
let initialEntries = [firstEntry]
if photos.isEmpty {
result = initialEntries
} else {
} else if let peerReference = PeerReference(peer) {
var index: Int32 = 0
for photo in photos {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first {
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil))
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil))
} else {
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil))
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil))
}
index += 1
}
@ -676,7 +685,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private func openEntryEdit(_ rawEntry: AvatarGalleryEntry) {
let mediaReference: AnyMediaReference
if let video = rawEntry.videoRepresentations.last {
if let video = rawEntry.videoRepresentations.last?.representation {
mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
} else {
let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: rawEntry.representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
@ -770,7 +779,13 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
// self?.openEntryEdit(rawEntry)
// }))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.GroupInfo_SetGroupPhotoDelete, color: .destructive, action: { [weak self] in
let deleteTitle: String
if let _ = rawEntry.videoRepresentations.last {
deleteTitle = self.presentationData.strings.Settings_RemoveVideo
} else {
deleteTitle = self.presentationData.strings.GroupInfo_SetGroupPhotoDelete
}
items.append(ActionSheetButtonItem(title: deleteTitle, color: .destructive, action: { [weak self] in
dismissAction()
self?.deleteEntry(rawEntry)
}))

View File

@ -106,7 +106,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
@ -188,8 +188,8 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.footerContentNode.share = { [weak self] interaction in
if let strongSelf = self, let entry = strongSelf.entry, !entry.representations.isEmpty {
let subject: ShareControllerSubject
if let video = entry.videoRepresentations.last {
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
subject = .media(videoFileReference.abstract)
} else {
subject = .image(entry.representations)
@ -253,23 +253,23 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, reference: representations[largestIndex].reference).start())
}
var id: Int64?
var id: Int64
var category: String?
if case let .image(image) = entry {
id = image.0.id
category = image.9
} else {
id = 1
id = Int64(entry.peer?.id.id ?? 1)
}
if let video = entry.videoRepresentations.last, let id = id {
if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer) {
if video != previousVideoRepresentations?.last {
let mediaManager = self.context.sharedContext.mediaManager
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: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, category), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, category), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
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
self.videoStartTimestamp = video.startTimestamp
self.videoStartTimestamp = video.representation.startTimestamp
self.videoContent = videoContent
self.videoNode = videoNode
@ -574,7 +574,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

View File

@ -18,12 +18,14 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, anyway: (
continue
}
if let value = resourceValues[.contentModificationDateKey] as? NSDate {
var unlinked = false
if Int32(value.timeIntervalSince1970) < minTimestamp {
if let file = url.path {
f(file)
unlinked = true
}
}
if let file = url.path {
if let file = url.path, !unlinked {
if let size = (resourceValues[.fileSizeKey] as? NSNumber)?.intValue {
anyway((file, size, Int32(value.timeIntervalSince1970)))
}

View File

@ -214,6 +214,7 @@ public enum AnyMediaReference: Equatable {
case webPage(webPage: WebpageReference, media: Media)
case stickerPack(stickerPack: StickerPackReference, media: Media)
case savedGif(media: Media)
case avatarList(peer: PeerReference, media: Media)
public static func ==(lhs: AnyMediaReference, rhs: AnyMediaReference) -> Bool {
switch lhs {
@ -247,6 +248,12 @@ public enum AnyMediaReference: Equatable {
} else {
return false
}
case let .avatarList(lhsPeer, lhsMedia):
if case let .avatarList(rhsPeer, rhsMedia) = rhs, lhsPeer == rhsPeer, lhsMedia.isEqual(to: rhsMedia) {
return true
} else {
return false
}
}
}
@ -262,6 +269,8 @@ public enum AnyMediaReference: Equatable {
return .stickerPack(stickerPack: stickerPack)
case .savedGif:
return .savedGif
case let .avatarList(peer, media):
return nil
}
}
@ -287,6 +296,10 @@ public enum AnyMediaReference: Equatable {
if let media = media as? T {
return .savedGif(media: media)
}
case let .avatarList(peer, media):
if let media = media as? T {
return .avatarList(peer: peer, media: media)
}
}
return nil
}
@ -303,6 +316,8 @@ public enum AnyMediaReference: Equatable {
return media
case let .savedGif(media):
return media
case let .avatarList(_, media):
return media
}
}
@ -380,6 +395,7 @@ public enum MediaReference<T: Media> {
case webPage
case stickerPack
case savedGif
case avatarList
}
case standalone(media: T)
@ -387,6 +403,7 @@ public enum MediaReference<T: Media> {
case webPage(webPage: WebpageReference, media: T)
case stickerPack(stickerPack: StickerPackReference, media: T)
case savedGif(media: T)
case avatarList(peer: PeerReference, media: T)
public init?(decoder: PostboxDecoder) {
guard let caseIdValue = decoder.decodeOptionalInt32ForKey("_r"), let caseId = CodingCase(rawValue: caseIdValue) else {
@ -421,6 +438,12 @@ public enum MediaReference<T: Media> {
return nil
}
self = .savedGif(media: media)
case .avatarList:
let peer = decoder.decodeObjectForKey("pr", decoder: { StickerPackReference(decoder: $0) }) as! PeerReference
guard let media = decoder.decodeObjectForKey("m") as? T else {
return nil
}
self = .avatarList(peer: peer, media: media)
}
}
@ -444,6 +467,10 @@ public enum MediaReference<T: Media> {
case let .savedGif(media):
encoder.encodeInt32(CodingCase.savedGif.rawValue, forKey: "_r")
encoder.encodeObject(media, forKey: "m")
case let .avatarList(peer, media):
encoder.encodeInt32(CodingCase.avatarList.rawValue, forKey: "_r")
encoder.encodeObject(peer, forKey: "pr")
encoder.encodeObject(media, forKey: "m")
}
}
@ -459,6 +486,8 @@ public enum MediaReference<T: Media> {
return .stickerPack(stickerPack: stickerPack, media: media)
case let .savedGif(media):
return .savedGif(media: media)
case let .avatarList(peer, media):
return .avatarList(peer: peer, media: media)
}
}
@ -478,6 +507,8 @@ public enum MediaReference<T: Media> {
return media
case let .savedGif(media):
return media
case let .avatarList(_, media):
return media
}
}
@ -493,6 +524,7 @@ public enum MediaResourceReference: Equatable {
case media(media: AnyMediaReference, resource: MediaResource)
case standalone(resource: MediaResource)
case avatar(peer: PeerReference, resource: MediaResource)
case avatarList(peer: PeerReference, resource: MediaResource)
case messageAuthorAvatar(message: MessageReference, resource: MediaResource)
case wallpaper(wallpaper: WallpaperReference?, resource: MediaResource)
case stickerPackThumbnail(stickerPack: StickerPackReference, resource: MediaResource)
@ -506,6 +538,8 @@ public enum MediaResourceReference: Equatable {
return resource
case let .avatar(_, resource):
return resource
case let .avatarList(_, resource):
return resource
case let .messageAuthorAvatar(_, resource):
return resource
case let .wallpaper(_, resource):
@ -537,6 +571,12 @@ public enum MediaResourceReference: Equatable {
} else {
return false
}
case let .avatarList(lhsPeer, lhsResource):
if case let .avatarList(rhsPeer, rhsResource) = rhs, lhsPeer == rhsPeer, lhsResource.isEqual(to: rhsResource) {
return true
} else {
return false
}
case let .messageAuthorAvatar(lhsMessage, lhsResource):
if case let .messageAuthorAvatar(rhsMessage, rhsResource) = rhs, lhsMessage == rhsMessage, lhsResource.isEqual(to: rhsResource) {
return true

View File

@ -76,6 +76,11 @@ private func findMediaResource(media: Media, previousMedia: Media?, resource: Me
return representation.resource
}
}
for representation in image.videoRepresentations {
if representation.resource.id.isEqual(to: resource.id) {
return representation.resource
}
}
if let legacyResource = resource as? CloudFileMediaResource {
for representation in image.representations {
if let updatedResource = representation.resource as? CloudPhotoSizeMediaResource {
@ -155,6 +160,7 @@ private enum MediaReferenceRevalidationKey: Hashable {
case wallpaper(wallpaper: WallpaperReference)
case wallpapers
case themes
case peerAvatars(peer: PeerReference)
}
private final class MediaReferenceRevalidationItemContext {
@ -447,6 +453,25 @@ final class MediaReferenceRevalidationContext {
}
}
}
func peerAvatars(postbox: Postbox, network: Network, background: Bool, peer: PeerReference) -> Signal<[TelegramPeerPhoto], RevalidateMediaReferenceError> {
return self.genericItem(key: .peerAvatars(peer: peer), background: background, request: { next, error in
return (requestPeerPhotos(postbox: postbox, network: network, peerId: peer.id)
|> mapError { _ -> RevalidateMediaReferenceError in
return .generic
}).start(next: { value in
next(value)
}, error: { _ in
error(.generic)
})
}) |> mapToSignal { next -> Signal<[TelegramPeerPhoto], RevalidateMediaReferenceError> in
if let next = next as? [TelegramPeerPhoto] {
return .single(next)
} else {
return .fail(.generic)
}
}
}
}
struct RevalidatedMediaResource {
@ -546,6 +571,16 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali
}
return .fail(.generic)
}
case let .avatarList(peer, media):
return revalidationContext.peerAvatars(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, peer: peer)
|> mapToSignal { result -> Signal<RevalidatedMediaResource, RevalidateMediaReferenceError> in
for photo in result {
if let updatedResource = findUpdatedMediaResource(media: photo.image, previousMedia: media, resource: resource) {
return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil))
}
}
return .fail(.generic)
}
case let .standalone(media):
if let file = media as? TelegramMediaFile {
for attribute in file.attributes {
@ -587,6 +622,16 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali
}
return .fail(.generic)
}
case let .avatarList(peer, _):
return revalidationContext.peerAvatars(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, peer: peer)
|> mapToSignal { updatedPeerAvatars -> Signal<RevalidatedMediaResource, RevalidateMediaReferenceError> in
for photo in updatedPeerAvatars {
if let updatedResource = findUpdatedMediaResource(media: photo.image, previousMedia: nil, resource: resource) {
return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil))
}
}
return .fail(.generic)
}
case let .messageAuthorAvatar(message, _):
return revalidationContext.message(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, message: message)
|> mapToSignal { updatedMessage -> Signal<RevalidatedMediaResource, RevalidateMediaReferenceError> in
@ -647,7 +692,7 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali
}
return .fail(.generic)
}
case let .theme(themeReference, resource):
case let .theme(_, resource):
return revalidationContext.themes(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation)
|> mapToSignal { themes -> Signal<RevalidatedMediaResource, RevalidateMediaReferenceError> in
for theme in themes {

View File

@ -10,3 +10,14 @@ public struct ImageRepresentationWithReference: Equatable {
self.reference = reference
}
}
public struct VideoRepresentationWithReference: Equatable {
public let representation: TelegramMediaImage.VideoRepresentation
public let reference: MediaResourceReference
public init(representation: TelegramMediaImage.VideoRepresentation, reference: MediaResourceReference) {
self.representation = representation
self.reference = reference
}
}

View File

@ -16,13 +16,13 @@ public struct TelegramPeerPhoto {
public let messageId: MessageId?
}
public func requestPeerPhotos(account: Account, peerId: PeerId) -> Signal<[TelegramPeerPhoto], NoError> {
return account.postbox.transaction{ transaction -> Peer? in
public func requestPeerPhotos(postbox: Postbox, network: Network, peerId: PeerId) -> Signal<[TelegramPeerPhoto], NoError> {
return postbox.transaction{ transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> mapToSignal { peer -> Signal<[TelegramPeerPhoto], NoError> in
if let peer = peer as? TelegramUser, let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.photos.getUserPhotos(userId: inputUser, offset: 0, maxId: 0, limit: 100))
return network.request(Api.functions.photos.getUserPhotos(userId: inputUser, offset: 0, maxId: 0, limit: 100))
|> map {Optional($0)}
|> mapError {_ in}
|> `catch` { _ -> Signal<Api.photos.Photos?, NoError> in
@ -61,7 +61,7 @@ public func requestPeerPhotos(account: Account, peerId: PeerId) -> Signal<[Teleg
}
}
} else if let peer = peer, let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: .inputMessagesFilterChatPhotos, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1000, maxId: 0, minId: 0, hash: 0))
return network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: .inputMessagesFilterChatPhotos, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1000, maxId: 0, minId: 0, hash: 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Messages?, NoError> in
return .single(nil)
@ -90,7 +90,7 @@ public func requestPeerPhotos(account: Account, peerId: PeerId) -> Signal<[Teleg
users = []
}
return account.postbox.transaction { transaction -> [Message] in
return postbox.transaction { transaction -> [Message] in
var peers: [PeerId: Peer] = [:]
for user in users {

View File

@ -8,6 +8,8 @@ func parsedTelegramProfilePhoto(_ photo: Api.UserProfilePhoto) -> [TelegramMedia
var representations: [TelegramMediaImageRepresentation] = []
switch photo {
case let .userProfilePhoto(flags, _, photoSmall, photoBig, dcId):
let hasVideo = (flags & (1 << 0)) != 0
let smallResource: TelegramMediaResource
let fullSizeResource: TelegramMediaResource
switch photoSmall {

View File

@ -317,6 +317,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var focusOnSearchAfterAppearance: (ChatSearchDomain, String)?
private let keepPeerInfoScreenDataHotDisposable = MetaDisposable()
private let preloadAvatarDisposable = MetaDisposable()
private let peekData: ChatPeekTimeout?
private let peekTimerDisposable = MetaDisposable()
@ -2749,6 +2750,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.reminderActivity?.invalidate()
self.updateSlowmodeStatusDisposable.dispose()
self.keepPeerInfoScreenDataHotDisposable.dispose()
self.preloadAvatarDisposable.dispose()
self.peekTimerDisposable.dispose()
}
@ -5063,7 +5065,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
if case let .peer(peerId) = self.chatLocation {
self.keepPeerInfoScreenDataHotDisposable.set(keepPeerInfoScreenDataHot(context: self.context, peerId: peerId).start())
let context = self.context
self.keepPeerInfoScreenDataHotDisposable.set(keepPeerInfoScreenDataHot(context: context, peerId: peerId).start())
self.preloadAvatarDisposable.set((peerInfoProfilePhotosWithCache(context: context, peerId: peerId)
|> mapToSignal { result -> Signal<Never, NoError> in
var signals: [Signal<Never, NoError>] = [.complete()]
for i in 0 ..< min(5, result.count) {
if let video = result[i].videoRepresentations.first {
let duration: Double = (video.representation.startTimestamp ?? 0.0) + (i == 0 ? 4.0 : 2.0)
signals.append(preloadVideoResource(postbox: context.account.postbox, resourceReference: video.reference, duration: duration))
}
}
return combineLatest(signals) |> mapToSignal { _ in
return .never()
}
}).start())
}
}

View File

@ -46,7 +46,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
switch action.action {
case let .photoUpdated(image):
if let peer = messageMainPeer(message), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations, peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")])
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")])
let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in
})

View File

@ -158,8 +158,8 @@ final class PeerInfoHeaderNavigationTransition {
}
enum PeerInfoAvatarListItem: Equatable {
case topImage([ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], Data?)
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], Data?)
case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?)
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?)
var id: WrappedMediaResourceId {
switch self {
@ -172,7 +172,7 @@ enum PeerInfoAvatarListItem: Equatable {
}
}
var videoRepresentations: [TelegramMediaImage.VideoRepresentation] {
var videoRepresentations: [VideoRepresentationWithReference] {
switch self {
case let .topImage(_, videoRepresentations, _):
return videoRepresentations
@ -184,7 +184,7 @@ enum PeerInfoAvatarListItem: Equatable {
final class PeerInfoAvatarListItemNode: ASDisplayNode {
private let context: AccountContext
private let peerId: PeerId?
private let peer: Peer
let imageNode: TransformImageNode
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
@ -200,29 +200,48 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
private var statusPromise = Promise<(MediaPlayerStatus?, Double?)?>()
var mediaStatus: Signal<(MediaPlayerStatus?, Double?)?, NoError> {
get {
if let videoNode = self.videoNode {
let videoStartTimestamp = self.videoStartTimestamp
return videoNode.status |> map { ($0, videoStartTimestamp) }
} else {
return self.statusPromise.get()
}
}
}
var isCentral: Bool = false {
var delayCentralityLose = false
var isCentral: Bool? = nil {
didSet {
if self.isCentral {
guard self.isCentral != oldValue, let isCentral = self.isCentral else {
return
}
if isCentral {
self.setupVideoPlayback()
self.preloadDisposable.set(nil)
} else {
if let videoNode = self.videoNode {
self.playbackStatusDisposable.set(nil)
self.statusPromise.set(.single(nil))
self.videoNode = nil
if self.delayCentralityLose {
Queue.mainQueue().after(0.5) {
videoNode.removeFromSupernode()
}
} else {
videoNode.removeFromSupernode()
}
}
if let videoContent = self.videoContent {
let duration: Double = (self.videoStartTimestamp ?? 0.0) + 4.0
self.preloadDisposable.set(preloadVideoResource(postbox: self.context.account.postbox, resourceReference: videoContent.fileReference.resourceReference(videoContent.fileReference.media.resource), duration: duration).start())
}
// self.preloadDisposable.set(preloadVideoResource(postbox: self.context.account.postbox, resourceReference: ))
}
}
}
init(context: AccountContext, peerId: PeerId?) {
init(context: AccountContext, peer: Peer) {
self.context = context
self.peerId = peerId
self.peer = peer
self.imageNode = TransformImageNode()
super.init()
@ -235,6 +254,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
deinit {
self.playbackStatusDisposable.dispose()
self.preloadDisposable.dispose()
}
func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) {
@ -247,7 +267,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
private func setupVideoPlayback() {
guard let videoContent = self.videoContent, self.isCentral, self.videoNode == nil else {
guard let videoContent = self.videoContent, let isCentral = self.isCentral, isCentral, self.videoNode == nil else {
return
}
@ -272,7 +292,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.15) {
Queue.mainQueue().after(0.1) {
strongSelf.videoNode?.isHidden = false
}
}
@ -296,34 +316,34 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
self.item = item
let representations: [ImageRepresentationWithReference]
let videoRepresentations: [TelegramMediaImage.VideoRepresentation]
let videoRepresentations: [VideoRepresentationWithReference]
let immediateThumbnailData: Data?
var id: Int64?
var id: Int64
switch item {
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = topRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
if let peerId = self.peerId {
id = Int64(peerId.id)
}
id = Int64(self.peer.id.id)
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = imageRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
if case let .cloud(imageId, _, _) = reference {
id = imageId
} else {
id = Int64(self.peer.id.id)
}
}
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, immediateThumbnailData: immediateThumbnailData, autoFetchFullSize: true, attemptSynchronously: synchronous), attemptSynchronously: synchronous, dispatchOnDisplayLink: false)
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: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
if videoContent.id != self.videoContent?.id {
self.videoContent = videoContent
self.videoStartTimestamp = video.startTimestamp
self.videoStartTimestamp = video.representation.startTimestamp
self.setupVideoPlayback()
}
} else {
@ -427,7 +447,7 @@ private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
final class PeerInfoAvatarListContainerNode: ASDisplayNode {
private let context: AccountContext
var peerId: PeerId?
var peer: Peer?
let controlsContainerNode: ASDisplayNode
let controlsClippingNode: ASDisplayNode
@ -765,7 +785,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if self.currentIndex != previousIndex {
self.currentIndexUpdated?()
}
self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring))
self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring), synchronous: true)
} else if self.items.count > 1 {
let previousIndex = self.currentIndex
self.currentIndex = self.items.count - 1
@ -781,7 +801,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if self.currentIndex != previousIndex {
self.currentIndexUpdated?()
}
self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring))
self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring), synchronous: true)
} else if self.items.count > 1 {
let previousIndex = self.currentIndex
self.currentIndex = 0
@ -798,6 +818,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
}
private var pageChangedByPan = false
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
@ -831,11 +852,13 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
let previousIndex = self.currentIndex
self.currentIndex = updatedIndex
if self.currentIndex != previousIndex {
self.pageChangedByPan = true
self.currentIndexUpdated?()
}
self.transitionFraction = 0.0
if let size = self.validLayout {
self.updateItems(size: size, transition: .animated(duration: 0.3, curve: .spring), stripTransition: .animated(duration: 0.3, curve: .spring))
self.pageChangedByPan = false
}
default:
break
@ -850,7 +873,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
var entries: [AvatarGalleryEntry] = []
for entry in self.galleryEntries {
switch entry {
case let .topImage(representations, videoRepresentations, _, immediateThumbnailData, _):
case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _):
entries.append(entry)
items.append(.topImage(representations, videoRepresentations, immediateThumbnailData))
case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _):
@ -886,7 +909,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
let previousIndex = self.currentIndex
for entry in self.galleryEntries {
switch entry {
case let .topImage(representations, videoRepresentations, _, immediateThumbnailData, _):
case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _):
entries.append(entry)
items.append(.topImage(representations, videoRepresentations, immediateThumbnailData))
case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _):
@ -950,7 +973,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
var items: [PeerInfoAvatarListItem] = []
for entry in entries {
switch entry {
case let .topImage(representations, videoRepresentations, _, 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))
@ -979,28 +1002,32 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
var additiveTransitionOffset: CGFloat = 0.0
var itemsAdded = false
if self.currentIndex >= 0 && self.currentIndex < self.items.count {
for i in max(0, self.currentIndex - 1) ... min(self.currentIndex + 1, self.items.count - 1) {
let preloadSpan: Int = 2
for i in max(0, self.currentIndex - preloadSpan) ... min(self.currentIndex + preloadSpan, self.items.count - 1) {
validIds.append(self.items[i].id)
let itemNode: PeerInfoAvatarListItemNode
var itemNode: PeerInfoAvatarListItemNode?
var wasAdded = false
if let current = self.itemNodes[self.items[i].id] {
itemNode = current
if update {
itemNode.setup(item: self.items[i], synchronous: synchronous && i == self.currentIndex)
current.setup(item: self.items[i], synchronous: synchronous && i == self.currentIndex)
}
} else {
} else if let peer = self.peer {
wasAdded = true
itemNode = PeerInfoAvatarListItemNode(context: self.context, peerId: self.peerId)
itemNode.setup(item: self.items[i], synchronous: synchronous && i == self.currentIndex)
self.itemNodes[self.items[i].id] = itemNode
self.contentNode.addSubnode(itemNode)
let addedItemNode = PeerInfoAvatarListItemNode(context: self.context, peer: peer)
itemNode = addedItemNode
addedItemNode.setup(item: self.items[i], synchronous: synchronous && i == self.currentIndex)
self.itemNodes[self.items[i].id] = addedItemNode
self.contentNode.addSubnode(addedItemNode)
}
if let itemNode = itemNode {
itemNode.delayCentralityLose = self.pageChangedByPan
itemNode.isCentral = i == self.currentIndex
itemNode.delayCentralityLose = false
let indexOffset = CGFloat(i - self.currentIndex)
let itemFrame = CGRect(origin: CGPoint(x: indexOffset * size.width + self.transitionFraction * size.width - size.width / 2.0, y: -size.height / 2.0), size: size)
if wasAdded {
itemsAdded = true
addedItemNodesForAdditiveTransition.append(itemNode)
@ -1009,7 +1036,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} else {
additiveTransitionOffset = itemNode.frame.minX - itemFrame.minX
transition.updateFrame(node: itemNode, frame: itemFrame)
itemNode.update(size: size, transition: transition)
itemNode.update(size: size, transition: .immediate)
}
}
}
}
@ -1090,9 +1118,12 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
var frame = self.stripNodes[self.currentIndex].frame
stripTransition.updateFrame(node: self.loadingStripNode, frame: frame)
if let playbackProgress = self.playbackProgress {
frame.size.width = max(0.0, frame.size.width * playbackProgress)
frame.size.width = max(frame.size.height, frame.size.width * playbackProgress)
}
stripTransition.updateFrameAdditive(node: self.activeStripNode, frame: frame)
stripTransition.updateAlpha(node: self.activeStripNode, alpha: self.loading ? 0.0 : 1.0)
stripTransition.updateAlpha(node: self.loadingStripNode, alpha: self.loading ? 1.0 : 0.0)
self.activeStripNode.isHidden = self.stripNodes.count < 2
self.loadingStripNode.isHidden = !self.loading
}
@ -1195,9 +1226,9 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if let item = item {
let representations: [ImageRepresentationWithReference]
let videoRepresentations: [TelegramMediaImage.VideoRepresentation]
let videoRepresentations: [VideoRepresentationWithReference]
let immediateThumbnailData: Data?
var id: Int64?
var id: Int64
switch item {
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = topRepresentations
@ -1210,19 +1241,21 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
immediateThumbnailData = immediateThumbnail
if case let .cloud(imageId, _, _) = reference {
id = imageId
} else {
id = Int64(peer.id.id)
}
}
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 let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.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 {
if let startTimestamp = video.representation.startTimestamp {
self.videoStartTimestamp = startTimestamp
self.playbackStatusDisposable.set((videoNode.status
|> map { status -> Bool in
@ -1474,9 +1507,9 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
if let item = item {
let representations: [ImageRepresentationWithReference]
let videoRepresentations: [TelegramMediaImage.VideoRepresentation]
let videoRepresentations: [VideoRepresentationWithReference]
let immediateThumbnailData: Data?
var id: Int64?
var id: Int64
switch item {
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = topRepresentations
@ -1489,17 +1522,19 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
immediateThumbnailData = immediateThumbnail
if case let .cloud(imageId, _, _) = reference {
id = imageId
} else {
id = Int64(peer.id.id)
}
}
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 let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.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
self.videoStartTimestamp = video.startTimestamp
self.videoStartTimestamp = video.representation.startTimestamp
self.videoContent = videoContent
self.videoNode = videoNode
@ -2542,8 +2577,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
func initiateAvatarExpansion(gallery: Bool) {
if self.isAvatarExpanded || gallery {
if let currentEntry = self.avatarListNode.listContainerNode.currentEntry {
self.requestAvatarExpansion?(true, self.avatarListNode.listContainerNode.galleryEntries, self.avatarListNode.listContainerNode.currentEntry, self.avatarTransitionArguments(entry: currentEntry))
if let currentEntry = self.avatarListNode.listContainerNode.currentEntry, let firstEntry = self.avatarListNode.listContainerNode.galleryEntries.first {
let entry = gallery ? firstEntry : currentEntry
self.requestAvatarExpansion?(true, self.avatarListNode.listContainerNode.galleryEntries, entry, self.avatarTransitionArguments(entry: currentEntry))
}
} else if let entry = self.avatarListNode.listContainerNode.galleryEntries.first {
let _ = self.avatarListNode.avatarContainerNode.avatarNode
@ -2594,7 +2630,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, notificationSettings: TelegramPeerNotificationSettings?, statusData: PeerInfoStatusData?, isContact: Bool, isSettings: Bool, state: PeerInfoState, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat {
self.state = state
self.peer = peer
self.avatarListNode.listContainerNode.peerId = peer?.id
self.avatarListNode.listContainerNode.peer = peer
let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0
self.avatarSize = avatarSize
@ -2705,7 +2741,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if self.isSettings, let user = peer as? TelegramUser {
let formattedPhone = formatPhoneNumber(user.phone ?? "")
subtitleString = NSAttributedString(string: formattedPhone, font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
usernameString = NSAttributedString(string: user.addressName.flatMap { "@\($0)" } ?? "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
var username = ""
if let addressName = user.addressName, !addressName.isEmpty {
username = "@\(addressName)"
}
usernameString = NSAttributedString(string: username, font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
} else if let statusData = statusData {
let subtitleColor: UIColor
if statusData.isActivity {

View File

@ -3838,7 +3838,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let mediaReference: AnyMediaReference
if let video = videoRepresentations.last {
mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
} else {
let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
mediaReference = .standalone(media: media)

View File

@ -2,7 +2,13 @@
#define OngoingCallContext_h
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#define UIView NSView
#endif
@interface OngoingCallConnectionDescriptionWebrtc : NSObject

View File

@ -1,11 +1,22 @@
#ifndef WEBRTC_IOS
#import "OngoingCallThreadLocalContext.h"
#else
#import <TgVoip/OngoingCallThreadLocalContext.h>
#endif
#import "Instance.h"
#import "InstanceImpl.h"
#import "VideoCaptureInterface.h"
#ifndef WEBRTC_IOS
#import "platform/darwin/VideoMetalViewMac.h"
#define GLVideoView VideoMetalView
#define UIViewContentModeScaleAspectFill kCAGravityResizeAspectFill
#else
#import "platform/darwin/VideoMetalView.h"
#import "platform/darwin/GLVideoView.h"
#endif
@implementation OngoingCallConnectionDescriptionWebrtc