Fix voice chat avatar expansion

This commit is contained in:
Ilya Laktyushin 2021-03-24 18:33:43 +05:00
parent 86e3e2bc35
commit 822ef1b32c
7 changed files with 78 additions and 28 deletions

View File

@ -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()

View File

@ -24,28 +24,37 @@ public enum AvatarGalleryEntryId: Hashable {
public func peerInfoProfilePhotos(context: AccountContext, peerId: PeerId) -> Signal<Any, NoError> {
return context.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|> mapToSignal { view -> Signal<AvatarGalleryEntry?, NoError> 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

View File

@ -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)
}

View File

@ -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)

View File

@ -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<Bool>(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<Bool, NoError>
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool, blurBackground: Bool, shouldBeDismissed: Signal<Bool, NoError>) {
self.controller = controller
self.sourceNode = sourceNode
self.keepInPlace = keepInPlace
self.blurBackground = blurBackground
self.shouldBeDismissed = shouldBeDismissed
}
func takeView() -> ContextControllerTakeViewInfo? {

View File

@ -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())
@ -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()

View File

@ -124,7 +124,7 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, network:
}
}
func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal<Bool, NoError> {
public func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal<Bool, NoError> {
return postbox.combinedView(keys: [.basicPeer(rawPeerId)])
|> mapToSignal { views -> Signal<Bool, NoError> in
if accountPeerId == rawPeerId {