diff --git a/bazel-Telegram-iOS-Beta b/bazel-Telegram-iOS-Beta new file mode 120000 index 0000000000..e30c8faa42 --- /dev/null +++ b/bazel-Telegram-iOS-Beta @@ -0,0 +1 @@ +/private/var/tmp/_bazel_ilya/b5422c0ab62ebf818a9d47ff552063a2/execroot/__main__ \ No newline at end of file diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 576abff2ec..e72d01226a 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -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 }) { diff --git a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift index c2c25e09e7..4bcc2091d9 100644 --- a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift @@ -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?) { @@ -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)) } diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 404414f5f5..16f78eee92 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -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 diff --git a/submodules/GalleryUI/Sources/GalleryControllerNode.swift b/submodules/GalleryUI/Sources/GalleryControllerNode.swift index 5d0e45f4df..a689f8ba4a 100644 --- a/submodules/GalleryUI/Sources/GalleryControllerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryControllerNode.swift @@ -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 { diff --git a/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift index 0c85e4e792..d1c25e9205 100644 --- a/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift @@ -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 } diff --git a/submodules/GalleryUI/Sources/GalleryFooterNode.swift b/submodules/GalleryUI/Sources/GalleryFooterNode.swift index b407ee98fd..57ef587554 100644 --- a/submodules/GalleryUI/Sources/GalleryFooterNode.swift +++ b/submodules/GalleryUI/Sources/GalleryFooterNode.swift @@ -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))) diff --git a/submodules/GalleryUI/Sources/GalleryItemNode.swift b/submodules/GalleryUI/Sources/GalleryItemNode.swift index 800c2fa36c..ebd39a13fe 100644 --- a/submodules/GalleryUI/Sources/GalleryItemNode.swift +++ b/submodules/GalleryUI/Sources/GalleryItemNode.swift @@ -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) { } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index c91fabfb92..1b48905ccf 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -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 { diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m index 039995c8b6..d4094aa7bd 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m @@ -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; diff --git a/submodules/LegacyComponents/Sources/TGGifConverter.m b/submodules/LegacyComponents/Sources/TGGifConverter.m index 01df95a41f..ce1560786b 100644 --- a/submodules/LegacyComponents/Sources/TGGifConverter.m +++ b/submodules/LegacyComponents/Sources/TGGifConverter.m @@ -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); diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index b2950865b3..56cbb162f0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -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]; diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 45dbfacb8d..0e260d08a6 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], [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) })) diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index ea9fe74891..b588d924ac 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -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 diff --git a/submodules/Postbox/Sources/TimeBasedCleanup.swift b/submodules/Postbox/Sources/TimeBasedCleanup.swift index 247932a442..a3f40b25d1 100644 --- a/submodules/Postbox/Sources/TimeBasedCleanup.swift +++ b/submodules/Postbox/Sources/TimeBasedCleanup.swift @@ -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))) } diff --git a/submodules/SyncCore/Sources/MediaReference.swift b/submodules/SyncCore/Sources/MediaReference.swift index 0a8876b606..089f3a275b 100644 --- a/submodules/SyncCore/Sources/MediaReference.swift +++ b/submodules/SyncCore/Sources/MediaReference.swift @@ -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 { case webPage case stickerPack case savedGif + case avatarList } case standalone(media: T) @@ -387,6 +403,7 @@ public enum MediaReference { 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 { 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 { 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 { 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 { 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 diff --git a/submodules/TelegramCore/Sources/FetchedMediaResource.swift b/submodules/TelegramCore/Sources/FetchedMediaResource.swift index 5f29868f03..f324e6ccdb 100644 --- a/submodules/TelegramCore/Sources/FetchedMediaResource.swift +++ b/submodules/TelegramCore/Sources/FetchedMediaResource.swift @@ -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 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 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 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 in for theme in themes { diff --git a/submodules/TelegramCore/Sources/ImageRepresentationWithReference.swift b/submodules/TelegramCore/Sources/ImageRepresentationWithReference.swift index 9c9242373e..5262bd91e0 100644 --- a/submodules/TelegramCore/Sources/ImageRepresentationWithReference.swift +++ b/submodules/TelegramCore/Sources/ImageRepresentationWithReference.swift @@ -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 + } +} diff --git a/submodules/TelegramCore/Sources/RequestUserPhotos.swift b/submodules/TelegramCore/Sources/RequestUserPhotos.swift index 1a63756521..c77b20abdf 100644 --- a/submodules/TelegramCore/Sources/RequestUserPhotos.swift +++ b/submodules/TelegramCore/Sources/RequestUserPhotos.swift @@ -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 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 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 { diff --git a/submodules/TelegramCore/Sources/TelegramUser.swift b/submodules/TelegramCore/Sources/TelegramUser.swift index d81a8a1220..e89b221736 100644 --- a/submodules/TelegramCore/Sources/TelegramUser.swift +++ b/submodules/TelegramCore/Sources/TelegramUser.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 7207edb23a..364109e5e5 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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() @@ -2238,7 +2239,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride) (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil } - + if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages { return } @@ -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 in + var signals: [Signal] = [.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()) } } diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 95c3a4cc38..fed47ab635 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -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 }) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index c7b30e739a..bfdd3db426 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -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 { - return self.statusPromise.get() - } - } - - var isCentral: Bool = false { - didSet { - if self.isCentral { - self.setupVideoPlayback() + if let videoNode = self.videoNode { + let videoStartTimestamp = self.videoStartTimestamp + return videoNode.status |> map { ($0, videoStartTimestamp) } } else { - if let videoNode = self.videoNode { - self.videoNode = nil - Queue.mainQueue().after(0.5) { - videoNode.removeFromSupernode() - } - } -// self.preloadDisposable.set(preloadVideoResource(postbox: self.context.account.postbox, resourceReference: )) + return self.statusPromise.get() } } } - init(context: AccountContext, peerId: PeerId?) { + var delayCentralityLose = false + var isCentral: Bool? = nil { + didSet { + 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()) + } + } + } + } + + 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,37 +1002,42 @@ 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) } - itemNode.isCentral = i == self.currentIndex - - 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) - itemNode.frame = itemFrame - itemNode.update(size: size, transition: .immediate) - } else { - additiveTransitionOffset = itemNode.frame.minX - itemFrame.minX - transition.updateFrame(node: itemNode, frame: itemFrame) - itemNode.update(size: size, transition: transition) + 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) + itemNode.frame = itemFrame + itemNode.update(size: size, transition: .immediate) + } else { + additiveTransitionOffset = itemNode.frame.minX - itemFrame.minX + transition.updateFrame(node: itemNode, frame: itemFrame) + 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 { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 6b2a138d98..dca1025f7f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -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) diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h index 15a851ef83..7915abba8b 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h @@ -2,7 +2,13 @@ #define OngoingCallContext_h #import + +#if TARGET_OS_IOS #import +#else +#import +#define UIView NSView +#endif @interface OngoingCallConnectionDescriptionWebrtc : NSObject diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 98f1292568..d3b387adba 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -1,11 +1,22 @@ +#ifndef WEBRTC_IOS +#import "OngoingCallThreadLocalContext.h" +#else #import +#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