diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index ab5dcb5491..d61f1342de 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -556,7 +556,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage } - let (_, initialHideAuthor, messageText, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) + let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" } @@ -590,7 +590,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage } - let (_, initialHideAuthor, messageText, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) + let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" } @@ -1095,7 +1095,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = params.leftInset + avatarLeftInset enum ContentData { - case chat(itemPeer: EngineRenderedPeer, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?) + case chat(itemPeer: EngineRenderedPeer, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) case group(peers: [EngineChatList.GroupItem.Item]) } @@ -1104,7 +1104,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var hideAuthor = false switch contentPeer { case let .chat(itemPeer): - var (peer, initialHideAuthor, messageText, spoilers) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup) + var (peer, initialHideAuthor, messageText, spoilers, customEmojiRanges) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup) if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText { initialHideAuthor = true @@ -1125,7 +1125,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { break } - contentData = .chat(itemPeer: itemPeer, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers) + contentData = .chat(itemPeer: itemPeer, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers, customEmojiRanges: customEmojiRanges) hideAuthor = initialHideAuthor case let .group(groupPeers): contentData = .group(peers: groupPeers) @@ -1156,7 +1156,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var contentImageSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = [] switch contentData { - case let .chat(itemPeer, _, _, text, spoilers): + case let .chat(itemPeer, _, _, text, spoilers, customEmojiRanges): var isUser = false if case .user = itemPeer.chatMainPeer { isUser = true @@ -1185,6 +1185,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { if let spoilers = spoilers, !spoilers.isEmpty { messageText = text + } else if let customEmojiRanges = customEmojiRanges, !customEmojiRanges.isEmpty { + messageText = text } else { messageText = foldLineBreaks(text) } @@ -1216,10 +1218,17 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let messageString: NSAttributedString if !message.text.isEmpty && entities.count > 0 { messageString = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: authorAttributedString == nil ? 2 : 1), entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage()) - } else if let spoilers = spoilers { + } else if spoilers != nil || customEmojiRanges != nil { let mutableString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor) - for range in spoilers { - mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: range) + if let spoilers = spoilers { + for range in spoilers { + mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: range) + } + } + if let customEmojiRanges = customEmojiRanges { + for (range, attribute) in customEmojiRanges { + mutableString.addAttribute(ChatTextInputAttributes.customEmoji, value: attribute, range: range) + } } messageString = mutableString } else { @@ -1268,8 +1277,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { attributedText = composedString - - var displayMediaPreviews = true if message._asMessage().containsSecretMedia { displayMediaPreviews = false @@ -1359,7 +1366,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } switch contentData { - case let .chat(itemPeer, _, _, _, _): + case let .chat(itemPeer, _, _, _, _, _): if let message = messages.last, case let .user(author) = message.author, displayAsMessage { titleAttributedString = NSAttributedString(string: author.id == account.peerId ? item.presentationData.strings.DialogList_You : EnginePeer.user(author).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: titleFont, textColor: theme.titleColor) } else if isPeerGroup { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index fdcb30c450..8ed87f6c69 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -4,6 +4,7 @@ import TelegramPresentationData import TelegramUIPreferences import TelegramStringFormatting import LocalizedPeerData +import TextFormat private enum MessageGroupType { case photos @@ -44,7 +45,7 @@ private func messageGroupType(messages: [EngineMessage]) -> MessageGroupType { return currentType } -public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?) { +public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) { let peer: EnginePeer? let message = messages.last @@ -52,6 +53,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: var hideAuthor = false var messageText: String var spoilers: [NSRange]? + var customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]? if let message = message { if let messageMain = messageMainPeer(message) { peer = messageMain @@ -269,13 +271,14 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: } default: hideAuthor = true - if let (text, textSpoilers) = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { + if let (text, textSpoilers, customEmojiRangesValue) = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { messageText = text spoilers = textSpoilers + customEmojiRanges = customEmojiRangesValue } } case _ as TelegramMediaExpiredContent: - if let (text, _) = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { + if let (text, _, _) = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { messageText = text } case let poll as TelegramMediaPoll: @@ -314,5 +317,5 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: } } - return (peer, hideAuthor, messageText, spoilers) + return (peer, hideAuthor, messageText, spoilers, customEmojiRanges) } diff --git a/submodules/Components/BlurredBackgroundComponent/Sources/BlurredBackgroundComponent.swift b/submodules/Components/BlurredBackgroundComponent/Sources/BlurredBackgroundComponent.swift index 9147ef599b..df5f156d2a 100644 --- a/submodules/Components/BlurredBackgroundComponent/Sources/BlurredBackgroundComponent.swift +++ b/submodules/Components/BlurredBackgroundComponent/Sources/BlurredBackgroundComponent.swift @@ -27,36 +27,93 @@ public final class BlurredBackgroundComponent: Component { } public final class View: BlurredBackgroundView { - private var tintMaskView: UIView? + private var tintContainerView: UIView? + private var vibrancyEffectView: UIVisualEffectView? public func update(component: BlurredBackgroundComponent, availableSize: CGSize, transition: Transition) -> CGSize { - if let tintContainerView = component.tintContainerView { - self.updateColor(color: .clear, forceKeepBlur: true, transition: transition.containedViewLayoutTransition) + /*if self.tintContainerView !== component.tintContainerView { + if let tintContainerView = self.tintContainerView { + self.tintContainerView = nil + if tintContainerView.superview === self { + tintContainerView.removeFromSuperview() + } + } - let tintMaskView: UIView - if let current = self.tintMaskView { - tintMaskView = current + self.tintContainerView = component.tintContainerView + + if let tintContainerView = self.tintContainerView { + let vibrancyEffectView: UIVisualEffectView + if let current = self.vibrancyEffectView { + vibrancyEffectView = current + } else { + let blurEffect = UIBlurEffect(style: .extraLight) + let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect) + vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect) + self.vibrancyEffectView = vibrancyEffectView + self.addSubview(vibrancyEffectView) + } + tintContainerView.backgroundColor = .white + vibrancyEffectView.contentView.addSubview(tintContainerView) } else { - tintMaskView = UIView() - self.tintMaskView = tintMaskView - self.addSubview(tintMaskView) - } - - tintMaskView.backgroundColor = component.color - transition.setFrame(view: tintMaskView, frame: CGRect(origin: CGPoint(), size: availableSize)) - - if tintMaskView.mask !== tintContainerView { - tintMaskView.mask = tintContainerView + if let vibrancyEffectView = self.vibrancyEffectView { + self.vibrancyEffectView = nil + vibrancyEffectView.removeFromSuperview() + } } + }*/ + + self.updateColor(color: component.color, transition: transition.containedViewLayoutTransition) + + /*if let _ = self.viewWithTag(123) { } else { - self.updateColor(color: component.color, transition: transition.containedViewLayoutTransition) + let blurEffect = UIBlurEffect(style: .extraLight) - if let tintMaskView = self.tintMaskView { - self.tintMaskView = nil - tintMaskView.removeFromSuperview() - } + + /*let segmentedControl = UISegmentedControl(items: ["First Item", "Second Item"]) + segmentedControl.sizeToFit() + segmentedControl.center = CGPoint(x: 250.0, y: 250.0)*/ + let testView = UIView(frame: CGRect(origin: CGPoint(x: 50.0, y: 100.0), size: CGSize(width: 250.0, height: 50.0))) + testView.backgroundColor = .white + + let testView2 = UILabel() + testView2.text = "Test 13245" + testView2.font = Font.semibold(17.0) + testView2.textColor = .black + testView2.sizeToFit() + + testView2.center = CGPoint(x: 250.0 - testView.frame.minX, y: 490.0 - testView.frame.minY) + + let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect) + let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect) + //vibrancyEffectView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 400.0, height: 300.0)) + vibrancyEffectView.tag = 123 + + vibrancyEffectView.contentView.addSubview(testView) + testView.addSubview(testView2) + //vibrancyEffectView.contentView.addSubview(testView2) + + self.addSubview(vibrancyEffectView) + + /*let view = UIView() + view.tag = 123 + view.layer.compositingFilter = "sourceOverCompositing" + view.backgroundColor = .white + view.frame = CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 200.0)) + self.addSubview(view)*/ } + + if let view = self.viewWithTag(123) { + view.frame = CGRect(origin: CGPoint(), size: availableSize) + }*/ + self.update(size: availableSize, transition: transition.containedViewLayoutTransition) + + if let tintContainerView = self.tintContainerView { + transition.setFrame(view: tintContainerView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } + if let vibrancyEffectView = self.vibrancyEffectView { + transition.setFrame(view: vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } return availableSize } diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index d6db0ca729..a042fd5b3b 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -12,6 +12,10 @@ public protocol PagerPanGestureRecognizer: UIGestureRecognizer { open class PagerExternalTopPanelContainer: SparseContainerView { } +public protocol PagerContentViewWithBackground: UIView { + func pagerUpdateBackground(backgroundFrame: CGRect, transition: Transition) +} + public final class PagerComponentChildEnvironment: Equatable { public struct ContentScrollingUpdate { public var relativeOffset: CGFloat @@ -292,8 +296,15 @@ public final class PagerComponent 0.0 { - paneTransitionGestureState.fraction = recognizer.translation(in: self).x / self.bounds.width + if let centralId = self.centralId, let component = self.component, let centralIndex = component.contents.firstIndex(where: { $0.id == centralId }), var paneTransitionGestureState = self.paneTransitionGestureState, self.bounds.width > 0.0 { + var fraction = recognizer.translation(in: self).x / self.bounds.width + if centralIndex <= 0 { + fraction = min(0.0, fraction) + } + if centralIndex >= component.contents.count - 1 { + fraction = max(0.0, fraction) + } + paneTransitionGestureState.fraction = fraction self.paneTransitionGestureState = paneTransitionGestureState self.state?.updated(transition: .immediate) @@ -554,6 +565,7 @@ public final class PagerComponent var contentBackgroundTransition = panelStateTransition @@ -565,13 +577,13 @@ public final class PagerComponent .ulpOfOne && color.alpha < 0.95) || forceKeepBlur) { if self.effectView == nil { let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + //effectView.isHidden = true for subview in effectView.subviews { if subview.description.contains("VisualEffectSubview") { diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 0666a6d3f2..db88c1e006 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -768,12 +768,13 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { strongSelf.animationNode = animationNode strongSelf.addSubnode(animationNode) - animationNode.setup(source: AnimatedStickerResourceSource(account: item.account, resource: resource, isVideo: isVideo), width: 80, height: 80, playbackMode: .loop, mode: .cached) + animationNode.setup(source: AnimatedStickerResourceSource(account: item.account, resource: resource, isVideo: isVideo), width: 80, height: 80, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) } animationNode.visibility = strongSelf.visibility != .none && item.playAnimatedStickers animationNode.isHidden = !item.playAnimatedStickers strongSelf.imageNode.isHidden = item.playAnimatedStickers if let animationNode = strongSelf.animationNode { + animationNode.updateLayout(size: imageFrame.size) transition.updateFrame(node: animationNode, frame: imageFrame) } } diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index 6800b1ee04..062fa9be06 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -729,7 +729,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta navigationControllerImpl?()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: action == .archive ? presentationData.strings.StickerPackActionInfo_ArchivedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(archivedItem.info.title).string, undo: true, info: archivedItem.info, topItem: archivedItem.topItems.first, context: context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { action in if case .undo = action { - let _ = context.engine.stickers.addStickerPackInteractively(info: archivedItem.info, items: items, positionInList: positionInList).start() + let _ = context.engine.stickers.addStickerPackInteractively(info: archivedItem.info, items: items.compactMap({ $0 as? StickerPackItem }), positionInList: positionInList).start() } return true })) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPackInteractiveOperations.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPackInteractiveOperations.swift index 46562bcf55..1dabaf79ed 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPackInteractiveOperations.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPackInteractiveOperations.swift @@ -3,7 +3,7 @@ import Postbox import SwiftSignalKit -func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCollectionInfo, items: [ItemCollectionItem], positionInList: Int? = nil) -> Signal { +func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil) -> Signal { return postbox.transaction { transaction -> Void in let namespace: SynchronizeInstalledStickerPacksOperationNamespace? switch info.id.namespace { @@ -33,7 +33,12 @@ func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCo } else { updatedInfos.insert(mappedInfo, at: 0) } - transaction.replaceItemCollectionItems(collectionId: mappedInfo.id, items: items) + + var indexedItems: [ItemCollectionItem] = [] + for item in items { + indexedItems.append(StickerPackItem(index: ItemCollectionItemIndex(index: Int32(indexedItems.count), id: item.index.id), file: item.file, indexKeys: item.indexKeys)) + } + transaction.replaceItemCollectionItems(collectionId: mappedInfo.id, items: indexedItems) } transaction.replaceItemCollectionInfos(namespace: mappedInfo.id.namespace, itemCollectionInfos: updatedInfos.map { ($0.id, $0) }) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index 1d9f5cce72..4159f4464a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -46,7 +46,7 @@ public extension TelegramEngine { return _internal_searchGifs(account: self.account, query: query, nextOffset: nextOffset) } - public func addStickerPackInteractively(info: StickerPackCollectionInfo, items: [ItemCollectionItem], positionInList: Int? = nil) -> Signal { + public func addStickerPackInteractively(info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil) -> Signal { return _internal_addStickerPackInteractively(postbox: self.account.postbox, info: info, items: items, positionInList: positionInList) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index a8174639c5..38bc5537d3 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -276,7 +276,6 @@ public enum PresentationResourceKey: Int32 { case chatKeyboardActionButtonProfileIcon case chatKeyboardActionButtonAddToChatIcon case chatKeyboardActionButtonWebAppIcon - case chatEntityKeyboardLock case uploadToneIcon } @@ -313,4 +312,7 @@ public enum PresentationResourceParameterKey: Hashable { case chatMessageCommentsArrowIcon(incoming: Bool) case chatMessageCommentsUnreadDotIcon(incoming: Bool) case chatMessageRepliesIcon(incoming: Bool) + + case chatEntityKeyboardLock(color: UInt32) + case chatInputMediaPanelGridDismissImage(color: UInt32) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 280bae0544..51d10d9031 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -371,9 +371,9 @@ public struct PresentationResourcesChat { }) } - public static func chatInputMediaPanelGridDismissImage(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatInputMediaPanelGridDismissImage.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/GridDismissIcon"), color: theme.chat.inputMediaPanel.stickersSectionTextColor) + public static func chatInputMediaPanelGridDismissImage(_ theme: PresentationTheme, color: UIColor) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatInputMediaPanelGridDismissImage(color: color.argb), { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/GridDismissIcon"), color: color) }) } @@ -1282,9 +1282,9 @@ public struct PresentationResourcesChat { }) } - public static func chatEntityKeyboardLock(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatEntityKeyboardLock.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: theme.chat.inputMediaPanel.stickersSectionTextColor) + public static func chatEntityKeyboardLock(_ theme: PresentationTheme, color: UIColor) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatEntityKeyboardLock(color: color.argb), { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: color) }) } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 9d3bbd9f01..22e99077a1 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -34,15 +34,18 @@ private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, E return result } -public func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id, forChatList: Bool) -> (String, [NSRange])? { +public func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id, forChatList: Bool) -> (text: String, spoilerRanges: [NSRange], customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)])? { if let attributedString = universalServiceMessageString(presentationData: nil, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: forChatList) { var ranges: [NSRange] = [] + var customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)] = [] attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: [], using: { attributes, range, _ in if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)] { ranges.append(range) + } else if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + customEmojiRanges.append((range, value)) } }) - return (attributedString.string, ranges) + return (attributedString.string, ranges, customEmojiRanges) } else { return nil } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index fb1133e422..75ee7ec788 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -29,6 +29,328 @@ private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bund private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white) private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) +private final class PassthroughLayer: CALayer { + var mirrorLayer: CALayer? + + override init() { + super.init() + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override var opacity: Float { + get { + return super.opacity + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.opacity = value + } + super.opacity = value + } + } + + override var sublayerTransform: CATransform3D { + get { + return super.sublayerTransform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.sublayerTransform = value + } + super.sublayerTransform = value + } + } + + override var transform: CATransform3D { + get { + return super.transform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.transform = value + } + super.transform = value + } + } + + override func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override func removeAllAnimations() { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override func removeAnimation(forKey: String) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } +} + +private class PassthroughShapeLayer: CAShapeLayer { + var mirrorLayer: CAShapeLayer? + + override init() { + super.init() + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override var opacity: Float { + get { + return super.opacity + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.opacity = value + } + super.opacity = value + } + } + + override var sublayerTransform: CATransform3D { + get { + return super.sublayerTransform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.sublayerTransform = value + } + super.sublayerTransform = value + } + } + + override var transform: CATransform3D { + get { + return super.transform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.transform = value + } + super.transform = value + } + } + + override var path: CGPath? { + get { + return super.path + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.path = value + } + super.path = value + } + } + + override var fillColor: CGColor? { + get { + return super.fillColor + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.fillColor = value + } + super.fillColor = value + } + } + + override var fillRule: CAShapeLayerFillRule { + get { + return super.fillRule + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.fillRule = value + } + super.fillRule = value + } + } + + override var strokeColor: CGColor? { + get { + return super.strokeColor + } set(value) { + /*if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeColor = value + }*/ + super.strokeColor = value + } + } + + override var strokeStart: CGFloat { + get { + return super.strokeStart + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeStart = value + } + super.strokeStart = value + } + } + + override var strokeEnd: CGFloat { + get { + return super.strokeEnd + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeEnd = value + } + super.strokeEnd = value + } + } + + override var lineWidth: CGFloat { + get { + return super.lineWidth + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineWidth = value + } + super.lineWidth = value + } + } + + override var miterLimit: CGFloat { + get { + return super.miterLimit + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.miterLimit = value + } + super.miterLimit = value + } + } + + override var lineCap: CAShapeLayerLineCap { + get { + return super.lineCap + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineCap = value + } + super.lineCap = value + } + } + + override var lineJoin: CAShapeLayerLineJoin { + get { + return super.lineJoin + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineJoin = value + } + super.lineJoin = value + } + } + + override var lineDashPhase: CGFloat { + get { + return super.lineDashPhase + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineDashPhase = value + } + super.lineDashPhase = value + } + } + + override var lineDashPattern: [NSNumber]? { + get { + return super.lineDashPattern + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineDashPattern = value + } + super.lineDashPattern = value + } + } + + override func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override func removeAllAnimations() { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override func removeAnimation(forKey: String) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } +} + private final class PremiumBadgeView: UIView { private var badge: EmojiPagerContentComponent.View.ItemLayer.Badge? @@ -191,14 +513,24 @@ private final class GroupHeaderActionButton: UIButton { } private final class GroupHeaderLayer: UIView { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + private let actionPressed: () -> Void private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void private let textLayer: SimpleLayer + private let tintTextLayer: SimpleLayer + private var subtitleLayer: SimpleLayer? + private var tintSubtitleLayer: SimpleLayer? private var lockIconLayer: SimpleLayer? + private var tintLockIconLayer: SimpleLayer? private(set) var clearIconLayer: SimpleLayer? + private var tintClearIconLayer: SimpleLayer? private var separatorLayer: SimpleLayer? + private var tintSeparatorLayer: SimpleLayer? private var actionButton: GroupHeaderActionButton? private var groupEmbeddedView: GroupEmbeddedView? @@ -208,15 +540,23 @@ private final class GroupHeaderLayer: UIView { private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? private var currentSubtitleLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + let tintContentLayer: SimpleLayer + init(actionPressed: @escaping () -> Void, performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { self.actionPressed = actionPressed self.performItemAction = performItemAction self.textLayer = SimpleLayer() + self.tintTextLayer = SimpleLayer() + + self.tintContentLayer = SimpleLayer() super.init(frame: CGRect()) self.layer.addSublayer(self.textLayer) + self.tintContentLayer.addSublayer(self.tintTextLayer) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContentLayer } required init?(coder: NSCoder) { @@ -254,12 +594,15 @@ private final class GroupHeaderLayer: UIView { } let color: UIColor + let needsTintText: Bool if subtitle != nil { color = theme.chat.inputPanel.primaryTextColor + needsTintText = false } else { - color = theme.chat.inputMediaPanel.stickersSectionTextColor + color = theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1) + needsTintText = true } - let subtitleColor = theme.chat.inputMediaPanel.stickersSectionTextColor + let subtitleColor = theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1) let titleHorizontalOffset: CGFloat if isPremiumLocked { @@ -271,7 +614,7 @@ private final class GroupHeaderLayer: UIView { self.lockIconLayer = lockIconLayer self.layer.addSublayer(lockIconLayer) } - if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme) { + if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: color) { let imageSize = image.size.aspectFitted(CGSize(width: 16.0, height: 16.0)) lockIconLayer.contents = image.cgImage titleHorizontalOffset = imageSize.width + 2.0 @@ -280,11 +623,31 @@ private final class GroupHeaderLayer: UIView { lockIconLayer.contents = nil titleHorizontalOffset = 0.0 } + + let tintLockIconLayer: SimpleLayer + if let current = self.tintLockIconLayer { + tintLockIconLayer = current + } else { + tintLockIconLayer = SimpleLayer() + self.tintLockIconLayer = tintLockIconLayer + self.tintContentLayer.addSublayer(tintLockIconLayer) + } + if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: .white) { + let imageSize = image.size.aspectFitted(CGSize(width: 16.0, height: 16.0)) + tintLockIconLayer.contents = image.cgImage + tintLockIconLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: imageSize) + } else { + tintLockIconLayer.contents = nil + } } else { if let lockIconLayer = self.lockIconLayer { self.lockIconLayer = nil lockIconLayer.removeFromSuperlayer() } + if let tintLockIconLayer = self.tintLockIconLayer { + self.tintLockIconLayer = nil + tintLockIconLayer.removeFromSuperlayer() + } titleHorizontalOffset = 0.0 } @@ -319,13 +682,14 @@ private final class GroupHeaderLayer: UIView { let font: UIFont let stringValue: String if subtitle == nil { - font = Font.medium(12.0) + font = Font.medium(13.0) stringValue = title.uppercased() } else { font = Font.semibold(16.0) stringValue = title } let string = NSAttributedString(string: stringValue, font: font, textColor: color) + let whiteString = NSAttributedString(string: stringValue, font: font, textColor: .white) let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in @@ -336,19 +700,31 @@ private final class GroupHeaderLayer: UIView { UIGraphicsPopContext() })?.cgImage + self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + whiteString.draw(in: stringBounds) + + UIGraphicsPopContext() + })?.cgImage self.currentTextLayout = (title, color, textConstrainedWidth, textSize) } let textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset, y: textOffsetY), size: textSize) self.textLayer.frame = textFrame + self.tintTextLayer.frame = textFrame + self.tintTextLayer.isHidden = !needsTintText let subtitleSize: CGSize if let subtitle = subtitle { var updateSubtitleContents: UIImage? + var updateTintSubtitleContents: UIImage? if let currentSubtitleLayout = self.currentSubtitleLayout, currentSubtitleLayout.string == subtitle, currentSubtitleLayout.color == subtitleColor, currentSubtitleLayout.constrainedWidth == textConstrainedWidth { subtitleSize = currentSubtitleLayout.size } else { let string = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: subtitleColor) + let whiteString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: .white) let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) subtitleSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) updateSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in @@ -359,6 +735,14 @@ private final class GroupHeaderLayer: UIView { UIGraphicsPopContext() }) + updateTintSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + whiteString.draw(in: stringBounds) + + UIGraphicsPopContext() + }) self.currentSubtitleLayout = (subtitle, subtitleColor, textConstrainedWidth, subtitleSize) } @@ -375,8 +759,22 @@ private final class GroupHeaderLayer: UIView { subtitleLayer.contents = updateSubtitleContents.cgImage } + let tintSubtitleLayer: SimpleLayer + if let current = self.tintSubtitleLayer { + tintSubtitleLayer = current + } else { + tintSubtitleLayer = SimpleLayer() + self.tintSubtitleLayer = tintSubtitleLayer + self.tintContentLayer.addSublayer(tintSubtitleLayer) + } + + if let updateTintSubtitleContents = updateTintSubtitleContents { + tintSubtitleLayer.contents = updateTintSubtitleContents.cgImage + } + let subtitleFrame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.maxY + 1.0), size: subtitleSize) subtitleLayer.frame = subtitleFrame + tintSubtitleLayer.frame = subtitleFrame } else { subtitleSize = CGSize() if let subtitleLayer = self.subtitleLayer { @@ -387,8 +785,9 @@ private final class GroupHeaderLayer: UIView { var clearWidth: CGFloat = 0.0 if hasClear { - let clearIconLayer: SimpleLayer var updateImage = themeUpdated + + let clearIconLayer: SimpleLayer if let current = self.clearIconLayer { clearIconLayer = current } else { @@ -397,13 +796,24 @@ private final class GroupHeaderLayer: UIView { self.clearIconLayer = clearIconLayer self.layer.addSublayer(clearIconLayer) } + let tintClearIconLayer: SimpleLayer + if let current = self.tintClearIconLayer { + tintClearIconLayer = current + } else { + updateImage = true + tintClearIconLayer = SimpleLayer() + self.tintClearIconLayer = tintClearIconLayer + self.tintContentLayer.addSublayer(tintClearIconLayer) + } + var clearSize = clearIconLayer.bounds.size - if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme) { + if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1)) { clearSize = image.size - clearSize.width = 10.0 - clearSize.height = 10.0 clearIconLayer.contents = image.cgImage } + if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: .white) { + tintClearIconLayer.contents = image.cgImage + } switch layoutType { case .compact: @@ -411,7 +821,17 @@ private final class GroupHeaderLayer: UIView { case .detailed: clearIconLayer.frame = CGRect(origin: CGPoint(x: constrainedSize.width - clearSize.width, y: floorToScreenPixels((textSize.height - clearSize.height) / 2.0)), size: clearSize) } + tintClearIconLayer.frame = clearIconLayer.frame clearWidth = 4.0 + clearSize.width + } else { + if let clearIconLayer = self.clearIconLayer { + self.clearIconLayer = nil + clearIconLayer.removeFromSuperlayer() + } + if let tintClearIconLayer = self.tintClearIconLayer { + self.tintClearIconLayer = nil + tintClearIconLayer.removeFromSuperlayer() + } } var size: CGSize @@ -464,17 +884,30 @@ private final class GroupHeaderLayer: UIView { self.separatorLayer = separatorLayer self.layer.addSublayer(separatorLayer) } - separatorLayer.backgroundColor = theme.chat.inputMediaPanel.stickersSectionTextColor.withAlphaComponent(0.3).cgColor + separatorLayer.backgroundColor = theme.chat.inputMediaPanel.stickersSectionTextColor.withAlphaComponent(0.15).cgColor separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) + + let tintSeparatorLayer: SimpleLayer + if let current = self.tintSeparatorLayer { + tintSeparatorLayer = current + } else { + tintSeparatorLayer = SimpleLayer() + self.tintSeparatorLayer = tintSeparatorLayer + self.tintContentLayer.addSublayer(tintSeparatorLayer) + } + tintSeparatorLayer.backgroundColor = UIColor.white.cgColor + tintSeparatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) } else { if let separatorLayer = self.separatorLayer { self.separatorLayer = separatorLayer separatorLayer.removeFromSuperlayer() } + if let tintSeparatorLayer = self.tintSeparatorLayer { + self.tintSeparatorLayer = tintSeparatorLayer + tintSeparatorLayer.removeFromSuperlayer() + } } - //self.backgroundColor = UIColor.lightGray.cgColor - return size } @@ -557,7 +990,7 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager self.showsVerticalScrollIndicator = true self.showsHorizontalScrollIndicator = false self.delegate = self - self.clipsToBounds = false + self.clipsToBounds = true self.scrollsToTop = false } @@ -678,22 +1111,37 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager } private final class GroupExpandActionButton: UIButton { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + + let tintContainerLayer: SimpleLayer + private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? private let backgroundLayer: SimpleLayer + private let tintBackgroundLayer: SimpleLayer private let textLayer: SimpleLayer private let pressed: () -> Void init(pressed: @escaping () -> Void) { self.pressed = pressed + self.tintContainerLayer = SimpleLayer() + self.backgroundLayer = SimpleLayer() self.backgroundLayer.masksToBounds = true + self.tintBackgroundLayer = SimpleLayer() + self.tintBackgroundLayer.masksToBounds = true + self.textLayer = SimpleLayer() super.init(frame: CGRect()) + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer + self.layer.addSublayer(self.backgroundLayer) + self.layer.addSublayer(self.textLayer) self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) @@ -741,7 +1189,8 @@ private final class GroupExpandActionButton: UIButton { let textConstrainedWidth: CGFloat = 100.0 let color = theme.list.itemCheckColors.foregroundColor - self.backgroundLayer.backgroundColor = theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1).cgColor + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1).cgColor + self.tintContainerLayer.backgroundColor = UIColor.white.cgColor let textSize: CGSize if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { @@ -768,7 +1217,9 @@ private final class GroupExpandActionButton: UIButton { self.textLayer.frame = textFrame self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) + self.tintBackgroundLayer.frame = CGRect(origin: CGPoint(), size: size) self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 + self.tintContainerLayer.cornerRadius = min(size.width, size.height) / 2.0 return size } @@ -781,6 +1232,7 @@ public final class EmojiPagerContentComponent: Component { public enum AnimationType { case generic case groupExpanded(id: AnyHashable) + case groupInstalled(id: AnyHashable) } public let type: AnimationType @@ -790,6 +1242,13 @@ public final class EmojiPagerContentComponent: Component { } } + public final class InputInteractionHolder { + public var inputInteraction: InputInteraction? + + public init() { + } + } + public final class InputInteraction { public let performItemAction: (AnyHashable, Item, UIView, CGRect, CALayer) -> Void public let deleteBackwards: () -> Void @@ -969,7 +1428,7 @@ public final class EmojiPagerContentComponent: Component { public let context: AccountContext public let animationCache: AnimationCache public let animationRenderer: MultiAnimationRenderer - public let inputInteraction: InputInteraction + public let inputInteractionHolder: InputInteractionHolder public let itemGroups: [ItemGroup] public let itemLayoutType: ItemLayoutType @@ -978,7 +1437,7 @@ public final class EmojiPagerContentComponent: Component { context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, - inputInteraction: InputInteraction, + inputInteractionHolder: InputInteractionHolder, itemGroups: [ItemGroup], itemLayoutType: ItemLayoutType ) { @@ -986,7 +1445,7 @@ public final class EmojiPagerContentComponent: Component { self.context = context self.animationCache = animationCache self.animationRenderer = animationRenderer - self.inputInteraction = inputInteraction + self.inputInteractionHolder = inputInteractionHolder self.itemGroups = itemGroups self.itemLayoutType = itemLayoutType } @@ -1007,7 +1466,7 @@ public final class EmojiPagerContentComponent: Component { if lhs.animationRenderer !== rhs.animationRenderer { return false } - if lhs.inputInteraction !== rhs.inputInteraction { + if lhs.inputInteractionHolder !== rhs.inputInteractionHolder { return false } if lhs.itemGroups != rhs.itemGroups { @@ -1028,7 +1487,7 @@ public final class EmojiPagerContentComponent: Component { } } - public final class View: UIView, UIScrollViewDelegate, ComponentTaggedView { + public final class View: UIView, UIScrollViewDelegate, PagerContentViewWithBackground, ComponentTaggedView { private struct ItemGroupDescription: Equatable { let supergroupId: AnyHashable let groupId: AnyHashable @@ -1553,11 +2012,35 @@ public final class EmojiPagerContentComponent: Component { } } - private final class GroupBorderLayer: CAShapeLayer { + private final class GroupBorderLayer: PassthroughShapeLayer { + let tintContainerLayer: CAShapeLayer + + override init() { + self.tintContainerLayer = CAShapeLayer() + + super.init() + + self.mirrorLayer = self.tintContainerLayer + } + override func action(forKey event: String) -> CAAction? { return nullAction } + override init(layer: Any) { + self.tintContainerLayer = CAShapeLayer() + + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + + private final class ContentScrollLayer: CALayer { + var mirrorLayer: CALayer? + override init() { super.init() } @@ -1569,14 +2052,80 @@ public final class EmojiPagerContentComponent: Component { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override func removeAllAnimations() { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override func removeAnimation(forKey: String) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } } private final class ContentScrollView: UIScrollView, PagerExpandableScrollView { + override static var layerClass: AnyClass { + return ContentScrollLayer.self + } + + private let mirrorView: UIView + + init(mirrorView: UIView) { + self.mirrorView = mirrorView + + super.init(frame: CGRect()) + + (self.layer as? ContentScrollLayer)?.mirrorLayer = mirrorView.layer + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } } private let shimmerHostView: PortalSourceView private let standaloneShimmerEffect: StandaloneShimmerEffect + private let backgroundView: BlurredBackgroundView + private var vibrancyEffectView: UIVisualEffectView? + private let mirrorContentScrollView: UIView private let scrollView: ContentScrollView private let boundsChangeTrackerLayer = SimpleLayer() private var effectiveVisibleSize: CGSize = CGSize() @@ -1604,16 +2153,23 @@ public final class EmojiPagerContentComponent: Component { private weak var peekController: PeekController? override init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: nil) + self.shimmerHostView = PortalSourceView() self.standaloneShimmerEffect = StandaloneShimmerEffect() - self.scrollView = ContentScrollView() + self.mirrorContentScrollView = UIView() + self.mirrorContentScrollView.layer.anchorPoint = CGPoint() + self.mirrorContentScrollView.clipsToBounds = false + self.scrollView = ContentScrollView(mirrorView: self.mirrorContentScrollView) self.scrollView.layer.anchorPoint = CGPoint() self.placeholdersContainerView = UIView() super.init(frame: frame) + self.addSubview(self.backgroundView) + self.shimmerHostView.alpha = 0.0 self.addSubview(self.shimmerHostView) @@ -1673,7 +2229,7 @@ public final class EmojiPagerContentComponent: Component { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - if let sendSticker = component.inputInteraction.sendSticker, let chatPeerId = component.inputInteraction.chatPeerId { + if let inputInteraction = component.inputInteractionHolder.inputInteraction, let sendSticker = inputInteraction.sendSticker, let chatPeerId = inputInteraction.chatPeerId { if chatPeerId != component.context.account.peerId && chatPeerId.namespace != Namespaces.Peer.SecretChat { menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor) @@ -1712,7 +2268,7 @@ public final class EmojiPagerContentComponent: Component { |> deliverOnMainQueue).start(next: { result in switch result { case .generic: - component.inputInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: !isStarred ? presentationData.strings.Conversation_StickerAddedToFavorites : presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false })) + component.inputInteractionHolder.inputInteraction?.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: !isStarred ? presentationData.strings.Conversation_StickerAddedToFavorites : presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false })) case let .limitExceeded(limit, premiumLimit): let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let text: String @@ -1721,10 +2277,10 @@ public final class EmojiPagerContentComponent: Component { } else { text = presentationData.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string } - component.inputInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: presentationData.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { action in + component.inputInteractionHolder.inputInteraction?.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: presentationData.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { action in if case .info = action { let controller = PremiumIntroScreen(context: context, source: .savedStickers) - component.inputInteraction.pushController(controller) + component.inputInteractionHolder.inputInteraction?.pushController(controller) return true } return false @@ -1743,13 +2299,13 @@ public final class EmojiPagerContentComponent: Component { switch attribute { case let .CustomEmoji(_, _, packReference), let .Sticker(_, packReference, _): if let packReference = packReference { - let controller = context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: component.inputInteraction.navigationController(), sendSticker: { file, sourceView, sourceRect in - component.inputInteraction.sendSticker?(file, false, false, nil, false, sourceView, sourceRect, nil) + let controller = context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: component.inputInteractionHolder.inputInteraction?.navigationController(), sendSticker: { file, sourceView, sourceRect in + component.inputInteractionHolder.inputInteraction?.sendSticker?(file, false, false, nil, false, sourceView, sourceRect, nil) return true }) - component.inputInteraction.navigationController()?.view.window?.endEditing(true) - component.inputInteraction.presentController(controller) + component.inputInteractionHolder.inputInteraction?.navigationController()?.view.window?.endEditing(true) + component.inputInteractionHolder.inputInteraction?.presentController(controller) } break loop default: @@ -1761,7 +2317,7 @@ public final class EmojiPagerContentComponent: Component { return (strongSelf, strongSelf.scrollView.convert(itemLayer.frame, to: strongSelf), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { let controller = PremiumIntroScreen(context: context, source: .stickers) - component.inputInteraction.pushController(controller) + component.inputInteractionHolder.inputInteraction?.pushController(controller) })) } }, present: { [weak self] content, sourceView, sourceRect in @@ -1779,7 +2335,7 @@ public final class EmojiPagerContentComponent: Component { self?.simulateUpdateLayout(isVisible: !visible) }*/ strongSelf.peekController = controller - component.inputInteraction.presentGlobalOverlayController(controller) + component.inputInteractionHolder.inputInteraction?.presentGlobalOverlayController(controller) return controller }, updateContent: { [weak self] content in guard let strongSelf = self else { @@ -1865,14 +2421,14 @@ public final class EmojiPagerContentComponent: Component { for (id, view) in self.visibleItemPlaceholderViews { previousVisiblePlaceholderViews[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisibleGroupHeaders: [AnyHashable: (UIView, CGRect)] = [:] + var previousVisibleGroupHeaders: [AnyHashable: (GroupHeaderLayer, CGRect)] = [:] for (id, view) in self.visibleGroupHeaders { if !self.scrollView.bounds.intersects(view.frame) { continue } previousVisibleGroupHeaders[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisibleGroupBorders: [AnyHashable: (CALayer, CGRect)] = [:] + var previousVisibleGroupBorders: [AnyHashable: (GroupBorderLayer, CGRect)] = [:] for (id, layer) in self.visibleGroupBorders { previousVisibleGroupBorders[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } @@ -1882,7 +2438,7 @@ public final class EmojiPagerContentComponent: Component { previousVisibleGroupPremiumButtons[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } } - var previousVisibleGroupExpandActionButtons: [AnyHashable: (UIView, CGRect)] = [:] + var previousVisibleGroupExpandActionButtons: [AnyHashable: (GroupExpandActionButton, CGRect)] = [:] for (id, view) in self.visibleGroupExpandActionButtons { previousVisibleGroupExpandActionButtons[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } @@ -2031,8 +2587,11 @@ public final class EmojiPagerContentComponent: Component { } let view = viewAndFrame.0 self.scrollView.addSubview(view) - view.layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak view] _ in + let tintContentLayer = view.tintContentLayer + self.mirrorContentScrollView.layer.addSublayer(tintContentLayer) + view.layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak view, weak tintContentLayer] _ in view?.removeFromSuperview() + tintContentLayer?.removeFromSuperlayer() }) } @@ -2045,8 +2604,11 @@ public final class EmojiPagerContentComponent: Component { } let layer = layerAndFrame.0 self.scrollView.layer.addSublayer(layer) - layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak layer] _ in + let tintContainerLayer = layer.tintContainerLayer + self.mirrorContentScrollView.layer.addSublayer(tintContainerLayer) + layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak layer, weak tintContainerLayer] _ in layer?.removeFromSuperlayer() + tintContainerLayer?.removeFromSuperlayer() }) } @@ -2075,8 +2637,11 @@ public final class EmojiPagerContentComponent: Component { } let view = viewAndFrame.0 self.scrollView.addSubview(view) - view.layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak view] _ in + let tintContainerLayer = view.tintContainerLayer + self.mirrorContentScrollView.layer.addSublayer(tintContainerLayer) + view.layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak view, weak tintContainerLayer] _ in view?.removeFromSuperview() + tintContainerLayer?.removeFromSuperlayer() }) } } else if let previousVisibleBoundingRect = previousVisibleBoundingRect { @@ -2190,8 +2755,11 @@ public final class EmojiPagerContentComponent: Component { let view = viewAndFrame.0 view.frame = viewAndFrame.1.offsetBy(dx: 0.0, dy: self.scrollView.bounds.minY) self.scrollView.addSubview(view) - view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -commonItemOffset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak view] _ in + let tintContentLayer = view.tintContentLayer + self.mirrorContentScrollView.layer.addSublayer(tintContentLayer) + view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -commonItemOffset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak view, weak tintContentLayer] _ in view?.removeFromSuperview() + tintContentLayer?.removeFromSuperlayer() }) } @@ -2208,8 +2776,11 @@ public final class EmojiPagerContentComponent: Component { let layer = layerAndFrame.0 layer.frame = layerAndFrame.1.offsetBy(dx: 0.0, dy: self.scrollView.bounds.minY) self.scrollView.layer.addSublayer(layer) - layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -commonItemOffset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak layer] _ in + let tintContainerLayer = layer.tintContainerLayer + self.mirrorContentScrollView.layer.addSublayer(tintContainerLayer) + layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -commonItemOffset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak layer, weak tintContainerLayer] _ in layer?.removeFromSuperlayer() + tintContainerLayer?.removeFromSuperlayer() }) } @@ -2240,8 +2811,11 @@ public final class EmojiPagerContentComponent: Component { let view = viewAndFrame.0 view.frame = viewAndFrame.1.offsetBy(dx: 0.0, dy: self.scrollView.bounds.minY) self.scrollView.addSubview(view) - view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -commonItemOffset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak view] _ in + let tintContainerLayer = view.tintContainerLayer + self.mirrorContentScrollView.layer.addSublayer(tintContainerLayer) + view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -commonItemOffset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak view, weak tintContainerLayer] _ in view?.removeFromSuperview() + tintContainerLayer?.removeFromSuperlayer() }) } } @@ -2260,7 +2834,7 @@ public final class EmojiPagerContentComponent: Component { if groupHeader.frame.insetBy(dx: -10.0, dy: -6.0).contains(locationInScrollView) { let groupHeaderPoint = self.scrollView.convert(locationInScrollView, to: groupHeader) if let clearIconLayer = groupHeader.clearIconLayer, clearIconLayer.frame.insetBy(dx: -4.0, dy: -4.0).contains(groupHeaderPoint) { - component.inputInteraction.clearGroup(id) + component.inputInteractionHolder.inputInteraction?.clearGroup(id) } else { if groupHeader.tapGesture(recognizer) { return @@ -2275,7 +2849,7 @@ public final class EmojiPagerContentComponent: Component { foundExactItem = true foundItem = true if !itemLayer.displayPlaceholder { - component.inputInteraction.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer) + component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer) } } @@ -2283,7 +2857,7 @@ public final class EmojiPagerContentComponent: Component { if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self), extendedHitRange: true), let itemLayer = self.visibleItemLayers[itemKey] { foundItem = true if !itemLayer.displayPlaceholder { - component.inputInteraction.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer) + component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer) } } } @@ -2445,6 +3019,10 @@ public final class EmojiPagerContentComponent: Component { let topVisibleDetectionBounds = effectiveVisibleBounds.offsetBy(dx: 0.0, dy: pagerEnvironment.containerInsets.top) let contentAnimation = transition.userData(ContentAnimation.self) + var transitionHintInstalledGroupId: AnyHashable? + if let contentAnimation = contentAnimation, case let .groupInstalled(groupId) = contentAnimation.type { + transitionHintInstalledGroupId = groupId + } for groupItems in itemLayout.visibleItems(for: effectiveVisibleBounds) { let itemGroup = component.itemGroups[groupItems.groupIndex] @@ -2472,17 +3050,18 @@ public final class EmojiPagerContentComponent: Component { guard let strongSelf = self, let component = strongSelf.component else { return } - component.inputInteraction.addGroupAction(groupId, false) + component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, false) }, performItemAction: { [weak self] item, view, rect, layer in guard let strongSelf = self, let component = strongSelf.component else { return } - component.inputInteraction.performItemAction(groupId, item, view, rect, layer) + component.inputInteractionHolder.inputInteraction?.performItemAction(groupId, item, view, rect, layer) } ) self.visibleGroupHeaders[itemGroup.groupId] = groupHeaderView self.scrollView.addSubview(groupHeaderView) + self.mirrorContentScrollView.layer.addSublayer(groupHeaderView.tintContentLayer) } var actionButtonTitle: String? @@ -2536,8 +3115,10 @@ public final class EmojiPagerContentComponent: Component { groupBorderLayer = GroupBorderLayer() self.visibleGroupBorders[itemGroup.groupId] = groupBorderLayer self.scrollView.layer.insertSublayer(groupBorderLayer, at: 0) + self.mirrorContentScrollView.layer.addSublayer(groupBorderLayer.tintContainerLayer) - groupBorderLayer.strokeColor = theme.chat.inputMediaPanel.stickersSectionTextColor.cgColor + groupBorderLayer.strokeColor = theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1).cgColor + groupBorderLayer.tintContainerLayer.strokeColor = UIColor.white.cgColor groupBorderLayer.lineWidth = 1.6 groupBorderLayer.lineCap = .round groupBorderLayer.fillColor = nil @@ -2653,7 +3234,7 @@ public final class EmojiPagerContentComponent: Component { guard let strongSelf = self, let component = strongSelf.component else { return } - component.inputInteraction.addGroupAction(groupId, isPremiumLocked) + component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, isPremiumLocked) } )), environment: {}, @@ -2693,6 +3274,7 @@ public final class EmojiPagerContentComponent: Component { }) self.visibleGroupExpandActionButtons[itemGroup.groupId] = groupExpandActionButton self.scrollView.addSubview(groupExpandActionButton) + self.mirrorContentScrollView.layer.addSublayer(groupExpandActionButton.tintContainerLayer) } let baseItemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: collapsedItemIndex) @@ -2805,7 +3387,7 @@ public final class EmojiPagerContentComponent: Component { itemTransition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) if animateItemIn, !transition.animation.isImmediate { - if let previousItemPosition = previousItemPositions?[itemId] { + if let previousItemPosition = previousItemPositions?[itemId], transitionHintInstalledGroupId != itemId.groupId { itemTransition = transition itemLayer.position = previousItemPosition } else { @@ -2850,7 +3432,7 @@ public final class EmojiPagerContentComponent: Component { removedIds.append(id) if !transition.animation.isImmediate { - if let position = updatedItemPositions?[id] { + if let position = updatedItemPositions?[id], transitionHintInstalledGroupId != id.groupId { transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in itemLayer?.removeFromSuperlayer() }) @@ -2883,11 +3465,14 @@ public final class EmojiPagerContentComponent: Component { if !transition.animation.isImmediate { groupHeaderLayer.alpha = 0.0 groupHeaderLayer.layer.animateScale(from: 1.0, to: 0.5, duration: 0.2) - groupHeaderLayer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak groupHeaderLayer] _ in + let tintContentLayer = groupHeaderLayer.tintContentLayer + groupHeaderLayer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak groupHeaderLayer, weak tintContentLayer] _ in groupHeaderLayer?.removeFromSuperview() + tintContentLayer?.removeFromSuperlayer() }) } else { groupHeaderLayer.removeFromSuperview() + groupHeaderLayer.tintContentLayer.removeFromSuperlayer() } } } @@ -2900,6 +3485,7 @@ public final class EmojiPagerContentComponent: Component { if !validGroupBorderIds.contains(id) { removedGroupBorderIds.append(id) groupBorderLayer.removeFromSuperlayer() + groupBorderLayer.tintContainerLayer.removeFromSuperlayer() } } for id in removedGroupBorderIds { @@ -2922,6 +3508,7 @@ public final class EmojiPagerContentComponent: Component { if !validGroupExpandActionButtons.contains(id) { removedGroupExpandActionButtonIds.append(id) button.removeFromSuperview() + button.tintContainerLayer.removeFromSuperlayer() } } for id in removedGroupExpandActionButtonIds { @@ -2951,6 +3538,29 @@ public final class EmojiPagerContentComponent: Component { self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(ContentAnimation(type: .groupExpanded(id: groupId)))) } + public func pagerUpdateBackground(backgroundFrame: CGRect, transition: Transition) { + guard let theme = self.theme else { + return + } + + if self.vibrancyEffectView == nil { + let blurEffect = UIBlurEffect(style: .extraLight) + let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect) + let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect) + self.vibrancyEffectView = vibrancyEffectView + self.backgroundView.addSubview(vibrancyEffectView) + vibrancyEffectView.contentView.addSubview(self.mirrorContentScrollView) + } + + self.backgroundView.updateColor(color: theme.chat.inputMediaPanel.stickersBackgroundColor.withMultipliedAlpha(0.75), enableBlur: true, forceKeepBlur: false, transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition) + + if let vibrancyEffectView = self.vibrancyEffectView { + transition.setFrame(view: vibrancyEffectView, frame: CGRect(origin: CGPoint(x: 0.0, y: -backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: backgroundFrame.height + backgroundFrame.minY))) + } + } + func update(component: EmojiPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { let previousComponent = self.component @@ -2960,11 +3570,11 @@ public final class EmojiPagerContentComponent: Component { self.peekRecognizer?.isEnabled = component.itemLayoutType == .detailed let keyboardChildEnvironment = environment[EntityKeyboardChildEnvironment.self].value + let pagerEnvironment = environment[PagerComponentChildEnvironment.self].value self.theme = keyboardChildEnvironment.theme self.activeItemUpdated = keyboardChildEnvironment.getContentActiveItemUpdated(component.id) - let pagerEnvironment = environment[PagerComponentChildEnvironment.self].value self.pagerEnvironment = pagerEnvironment transition.setFrame(view: self.shimmerHostView, frame: CGRect(origin: CGPoint(), size: availableSize)) @@ -3049,6 +3659,8 @@ public final class EmojiPagerContentComponent: Component { if let previousItemLayout = self.itemLayout { if previousItemLayout.width != itemLayout.width { itemTransition = .immediate + } else if transition.userData(ContentAnimation.self) == nil { + itemTransition = .immediate } } else { itemTransition = .immediate @@ -3111,7 +3723,10 @@ public final class EmojiPagerContentComponent: Component { if contentOffsetY < 0.0 { contentOffsetY = 0.0 } + + let previousBounds = self.scrollView.bounds self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffsetY), animated: false) + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: previousBounds.minY - contentOffsetY), to: CGPoint(), additive: true) break outer } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index 4fc8b78481..3bc5e874cd 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -171,6 +171,8 @@ public final class EntityKeyboardComponent: Component { } public final class View: UIView { + private let tintContainerView: UIView + private let pagerView: ComponentHostView private var component: EntityKeyboardComponent? @@ -191,6 +193,7 @@ public final class EntityKeyboardComponent: Component { } override init(frame: CGRect) { + self.tintContainerView = UIView() self.pagerView = ComponentHostView() super.init(frame: frame) @@ -392,7 +395,7 @@ public final class EntityKeyboardComponent: Component { maxSize: nil )), action: { - stickerContent.inputInteraction.openStickerSettings() + stickerContent.inputInteractionHolder.inputInteraction?.openStickerSettings() } ).minSize(CGSize(width: 38.0, height: 38.0))))) } @@ -494,7 +497,7 @@ public final class EntityKeyboardComponent: Component { component.switchToTextInput() } ).minSize(CGSize(width: 38.0, height: 38.0))))) - let deleteBackwards = component.emojiContent.inputInteraction.deleteBackwards + let deleteBackwards = component.emojiContent.inputInteractionHolder.inputInteraction?.deleteBackwards contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(Button( content: AnyComponent(BundleIconComponent( name: "Chat/Input/Media/EntityInputClearIcon", @@ -502,11 +505,11 @@ public final class EntityKeyboardComponent: Component { maxSize: nil )), action: { - deleteBackwards() + deleteBackwards?() AudioServicesPlaySystemSound(1155) } ).withHoldAction({ - deleteBackwards() + deleteBackwards?() AudioServicesPlaySystemSound(1155) }).minSize(CGSize(width: 38.0, height: 38.0))))) @@ -529,9 +532,10 @@ public final class EntityKeyboardComponent: Component { contentAccessoryLeftButtons: contentAccessoryLeftButtons, contentAccessoryRightButtons: contentAccessoryRightButtons, defaultId: component.defaultToEmojiTab ? "emoji" : "stickers", - contentBackground: AnyComponent(BlurredBackgroundComponent( - color: component.theme.chat.inputMediaPanel.stickersBackgroundColor.withMultipliedAlpha(0.75) - )), + contentBackground: nil/*AnyComponent(BlurredBackgroundComponent( + color: component.theme.chat.inputMediaPanel.stickersBackgroundColor.withMultipliedAlpha(0.75), + tintContainerView: self.tintContainerView + ))*/, topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent( theme: component.theme, overflowHeight: component.hiddenInputHeight, @@ -542,7 +546,7 @@ public final class EntityKeyboardComponent: Component { theme: component.theme, bottomInset: component.bottomInset, deleteBackwards: { [weak self] in - self?.component?.emojiContent.inputInteraction.deleteBackwards() + self?.component?.emojiContent.inputInteractionHolder.inputInteraction?.deleteBackwards() AudioServicesPlaySystemSound(0x451) } )), diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift index c6676fbaf3..7f8c132158 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift @@ -213,7 +213,7 @@ public final class GifPagerContentComponent: Component { return true } - public final class View: ContextControllerSourceView, UIScrollViewDelegate { + public final class View: ContextControllerSourceView, PagerContentViewWithBackground, UIScrollViewDelegate { private struct ItemGroupDescription: Equatable { let hasTitle: Bool let itemCount: Int @@ -400,6 +400,8 @@ public final class GifPagerContentComponent: Component { private final class ContentScrollView: UIScrollView, PagerExpandableScrollView { } + private let backgroundView: BlurredBackgroundView + private let shimmerHostView: PortalSourceView private let standaloneShimmerEffect: StandaloneShimmerEffect @@ -418,6 +420,8 @@ public final class GifPagerContentComponent: Component { private var currentLoadMoreToken: String? override init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: nil) + self.shimmerHostView = PortalSourceView() self.standaloneShimmerEffect = StandaloneShimmerEffect() @@ -427,6 +431,8 @@ public final class GifPagerContentComponent: Component { super.init(frame: frame) + self.addSubview(self.backgroundView) + self.shimmerHostView.alpha = 0.0 self.addSubview(self.shimmerHostView) @@ -745,6 +751,15 @@ public final class GifPagerContentComponent: Component { } } + public func pagerUpdateBackground(backgroundFrame: CGRect, transition: Transition) { + guard let theme = self.theme else { + return + } + self.backgroundView.updateColor(color: theme.chat.inputMediaPanel.stickersBackgroundColor.withMultipliedAlpha(0.75), enableBlur: true, forceKeepBlur: false, transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition) + } + func update(component: GifPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { var contentReset = false if let previousComponent = self.component, previousComponent.subject != component.subject { diff --git a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift index 9f3b56b741..aed0d0e445 100644 --- a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift +++ b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift @@ -127,14 +127,14 @@ public final class TextNodeWithEntities { var found = false string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, stop in if let value = value as? ChatTextInputTextCustomEmojiAttribute, let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont { - let updatedSubstring = NSMutableAttributedString(string: ".") + let updatedSubstring = NSMutableAttributedString(string: "😀") let replacementRange = NSRange(location: 0, length: updatedSubstring.length) updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange) updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange) updatedSubstring.addAttribute(originalTextAttributeKey, value: string.attributedSubstring(from: range).string, range: replacementRange) - let itemSize = font.pointSize * 24.0 / 17.0 + let itemSize = (font.pointSize * 24.0 / 17.0) * 0.5 let runDelegateData = RunDelegateData( ascent: font.ascender, @@ -230,7 +230,9 @@ public final class TextNodeWithEntities { let itemSize = floor(stickerItem.fontSize * 24.0 / 17.0) - let itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 0.0).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0) + var itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 1.0).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0) + itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x) + itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y) let itemLayer: InlineStickerItemLayer if let current = self.inlineStickerItemLayers[id] { diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Contents.json index f9b52ce6f6..8dfe0c8a9c 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Contents.json @@ -1,22 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "StickersGroupCross@2x-1.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "StickersGroupCross@3x.png", - "scale" : "3x" + "filename" : "Frame 1.svg", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Frame 1.svg b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Frame 1.svg new file mode 100644 index 0000000000..8b5fc9d694 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Frame 1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@2x-1.png b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@2x-1.png deleted file mode 100644 index 3c45e3b85a..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@2x-1.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@3x.png b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@3x.png deleted file mode 100644 index 44dfdfabb3..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ab34e8964d..98634f51ca 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1576,6 +1576,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -2111,6 +2113,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreview(nil) } }) @@ -7342,6 +7346,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreview(nil) } }) @@ -11219,6 +11225,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil) strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -11281,6 +11289,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -11340,6 +11350,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -11363,6 +11375,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -11819,6 +11833,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -12134,6 +12150,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil) strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -12181,6 +12199,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -12240,6 +12260,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -12258,6 +12280,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -12580,6 +12604,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -12896,6 +12922,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -12967,6 +12995,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -12983,6 +13013,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -13011,6 +13043,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -13156,6 +13190,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -13265,6 +13301,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) @@ -13360,6 +13398,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedRecordedMediaPreview(nil).updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 6d26a9f3ce..5503ea1c4b 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -126,6 +126,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private(set) var accessoryPanelNode: AccessoryPanelNode? private var inputContextPanelNode: ChatInputContextPanelNode? private let inputContextPanelContainer: ChatControllerTitlePanelNodeContainer + private let inputContextOverTextPanelContainer: ChatControllerTitlePanelNodeContainer private var overlayContextPanelNode: ChatInputContextPanelNode? private var inputNode: ChatInputNode? @@ -271,6 +272,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.titleAccessoryPanelContainer.clipsToBounds = true self.inputContextPanelContainer = ChatControllerTitlePanelNodeContainer() + self.inputContextOverTextPanelContainer = ChatControllerTitlePanelNodeContainer() var source: ChatHistoryListSource if case let .forwardedMessages(messageIds, options) = subject { @@ -540,8 +542,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - self.addSubnode(self.inputPanelContainerNode) self.addSubnode(self.inputContextPanelContainer) + self.addSubnode(self.inputPanelContainerNode) + self.addSubnode(self.inputContextOverTextPanelContainer) self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode) self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode) @@ -1213,7 +1216,18 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self?.updateInputPanelBackgroundExtension(transition: transition) } inputNode.hideInputUpdated = { [weak self] transition in - self?.updateInputPanelBackgroundExpansion(transition: transition) + guard let strongSelf = self else { + return + } + let applyAutocorrection = strongSelf.inputNode?.hideInput ?? false + + strongSelf.updateInputPanelBackgroundExpansion(transition: transition) + + if applyAutocorrection, let textInputPanelNode = strongSelf.textInputPanelNode { + if let textInputNode = textInputPanelNode.textInputNode, textInputNode.isFirstResponder() { + Keyboard.applyAutocorrection(textView: textInputNode.textView) + } + } } dismissedInputNode = self.inputNode @@ -1308,6 +1322,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 66.0))) transition.updateFrame(node: self.inputContextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) + transition.updateFrame(node: self.inputContextOverTextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) var titleAccessoryPanelFrame: CGRect? if let _ = self.titleAccessoryPanelNode, let panelHeight = titleAccessoryPanelHeight { @@ -1355,8 +1370,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if inputContextPanelNode !== self.inputContextPanelNode { dismissedInputContextPanelNode = self.inputContextPanelNode self.inputContextPanelNode = inputContextPanelNode - - self.inputContextPanelContainer.addSubnode(inputContextPanelNode) + switch inputContextPanelNode.placement { + case .overPanels: + self.inputContextPanelContainer.addSubnode(inputContextPanelNode) + case .overTextInput: + self.inputContextOverTextPanelContainer.addSubnode(inputContextPanelNode) + } immediatelyLayoutInputContextPanelAndAnimateAppearance = true } } else if let inputContextPanelNode = self.inputContextPanelNode { @@ -2358,14 +2377,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if case let .peer(id) = self.chatPresentationInterfaceState.chatLocation { peerId = id } - let _ = peerId let inputNode = ChatEntityKeyboardInputNode( context: self.context, currentInputData: inputMediaNodeData, updatedInputData: self.inputMediaNodeDataPromise.get(), defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty, - controllerInteraction: self.controllerInteraction + controllerInteraction: self.controllerInteraction, + interfaceInteraction: self.interfaceInteraction, + chatPeerId: peerId ) return inputNode @@ -2962,6 +2982,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.setupSendActionOnViewUpdate({ [weak self] in if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode { + strongSelf.collapseInput() + strongSelf.ignoreUpdateHeight = true textInputPanelNode.text = "" strongSelf.requestUpdateChatInterfaceState(.immediate, true, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeDisableUrlPreview(nil) }) diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index b77c7bb21e..bc20d3b0d1 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -86,7 +86,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } } - static func emojiInputData(context: AccountContext, inputInteraction: EmojiPagerContentComponent.InputInteraction, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> Signal { + static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> Signal { let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> map { peer -> Bool in guard case let .user(user) = peer else { @@ -250,7 +250,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { context: context, animationCache: animationCache, animationRenderer: animationRenderer, - inputInteraction: inputInteraction, + inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(), itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in var hasClear = false if group.id == AnyHashable("recent") { @@ -291,321 +291,6 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } |> distinctUntilChanged - let emojiInputInteraction = EmojiPagerContentComponent.InputInteraction( - performItemAction: { [weak interfaceInteraction, weak controllerInteraction] _, item, _, _, _ in - let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in - guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { - return - } - - if let file = item.file { - var text = "." - var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? - loop: for attribute in file.attributes { - switch attribute { - case let .CustomEmoji(_, displayText, packReference): - text = displayText - emojiAttribute = ChatTextInputTextCustomEmojiAttribute(stickerPack: packReference, fileId: file.fileId.id, file: file) - break loop - default: - break - } - } - - if file.isPremiumEmoji && !hasPremium { - //TODO:localize - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Subscribe to Telegram Premium to unlock this emoji.", undoText: "More", customAction: { [weak controllerInteraction] in - guard let controllerInteraction = controllerInteraction else { - return - } - - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumDemoScreen(context: context, subject: .animatedEmoji, action: { - let controller = PremiumIntroScreen(context: context, source: .animatedEmoji) - replaceImpl?(controller) - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - controllerInteraction.navigationController()?.pushViewController(controller) - - /*let controller = PremiumIntroScreen(context: context, source: .stickers) - controllerInteraction.navigationController()?.pushViewController(controller)*/ - }), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) - return - } - - if let emojiAttribute = emojiAttribute { - AudioServicesPlaySystemSound(0x450) - interfaceInteraction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])) - } - } else if let staticEmoji = item.staticEmoji { - AudioServicesPlaySystemSound(0x450) - interfaceInteraction.insertText(NSAttributedString(string: staticEmoji, attributes: [:])) - } - }) - }, - deleteBackwards: { [weak interfaceInteraction] in - guard let interfaceInteraction = interfaceInteraction else { - return - } - interfaceInteraction.backwardsDeleteText() - }, - openStickerSettings: { - }, - addGroupAction: { [weak controllerInteraction] groupId, isPremiumLocked in - guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else { - return - } - - if isPremiumLocked { - let controller = PremiumIntroScreen(context: context, source: .stickers) - controllerInteraction.navigationController()?.pushViewController(controller) - - return - } - - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { - if featuredEmojiPack.info.id == collectionId { - let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start() - - break - } - } - }) - }, - clearGroup: { [weak controllerInteraction] groupId in - guard let controllerInteraction = controllerInteraction else { - return - } - if groupId == AnyHashable("recent") { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize)) - var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: presentationData.strings.Emoji_ClearRecent, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - let _ = context.engine.stickers.clearRecentlyUsedEmoji().start() - })) - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - controllerInteraction.presentController(actionSheet, nil) - } - }, - pushController: { [weak controllerInteraction] controller in - guard let controllerInteraction = controllerInteraction else { - return - } - controllerInteraction.navigationController()?.pushViewController(controller) - }, - presentController: { [weak controllerInteraction] controller in - guard let controllerInteraction = controllerInteraction else { - return - } - controllerInteraction.presentController(controller, nil) - }, - presentGlobalOverlayController: { [weak controllerInteraction] controller in - guard let controllerInteraction = controllerInteraction else { - return - } - controllerInteraction.presentGlobalOverlayController(controller, nil) - }, - navigationController: { [weak controllerInteraction] in - return controllerInteraction?.navigationController() - }, - sendSticker: { [weak controllerInteraction] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in - guard let controllerInteraction = controllerInteraction else { - return - } - let _ = controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer) - }, - chatPeerId: chatPeerId - ) - let stickerInputInteraction = EmojiPagerContentComponent.InputInteraction( - performItemAction: { [weak controllerInteraction, weak interfaceInteraction] groupId, item, view, rect, layer in - let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in - guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { - return - } - guard let file = item.file else { - return - } - - if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak controllerInteraction] views in - guard let controllerInteraction = controllerInteraction else { - return - } - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { - if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) { - controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( - context: context, - highlightedPackId: featuredStickerPack.info.id, - sendSticker: { [weak controllerInteraction] fileReference, sourceNode, sourceRect in - guard let controllerInteraction = controllerInteraction else { - return false - } - return controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil) - } - )) - - break - } - } - }) - } else { - if file.isPremiumSticker && !hasPremium { - let controller = PremiumIntroScreen(context: context, source: .stickers) - controllerInteraction.navigationController()?.pushViewController(controller) - - return - } - let _ = interfaceInteraction.sendSticker(.standalone(media: file), false, view, rect, layer) - } - }) - }, - deleteBackwards: { [weak interfaceInteraction] in - guard let interfaceInteraction = interfaceInteraction else { - return - } - interfaceInteraction.backwardsDeleteText() - }, - openStickerSettings: { [weak controllerInteraction] in - guard let controllerInteraction = controllerInteraction else { - return - } - let controller = installedStickerPacksController(context: context, mode: .modal) - controller.navigationPresentation = .modal - controllerInteraction.navigationController()?.pushViewController(controller) - }, - addGroupAction: { groupId, isPremiumLocked in - guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else { - return - } - - if isPremiumLocked { - let controller = PremiumIntroScreen(context: context, source: .stickers) - controllerInteraction.navigationController()?.pushViewController(controller) - - return - } - - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { - if featuredStickerPack.info.id == collectionId { - //let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start() - - let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) - |> mapToSignal { result -> Signal in - switch result { - case let .result(info, items, installed): - if installed { - return .complete() - } else { - return context.engine.stickers.addStickerPackInteractively(info: info, items: items) - } - case .fetching: - break - case .none: - break - } - return .complete() - } - |> deliverOnMainQueue).start(completed: { - }) - - break - } - } - }) - }, - clearGroup: { [weak controllerInteraction] groupId in - guard let controllerInteraction = controllerInteraction else { - return - } - if groupId == AnyHashable("recent") { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize)) - var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - let _ = context.engine.stickers.clearRecentlyUsedStickers().start() - })) - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - controllerInteraction.presentController(actionSheet, nil) - } else if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - var stickerPackIds: [Int64] = [] - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { - stickerPackIds.append(featuredStickerPack.info.id.id) - } - let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: stickerPackIds).start() - }) - } - }, - pushController: { [weak controllerInteraction] controller in - guard let controllerInteraction = controllerInteraction else { - return - } - controllerInteraction.navigationController()?.pushViewController(controller) - }, - presentController: { [weak controllerInteraction] controller in - guard let controllerInteraction = controllerInteraction else { - return - } - controllerInteraction.presentController(controller, nil) - }, - presentGlobalOverlayController: { [weak controllerInteraction] controller in - guard let controllerInteraction = controllerInteraction else { - return - } - controllerInteraction.presentGlobalOverlayController(controller, nil) - }, - navigationController: { [weak controllerInteraction] in - return controllerInteraction?.navigationController() - }, - sendSticker: { [weak controllerInteraction] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in - guard let controllerInteraction = controllerInteraction else { - return - } - let _ = controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer) - }, - chatPeerId: chatPeerId - ) - let animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: { return TempBox.shared.tempFile(fileName: "file").path }) @@ -616,7 +301,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { animationRenderer = MultiAnimationRendererImpl() //} - let emojiItems = emojiInputData(context: context, inputInteraction: emojiInputInteraction, animationCache: animationCache, animationRenderer: animationRenderer) + let emojiItems = emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer) let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks] let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers] @@ -863,7 +548,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { context: context, animationCache: animationCache, animationRenderer: animationRenderer, - inputInteraction: stickerInputInteraction, + inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(), itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in var hasClear = false var isEmbedded = false @@ -1028,6 +713,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { var switchToTextInput: (() -> Void)? private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool)? + + private var scheduledContentAnimationHint: EmojiPagerContentComponent.ContentAnimation? private var scheduledInnerTransition: Transition? private var gifMode: GifPagerContentComponent.Subject? { @@ -1253,7 +940,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { private let gifComponent = Promise() private var gifInputInteraction: GifPagerContentComponent.InputInteraction? - init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?) { + fileprivate var emojiInputInteraction: EmojiPagerContentComponent.InputInteraction? + private var stickerInputInteraction: EmojiPagerContentComponent.InputInteraction? + + init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPeerId: PeerId?) { self.context = context self.currentInputData = currentInputData self.defaultToEmojiTab = defaultToEmojiTab @@ -1271,6 +961,331 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { self.externalTopPanelContainerImpl = PagerExternalTopPanelContainer() + let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + } + |> distinctUntilChanged + + self.emojiInputInteraction = EmojiPagerContentComponent.InputInteraction( + performItemAction: { [weak interfaceInteraction, weak controllerInteraction] _, item, _, _, _ in + let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in + guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { + return + } + + if let file = item.file { + var text = "." + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, displayText, packReference): + text = displayText + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(stickerPack: packReference, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if file.isPremiumEmoji && !hasPremium { + //TODO:localize + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Subscribe to Telegram Premium to unlock this emoji.", undoText: "More", customAction: { [weak controllerInteraction] in + guard let controllerInteraction = controllerInteraction else { + return + } + + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: .animatedEmoji, action: { + let controller = PremiumIntroScreen(context: context, source: .animatedEmoji) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + controllerInteraction.navigationController()?.pushViewController(controller) + + /*let controller = PremiumIntroScreen(context: context, source: .stickers) + controllerInteraction.navigationController()?.pushViewController(controller)*/ + }), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + return + } + + if let emojiAttribute = emojiAttribute { + AudioServicesPlaySystemSound(0x450) + interfaceInteraction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])) + } + } else if let staticEmoji = item.staticEmoji { + AudioServicesPlaySystemSound(0x450) + interfaceInteraction.insertText(NSAttributedString(string: staticEmoji, attributes: [:])) + } + }) + }, + deleteBackwards: { [weak interfaceInteraction] in + guard let interfaceInteraction = interfaceInteraction else { + return + } + interfaceInteraction.backwardsDeleteText() + }, + openStickerSettings: { + }, + addGroupAction: { [weak self, weak controllerInteraction] groupId, isPremiumLocked in + guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else { + return + } + + if isPremiumLocked { + let controller = PremiumIntroScreen(context: context, source: .stickers) + controllerInteraction.navigationController()?.pushViewController(controller) + + return + } + + let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) + let _ = (context.account.postbox.combinedView(keys: [viewKey]) + |> take(1) + |> deliverOnMainQueue).start(next: { views in + guard let view = views.views[viewKey] as? OrderedItemListView else { + return + } + for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + if featuredEmojiPack.info.id == collectionId { + if let strongSelf = self { + strongSelf.scheduledContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId)) + } + let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start() + + break + } + } + }) + }, + clearGroup: { [weak controllerInteraction] groupId in + guard let controllerInteraction = controllerInteraction else { + return + } + if groupId == AnyHashable("recent") { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize)) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: presentationData.strings.Emoji_ClearRecent, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + let _ = context.engine.stickers.clearRecentlyUsedEmoji().start() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + controllerInteraction.presentController(actionSheet, nil) + } + }, + pushController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.navigationController()?.pushViewController(controller) + }, + presentController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.presentController(controller, nil) + }, + presentGlobalOverlayController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.presentGlobalOverlayController(controller, nil) + }, + navigationController: { [weak controllerInteraction] in + return controllerInteraction?.navigationController() + }, + sendSticker: { [weak controllerInteraction] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in + guard let controllerInteraction = controllerInteraction else { + return + } + let _ = controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer) + }, + chatPeerId: chatPeerId + ) + + self.stickerInputInteraction = EmojiPagerContentComponent.InputInteraction( + performItemAction: { [weak controllerInteraction, weak interfaceInteraction] groupId, item, view, rect, layer in + let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in + guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { + return + } + guard let file = item.file else { + return + } + + if groupId == AnyHashable("featuredTop") { + let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) + let _ = (context.account.postbox.combinedView(keys: [viewKey]) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak controllerInteraction] views in + guard let controllerInteraction = controllerInteraction else { + return + } + guard let view = views.views[viewKey] as? OrderedItemListView else { + return + } + for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) { + controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( + context: context, + highlightedPackId: featuredStickerPack.info.id, + sendSticker: { [weak controllerInteraction] fileReference, sourceNode, sourceRect in + guard let controllerInteraction = controllerInteraction else { + return false + } + return controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil) + } + )) + + break + } + } + }) + } else { + if file.isPremiumSticker && !hasPremium { + let controller = PremiumIntroScreen(context: context, source: .stickers) + controllerInteraction.navigationController()?.pushViewController(controller) + + return + } + let _ = interfaceInteraction.sendSticker(.standalone(media: file), false, view, rect, layer) + } + }) + }, + deleteBackwards: { [weak interfaceInteraction] in + guard let interfaceInteraction = interfaceInteraction else { + return + } + interfaceInteraction.backwardsDeleteText() + }, + openStickerSettings: { [weak controllerInteraction] in + guard let controllerInteraction = controllerInteraction else { + return + } + let controller = installedStickerPacksController(context: context, mode: .modal) + controller.navigationPresentation = .modal + controllerInteraction.navigationController()?.pushViewController(controller) + }, + addGroupAction: { groupId, isPremiumLocked in + guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else { + return + } + + if isPremiumLocked { + let controller = PremiumIntroScreen(context: context, source: .stickers) + controllerInteraction.navigationController()?.pushViewController(controller) + + return + } + + let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) + let _ = (context.account.postbox.combinedView(keys: [viewKey]) + |> take(1) + |> deliverOnMainQueue).start(next: { views in + guard let view = views.views[viewKey] as? OrderedItemListView else { + return + } + for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + if featuredStickerPack.info.id == collectionId { + let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) + |> mapToSignal { result -> Signal in + switch result { + case let .result(info, items, installed): + if installed { + return .complete() + } else { + return context.engine.stickers.addStickerPackInteractively(info: info, items: items) + } + case .fetching: + break + case .none: + break + } + return .complete() + } + |> deliverOnMainQueue).start(completed: { + }) + + break + } + } + }) + }, + clearGroup: { [weak controllerInteraction] groupId in + guard let controllerInteraction = controllerInteraction else { + return + } + if groupId == AnyHashable("recent") { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize)) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + let _ = context.engine.stickers.clearRecentlyUsedStickers().start() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + controllerInteraction.presentController(actionSheet, nil) + } else if groupId == AnyHashable("featuredTop") { + let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) + let _ = (context.account.postbox.combinedView(keys: [viewKey]) + |> take(1) + |> deliverOnMainQueue).start(next: { views in + guard let view = views.views[viewKey] as? OrderedItemListView else { + return + } + var stickerPackIds: [Int64] = [] + for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + stickerPackIds.append(featuredStickerPack.info.id.id) + } + let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: stickerPackIds).start() + }) + } + }, + pushController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.navigationController()?.pushViewController(controller) + }, + presentController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.presentController(controller, nil) + }, + presentGlobalOverlayController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.presentGlobalOverlayController(controller, nil) + }, + navigationController: { [weak controllerInteraction] in + return controllerInteraction?.navigationController() + }, + sendSticker: { [weak controllerInteraction] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in + guard let controllerInteraction = controllerInteraction else { + return + } + let _ = controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer) + }, + chatPeerId: chatPeerId + ) self.inputDataDisposable = (combineLatest(queue: .mainQueue(), updatedInputData, @@ -1295,7 +1310,14 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } if useAnimation { - transition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(EmojiPagerContentComponent.ContentAnimation(type: .generic)) + let contentAnimation: EmojiPagerContentComponent.ContentAnimation + if let scheduledContentAnimationHint = strongSelf.scheduledContentAnimationHint { + strongSelf.scheduledContentAnimationHint = nil + contentAnimation = scheduledContentAnimationHint + } else { + contentAnimation = EmojiPagerContentComponent.ContentAnimation(type: .generic) + } + transition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation) } strongSelf.currentInputData = inputData strongSelf.performLayout(transition: transition) @@ -1459,6 +1481,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { gifContent = nil } + stickerContent?.inputInteractionHolder.inputInteraction = self.stickerInputInteraction + self.currentInputData.emoji.inputInteractionHolder.inputInteraction = self.emojiInputInteraction + let entityKeyboardSize = self.entityKeyboardView.update( transition: mappedTransition, component: AnyComponent(EntityKeyboardComponent( @@ -1824,7 +1849,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV let semaphore = DispatchSemaphore(value: 0) var emojiComponent: EmojiPagerContentComponent? - let _ = ChatEntityKeyboardInputNode.emojiInputData(context: context, inputInteraction: inputInteraction, animationCache: self.animationCache, animationRenderer: self.animationRenderer).start(next: { value in + let _ = ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer).start(next: { value in emojiComponent = value semaphore.signal() }) @@ -1841,9 +1866,12 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV ), updatedInputData: .never(), defaultToEmojiTab: true, - controllerInteraction: nil + controllerInteraction: nil, + interfaceInteraction: nil, + chatPeerId: nil ) self.inputNode = inputNode + inputNode.emojiInputInteraction = inputInteraction inputNode.externalTopPanelContainerImpl = nil inputNode.switchToTextInput = { [weak self] in self?.switchToKeyboard?() diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift index 06bdc56632..47635290c9 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift @@ -74,7 +74,7 @@ final class ChatMediaInputStickerGridSectionNode: ASDisplayNode { self.setupNode = setupNode case .clear: let setupNode = HighlightableButtonNode() - setupNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme), for: []) + setupNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: theme.chat.inputMediaPanel.stickersSectionTextColor), for: []) self.setupNode = setupNode } diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift index 2a9c0d613c..c4773edf75 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift @@ -42,6 +42,24 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { private var cachedMaskBackgroundImage: (CGPoint, UIImage, [CGRect])? private var absoluteRect: (CGRect, CGSize)? + override var visibility: ListViewItemNodeVisibility { + didSet { + if oldValue != self.visibility { + switch self.visibility { + case .none: + self.labelNode.visibilityRect = nil + //self.spoilerTextNode?.visibilityRect = nil + case let .visible(_, subRect): + var subRect = subRect + subRect.origin.x = 0.0 + subRect.size.width = 10000.0 + self.labelNode.visibilityRect = subRect + //self.spoilerTextNode?.visibilityRect = subRect + } + } + } + } + required init() { self.labelNode = TextNodeWithEntities() self.labelNode.textNode.isUserInteractionEnabled = false @@ -340,6 +358,18 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.backgroundColorNode.frame = CGRect(origin: CGPoint(), size: image.size) strongSelf.cachedMaskBackgroundImage = (offset, image, labelRects) + + switch strongSelf.visibility { + case .none: + strongSelf.labelNode.visibilityRect = nil + //strongSelf.spoilerTextNode?.visibilityRect = nil + case let .visible(_, subRect): + var subRect = subRect + subRect.origin.x = 0.0 + subRect.size.width = 10000.0 + strongSelf.labelNode.visibilityRect = subRect + //strongSelf.spoilerTextNode?.visibilityRect = subRect + } } } }) diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index f34d924608..881e1cdecb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -206,7 +206,7 @@ final class ChatMessageAccessibilityData { if let chatPeer = message.peers[item.message.id.peerId] { let authorName = message.author.flatMap(EnginePeer.init)?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - let (_, _, messageText, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: [EngineMessage(message)], chatPeer: EngineRenderedPeer(peer: EnginePeer(chatPeer)), accountPeerId: item.context.account.peerId) + let (_, _, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: [EngineMessage(message)], chatPeer: EngineRenderedPeer(peer: EnginePeer(chatPeer)), accountPeerId: item.context.account.peerId) var text = messageText diff --git a/submodules/TelegramUI/Sources/StickerPanePeerSpecificSetupGridItem.swift b/submodules/TelegramUI/Sources/StickerPanePeerSpecificSetupGridItem.swift index cdd8f1b469..5f6f4cc4b4 100644 --- a/submodules/TelegramUI/Sources/StickerPanePeerSpecificSetupGridItem.swift +++ b/submodules/TelegramUI/Sources/StickerPanePeerSpecificSetupGridItem.swift @@ -138,7 +138,7 @@ class StickerPanePeerSpecificSetupGridItemNode: GridItemNode { var updateButtonBackgroundImage: UIImage? if currentItem?.theme !== item.theme { updateButtonBackgroundImage = PresentationResourcesChat.chatInputMediaPanelAddPackButtonImage(item.theme) - self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme), for: []) + self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme, color: item.theme.chat.inputMediaPanel.stickersSectionTextColor), for: []) } let leftInset: CGFloat = 12.0 diff --git a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift index 66eb834827..19dd723f22 100644 --- a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift +++ b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift @@ -525,7 +525,7 @@ class StickerPaneTrendingListGridItemNode: GridItemNode { self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if currentItem?.theme !== item.theme { - self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme), for: []) + self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme, color: item.theme.chat.inputMediaPanel.stickersSectionTextColor), for: []) } let leftInset: CGFloat = 9.0