diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 6c3aeca093..a2fefbd79b 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -50,6 +50,15 @@ public enum AvatarGalleryEntry: Equatable { } } + public var immediateThumbnailData: Data? { + switch self { + case let .topImage(_, _, _, immediateThumbnailData, _): + return immediateThumbnailData + case let .image(_, _, _, _, _, _, _, _, immediateThumbnailData, _): + return immediateThumbnailData + } + } + public var videoRepresentations: [TelegramMediaImage.VideoRepresentation] { switch self { case let .topImage(_, videoRepresentations, _, _, _): diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index a077c2b3ee..3e1b753bdc 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -237,7 +237,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))() let representations = entry.representations if representations.last != previousRepresentations?.last { - self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, attemptSynchronously: synchronous), attemptSynchronously: synchronous, dispatchOnDisplayLink: false) + self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, immediateThumbnailData: entry.immediateThumbnailData, attemptSynchronously: synchronous), attemptSynchronously: synchronous, dispatchOnDisplayLink: false) if entry.videoRepresentations.isEmpty { self.imageNode.imageUpdated = { [weak self] _ in self?._ready.set(.single(Void())) diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index bdaf6dca19..09ce16b6f3 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -2086,7 +2086,7 @@ public func instantPageImageFile(account: Account, fileReference: FileMediaRefer } } -private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false) -> Signal, NoError> { +private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], immediateThumbnailData: Data?, autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false) -> Signal, NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) { let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource, attemptSynchronously: attemptSynchronously) @@ -2098,18 +2098,30 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single(Tuple(nil, loadedData, true)) } else { - let fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: representations[smallestIndex].reference) + let decodedThumbnailData = immediateThumbnailData.flatMap(decodeTinyThumbnail) + let fetchedThumbnail: Signal + if let _ = decodedThumbnailData { + fetchedThumbnail = .complete() + } else { + fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: representations[smallestIndex].reference) + } let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: representations[largestIndex].reference) let thumbnail = Signal { subscriber in - let fetchedDisposable = fetchedThumbnail.start() - let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: attemptSynchronously).start(next: { next in - subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) - }, error: subscriber.putError, completed: subscriber.putCompletion) - - return ActionDisposable { - fetchedDisposable.dispose() - thumbnailDisposable.dispose() + if let decodedThumbnailData = decodedThumbnailData { + subscriber.putNext(decodedThumbnailData) + subscriber.putCompletion() + return EmptyDisposable + } else { + let fetchedDisposable = fetchedThumbnail.start() + let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: attemptSynchronously).start(next: { next in + subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } } } @@ -2148,8 +2160,8 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR } } -public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = avatarGalleryPhotoDatas(account: account, representations: representations, autoFetchFullSize: autoFetchFullSize, attemptSynchronously: attemptSynchronously) +public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentationWithReference], immediateThumbnailData: Data?, autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let signal = avatarGalleryPhotoDatas(account: account, representations: representations, immediateThumbnailData: immediateThumbnailData, autoFetchFullSize: autoFetchFullSize, attemptSynchronously: attemptSynchronously) return signal |> map { value in diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index 6b2efe41f3..b0c85e49f1 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -353,7 +353,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro self.peer = peer if let peerReference = PeerReference(peer), !peer.profileImageRepresentations.isEmpty { let representations: [ImageRepresentationWithReference] = peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: .avatar(peer: peerReference, resource: $0.resource)) }) - self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.account, representations: representations, autoFetchFullSize: true)) + self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.account, representations: representations, immediateThumbnailData: nil, autoFetchFullSize: true)) self.dimNode.isHidden = false } else { self.imageNode.setSignal(callDefaultBackground()) diff --git a/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift b/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift index 37209ee233..fcf202151b 100644 --- a/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift @@ -253,7 +253,7 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol self.peer = peer if let peerReference = PeerReference(peer), !peer.profileImageRepresentations.isEmpty { let representations: [ImageRepresentationWithReference] = peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: .avatar(peer: peerReference, resource: $0.resource)) }) - self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.account, representations: representations, autoFetchFullSize: true)) + self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.account, representations: representations, immediateThumbnailData: nil, autoFetchFullSize: true)) self.dimNode.isHidden = false } else { self.imageNode.setSignal(callDefaultBackground()) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f68da8abe1..7207edb23a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2018,7 +2018,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, displayDiceTooltip: { [weak self] dice in self?.displayDiceTooltip(dice: dice) }, animateDiceSuccess: { [weak self] in - self?.chatDisplayNode.animateQuizCorrectOptionSelected() + guard let strongSelf = self else { + return + } + if strongSelf.selectPollOptionFeedback == nil { + strongSelf.selectPollOptionFeedback = HapticFeedback() + } + strongSelf.selectPollOptionFeedback?.success() + strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected() }, greetingStickerNode: { [weak self] in return self?.chatDisplayNode.greetingStickerNode }, requestMessageUpdate: { [weak self] id in diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 03ad2ca9a9..6849b7bde7 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -263,7 +263,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { id = imageId } } - self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, autoFetchFullSize: true, attemptSynchronously: synchronous), attemptSynchronously: synchronous, dispatchOnDisplayLink: false) + 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 mediaManager = self.context.sharedContext.mediaManager @@ -1066,6 +1066,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { func update(peer: Peer?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool) { if let peer = peer { let previousItem = self.item + var item = item self.item = item var overrideImage: AvatarNodeImageOverride? @@ -1076,8 +1077,10 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.removedPhotoResourceIds.insert(rep.representation.resource.id.uniqueId) } overrideImage = AvatarNodeImageOverride.none + item = nil } else if let rep = peer.profileImageRepresentations.last, self.removedPhotoResourceIds.contains(rep.resource.id.uniqueId) { overrideImage = AvatarNodeImageOverride.none + item = nil } self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true) self.isFirstAvatarLoading = false @@ -1319,6 +1322,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { fileprivate var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? private var videoStartTimestamp: Double? + var item: PeerInfoAvatarListItem? var tapped: ((Bool) -> Void)? @@ -1354,14 +1358,28 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { } } + var removedPhotoResourceIds = Set() func update(peer: Peer?, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: CGFloat?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { guard let peer = peer else { return } + + let previousItem = self.item + var item = item + self.item = item let overrideImage: AvatarNodeImageOverride? if canEditPeerInfo(context: self.context, peer: peer), peer.profileImageRepresentations.isEmpty { overrideImage = .editAvatarIcon + } else if let previousItem = previousItem, item == nil { + if case let .image(image) = previousItem, let rep = image.1.last { + self.removedPhotoResourceIds.insert(rep.representation.resource.id.uniqueId) + } + overrideImage = AvatarNodeImageOverride.none + item = nil + } else if let rep = peer.profileImageRepresentations.last, self.removedPhotoResourceIds.contains(rep.resource.id.uniqueId) { + overrideImage = AvatarNodeImageOverride.none + item = nil } else { overrideImage = nil } @@ -1415,7 +1433,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { videoNode.removeFromSupernode() } } else if let videoNode = self.videoNode { - + self.videoStartTimestamp = nil self.videoContent = nil self.videoNode = nil diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 6f91763ac9..e02fd2c9f3 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4230,6 +4230,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD return } + if let item = item { + strongSelf.deleteAvatar(item) + } + let _ = strongSelf.currentAvatarMixin.swap(nil) if let _ = peer.smallProfileImage { strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)