From 822ef1b32c24f730e4f2679f699e0ca2cc99213a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 24 Mar 2021 18:33:43 +0500 Subject: [PATCH] Fix voice chat avatar expansion --- .../Source/ContextContentSourceNode.swift | 1 + .../Sources/AvatarGalleryController.swift | 45 +++++++++++++------ .../Sources/PeerInfoAvatarListNode.swift | 20 +++++++-- .../Sources/PhotoResources.swift | 6 +-- .../Sources/VoiceChatController.swift | 14 +++++- .../Sources/VoiceChatParticipantItem.swift | 18 ++++++-- .../Sources/UpdateCachedPeerData.swift | 2 +- 7 files changed, 78 insertions(+), 28 deletions(-) diff --git a/submodules/Display/Source/ContextContentSourceNode.swift b/submodules/Display/Source/ContextContentSourceNode.swift index 108f642a7e..145e0d1115 100644 --- a/submodules/Display/Source/ContextContentSourceNode.swift +++ b/submodules/Display/Source/ContextContentSourceNode.swift @@ -15,6 +15,7 @@ public final class ContextExtractedContentContainingNode: ASDisplayNode { public var applyAbsoluteOffsetSpring: ((CGFloat, Double, CGFloat) -> Void)? public var layoutUpdated: ((CGSize) -> Void)? public var updateDistractionFreeMode: ((Bool) -> Void)? + public var requestDismiss: (() -> Void)? public override init() { self.contentNode = ContextExtractedContentNode() diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index ab740baee5..6000e087a2 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -24,28 +24,37 @@ public enum AvatarGalleryEntryId: Hashable { public func peerInfoProfilePhotos(context: AccountContext, peerId: PeerId) -> Signal { return context.account.postbox.combinedView(keys: [.basicPeer(peerId)]) - |> mapToSignal { view -> Signal in + |> mapToSignal { view -> Signal<[AvatarGalleryEntry]?, NoError> in guard let peer = (view.views[.basicPeer(peerId)] as? BasicPeerView)?.peer else { return .single(nil) } return initialAvatarGalleryEntries(account: context.account, peer: peer) - |> map { entries in - return entries.first - } } |> distinctUntilChanged - |> mapToSignal { firstEntry -> Signal<(Bool, [AvatarGalleryEntry]), NoError> in - if let firstEntry = firstEntry { - return context.account.postbox.loadedPeerWithId(peerId) - |> mapToSignal { peer -> Signal<(Bool, [AvatarGalleryEntry]), NoError>in - return fetchedAvatarGalleryEntries(account: context.account, peer: peer, firstEntry: firstEntry) + |> mapToSignal { entries -> Signal<(Bool, [AvatarGalleryEntry])?, NoError> in + if let entries = entries { + if let firstEntry = entries.first { + return context.account.postbox.loadedPeerWithId(peerId) + |> mapToSignal { peer -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in + return fetchedAvatarGalleryEntries(account: context.account, peer: peer, firstEntry: firstEntry) + |> map(Optional.init) + } + } else { + return .single((true, [])) } } else { - return .single((true, [])) + return fetchAndUpdateCachedPeerData(accountPeerId: context.account.peerId, peerId: peerId, network: context.account.network, postbox: context.account.postbox) + |> map { _ -> (Bool, [AvatarGalleryEntry])? in + return nil + } } } |> map { items -> Any in - return items + if let items = items { + return items + } else { + return peerInfoProfilePhotos(context: context, peerId: peerId) + } } } @@ -164,7 +173,7 @@ public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryE return updatedEntries } -public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<[AvatarGalleryEntry], NoError> { +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)) }), [], peer, nil, nil, nil)) @@ -189,7 +198,7 @@ public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal< } return [.image(photo.imageId, photo.reference, representations, photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil, photo.immediateThumbnailData, nil)] } else { - return [] + return cachedData != nil ? [] : nil } } } else { @@ -199,6 +208,9 @@ public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal< public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<[AvatarGalleryEntry], NoError> { return initialAvatarGalleryEntries(account: account, peer: peer) + |> map { entries -> [AvatarGalleryEntry] in + return entries ?? [] + } |> mapToSignal { initialEntries in return .single(initialEntries) |> then( @@ -390,7 +402,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr remoteEntriesSignal = fetchedAvatarGalleryEntries(account: context.account, peer: peer) } - let entriesSignal: Signal<[AvatarGalleryEntry], NoError> = skipInitial ? remoteEntriesSignal : (initialAvatarGalleryEntries(account: context.account, peer: peer) |> then(remoteEntriesSignal)) + let initialSignal = initialAvatarGalleryEntries(account: context.account, peer: peer) + |> map { entries -> [AvatarGalleryEntry] in + return entries ?? [] + } + + let entriesSignal: Signal<[AvatarGalleryEntry], NoError> = skipInitial ? remoteEntriesSignal : (initialSignal |> then(remoteEntriesSignal)) let presentationData = self.presentationData diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index 1fe2f1cc0a..46acc6547c 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -310,7 +310,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { self.isReady.set(videoNode.ready |> map { return true }) } - func setup(item: PeerInfoAvatarListItem, synchronous: Bool) { + func setup(item: PeerInfoAvatarListItem, synchronous: Bool, fullSizeOnly: Bool = false) { self.item = item let representations: [ImageRepresentationWithReference] @@ -336,7 +336,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { 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) + self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, immediateThumbnailData: immediateThumbnailData, autoFetchFullSize: true, attemptSynchronously: synchronous, skipThumbnail: fullSizeOnly), attemptSynchronously: synchronous, dispatchOnDisplayLink: false) 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: [])])) @@ -419,6 +419,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { public var isCollapsing = false private var isExpanded = false + public var firstFullSizeOnly = false + public var customCenterTapAction: (() -> Void)? + private let disposable = MetaDisposable() private let positionDisposable = MetaDisposable() private var initializedList = false @@ -717,11 +720,16 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { } } + public var offsetLocation = false @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { if let size = self.validLayout, case .tap = gesture { + var location = location + if self.offsetLocation { + location.x += size.width / 2.0 + } if location.x < size.width * 1.0 / 5.0 { if self.currentIndex != 0 { let previousIndex = self.currentIndex @@ -739,6 +747,10 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring), synchronous: true) } } else { + if let customAction = self.customCenterTapAction, location.x < size.width - size.width * 1.0 / 5.0 { + customAction() + return + } if self.currentIndex < self.items.count - 1 { let previousIndex = self.currentIndex self.currentIndex += 1 @@ -1049,13 +1061,13 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { if let current = self.itemNodes[self.items[i].id] { itemNode = current if update { - current.setup(item: self.items[i], synchronous: synchronous && i == self.currentIndex) + current.setup(item: self.items[i], synchronous: synchronous && i == self.currentIndex, fullSizeOnly: self.firstFullSizeOnly && i == 0) } } else if let peer = self.peer { wasAdded = true let addedItemNode = PeerInfoAvatarListItemNode(context: self.context, peer: peer) itemNode = addedItemNode - addedItemNode.setup(item: self.items[i], synchronous: (i == 0 && i == self.currentIndex) || (synchronous && i == self.currentIndex)) + addedItemNode.setup(item: self.items[i], synchronous: (i == 0 && i == self.currentIndex) || (synchronous && i == self.currentIndex), fullSizeOnly: self.firstFullSizeOnly && i == 0) self.itemNodes[self.items[i].id] = addedItemNode self.contentNode.addSubnode(addedItemNode) } diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 91ec1a3454..6202d5678f 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -2303,7 +2303,7 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR } } -public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentationWithReference], immediateThumbnailData: Data?, autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentationWithReference], immediateThumbnailData: Data?, autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false, skipThumbnail: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal = avatarGalleryPhotoDatas(account: account, representations: representations, immediateThumbnailData: immediateThumbnailData, autoFetchFullSize: autoFetchFullSize, attemptSynchronously: attemptSynchronously) return signal @@ -2352,8 +2352,8 @@ public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepr } var blurredThumbnailImage: UIImage? - if let thumbnailImage = thumbnailImage { - if max(thumbnailImage.width, thumbnailImage.height) > 200 { + if let thumbnailImage = thumbnailImage, !skipThumbnail { + if max(thumbnailImage.width, thumbnailImage.height) > 200 { blurredThumbnailImage = UIImage(cgImage: thumbnailImage) } else { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 7f6d4f2e7e..a40ea0986f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1427,7 +1427,14 @@ public final class VoiceChatController: ViewController { return itemsForEntry(entry, muteState) } - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false, blurBackground: true)), items: items, reactionItems: [], gesture: gesture) + + let dismissPromise = ValuePromise(false) + let source = VoiceChatContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false, blurBackground: true, shouldBeDismissed: dismissPromise.get()) + sourceNode.requestDismiss = { + dismissPromise.set(true) + } + + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(source), items: items, reactionItems: [], gesture: gesture) strongSelf.controller?.presentInGlobalOverlay(contextController) }, setPeerIdWithRevealedOptions: { peerId, _ in updateState { state in @@ -3891,11 +3898,14 @@ private final class VoiceChatContextExtractedContentSource: ContextExtractedCont private let controller: ViewController private let sourceNode: ContextExtractedContentContainingNode - init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool, blurBackground: Bool) { + var shouldBeDismissed: Signal + + init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool, blurBackground: Bool, shouldBeDismissed: Signal) { self.controller = controller self.sourceNode = sourceNode self.keepInPlace = keepInPlace self.blurBackground = blurBackground + self.shouldBeDismissed = shouldBeDismissed } func takeView() -> ContextControllerTakeViewInfo? { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index 3409078a66..d9c5cff2a3 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -295,7 +295,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { if isExtracted { strongSelf.contextSourceNode.contentNode.customHitTest = { [weak self] point in if let strongSelf = self { - if let avatarListContainerNode = strongSelf.avatarListContainerNode, avatarListContainerNode.frame.contains(point) { + if let avatarListWrapperNode = strongSelf.avatarListWrapperNode, avatarListWrapperNode.frame.contains(point) { return strongSelf.avatarListNode?.view } } @@ -371,7 +371,13 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { transition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: 0.0) let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context) + avatarListNode.backgroundColor = .clear avatarListNode.peer = item.peer + avatarListNode.firstFullSizeOnly = true + avatarListNode.offsetLocation = true + avatarListNode.customCenterTapAction = { [weak self] in + self?.contextSourceNode.requestDismiss?() + } avatarListNode.frame = CGRect(x: targetRect.width / 2.0, y: targetRect.height / 2.0, width: targetRect.width, height: targetRect.height) avatarListNode.controlsClippingNode.frame = CGRect(x: -targetRect.width / 2.0, y: -targetRect.height / 2.0, width: targetRect.width, height: targetRect.height) avatarListNode.controlsClippingOffsetNode.frame = CGRect(origin: CGPoint(x: targetRect.width / 2.0, y: targetRect.height / 2.0), size: CGSize()) @@ -383,7 +389,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { avatarListNode.update(size: targetRect.size, peer: item.peer, isExpanded: true, transition: .immediate) strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode) - + strongSelf.avatarListWrapperNode = avatarListWrapperNode strongSelf.avatarListContainerNode = avatarListContainerNode strongSelf.avatarListNode = avatarListNode @@ -445,7 +451,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.06, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) } else { strongSelf.extractedBackgroundImageNode.alpha = 0.0 - strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, delay: 0.15, removeOnCompletion: false, completion: { [weak self] _ in + strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, delay: 0.1, removeOnCompletion: false, completion: { [weak self] _ in self?.extractedBackgroundImageNode.image = nil self?.extractedBackgroundImageNode.layer.removeAllAnimations() }) @@ -479,6 +485,10 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { self.raiseHandTimer?.invalidate() } + @objc private func handleTap() { + print("tap") + } + override func selected() { super.selected() self.layoutParams?.0.action?(self.contextSourceNode) @@ -616,7 +626,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - rightInset - 30.0 - titleIconsWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (expandedStatusLayout, expandedStatusApply) = makeExpandedStatusLayout(TextNodeLayoutArguments(attributedString: expandedStatusAttributedString, backgroundColor: nil, maximumNumberOfLines: 4, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (expandedStatusLayout, expandedStatusApply) = makeExpandedStatusLayout(TextNodeLayoutArguments(attributedString: expandedStatusAttributedString, backgroundColor: nil, maximumNumberOfLines: 6, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let insets = UIEdgeInsets() diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index fab05e44a0..9bbb2d71d0 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -124,7 +124,7 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, network: } } -func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { +public func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { return postbox.combinedView(keys: [.basicPeer(rawPeerId)]) |> mapToSignal { views -> Signal in if accountPeerId == rawPeerId {