Emoji input improvements

This commit is contained in:
Ali 2022-07-22 02:12:10 +02:00
parent 3384d86415
commit 7b663a3445
28 changed files with 1311 additions and 466 deletions

View File

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

View File

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

View File

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

View File

@ -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<ChildEnvironmentType: Equatable, TopPanelEnvir
case .began:
self.paneTransitionGestureState = PaneTransitionGestureState()
case .changed:
if var paneTransitionGestureState = self.paneTransitionGestureState, self.bounds.width > 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<ChildEnvironmentType: Equatable, TopPanelEnvir
}
}
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: effectiveTopPanelHeight), size: CGSize(width: availableSize.width, height: availableSize.height - effectiveTopPanelHeight - contentInsets.bottom + bottomPanelOffset))
if let contentBackground = component.contentBackground {
let contentBackgroundView: ComponentHostView<Empty>
var contentBackgroundTransition = panelStateTransition
@ -565,13 +577,13 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
self.contentBackgroundView = contentBackgroundView
self.insertSubview(contentBackgroundView, at: 0)
}
let contentBackgroundSize = contentBackgroundView.update(
let _ = contentBackgroundView.update(
transition: contentBackgroundTransition,
component: contentBackground,
environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.height - effectiveTopPanelHeight - contentInsets.bottom + bottomPanelOffset)
containerSize: backgroundFrame.size
)
contentBackgroundTransition.setFrame(view: contentBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: effectiveTopPanelHeight), size: contentBackgroundSize))
contentBackgroundTransition.setFrame(view: contentBackgroundView, frame: backgroundFrame)
} else {
if let contentBackgroundView = self.contentBackgroundView {
self.contentBackgroundView = nil
@ -691,6 +703,10 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
}
})
}
if let contentViewWithBackground = contentView.view.componentView as? PagerContentViewWithBackground {
contentViewWithBackground.pagerUpdateBackground(backgroundFrame: backgroundFrame, transition: contentTransition)
}
}
}
}

View File

@ -327,6 +327,7 @@ open class BlurredBackgroundView: UIView {
if let color = self._color, self.enableBlur && !sharedIsReduceTransparencyEnabled && ((color.alpha > .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") {

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import Postbox
import SwiftSignalKit
func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCollectionInfo, items: [ItemCollectionItem], positionInList: Int? = nil) -> Signal<Void, NoError> {
func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil) -> Signal<Void, NoError> {
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) })
}

View File

@ -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<Void, NoError> {
public func addStickerPackInteractively(info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil) -> Signal<Void, NoError> {
return _internal_addStickerPackInteractively(postbox: self.account.postbox, info: info, items: items, positionInList: positionInList)
}

View File

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

View File

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

View File

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

View File

@ -171,6 +171,8 @@ public final class EntityKeyboardComponent: Component {
}
public final class View: UIView {
private let tintContainerView: UIView
private let pagerView: ComponentHostView<EntityKeyboardChildEnvironment>
private var component: EntityKeyboardComponent?
@ -191,6 +193,7 @@ public final class EntityKeyboardComponent: Component {
}
override init(frame: CGRect) {
self.tintContainerView = UIView()
self.pagerView = ComponentHostView<EntityKeyboardChildEnvironment>()
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)
}
)),

View File

@ -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<EnvironmentType>, transition: Transition) -> CGSize {
var contentReset = false
if let previousComponent = self.component, previousComponent.subject != component.subject {

View File

@ -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] {

View File

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

View File

@ -0,0 +1,4 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.4131 0.413101C8.73724 0.0889662 9.26276 0.0889662 9.5869 0.413101C9.91103 0.737236 9.91103 1.26276 9.5869 1.5869L6.1738 5L9.5869 8.4131C9.91103 8.73724 9.91103 9.26276 9.5869 9.5869C9.26276 9.91103 8.73724 9.91103 8.4131 9.5869L5 6.1738L1.5869 9.5869C1.26276 9.91103 0.737237 9.91103 0.413101 9.5869C0.0889663 9.26276 0.0889663 8.73724 0.413101 8.4131L3.8262 5L0.413101 1.5869C0.0889662 1.26276 0.0889662 0.737237 0.413101 0.413101C0.737237 0.0889663 1.26276 0.0889663 1.5869 0.413101L5 3.8262L8.4131 0.413101Z" fill="black" style="mix-blend-mode:overlay"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.4131 0.413101C8.73724 0.0889662 9.26276 0.0889662 9.5869 0.413101C9.91103 0.737236 9.91103 1.26276 9.5869 1.5869L6.1738 5L9.5869 8.4131C9.91103 8.73724 9.91103 9.26276 9.5869 9.5869C9.26276 9.91103 8.73724 9.91103 8.4131 9.5869L5 6.1738L1.5869 9.5869C1.26276 9.91103 0.737237 9.91103 0.413101 9.5869C0.0889663 9.26276 0.0889663 8.73724 0.413101 8.4131L3.8262 5L0.413101 1.5869C0.0889662 1.26276 0.0889662 0.737237 0.413101 0.413101C0.737237 0.0889663 1.26276 0.0889663 1.5869 0.413101L5 3.8262L8.4131 0.413101Z" fill="black" fill-opacity="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 B

View File

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

View File

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

View File

@ -86,7 +86,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
}
}
static func emojiInputData(context: AccountContext, inputInteraction: EmojiPagerContentComponent.InputInteraction, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError> {
static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError> {
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<Void, NoError> 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<EntityKeyboardGifContent>()
private var gifInputInteraction: GifPagerContentComponent.InputInteraction?
init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?) {
fileprivate var emojiInputInteraction: EmojiPagerContentComponent.InputInteraction?
private var stickerInputInteraction: EmojiPagerContentComponent.InputInteraction?
init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, 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<Void, NoError> 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?()

View File

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

View File

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

View File

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

View File

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

View File

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