diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 0e260d08a6..f8b2713faa 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -217,7 +217,7 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer, firstEntry public class AvatarGalleryController: ViewController, StandalonePresentableController { public enum SourceCorners { case none - case round + case round(Bool) case roundRect(CGFloat) } @@ -256,6 +256,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr public var avatarPhotoEditCompletion: ((UIImage) -> Void)? public var avatarVideoEditCompletion: ((UIImage, URL, TGVideoEditAdjustments?) -> Void)? + public var removedEntry: ((AvatarGalleryEntry) -> Void)? + private let _hiddenMedia = Promise(nil) public var hiddenMedia: Signal { return self._hiddenMedia.get() @@ -265,7 +267,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private let editDisposable = MetaDisposable () - public init(context: AccountContext, peer: Peer, sourceCorners: SourceCorners = .round, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { + public init(context: AccountContext, peer: Peer, sourceCorners: SourceCorners = .round(true), remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { self.context = context self.peer = peer self.sourceCorners = sourceCorners @@ -318,23 +320,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr strongSelf.centralEntryIndex = 0 } if strongSelf.isViewLoaded { - let canDelete: Bool - if strongSelf.peer.id == strongSelf.context.account.peerId { - canDelete = true - } else if let group = strongSelf.peer as? TelegramGroup { - switch group.role { - case .creator, .admin: - canDelete = true - case .member: - canDelete = false - } - } else if let channel = strongSelf.peer as? TelegramChannel { - canDelete = channel.hasPermission(.changeInfo) - } else { - canDelete = false - } - - strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(context: context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: sourceCorners, delete: canDelete ? { + strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(context: context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: sourceCorners, delete: strongSelf.canDelete ? { self?.deleteEntry(entry) } : nil, setMain: { [weak self] in self?.setMainEntry(entry) @@ -495,24 +481,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr self?.presentingViewController?.dismiss(animated: false, completion: nil) } - let canDelete: Bool - if self.peer.id == self.context.account.peerId { - canDelete = true - } else if let group = self.peer as? TelegramGroup { - switch group.role { - case .creator, .admin: - canDelete = true - case .member: - canDelete = false - } - } else if let channel = self.peer as? TelegramChannel { - canDelete = channel.hasPermission(.changeInfo) - } else { - canDelete = false - } - let presentationData = self.presentationData - self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: canDelete ? { [weak self] in + self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in self?.deleteEntry(entry) } : nil, setMain: { [weak self] in self?.setMainEntry(entry) @@ -610,6 +580,25 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } } + private var canDelete: Bool { + let canDelete: Bool + if self.peer.id == self.context.account.peerId { + canDelete = true + } else if let group = self.peer as? TelegramGroup { + switch group.role { + case .creator, .admin: + canDelete = true + case .member: + canDelete = false + } + } else if let channel = self.peer as? TelegramChannel { + canDelete = channel.hasPermission(.changeInfo) + } else { + canDelete = false + } + return canDelete + } + private func setMainEntry(_ rawEntry: AvatarGalleryEntry) { var entry = rawEntry if case .topImage = entry, !self.entries.isEmpty { @@ -638,25 +627,10 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr entries.insert(previousFirstEntry, at: index) } - let canDelete: Bool - if self.peer.id == self.context.account.peerId { - canDelete = true - } else if let group = self.peer as? TelegramGroup { - switch group.role { - case .creator, .admin: - canDelete = true - case .member: - canDelete = false - } - } else if let channel = self.peer as? TelegramChannel { - canDelete = channel.hasPermission(.changeInfo) - } else { - canDelete = false - } + entries = normalizeEntries(entries) - - self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: canDelete ? { [weak self] in + self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in self?.deleteEntry(entry) } : nil, setMain: { [weak self] in self?.setMainEntry(entry) @@ -798,12 +772,19 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } private func deleteEntry(_ rawEntry: AvatarGalleryEntry) { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let proceed = { var entry = rawEntry if case .topImage = entry, !self.entries.isEmpty { entry = self.entries[0] } + self.removedEntry?(rawEntry) + + var focusOnItem: Int? + var updatedEntries = self.entries + var replaceItems = false + switch entry { case .topImage: if self.peer.id == self.context.account.peerId { @@ -827,8 +808,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr self.dismiss(forceAway: true) } else { if let index = self.entries.firstIndex(of: entry) { - self.entries.remove(at: index) - self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false)) + replaceItems = true + updatedEntries.remove(at: index) + focusOnItem = index - 1 } } } else { @@ -841,14 +823,26 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr self.dismiss(forceAway: true) } else { if let index = self.entries.firstIndex(of: entry) { - self.entries.remove(at: index) - self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false)) + replaceItems = true + updatedEntries.remove(at: index) + focusOnItem = index - 1 } } } } + + if replaceItems { + updatedEntries = normalizeEntries(updatedEntries) + self.galleryNode.pager.replaceItems(updatedEntries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in + self?.deleteEntry(entry) + } : nil, setMain: { [weak self] in + self?.setMainEntry(entry) + }, edit: { [weak self] in + self?.editEntry(entry) + }) }), centralItemIndex: focusOnItem, synchronous: true) + self.entries = updatedEntries + } } - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let actionSheet = ActionSheetController(presentationData: presentationData) let items: [ActionSheetItem] = [ ActionSheetButtonItem(title: presentationData.strings.Common_Delete, color: .destructive, action: { [weak actionSheet] in diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index b588d924ac..f59e22c638 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -266,7 +266,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { let mediaManager = self.context.sharedContext.mediaManager 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) + let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) videoNode.isUserInteractionEnabled = false videoNode.isHidden = true self.videoStartTimestamp = video.representation.startTimestamp @@ -429,7 +429,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { self.contentNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: self.contentNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) self.contentNode.clipsToBounds = true - if case .round = self.sourceCorners { + if case .round(true) = self.sourceCorners { self.contentNode.layer.animate(from: (self.contentNode.frame.width / 2.0) as NSNumber, to: 0.0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.default.rawValue, duration: 0.18, removeOnCompletion: false, completion: { [weak self] value in if value { self?.contentNode.clipsToBounds = false @@ -443,6 +443,8 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { self?.contentNode.clipsToBounds = false } }) + } else { + self.contentNode.clipsToBounds = false } self.statusNodeContainer.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.statusNodeContainer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index bfdd3db426..27a3bb13d2 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -180,6 +180,15 @@ enum PeerInfoAvatarListItem: Equatable { return videoRepresentations } } + + init(entry: AvatarGalleryEntry) { + switch entry { + case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _): + self = .topImage(representations, videoRepresentations, immediateThumbnailData) + case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): + self = .image(reference, representations, videoRepresentations, immediateThumbnailData) + } + } } final class PeerInfoAvatarListItemNode: ASDisplayNode { @@ -972,12 +981,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode { var items: [PeerInfoAvatarListItem] = [] for entry in entries { - switch entry { - 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)) - } + items.append(PeerInfoAvatarListItem(entry: entry)) } strongSelf.galleryEntries = entries strongSelf.items = items @@ -1532,7 +1536,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { 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) + let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .gallery) videoNode.isUserInteractionEnabled = false self.videoStartTimestamp = video.representation.startTimestamp self.videoContent = videoContent @@ -2536,10 +2540,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.addSubnode(self.separatorNode) self.avatarListNode.avatarContainerNode.tapped = { [weak self] in - self?.initiateAvatarExpansion(gallery: false) + self?.initiateAvatarExpansion(gallery: false, first: false) } self.editingContentNode.avatarNode.tapped = { [weak self] confirm in - self?.initiateAvatarExpansion(gallery: true) + self?.initiateAvatarExpansion(gallery: true, first: true) } self.editingContentNode.requestEditing = { [weak self] in self?.requestOpenAvatarForEditing?(true) @@ -2575,10 +2579,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { } } - func initiateAvatarExpansion(gallery: Bool) { + func initiateAvatarExpansion(gallery: Bool, first: Bool) { if self.isAvatarExpanded || gallery { if let currentEntry = self.avatarListNode.listContainerNode.currentEntry, let firstEntry = self.avatarListNode.listContainerNode.galleryEntries.first { - let entry = gallery ? firstEntry : currentEntry + let entry = first ? firstEntry : currentEntry self.requestAvatarExpansion?(true, self.avatarListNode.listContainerNode.galleryEntries, entry, self.avatarTransitionArguments(entry: currentEntry)) } } else if let entry = self.avatarListNode.listContainerNode.galleryEntries.first { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index dca1025f7f..e6c2f92556 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -2105,7 +2105,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } let entriesPromise = Promise<[AvatarGalleryEntry]>(entries) - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, sourceCorners: !strongSelf.headerNode.isAvatarExpanded ? .round : .none, remoteEntries: entriesPromise, skipInitial: true, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, sourceCorners: .round(!strongSelf.headerNode.isAvatarExpanded), remoteEntries: entriesPromise, skipInitial: true, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in }) galleryController.openAvatarSetup = { [weak self] completion in self?.openAvatarForEditing(hasRemove: false, completion: completion) @@ -2116,6 +2116,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD galleryController.avatarVideoEditCompletion = { [weak self] image, url, adjustments in self?.updateProfileVideo(image, url: url, adjustments: adjustments) } + galleryController.removedEntry = { [weak self] entry in + self?.headerNode.avatarListNode.listContainerNode.deleteItem(PeerInfoAvatarListItem(entry: entry)) + } strongSelf.hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in self?.headerNode.updateAvatarIsHidden(entry: entry) })) @@ -2129,6 +2132,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD return nil } })) + + Queue.mainQueue().after(0.4) { + strongSelf.resetHeaderExpansion() + } } self.headerNode.requestOpenAvatarForEditing = { [weak self] confirm in @@ -5401,7 +5408,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD self.canOpenAvatarByDragging = false let contentOffset = scrollView.contentOffset.y scrollView.panGestureRecognizer.isEnabled = false - self.headerNode.initiateAvatarExpansion(gallery: true) + self.headerNode.initiateAvatarExpansion(gallery: true, first: false) scrollView.panGestureRecognizer.isEnabled = true scrollView.contentOffset = CGPoint(x: 0.0, y: contentOffset) UIView.animate(withDuration: 0.1) {