From 6c7b9210307ea35272902323a32fca118a533d0e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Jul 2023 20:17:01 +0200 Subject: [PATCH] Fix stickers search --- .../Sources/StickerPickerScreen.swift | 1 - submodules/FeaturedStickersScreen/BUILD | 2 + .../Sources/FeaturedStickersScreen.swift | 10 +- .../StickerPaneSearchStickerItem.swift | 112 +++++++++--------- .../StickerPaneSearchContentNode.swift | 11 +- .../Sources/EntityKeyboard.swift | 7 +- .../StickersResultPanelComponent.swift | 5 - .../TelegramUI/Sources/ChatController.swift | 2 - .../Sources/ChatMessageTransitionNode.swift | 6 - 9 files changed, 75 insertions(+), 81 deletions(-) diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index 22da1fd1f9..a616541fe3 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -295,7 +295,6 @@ private final class StickerSelectionComponent: Component { }, peekBehavior: stickerPeekBehavior ) - return searchContainerNode }, contentIdUpdated: { _ in }, diff --git a/submodules/FeaturedStickersScreen/BUILD b/submodules/FeaturedStickersScreen/BUILD index 629784cd42..450df08ea2 100644 --- a/submodules/FeaturedStickersScreen/BUILD +++ b/submodules/FeaturedStickersScreen/BUILD @@ -31,6 +31,8 @@ swift_library( "//submodules/StickerResources:StickerResources", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/TelegramUI/Components/EmojiTextAttachmentView", + "//submodules/TextFormat", ], visibility = [ "//visibility:public", diff --git a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift index 2347ee190a..6c76dfbc00 100644 --- a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift +++ b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift @@ -1059,8 +1059,8 @@ private enum FeaturedSearchEntry: Identifiable, Comparable { func item(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, itemContext: StickerPaneSearchGlobalItemContext) -> GridItem { switch self { case let .sticker(_, code, stickerItem, theme): - return StickerPaneSearchStickerItem(context: context, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { node, rect in - interaction.sendSticker(.standalone(media: stickerItem.file), node.view, rect) + return StickerPaneSearchStickerItem(context: context, theme: theme, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, selected: { node, layer, rect in + interaction.sendSticker(.standalone(media: stickerItem.file), node.view, layer, rect) }) case let .global(_, info, topItems, installed, topSeparator): return StickerPaneSearchGlobalItem(context: context, theme: theme, strings: strings, listAppearance: true, fillsRow: true, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: { @@ -1201,7 +1201,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { |> deliverOnMainQueue).start(next: { _ in }) } - }, sendSticker: { [weak self] file, sourceView, sourceRect in + }, sendSticker: { [weak self] file, sourceView, layer, sourceRect in if let strongSelf = self { let _ = strongSelf.sendSticker?(file, sourceView, sourceRect) } @@ -1521,10 +1521,10 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { public final class StickerPaneSearchInteraction { public let open: (StickerPackCollectionInfo) -> Void public let install: (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void - public let sendSticker: (FileMediaReference, UIView, CGRect) -> Void + public let sendSticker: (FileMediaReference, UIView, CALayer, CGRect) -> Void public let getItemIsPreviewed: (StickerPackItem) -> Bool - public init(open: @escaping (StickerPackCollectionInfo) -> Void, install: @escaping (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void, sendSticker: @escaping (FileMediaReference, UIView, CGRect) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { + public init(open: @escaping (StickerPackCollectionInfo) -> Void, install: @escaping (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void, sendSticker: @escaping (FileMediaReference, UIView, CALayer, CGRect) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { self.open = open self.install = install self.sendSticker = sendSticker diff --git a/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift index c7dc867bd1..d1521567eb 100644 --- a/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift +++ b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift @@ -11,6 +11,8 @@ import AccountContext import AnimatedStickerNode import TelegramAnimatedStickerNode import ChatPresentationInterfaceState +import EmojiTextAttachmentView +import TextFormat final class StickerPaneSearchStickerSection: GridSection { let code: String @@ -50,10 +52,11 @@ final class StickerPaneSearchStickerSectionNode: ASDisplayNode { super.init() - self.addSubnode(self.titleNode) self.titleNode.attributedText = NSAttributedString(string: code, font: sectionTitleFont, textColor: theme.chat.inputMediaPanel.stickersSectionTextColor) self.titleNode.maximumNumberOfLines = 1 self.titleNode.truncationMode = .byTruncatingTail + + self.addSubnode(self.titleNode) } override func layout() { @@ -68,15 +71,17 @@ final class StickerPaneSearchStickerSectionNode: ASDisplayNode { public final class StickerPaneSearchStickerItem: GridItem { public let context: AccountContext + public let theme: PresentationTheme public let code: String? public let stickerItem: FoundStickerItem - public let selected: (ASDisplayNode, CGRect) -> Void + public let selected: (ASDisplayNode, CALayer, CGRect) -> Void public let inputNodeInteraction: ChatMediaInputNodeInteraction public let section: GridSection? - public init(context: AccountContext, code: String?, stickerItem: FoundStickerItem, inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping (ASDisplayNode, CGRect) -> Void) { + public init(context: AccountContext, theme: PresentationTheme, code: String?, stickerItem: FoundStickerItem, inputNodeInteraction: ChatMediaInputNodeInteraction, selected: @escaping (ASDisplayNode, CALayer, CGRect) -> Void) { self.context = context + self.theme = theme self.stickerItem = stickerItem self.inputNodeInteraction = inputNodeInteraction self.selected = selected @@ -87,7 +92,7 @@ public final class StickerPaneSearchStickerItem: GridItem { public func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { let node = StickerPaneSearchStickerItemNode() node.inputNodeInteraction = self.inputNodeInteraction - node.setup(context: self.context, stickerItem: self.stickerItem, code: self.code) + node.setup(context: self.context, theme: self.theme, stickerItem: self.stickerItem, code: self.code) node.selected = self.selected return node } @@ -98,7 +103,7 @@ public final class StickerPaneSearchStickerItem: GridItem { return } node.inputNodeInteraction = self.inputNodeInteraction - node.setup(context: self.context, stickerItem: self.stickerItem, code: self.code) + node.setup(context: self.context, theme: self.theme, stickerItem: self.stickerItem, code: self.code) node.selected = self.selected } } @@ -107,8 +112,7 @@ private let textFont = Font.regular(20.0) public final class StickerPaneSearchStickerItemNode: GridItemNode { private var currentState: (AccountContext, FoundStickerItem, CGSize)? - public let imageNode: TransformImageNode - public private(set) var animationNode: AnimatedStickerNode? + var itemLayer: InlineStickerItemLayer? private let textNode: ASTextNode private let stickerFetchedDisposable = MetaDisposable() @@ -124,22 +128,21 @@ public final class StickerPaneSearchStickerItemNode: GridItemNode { private var isPlaying = false public var inputNodeInteraction: ChatMediaInputNodeInteraction? - public var selected: ((ASDisplayNode, CGRect) -> Void)? - + public var selected: ((ASDisplayNode, CALayer, CGRect) -> Void)? + public var stickerItem: FoundStickerItem? { return self.currentState?.1 } public override init() { - self.imageNode = TransformImageNode() self.textNode = ASTextNode() self.textNode.isUserInteractionEnabled = false super.init() - self.addSubnode(self.imageNode) - self.addSubnode(self.textNode) self.textNode.maximumNumberOfLines = 1 + + self.addSubnode(self.textNode) } deinit { @@ -149,39 +152,43 @@ public final class StickerPaneSearchStickerItemNode: GridItemNode { public override func didLoad() { super.didLoad() - self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) } - func setup(context: AccountContext, stickerItem: FoundStickerItem, code: String?) { + func setup(context: AccountContext, theme: PresentationTheme, stickerItem: FoundStickerItem, code: String?) { if self.currentState == nil || self.currentState!.0 !== context || self.currentState!.1 != stickerItem { self.textNode.attributedText = NSAttributedString(string: code ?? "", font: textFont, textColor: .black) - if let dimensions = stickerItem.file.dimensions { - if stickerItem.file.isAnimatedSticker || stickerItem.file.isVideoSticker { - if self.animationNode == nil { - let animationNode = DefaultAnimatedStickerNodeImpl() - animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) - self.animationNode = animationNode - self.insertSubnode(animationNode, belowSubnode: self.textNode) - } - let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512) - let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)) - self.animationNode?.setup(source: AnimatedStickerResourceSource(account: context.account, resource: stickerItem.file.resource, isVideo: stickerItem.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .cached) - self.animationNode?.visibility = self.isVisibleInGrid && context.sharedContext.energyUsageSettings.loopStickers - self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start()) - } else { - if let animationNode = self.animationNode { - animationNode.visibility = false - self.animationNode = nil - animationNode.removeFromSupernode() - } - self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: stickerItem.file, small: true)) - self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start()) - } - - self.currentState = (context, stickerItem, dimensions.cgSize) - self.setNeedsLayout() + let file = stickerItem.file + let itemDimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) + let playbackItemSize = CGSize(width: 96.0, height: 96.0) + + let itemPlaybackSize = itemDimensions.aspectFitted(playbackItemSize) + + let itemLayer: InlineStickerItemLayer + if let current = self.itemLayer { + itemLayer = current + itemLayer.dynamicColor = .white + } else { + itemLayer = InlineStickerItemLayer( + context: context, + userLocation: .other, + attemptSynchronousLoad: false, + emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file), + file: file, + cache: context.animationCache, + renderer: context.animationRenderer, + placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1), + pointSize: itemPlaybackSize, + dynamicColor: .white + ) + self.itemLayer = itemLayer + self.layer.insertSublayer(itemLayer, at: 0) } + + self.currentState = (context, stickerItem, itemDimensions) + self.setNeedsLayout() + self.updateVisibility() } } @@ -191,29 +198,26 @@ public final class StickerPaneSearchStickerItemNode: GridItemNode { let bounds = self.bounds let boundingSize = bounds.insetBy(dx: 6.0, dy: 6.0).size - if let (_, _, mediaDimensions) = self.currentState { - let imageSize = mediaDimensions.aspectFitted(boundingSize) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() - - let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize) - self.imageNode.frame = imageFrame - - if let animationNode = self.animationNode { - animationNode.frame = imageFrame - animationNode.updateLayout(size: imageSize) + if let (_, _, itemDimensions) = self.currentState { + let itemSize = itemDimensions.aspectFitted(boundingSize) + let itemFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - itemSize.width) / 2.0), y: (bounds.size.height - itemSize.height) / 2.0), size: itemSize) + if let itemLayer = self.itemLayer { + itemLayer.frame = itemFrame } - let textSize = self.textNode.measure(CGSize(width: bounds.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude)) self.textNode.frame = CGRect(origin: CGPoint(x: bounds.size.width - textSize.width, y: bounds.size.height - textSize.height), size: textSize) } } @objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) { - self.selected?(self, self.bounds) + guard let itemLayer = self.itemLayer else { + return + } + self.selected?(self, itemLayer, self.bounds) } public func transitionNode() -> ASDisplayNode? { - return self.imageNode + return self } public func updateVisibility() { @@ -222,9 +226,9 @@ public final class StickerPaneSearchStickerItemNode: GridItemNode { } let isPlaying = self.isVisibleInGrid && context.sharedContext.energyUsageSettings.loopStickers - if self.isPlaying != isPlaying { + if self.isPlaying != isPlaying, let itemLayer = self.itemLayer { self.isPlaying = isPlaying - self.animationNode?.visibility = isPlaying + itemLayer.isVisibleForAnimations = isPlaying } } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift index 532d562ba5..1624458c36 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift @@ -89,8 +89,8 @@ private enum StickerSearchEntry: Identifiable, Comparable { func item(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction) -> GridItem { switch self { case let .sticker(_, code, stickerItem, theme): - return StickerPaneSearchStickerItem(context: context, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { node, rect in - interaction.sendSticker(.standalone(media: stickerItem.file), node.view, rect) + return StickerPaneSearchStickerItem(context: context, theme: theme, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, selected: { node, layer, rect in + interaction.sendSticker(.standalone(media: stickerItem.file), node.view, layer, rect) }) case let .global(_, info, topItems, installed, topSeparator): let itemContext = StickerPaneSearchGlobalItemContext() @@ -316,9 +316,10 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { |> deliverOnMainQueue).start(next: { _ in }) } - }, sendSticker: { [weak self] file, sourceView, sourceRect in - if let strongSelf = self { - let _ = strongSelf.interaction.sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil, []) + }, sendSticker: { [weak self] file, sourceView, sourceLayer, sourceRect in + if let self { + let sourceRect = sourceView.convert(sourceRect, to: self.view) + let _ = self.interaction.sendSticker(file, false, false, nil, false, self.view, sourceRect, sourceLayer, []) } }, getItemIsPreviewed: { item in return inputNodeInteraction.previewedStickerPackItemFile?.id == item.file.id diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index d2cb63c5fd..8c97b2bbce 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -743,10 +743,11 @@ public final class EntityKeyboardComponent: Component { panelHideBehavior = .hideOnScroll } + let isContentInFocus = component.isContentInFocus && self.searchComponent == nil let pagerSize = self.pagerView.update( transition: transition, component: AnyComponent(PagerComponent( - isContentInFocus: component.isContentInFocus, + isContentInFocus: isContentInFocus, contentInsets: component.containerInsets, contents: contents, contentTopPanels: contentTopPanels, @@ -801,7 +802,7 @@ public final class EntityKeyboardComponent: Component { EntityKeyboardChildEnvironment( theme: component.theme, strings: component.strings, - isContentInFocus: component.isContentInFocus, + isContentInFocus: isContentInFocus, getContentActiveItemUpdated: { id in if id == AnyHashable("gifs") { return gifsContentItemIdUpdated @@ -950,7 +951,7 @@ public final class EntityKeyboardComponent: Component { } ) } - //self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) + component.hideInputUpdated(true, true, Transition(animation: .curve(duration: 0.3, curve: .spring))) } } diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/StickersResultPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/StickersResultPanelComponent.swift index 008a524612..a18b70d09a 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/StickersResultPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/StickersResultPanelComponent.swift @@ -295,12 +295,7 @@ final class StickersResultPanelComponent: Component { let controller = PeekController(presentationData: presentationData, content: content, sourceView: { return (sourceView, sourceRect) }) - // controller.visibilityUpdated = { [weak self] visible in - // self?.previewingStickersPromise.set(visible) - // } component.presentInGlobalOverlay(controller) - // strongSelf.peekController = controller - // strongSelf.getControllerInteraction?()?.presentGlobalOverlayController(controller, nil) return controller } return nil diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 66908efe7f..e4d1c236cf 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2160,8 +2160,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } else if let sourceNode = sourceView.asyncdisplaykit_node as? HorizontalStickerGridItemNode { strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .mediaPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: {}) - } else if let sourceNode = sourceView.asyncdisplaykit_node as? StickerPaneSearchStickerItemNode { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanelSearch(itemNode: sourceNode), replyPanel: replyPanel), initiated: {}) } else if let sourceNode = sourceView.asyncdisplaykit_node as? ChatEmptyNodeStickerContentNode { strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .emptyPanel(itemNode: sourceNode), replyPanel: nil), initiated: {}) } else if let sourceLayer = sourceLayer { diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index cb3fd052e1..c4430e5403 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -188,7 +188,6 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti case inputPanel(itemNode: ChatMediaInputStickerGridItemNode) case mediaPanel(itemNode: HorizontalStickerGridItemNode) case universal(sourceContainerView: UIView, sourceRect: CGRect, sourceLayer: CALayer) - case inputPanelSearch(itemNode: StickerPaneSearchStickerItemNode) case emptyPanel(itemNode: ChatEmptyNodeStickerContentNode) } @@ -442,9 +441,6 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti case let .universal(sourceContainerView, sourceRect, sourceLayer): stickerSource = Sticker(imageNode: nil, animationNode: nil, placeholderNode: nil, imageLayer: sourceLayer, relativeSourceRect: sourceLayer.frame) sourceAbsoluteRect = convertAnimatingSourceRect(sourceRect, fromView: sourceContainerView, toView: self.view) - case let .inputPanelSearch(sourceItemNode): - stickerSource = Sticker(imageNode: sourceItemNode.imageNode, animationNode: sourceItemNode.animationNode, placeholderNode: nil, imageLayer: nil, relativeSourceRect: sourceItemNode.imageNode.frame) - sourceAbsoluteRect = sourceItemNode.view.convert(sourceItemNode.imageNode.frame, to: self.view) case let .emptyPanel(sourceItemNode): stickerSource = Sticker(imageNode: sourceItemNode.stickerNode.imageNode, animationNode: sourceItemNode.stickerNode.animationNode, placeholderNode: nil, imageLayer: nil, relativeSourceRect: sourceItemNode.stickerNode.imageNode.frame) sourceAbsoluteRect = sourceItemNode.stickerNode.view.convert(sourceItemNode.stickerNode.imageNode.frame, to: self.view) @@ -495,8 +491,6 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti break case let .mediaPanel(sourceItemNode): sourceItemNode.isHidden = true - case let .inputPanelSearch(sourceItemNode): - sourceItemNode.isHidden = true case let .emptyPanel(sourceItemNode): sourceItemNode.isHidden = true }