diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD index 1d94ad70e4..5a3d6578ef 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD +++ b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD @@ -38,6 +38,7 @@ swift_library( "//submodules/AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode", "//submodules/LegacyComponents", + "//submodules/GalleryData", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageFileListPanelComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageFileListPanelComponent.swift index 824263cbc3..5fed1ed36e 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageFileListPanelComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageFileListPanelComponent.swift @@ -151,6 +151,7 @@ private final class FileListItemComponent: Component { let selectionState: SelectionState let hasNext: Bool let action: (EngineMessage.Id) -> Void + let contextAction: (EngineMessage.Id, ContextExtractedContentContainingView, ContextGesture) -> Void init( context: AccountContext, @@ -163,7 +164,8 @@ private final class FileListItemComponent: Component { sideInset: CGFloat, selectionState: SelectionState, hasNext: Bool, - action: @escaping (EngineMessage.Id) -> Void + action: @escaping (EngineMessage.Id) -> Void, + contextAction: @escaping (EngineMessage.Id, ContextExtractedContentContainingView, ContextGesture) -> Void ) { self.context = context self.theme = theme @@ -176,6 +178,7 @@ private final class FileListItemComponent: Component { self.selectionState = selectionState self.hasNext = hasNext self.action = action + self.contextAction = contextAction } static func ==(lhs: FileListItemComponent, rhs: FileListItemComponent) -> Bool { @@ -212,7 +215,10 @@ private final class FileListItemComponent: Component { return true } - final class View: HighlightTrackingButton { + final class View: ContextControllerSourceView { + private let extractedContainerView: ContextExtractedContentContainingView + private let containerButton: HighlightTrackingButton + private let title = ComponentView() private let subtitle = ComponentView() private let label = ComponentView() @@ -227,19 +233,53 @@ private final class FileListItemComponent: Component { private var checkLayer: CheckLayer? + private var isExtractedToContextMenu: Bool = false + private var highlightBackgroundFrame: CGRect? private var highlightBackgroundLayer: SimpleLayer? private var component: FileListItemComponent? + private weak var state: EmptyComponentState? override init(frame: CGRect) { + self.extractedContainerView = ContextExtractedContentContainingView() + self.containerButton = HighlightTrackingButton() + self.separatorLayer = SimpleLayer() super.init(frame: frame) self.layer.addSublayer(self.separatorLayer) - self.highligthedChanged = { [weak self] isHighlighted in + self.addSubview(self.extractedContainerView) + self.targetViewForActivationProgress = self.extractedContainerView.contentView + + self.extractedContainerView.contentView.addSubview(self.containerButton) + + self.extractedContainerView.isExtractedToContextPreviewUpdated = { [weak self] value in + guard let self, let component = self.component else { + return + } + self.containerButton.clipsToBounds = value + self.containerButton.backgroundColor = value ? component.theme.list.plainBackgroundColor : nil + self.containerButton.layer.cornerRadius = value ? 10.0 : 0.0 + } + self.extractedContainerView.willUpdateIsExtractedToContextPreview = { [weak self] value, transition in + guard let self else { + return + } + self.isExtractedToContextMenu = value + + let mappedTransition: Transition + if value { + mappedTransition = Transition(transition) + } else { + mappedTransition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) + } + self.state?.updated(transition: mappedTransition) + } + + self.containerButton.highligthedChanged = { [weak self] isHighlighted in guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else { return } @@ -267,7 +307,15 @@ private final class FileListItemComponent: Component { } } } - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + + self.activated = { [weak self] gesture, _ in + guard let self, let component = self.component else { + gesture.cancel() + return + } + component.contextAction(component.messageId, self.extractedContainerView, gesture) + } } required init?(coder: NSCoder) { @@ -301,6 +349,11 @@ private final class FileListItemComponent: Component { } self.component = component + self.state = state + + self.isGestureEnabled = component.selectionState == .none + + let contextInset: CGFloat = self.isExtractedToContextMenu ? 12.0 : 0.0 let spacing: CGFloat = 1.0 let height: CGFloat = 52.0 @@ -323,7 +376,7 @@ private final class FileListItemComponent: Component { } else { checkLayer = CheckLayer(theme: CheckNodeTheme(theme: component.theme, style: .plain)) self.checkLayer = checkLayer - self.layer.addSublayer(checkLayer) + self.containerButton.layer.addSublayer(checkLayer) checkLayer.frame = CGRect(origin: CGPoint(x: -checkSize, y: floor((height - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)) checkLayer.setSelected(isSelected, animated: false) checkLayer.setNeedsDisplay() @@ -338,7 +391,8 @@ private final class FileListItemComponent: Component { } } - let rightInset: CGFloat = 16.0 + component.sideInset + let rightInset: CGFloat = contextInset * 2.0 + 16.0 + component.sideInset + let verticalInset: CGFloat = 1.0 if case let .fileExtension(text) = component.icon { let iconView: UIImageView @@ -347,7 +401,7 @@ private final class FileListItemComponent: Component { } else { iconView = UIImageView() self.iconView = iconView - self.addSubview(iconView) + self.containerButton.addSubview(iconView) } let iconText: ComponentView @@ -362,7 +416,7 @@ private final class FileListItemComponent: Component { iconView.image = extensionImage(fileExtension: "mp3") } if let image = iconView.image { - let iconFrame = CGRect(origin: CGPoint(x: iconLeftInset + floor(( leftInset - iconLeftInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size) + let iconFrame = CGRect(origin: CGPoint(x: iconLeftInset + floor((leftInset - iconLeftInset - image.size.width) / 2.0), y: floor((height - verticalInset * 2.0 - image.size.height) / 2.0)), size: image.size) transition.setFrame(view: iconView, frame: iconFrame) let iconTextSize = iconText.update( @@ -377,7 +431,7 @@ private final class FileListItemComponent: Component { ) if let iconTextView = iconText.view { if iconTextView.superview == nil { - self.addSubview(iconTextView) + self.containerButton.addSubview(iconTextView) } transition.setFrame(view: iconTextView, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floor((iconFrame.width - iconTextSize.width) / 2.0), y: iconFrame.maxY - iconTextSize.height - 4.0), size: iconTextSize)) } @@ -404,7 +458,7 @@ private final class FileListItemComponent: Component { iconImageNode = TransformImageNode() self.iconImageNode = iconImageNode - self.addSubview(iconImageNode.view) + self.containerButton.addSubview(iconImageNode.view) } let iconSize = CGSize(width: 40.0, height: 40.0) @@ -429,7 +483,7 @@ private final class FileListItemComponent: Component { } } - let iconFrame = CGRect(origin: CGPoint(x: iconLeftInset + floor((leftInset - iconLeftInset - iconSize.width) / 2.0), y: floor((height - iconSize.height) / 2.0)), size: iconSize) + let iconFrame = CGRect(origin: CGPoint(x: iconLeftInset + floor((leftInset - iconLeftInset - iconSize.width) / 2.0), y: floor((height - verticalInset * 2.0 - iconSize.height) / 2.0)), size: iconSize) transition.setFrame(view: iconImageNode.view, frame: iconFrame) let iconImageLayout = iconImageNode.asyncLayout() @@ -454,11 +508,11 @@ private final class FileListItemComponent: Component { } else { semanticStatusNode = SemanticStatusNode(backgroundNodeColor: .clear, foregroundNodeColor: .white) self.semanticStatusNode = semanticStatusNode - self.addSubview(semanticStatusNode.view) + self.containerButton.addSubview(semanticStatusNode.view) } let iconSize = CGSize(width: 40.0, height: 40.0) - let iconFrame = CGRect(origin: CGPoint(x: iconLeftInset + floor((leftInset - iconLeftInset - iconSize.width) / 2.0), y: floor((height - iconSize.height) / 2.0)), size: iconSize) + let iconFrame = CGRect(origin: CGPoint(x: iconLeftInset + floor((leftInset - iconLeftInset - iconSize.width) / 2.0), y: floor((height - verticalInset * 2.0 - iconSize.height) / 2.0)), size: iconSize) transition.setFrame(view: semanticStatusNode.view, frame: iconFrame) semanticStatusNode.backgroundNodeColor = component.theme.list.itemCheckColors.fillColor @@ -507,13 +561,13 @@ private final class FileListItemComponent: Component { let contentHeight = titleSize.height + spacing + subtitleSize.height - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - contentHeight) / 2.0)), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - verticalInset * 2.0 - contentHeight) / 2.0)), size: titleSize) let subtitleFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + spacing), size: subtitleSize) if let titleView = self.title.view { if titleView.superview == nil { titleView.isUserInteractionEnabled = false - self.addSubview(titleView) + self.containerButton.addSubview(titleView) } titleView.frame = titleFrame if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x { @@ -522,7 +576,7 @@ private final class FileListItemComponent: Component { if let previousTitleFrame, let previousTitleContents, previousTitleFrame.size != titleSize { previousTitleContents.frame = CGRect(origin: previousTitleFrame.origin, size: previousTitleFrame.size) - self.addSubview(previousTitleContents) + self.containerButton.addSubview(previousTitleContents) transition.setFrame(view: previousTitleContents, frame: CGRect(origin: titleFrame.origin, size: previousTitleFrame.size)) transition.setAlpha(view: previousTitleContents, alpha: 0.0, completion: { [weak previousTitleContents] _ in @@ -534,14 +588,14 @@ private final class FileListItemComponent: Component { if let subtitleView = self.subtitle.view { if subtitleView.superview == nil { subtitleView.isUserInteractionEnabled = false - self.addSubview(subtitleView) + self.containerButton.addSubview(subtitleView) } transition.setFrame(view: subtitleView, frame: subtitleFrame) } if let labelView = self.label.view { if labelView.superview == nil { labelView.isUserInteractionEnabled = false - self.addSubview(labelView) + self.containerButton.addSubview(labelView) } transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset - labelSize.width, y: floor((height - labelSize.height) / 2.0)), size: labelSize)) } @@ -554,6 +608,14 @@ private final class FileListItemComponent: Component { self.highlightBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height + ((component.hasNext) ? UIScreenPixel : 0.0))) + let resultBounds = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)) + transition.setFrame(view: self.extractedContainerView, frame: resultBounds) + transition.setFrame(view: self.extractedContainerView.contentView, frame: resultBounds) + self.extractedContainerView.contentRect = resultBounds + + let containerFrame = CGRect(origin: CGPoint(x: contextInset, y: verticalInset), size: CGSize(width: availableSize.width - contextInset * 2.0, height: height - verticalInset * 2.0)) + transition.setFrame(view: self.containerButton, frame: containerFrame) + return CGSize(width: availableSize.width, height: height) } } @@ -611,18 +673,21 @@ final class StorageFileListPanelComponent: Component { let context: AccountContext let items: Items? let selectionState: StorageUsageScreenComponent.SelectionState? - let peerAction: (EngineMessage.Id) -> Void + let action: (EngineMessage.Id) -> Void + let contextAction: (EngineMessage.Id, ContextExtractedContentContainingView, ContextGesture) -> Void init( context: AccountContext, items: Items?, selectionState: StorageUsageScreenComponent.SelectionState?, - peerAction: @escaping (EngineMessage.Id) -> Void + action: @escaping (EngineMessage.Id) -> Void, + contextAction: @escaping (EngineMessage.Id, ContextExtractedContentContainingView, ContextGesture) -> Void ) { self.context = context self.items = items self.selectionState = selectionState - self.peerAction = peerAction + self.action = action + self.contextAction = contextAction } static func ==(lhs: StorageFileListPanelComponent, rhs: StorageFileListPanelComponent) -> Bool { @@ -681,8 +746,14 @@ final class StorageFileListPanelComponent: Component { } } + private final class ScrollViewImpl: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + class View: UIView, UIScrollViewDelegate { - private let scrollView: UIScrollView + private let scrollView: ScrollViewImpl private let measureItem = ComponentView() private var visibleItems: [EngineMessage.Id: ComponentView] = [:] @@ -694,7 +765,7 @@ final class StorageFileListPanelComponent: Component { private var itemLayout: ItemLayout? override init(frame: CGRect) { - self.scrollView = UIScrollView() + self.scrollView = ScrollViewImpl() super.init(frame: frame) @@ -726,6 +797,10 @@ final class StorageFileListPanelComponent: Component { } } + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + cancelContextGestures(view: scrollView) + } + private func updateScrolling(transition: Transition) { guard let component = self.component, let environment = self.environment, let items = component.items, let itemLayout = self.itemLayout else { return @@ -898,7 +973,8 @@ final class StorageFileListPanelComponent: Component { sideInset: environment.containerInsets.left, selectionState: itemSelectionState, hasNext: index != items.items.count - 1, - action: component.peerAction + action: component.action, + contextAction: component.contextAction )), environment: {}, containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight) @@ -949,6 +1025,8 @@ final class StorageFileListPanelComponent: Component { selectionState: .none, hasNext: false, action: { _ in + }, + contextAction: { _, _, _ in } )), environment: {}, diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageMediaGridPanelComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageMediaGridPanelComponent.swift index 78ce1d92dd..8057e16c8b 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageMediaGridPanelComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageMediaGridPanelComponent.swift @@ -261,18 +261,21 @@ final class StorageMediaGridPanelComponent: Component { let context: AccountContext let items: Items? let selectionState: StorageUsageScreenComponent.SelectionState? - let peerAction: (EngineMessage.Id) -> Void + let action: (EngineMessage.Id) -> Void + let contextAction: (EngineMessage.Id, UIView, CGRect, ContextGesture) -> Void init( context: AccountContext, items: Items?, selectionState: StorageUsageScreenComponent.SelectionState?, - peerAction: @escaping (EngineMessage.Id) -> Void + action: @escaping (EngineMessage.Id) -> Void, + contextAction: @escaping (EngineMessage.Id, UIView, CGRect, ContextGesture) -> Void ) { self.context = context self.items = items self.selectionState = selectionState - self.peerAction = peerAction + self.action = action + self.contextAction = contextAction } static func ==(lhs: StorageMediaGridPanelComponent, rhs: StorageMediaGridPanelComponent) -> Bool { @@ -360,7 +363,7 @@ final class StorageMediaGridPanelComponent: Component { } } - class View: UIView, UIScrollViewDelegate { + class View: ContextControllerSourceView, UIScrollViewDelegate { private let scrollView: UIScrollView private var visibleLayers: [EngineMessage.Id: MediaGridLayer] = [:] @@ -372,6 +375,8 @@ final class StorageMediaGridPanelComponent: Component { private var environment: StorageUsagePanelEnvironment? private var itemLayout: ItemLayout? + private weak var currentGestureItemLayer: MediaGridLayer? + override init(frame: CGRect) { self.scrollView = UIScrollView() @@ -395,12 +400,121 @@ final class StorageMediaGridPanelComponent: Component { self.addSubview(self.scrollView) self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + + self.shouldBegin = { [weak self] point in + guard let self else { + return false + } + + var itemLayer: MediaGridLayer? + let scrollPoint = self.convert(point, to: self.scrollView) + for (_, itemLayerValue) in self.visibleLayers { + if itemLayerValue.frame.contains(scrollPoint) { + itemLayer = itemLayerValue + break + } + } + + guard let itemLayer else { + return false + } + + self.currentGestureItemLayer = itemLayer + + return true + } + + self.customActivationProgress = { [weak self] progress, update in + guard let self, let itemLayer = self.currentGestureItemLayer else { + return + } + + let targetContentRect = CGRect(origin: CGPoint(), size: itemLayer.bounds.size) + + let scaleSide = itemLayer.bounds.width + let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) + let currentScale = 1.0 * (1.0 - progress) + minScale * progress + + let originalCenterOffsetX: CGFloat = itemLayer.bounds.width / 2.0 - targetContentRect.midX + let scaledCenterOffsetX: CGFloat = originalCenterOffsetX * currentScale + + let originalCenterOffsetY: CGFloat = itemLayer.bounds.height / 2.0 - targetContentRect.midY + let scaledCenterOffsetY: CGFloat = originalCenterOffsetY * currentScale + + let scaleMidX: CGFloat = scaledCenterOffsetX - originalCenterOffsetX + let scaleMidY: CGFloat = scaledCenterOffsetY - originalCenterOffsetY + + switch update { + case .update: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + itemLayer.transform = sublayerTransform + case .begin: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + itemLayer.transform = sublayerTransform + case .ended: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + let previousTransform = itemLayer.transform + itemLayer.transform = sublayerTransform + + itemLayer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "transform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) + } + } + + self.activated = { [weak self] gesture, _ in + guard let self, let component = self.component, let itemLayer = self.currentGestureItemLayer else { + return + } + self.currentGestureItemLayer = nil + guard let message = itemLayer.message else { + return + } + let rect = self.convert(itemLayer.frame, from: self.scrollView) + + component.contextAction(message.id, self, rect, gesture) + } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + var foundItemLayer: MediaGridLayer? + for (_, itemLayer) in self.visibleLayers { + if let message = itemLayer.message, message.id == messageId { + foundItemLayer = itemLayer + } + } + guard let itemLayer = foundItemLayer else { + return nil + } + + let itemFrame = self.convert(itemLayer.frame, from: self.scrollView) + let proxyNode = ASDisplayNode() + proxyNode.frame = itemFrame + if let contents = itemLayer.contents { + if let image = contents as? UIImage { + proxyNode.contents = image.cgImage + } else { + proxyNode.contents = contents + } + } + proxyNode.isHidden = true + self.addSubnode(proxyNode) + + let escapeNotification = EscapeNotification { + proxyNode.removeFromSupernode() + } + + return (proxyNode, proxyNode.bounds, { + let view = UIView() + view.frame = proxyNode.frame + view.layer.contents = proxyNode.layer.contents + escapeNotification.keep() + return (view, nil) + }) + } + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { guard let component = self.component else { @@ -409,7 +523,7 @@ final class StorageMediaGridPanelComponent: Component { let point = recognizer.location(in: self.scrollView) for (id, itemLayer) in self.visibleLayers { if itemLayer.frame.contains(point) { - component.peerAction(id) + component.action(id) break } } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerListPanelComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerListPanelComponent.swift index c4ba399165..291041c014 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerListPanelComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerListPanelComponent.swift @@ -18,6 +18,19 @@ import AvatarNode private let avatarFont = avatarPlaceholderFont(size: 15.0) +func cancelContextGestures(view: UIView) { + if let gestureRecognizers = view.gestureRecognizers { + for gesture in gestureRecognizers { + if let gesture = gesture as? ContextGesture { + gesture.cancel() + } + } + } + for subview in view.subviews { + cancelContextGestures(view: subview) + } +} + private final class PeerListItemComponent: Component { enum SelectionState: Equatable { case none @@ -33,6 +46,7 @@ private final class PeerListItemComponent: Component { let selectionState: SelectionState let hasNext: Bool let action: (EnginePeer) -> Void + let contextAction: (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void init( context: AccountContext, @@ -43,7 +57,8 @@ private final class PeerListItemComponent: Component { label: String, selectionState: SelectionState, hasNext: Bool, - action: @escaping (EnginePeer) -> Void + action: @escaping (EnginePeer) -> Void, + contextAction: @escaping (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void ) { self.context = context self.theme = theme @@ -54,6 +69,7 @@ private final class PeerListItemComponent: Component { self.selectionState = selectionState self.hasNext = hasNext self.action = action + self.contextAction = contextAction } static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool { @@ -84,7 +100,10 @@ private final class PeerListItemComponent: Component { return true } - final class View: HighlightTrackingButton { + final class View: ContextControllerSourceView { + private let extractedContainerView: ContextExtractedContentContainingView + private let containerButton: HighlightTrackingButton + private let title = ComponentView() private let label = ComponentView() private let separatorLayer: SimpleLayer @@ -92,22 +111,58 @@ private final class PeerListItemComponent: Component { private var checkLayer: CheckLayer? + private var isExtractedToContextMenu: Bool = false + private var highlightBackgroundFrame: CGRect? private var highlightBackgroundLayer: SimpleLayer? private var component: PeerListItemComponent? + private weak var state: EmptyComponentState? override init(frame: CGRect) { self.separatorLayer = SimpleLayer() + + self.extractedContainerView = ContextExtractedContentContainingView() + self.containerButton = HighlightTrackingButton() + self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode.isLayerBacked = true super.init(frame: frame) self.layer.addSublayer(self.separatorLayer) - self.layer.addSublayer(self.avatarNode.layer) - self.highligthedChanged = { [weak self] isHighlighted in + self.addSubview(self.extractedContainerView) + self.targetViewForActivationProgress = self.extractedContainerView.contentView + + self.extractedContainerView.contentView.addSubview(self.containerButton) + + self.containerButton.layer.addSublayer(self.avatarNode.layer) + + self.extractedContainerView.isExtractedToContextPreviewUpdated = { [weak self] value in + guard let self, let component = self.component else { + return + } + self.containerButton.clipsToBounds = value + self.containerButton.backgroundColor = value ? component.theme.list.plainBackgroundColor : nil + self.containerButton.layer.cornerRadius = value ? 10.0 : 0.0 + } + self.extractedContainerView.willUpdateIsExtractedToContextPreview = { [weak self] value, transition in + guard let self else { + return + } + self.isExtractedToContextMenu = value + + let mappedTransition: Transition + if value { + mappedTransition = Transition(transition) + } else { + mappedTransition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) + } + self.state?.updated(transition: mappedTransition) + } + + self.containerButton.highligthedChanged = { [weak self] isHighlighted in guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else { return } @@ -135,7 +190,15 @@ private final class PeerListItemComponent: Component { } } } - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + + self.activated = { [weak self] gesture, _ in + guard let self, let component = self.component, let peer = component.peer else { + gesture.cancel() + return + } + component.contextAction(peer, self.extractedContainerView, gesture) + } } required init?(coder: NSCoder) { @@ -169,6 +232,11 @@ private final class PeerListItemComponent: Component { } self.component = component + self.state = state + + self.isGestureEnabled = component.selectionState == .none + + let contextInset: CGFloat = self.isExtractedToContextMenu ? 12.0 : 0.0 let height: CGFloat = 52.0 var leftInset: CGFloat = 62.0 + component.sideInset @@ -190,7 +258,7 @@ private final class PeerListItemComponent: Component { } else { checkLayer = CheckLayer(theme: CheckNodeTheme(theme: component.theme, style: .plain)) self.checkLayer = checkLayer - self.layer.addSublayer(checkLayer) + self.containerButton.layer.addSublayer(checkLayer) checkLayer.frame = CGRect(origin: CGPoint(x: -checkSize, y: floor((height - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)) checkLayer.setSelected(isSelected, animated: false) checkLayer.setNeedsDisplay() @@ -205,7 +273,8 @@ private final class PeerListItemComponent: Component { } } - let rightInset: CGFloat = 16.0 + component.sideInset + let rightInset: CGFloat = contextInset * 2.0 + 16.0 + component.sideInset + let verticalInset: CGFloat = 1.0 let avatarSize: CGFloat = 40.0 @@ -248,11 +317,11 @@ private final class PeerListItemComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width - leftInset - rightInset - labelSize.width - 4.0, height: 100.0) ) - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - verticalInset * 2.0 - titleSize.height) / 2.0)), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { titleView.isUserInteractionEnabled = false - self.addSubview(titleView) + self.containerButton.addSubview(titleView) } titleView.frame = titleFrame if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x { @@ -273,9 +342,9 @@ private final class PeerListItemComponent: Component { if let labelView = self.label.view { if labelView.superview == nil { labelView.isUserInteractionEnabled = false - self.addSubview(labelView) + self.containerButton.addSubview(labelView) } - transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset - labelSize.width, y: floor((height - labelSize.height) / 2.0)), size: labelSize)) + transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset - labelSize.width, y: floor((height - verticalInset * 2.0 - labelSize.height) / 2.0)), size: labelSize)) } if themeUpdated { @@ -286,6 +355,14 @@ private final class PeerListItemComponent: Component { self.highlightBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height + ((component.hasNext) ? UIScreenPixel : 0.0))) + let resultBounds = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)) + transition.setFrame(view: self.extractedContainerView, frame: resultBounds) + transition.setFrame(view: self.extractedContainerView.contentView, frame: resultBounds) + self.extractedContainerView.contentRect = resultBounds + + let containerFrame = CGRect(origin: CGPoint(x: contextInset, y: verticalInset), size: CGSize(width: availableSize.width - contextInset * 2.0, height: height - verticalInset * 2.0)) + transition.setFrame(view: self.containerButton, frame: containerFrame) + return CGSize(width: availableSize.width, height: height) } } @@ -344,17 +421,20 @@ final class StoragePeerListPanelComponent: Component { let items: Items? let selectionState: StorageUsageScreenComponent.SelectionState? let peerAction: (EnginePeer) -> Void + let contextAction: (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void init( context: AccountContext, items: Items?, selectionState: StorageUsageScreenComponent.SelectionState?, - peerAction: @escaping (EnginePeer) -> Void + peerAction: @escaping (EnginePeer) -> Void, + contextAction: @escaping (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void ) { self.context = context self.items = items self.selectionState = selectionState self.peerAction = peerAction + self.contextAction = contextAction } static func ==(lhs: StoragePeerListPanelComponent, rhs: StoragePeerListPanelComponent) -> Bool { @@ -413,8 +493,14 @@ final class StoragePeerListPanelComponent: Component { } } + private final class ScrollViewImpl: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + class View: UIView, UIScrollViewDelegate { - private let scrollView: UIScrollView + private let scrollView: ScrollViewImpl private let measureItem = ComponentView() private var visibleItems: [EnginePeer.Id: ComponentView] = [:] @@ -426,7 +512,7 @@ final class StoragePeerListPanelComponent: Component { private var itemLayout: ItemLayout? override init(frame: CGRect) { - self.scrollView = UIScrollView() + self.scrollView = ScrollViewImpl() super.init(frame: frame) @@ -458,6 +544,10 @@ final class StoragePeerListPanelComponent: Component { } } + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + cancelContextGestures(view: scrollView) + } + private func updateScrolling(transition: Transition) { guard let component = self.component, let environment = self.environment, let items = component.items, let itemLayout = self.itemLayout else { return @@ -505,7 +595,8 @@ final class StoragePeerListPanelComponent: Component { label: dataSizeString(item.size, formatting: dataSizeFormatting), selectionState: itemSelectionState, hasNext: index != items.items.count - 1, - action: component.peerAction + action: component.peerAction, + contextAction: component.contextAction )), environment: {}, containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight) @@ -554,6 +645,8 @@ final class StoragePeerListPanelComponent: Component { selectionState: .none, hasNext: false, action: { _ in + }, + contextAction: { _, _, _ in } )), environment: {}, diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsagePanelContainerComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsagePanelContainerComponent.swift index 3be178f919..73b136a70d 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsagePanelContainerComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsagePanelContainerComponent.swift @@ -439,6 +439,13 @@ final class StorageUsagePanelContainerComponent: Component { fatalError("init(coder:) has not been implemented") } + var currentPanelView: UIView? { + guard let currentId = self.currentId, let panel = self.visiblePanels[currentId] else { + return nil + } + return panel.view + } + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index d4f0f9f1d4..4babd975f3 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -21,6 +21,7 @@ import UndoUI import AnimatedStickerNode import TelegramAnimatedStickerNode import TelegramStringFormatting +import GalleryData #if DEBUG import os.signpost @@ -1561,6 +1562,88 @@ final class StorageUsageScreenComponent: Component { } else { self.openPeer(peer: peer) } + }, + contextAction: { [weak self] peer, sourceView, gesture in + guard let self, let component = self.component else { + return + } + + var itemList: [ContextMenuItem] = [] + //TODO:localize + itemList.append(.action(ContextMenuActionItem( + text: "Show Details", + icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) }, + action: { [weak self] c, _ in + c.dismiss(completion: { [weak self] in + guard let self else { + return + } + self.openPeer(peer: peer) + }) + }) + )) + itemList.append(.action(ContextMenuActionItem( + text: "Open Profile", + icon: { theme in + if case .user = peer { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) + } else { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Groups"), color: theme.contextMenu.primaryColor) + } + }, + action: { [weak self] c, _ in + c.dismiss(completion: { [weak self] in + guard let self, let component = self.component, let controller = self.controller?() else { + return + } + let peerInfoController = component.context.sharedContext.makePeerInfoController( + context: component.context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .generic, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) + if let peerInfoController { + controller.push(peerInfoController) + } + }) + }) + )) + itemList.append(.action(ContextMenuActionItem( + text: "Select", + icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, + action: { [weak self] c, _ in + c.dismiss(completion: { + }) + + guard let self else { + return + } + if self.selectionState == nil { + self.selectionState = SelectionState() + } + self.selectionState = self.selectionState?.togglePeer(id: peer.id) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + }) + )) + let items = ContextController.Items(content: .list(itemList)) + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + let controller = ContextController( + account: component.context.account, + presentationData: presentationData, + source: .extracted(StorageUsageListContextExtractedContentSource(contentView: sourceView)), items: .single(items), recognizer: nil, gesture: gesture) + + self.controller?()?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismiss() + } + return true + }) + self.controller?()?.presentInGlobalOverlay(controller) } )) )) @@ -1573,15 +1656,25 @@ final class StorageUsageScreenComponent: Component { context: component.context, items: self.imageItems, selectionState: self.selectionState, - peerAction: { [weak self] messageId in + action: { [weak self] messageId in guard let self else { return } - if self.selectionState == nil { - self.selectionState = SelectionState() + guard let message = self.currentMessages[messageId] else { + return } - self.selectionState = self.selectionState?.toggleMessage(id: messageId) - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + if self.selectionState == nil { + self.openMessage(message: message) + } else { + self.selectionState = self.selectionState?.toggleMessage(id: messageId) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + } + }, + contextAction: { [weak self] messageId, containerView, sourceRect, gesture in + guard let self else { + return + } + self.messageGaleryContextAction(messageId: messageId, sourceView: containerView, sourceRect: sourceRect, gesture: gesture) } )) )) @@ -1594,15 +1687,25 @@ final class StorageUsageScreenComponent: Component { context: component.context, items: self.fileItems, selectionState: self.selectionState, - peerAction: { [weak self] messageId in + action: { [weak self] messageId in guard let self else { return } - if self.selectionState == nil { - self.selectionState = SelectionState() + guard let message = self.currentMessages[messageId] else { + return } - self.selectionState = self.selectionState?.toggleMessage(id: messageId) - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + if self.selectionState == nil { + self.openMessage(message: message) + } else { + self.selectionState = self.selectionState?.toggleMessage(id: messageId) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + } + }, + contextAction: { [weak self] messageId, containerView, gesture in + guard let self else { + return + } + self.messageContextAction(messageId: messageId, sourceView: containerView, gesture: gesture) } )) )) @@ -1615,15 +1718,30 @@ final class StorageUsageScreenComponent: Component { context: component.context, items: self.musicItems, selectionState: self.selectionState, - peerAction: { [weak self] messageId in + action: { [weak self] messageId in guard let self else { return } - if self.selectionState == nil { - self.selectionState = SelectionState() + guard let message = self.currentMessages[messageId] else { + return } - self.selectionState = self.selectionState?.toggleMessage(id: messageId) - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + if self.selectionState == nil { + //self.openMessage(message: message) + let _ = message + + self.selectionState = SelectionState() + self.selectionState = self.selectionState?.toggleMessage(id: messageId) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + } else { + self.selectionState = self.selectionState?.toggleMessage(id: messageId) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + } + }, + contextAction: { [weak self] messageId, containerView, gesture in + guard let self else { + return + } + self.messageContextAction(messageId: messageId, sourceView: containerView, gesture: gesture) } )) )) @@ -1974,6 +2092,316 @@ final class StorageUsageScreenComponent: Component { controller.push(childController) } + private func messageGaleryContextAction(messageId: EngineMessage.Id, sourceView: UIView, sourceRect: CGRect, gesture: ContextGesture) { + guard let component = self.component, let message = self.currentMessages[messageId] else { + gesture.cancel() + return + } + + let _ = (chatMediaListPreviewControllerData( + context: component.context, + chatLocation: .peer(id: message.id.peerId), + chatLocationContextHolder: nil, + message: message, + standalone: true, + reverseMessageGalleryOrder: false, + navigationController: self.controller?()?.navigationController as? NavigationController + ) + |> deliverOnMainQueue).start(next: { [weak self] previewData in + guard let self, let component = self.component, let previewData else { + gesture.cancel() + return + } + + let context = component.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in + c.dismiss(completion: { [weak self] in + guard let self, let component = self.component, let controller = self.controller?(), let navigationController = controller.navigationController as? NavigationController else { + return + } + guard let peer = message.peers[message.id.peerId].flatMap(EnginePeer.init) else { + return + } + + var chatLocation: NavigateToChatControllerParams.Location = .peer(peer) + if case let .channel(channel) = peer, channel.flags.contains(.isForum), let threadId = message.threadId { + chatLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)) + } + + component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams( + navigationController: navigationController, + context: component.context, + chatLocation: chatLocation, + subject: .message(id: .id(message.id), highlight: true, timecode: nil), + keepStack: .always + )) + }) + }))) + + items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuSelect, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor) + }, action: { [weak self] c, _ in + c.dismiss(completion: { + }) + + guard let self else { + return + } + if self.selectionState == nil { + self.selectionState = SelectionState() + } + self.selectionState = self.selectionState?.toggleMessage(id: message.id) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + }))) + + switch previewData { + case let .gallery(gallery): + gallery.setHintWillBePresentedInPreviewingContext(true) + let contextController = ContextController( + account: component.context.account, + presentationData: presentationData, + source: .controller(StorageUsageListContextGalleryContentSourceImpl( + controller: gallery, + sourceView: sourceView, + sourceRect: sourceRect + )), + items: .single(ContextController.Items(content: .list(items))), + gesture: gesture + ) + self.controller?()?.presentInGlobalOverlay(contextController) + case .instantPage: + break + } + }) + } + + private func messageContextAction(messageId: EngineMessage.Id, sourceView: ContextExtractedContentContainingView, gesture: ContextGesture) { + guard let component = self.component else { + return + } + guard let message = self.currentMessages[messageId] else { + return + } + + //TODO:localize + var openTitle: String = "Open" + var isAudio: Bool = false + for media in message.media { + if let _ = media as? TelegramMediaImage { + openTitle = "Open Photo" + } else if let file = media as? TelegramMediaFile { + if file.isVideo { + openTitle = "Open Video" + } else { + openTitle = "Open File" + } + isAudio = file.isMusic || file.isVoice + } + } + + var itemList: [ContextMenuItem] = [] + //TODO:localize + if !isAudio { + itemList.append(.action(ContextMenuActionItem( + text: openTitle, + icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Expand"), color: theme.contextMenu.primaryColor) }, + action: { [weak self] c, _ in + c.dismiss(completion: { [weak self] in + guard let self else { + return + } + self.openMessage(message: message) + }) + }) + )) + } + itemList.append(.action(ContextMenuActionItem( + text: "View in Chat", + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) + }, + action: { [weak self] c, _ in + c.dismiss(completion: { [weak self] in + guard let self, let component = self.component, let controller = self.controller?(), let navigationController = controller.navigationController as? NavigationController else { + return + } + guard let peer = message.peers[message.id.peerId].flatMap(EnginePeer.init) else { + return + } + + var chatLocation: NavigateToChatControllerParams.Location = .peer(peer) + if case let .channel(channel) = peer, channel.flags.contains(.isForum), let threadId = message.threadId { + chatLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)) + } + + component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams( + navigationController: navigationController, + context: component.context, + chatLocation: chatLocation, + subject: .message(id: .id(message.id), highlight: true, timecode: nil), + keepStack: .always + )) + }) + }) + )) + itemList.append(.action(ContextMenuActionItem( + text: "Select", + icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, + action: { [weak self] c, _ in + c.dismiss(completion: { + }) + + guard let self else { + return + } + if self.selectionState == nil { + self.selectionState = SelectionState() + } + self.selectionState = self.selectionState?.toggleMessage(id: message.id) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + }) + )) + let items = ContextController.Items(content: .list(itemList)) + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + let controller = ContextController( + account: component.context.account, + presentationData: presentationData, + source: .extracted(StorageUsageListContextExtractedContentSource(contentView: sourceView)), items: .single(items), recognizer: nil, gesture: gesture) + + self.controller?()?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismiss() + } + return true + }) + self.controller?()?.presentInGlobalOverlay(controller) + } + + private func openMessage(message: Message) { + guard let component = self.component else { + return + } + guard let controller = self.controller?(), let navigationController = controller.navigationController as? NavigationController else { + return + } + let foundGalleryMessage: Message? = message + guard let galleryMessage = foundGalleryMessage else { + return + } + self.endEditing(true) + + let _ = component.context.sharedContext.openChatMessage(OpenChatMessageParams( + context: component.context, + chatLocation: .peer(id: message.id.peerId), + chatLocationContextHolder: nil, + message: galleryMessage, + standalone: true, + reverseMessageGalleryOrder: true, + navigationController: navigationController, + dismissInput: { [weak self] in + self?.endEditing(true) + }, present: { [weak self] c, a in + guard let self else { + return + } + self.controller?()?.present(c, in: .window(.root), with: a, blockInteraction: true) + }, + transitionNode: { [weak self] messageId, media in + guard let self else { + return nil + } + + if let panelContainerView = self.panelContainer.view as? StorageUsagePanelContainerComponent.View { + if let currentPanelView = panelContainerView.currentPanelView as? StorageMediaGridPanelComponent.View { + return currentPanelView.transitionNodeForGallery(messageId: messageId, media: media) + } + } + + return nil + }, addToTransitionSurface: { [weak self] view in + guard let self else { + return + } + if let panelContainerView = self.panelContainer.view as? StorageUsagePanelContainerComponent.View { + panelContainerView.currentPanelView?.addSubview(view) + } + }, openUrl: { [weak self] url in + guard let self else { + return + } + let _ = self + }, openPeer: { [weak self] peer, navigation in + guard let self else { + return + } + let _ = self + }, + callPeer: { _, _ in + //self?.controllerInteraction?.callPeer(peerId) + }, + enqueueMessage: { _ in + }, + sendSticker: nil, + sendEmoji: nil, + setupTemporaryHiddenMedia: { _, _, _ in }, + chatAvatarHiddenMedia: { _, _ in }, + actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in + guard let self else { + return + } + let _ = self + //strongSelf.openUrl(url: url, concealed: false, external: false) + }, openUrlIn: { [weak self] url in + guard let self else { + return + } + let _ = self + }, openPeerMention: { [weak self] mention in + guard let self else { + return + } + let _ = self + }, openPeer: { [weak self] peer in + guard let self else { + return + } + let _ = self + }, openHashtag: { [weak self] peerName, hashtag in + guard let self else { + return + } + let _ = self + }, openBotCommand: { _ in + }, addContact: { _ in + }, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in + guard let self else { + return + } + let _ = self + }, editMedia: { [weak self] messageId, snapshots, transitionCompletion in + guard let self else { + return + } + let _ = self + }), + centralItemUpdated: { [weak self] messageId in + //let _ = self?.paneContainerNode.requestExpandTabs?() + //self?.paneContainerNode.currentPane?.node.ensureMessageIsVisible(id: messageId) + + guard let self else { + return + } + let _ = self + } + )) + } + private func requestClear(categories: Set, peers: Set, messages: Set) { guard let component = self.component else { return @@ -2730,3 +3158,59 @@ private class StorageUsageClearProgressOverlayNode: ASDisplayNode { self.descriptionTextNode.frame = descriptionTextFrame } } + +private final class StorageUsageListContextGalleryContentSourceImpl: ContextControllerContentSource { + let controller: ViewController + weak var sourceView: UIView? + let sourceRect: CGRect + + let navigationController: NavigationController? = nil + + let passthroughTouches: Bool + + init(controller: ViewController, sourceView: UIView?, sourceRect: CGRect = CGRect(origin: CGPoint(), size: CGSize()), passthroughTouches: Bool = false) { + self.controller = controller + self.sourceView = sourceView + self.sourceRect = sourceRect + self.passthroughTouches = passthroughTouches + } + + func transitionInfo() -> ContextControllerTakeControllerInfo? { + let sourceView = self.sourceView + let sourceRect = self.sourceRect + return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceView] in + if let sourceView = sourceView { + let rect = sourceRect.isEmpty ? sourceView.bounds : sourceRect + return (sourceView, rect) + } else { + return nil + } + }) + } + + func animatedIn() { + self.controller.didAppearInContextPreview() + } +} + +private final class StorageUsageListContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool = false + let ignoreContentTouches: Bool = false + let blurBackground: Bool = true + + //let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center + + private let contentView: ContextExtractedContentContainingView + + init(contentView: ContextExtractedContentContainingView) { + self.contentView = contentView + } + + func takeView() -> ContextControllerTakeViewInfo? { + return ContextControllerTakeViewInfo(containingItem: .view(self.contentView), contentAreaInScreenSpace: UIScreen.main.bounds) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) + } +}