UI improvements

This commit is contained in:
Ali 2023-01-23 14:36:47 +04:00
parent 55d076a494
commit 3b42f14613
18 changed files with 1085 additions and 155 deletions

View File

@ -150,7 +150,7 @@ public struct Transition {
//view.center = CGPoint(x: frame.midX, y: frame.midY)
self.animatePosition(view: view, from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: CGPoint(x: frame.midX, y: frame.midY), completion: completion)
self.animateBounds(view: view, from: CGRect(origin: view.bounds.origin, size: previousFrame.size), to: CGRect(origin: view.bounds.origin, size: frame.size))
self.animateBoundsSize(view: view, from: previousFrame.size, to: frame.size)
}
}

View File

@ -149,6 +149,7 @@ public enum PagerComponentPanelHideBehavior {
case hideOnScroll
case show
case hide
case disable
}
public final class PagerComponentContentIcon: Equatable {
@ -559,6 +560,10 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
}
}
if case .disable = component.panelHideBehavior {
topPanelVisibility = 0.0
}
var topPanelHeight: CGFloat = 0.0
if let topPanel = component.topPanel {
let effectiveTopPanelOffsetFraction = scrollingPanelOffsetFraction
@ -616,7 +621,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
var topPanelVisibilityFraction: CGFloat = 1.0 - effectiveTopPanelOffsetFraction
switch component.panelHideBehavior {
case .hide:
case .hide, .disable:
topPanelVisibilityFraction = 0.0
case .show:
topPanelVisibilityFraction = 1.0
@ -631,12 +636,16 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
if case .hide = component.panelHideBehavior {
topPanelOffset = topPanelSize.height
} else if case .disable = component.panelHideBehavior {
topPanelOffset = topPanelSize.height
}
if component.externalTopPanelContainer != nil {
var visibleTopPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
if case .hide = component.panelHideBehavior {
visibleTopPanelHeight = 0.0
} else if case .disable = component.panelHideBehavior {
visibleTopPanelHeight = 0.0
}
panelStateTransition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(), size: CGSize(width: topPanelSize.width, height: visibleTopPanelHeight)))
@ -705,6 +714,8 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
bottomPanelOffset = bottomPanelSize.height * scrollingPanelOffsetFraction
if case .hide = component.panelHideBehavior {
bottomPanelOffset = bottomPanelSize.height
} else if case .disable = component.panelHideBehavior {
bottomPanelOffset = bottomPanelSize.height
}
panelStateTransition.setFrame(view: bottomPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height + bottomPanelOffset), size: bottomPanelSize))
@ -724,7 +735,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
let effectiveTopPanelHeight: CGFloat
switch component.panelHideBehavior {
case .hide:
case .hide, .disable:
effectiveTopPanelHeight = 0.0
case .show, .hideOnScroll:
if component.externalTopPanelContainer != nil {

View File

@ -276,7 +276,7 @@ class StickerPickerScreen: ViewController {
openFeatured: nil,
openSearch: {
},
addGroupAction: { [weak self] groupId, isPremiumLocked in
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {
return
}
@ -391,7 +391,7 @@ class StickerPickerScreen: ViewController {
openStickerSettings: nil,
openFeatured: nil,
openSearch: {},
addGroupAction: { [weak self] groupId, isPremiumLocked in
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {
return
}
@ -481,7 +481,7 @@ class StickerPickerScreen: ViewController {
openFeatured: nil,
openSearch: {
},
addGroupAction: { [weak self] groupId, isPremiumLocked in
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {
return
}

View File

@ -783,6 +783,8 @@
authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes];
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector];
}];
restartRequest = true;
} else if (rpcError.errorCode == 406) {
if (_didReceiveSoftAuthResetError) {
_didReceiveSoftAuthResetError();

View File

@ -1264,7 +1264,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
},
openSearch: {
},
addGroupAction: { [weak self] groupId, isPremiumLocked in
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
return
}
@ -1284,7 +1284,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredEmojiPack.info.id == collectionId {
if let strongSelf = self {
strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId))
strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: true))
}
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()

View File

@ -481,7 +481,7 @@ final class AvatarEditorScreenComponent: Component {
openFeatured: nil,
openSearch: {
},
addGroupAction: { [weak self] groupId, isPremiumLocked in
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
guard let strongSelf = self, let controller = strongSelf.controller?(), let collectionId = groupId.base as? ItemCollectionId else {
return
}
@ -615,7 +615,7 @@ final class AvatarEditorScreenComponent: Component {
openFeatured: nil,
openSearch: {
},
addGroupAction: { [weak self] groupId, isPremiumLocked in
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
guard let strongSelf = self, let controller = strongSelf.controller?(), let collectionId = groupId.base as? ItemCollectionId else {
return
}

View File

@ -641,25 +641,61 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
var premiumToastCounter = 0
self.emojiInputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self, weak interfaceInteraction, weak controllerInteraction] groupId, item, _, _, _, _ in
let _ = (ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: true) |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in
let _ = (
combineLatest(
ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: true),
ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: false)
)
|> take(1)
|> deliverOnMainQueue).start(next: { hasPremium, hasGlobalPremium in
guard let strongSelf = self, let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
return
}
if groupId == AnyHashable("featuredTop"), let file = item.itemFile {
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
let _ = (context.account.postbox.combinedView(keys: [viewKey])
let _ = (combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
context.account.postbox.combinedView(keys: [viewKey])
)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak interfaceInteraction, weak controllerInteraction] views in
|> deliverOnMainQueue).start(next: { [weak interfaceInteraction, weak controllerInteraction] emojiPacksView, 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)! }) {
guard let self else {
return
}
let _ = interfaceInteraction
let _ = controllerInteraction
var installedCollectionIds = Set<ItemCollectionId>()
for (id, _, _) in emojiPacksView.collectionInfos {
installedCollectionIds.insert(id)
}
let stickerPacks = view.items.map({ $0.contents.get(FeaturedStickerPackItem.self)! }).filter({
!installedCollectionIds.contains($0.info.id)
})
for featuredStickerPack in stickerPacks {
if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) {
let controller = StickerPackScreen(
if let pagerView = self.entityKeyboardView.componentView as? EntityKeyboardComponent.View, let emojiInputInteraction = self.emojiInputInteraction {
pagerView.openCustomSearch(content: EmojiSearchContent(
context: self.context,
items: stickerPacks,
initialFocusId: featuredStickerPack.info.id,
hasPremiumForUse: hasPremium,
hasPremiumForInstallation: hasGlobalPremium,
parentInputInteraction: emojiInputInteraction
))
}
/*let controller = StickerPackScreen(
context: context,
updatedPresentationData: controllerInteraction.updatedPresentationData,
mode: .default,
@ -675,7 +711,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
interfaceInteraction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
}
)
controllerInteraction.presentController(controller, nil)
controllerInteraction.presentController(controller, nil)*/
break
}
@ -786,7 +822,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
},
openSearch: {
},
addGroupAction: { [weak self, weak controllerInteraction] groupId, isPremiumLocked in
addGroupAction: { [weak self, weak controllerInteraction] groupId, isPremiumLocked, scrollToGroup in
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
return
}
@ -815,7 +851,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
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))
strongSelf.scheduledContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: scrollToGroup))
}
let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
@ -1181,7 +1217,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
pagerView.openSearch()
}
},
addGroupAction: { groupId, isPremiumLocked in
addGroupAction: { groupId, isPremiumLocked, _ in
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
return
}
@ -1479,7 +1515,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
guard let strongSelf = self else {
return
}
strongSelf.openGifContextMenu(item: item, sourceView: sourceView, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved)
strongSelf.openGifContextMenu(file: item.file, contextResult: item.contextResult, sourceView: sourceView, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved)
},
loadMore: { [weak self] token in
guard let strongSelf = self, let gifContext = strongSelf.gifContext else {
@ -1697,7 +1733,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
strongSelf.reorderItems(category: category, items: items)
},
makeSearchContainerNode: { [weak controllerInteraction] content in
makeSearchContainerNode: { [weak self, weak controllerInteraction] content in
guard let controllerInteraction = controllerInteraction else {
return nil
}
@ -1711,7 +1747,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
return PaneSearchContainerNode(
let searchContainerNode = PaneSearchContainerNode(
context: context,
theme: presentationData.theme,
strings: presentationData.strings,
@ -1722,6 +1758,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
cancel: {
}
)
searchContainerNode.openGifContextMenu = { item, sourceNode, sourceRect, gesture, isSaved in
guard let self else {
return
}
self.openGifContextMenu(file: item.file, contextResult: item.contextResult, sourceView: sourceNode.view, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved)
}
return searchContainerNode
},
contentIdUpdated: { _ in },
deviceMetrics: deviceMetrics,
@ -1854,17 +1898,15 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
})
}
private func openGifContextMenu(item: GifPagerContentComponent.Item, sourceView: UIView, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) {
let file = item
private func openGifContextMenu(file: FileMediaReference, contextResult: (ChatContextResultCollection, ChatContextResult)?, sourceView: UIView, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) {
let canSaveGif: Bool
if file.file.media.fileId.namespace == Namespaces.Media.CloudFile {
if file.media.fileId.namespace == Namespaces.Media.CloudFile {
canSaveGif = true
} else {
canSaveGif = false
}
let _ = (self.context.engine.stickers.isGifSaved(id: file.file.media.fileId)
let _ = (self.context.engine.stickers.isGifSaved(id: file.media.fileId)
|> deliverOnMainQueue).start(next: { [weak self] isGifSaved in
guard let strongSelf = self else {
return
@ -1875,7 +1917,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let gallery = GalleryController(context: strongSelf.context, source: .standaloneMessage(message), streamSingleVideo: true, replaceRootController: { _, _ in
}, baseNavigationController: nil)
@ -1887,8 +1929,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}, action: { _, f in
f(.default)
if isSaved {
let _ = self?.controllerInteraction?.sendGif(file.file, sourceView, sourceRect, false, false)
} else if let (collection, result) = file.contextResult {
let _ = self?.controllerInteraction?.sendGif(file, sourceView, sourceRect, false, false)
} else if let (collection, result) = contextResult {
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, false)
}
})))
@ -1908,8 +1950,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}, action: { _, f in
f(.default)
if isSaved {
let _ = self?.controllerInteraction?.sendGif(file.file, sourceView, sourceRect, true, false)
} else if let (collection, result) = file.contextResult {
let _ = self?.controllerInteraction?.sendGif(file, sourceView, sourceRect, true, false)
} else if let (collection, result) = contextResult {
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, true)
}
})))
@ -1921,7 +1963,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}, action: { _, f in
f(.default)
let _ = self?.controllerInteraction?.sendGif(file.file, sourceView, sourceRect, false, true)
let _ = self?.controllerInteraction?.sendGif(file, sourceView, sourceRect, false, true)
})))
}
}
@ -1937,7 +1979,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
guard let strongSelf = self else {
return
}
let _ = removeSavedGif(postbox: strongSelf.context.account.postbox, mediaId: file.file.media.fileId).start()
let _ = removeSavedGif(postbox: strongSelf.context.account.postbox, mediaId: file.media.fileId).start()
})))
} else if canSaveGif && !isGifSaved {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Preview_SaveGif, icon: { theme in
@ -1951,7 +1993,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
let context = strongSelf.context
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (toggleGifSaved(account: context.account, fileReference: file.file, saved: true)
let _ = (toggleGifSaved(account: context.account, fileReference: file, saved: true)
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
@ -2136,7 +2178,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi
},
openSearch: {
},
addGroupAction: { _, _ in
addGroupAction: { _, _, _ in
},
clearGroup: { [weak self] groupId in
guard let strongSelf = self else {

View File

@ -395,7 +395,7 @@ public final class EmojiStatusSelectionController: ViewController {
},
openSearch: {
},
addGroupAction: { groupId, isPremiumLocked in
addGroupAction: { groupId, isPremiumLocked, _ in
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
return
}
@ -410,7 +410,7 @@ public final class EmojiStatusSelectionController: ViewController {
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredEmojiPack.info.id == collectionId {
if let strongSelf = self {
strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId))
strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: true))
}
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()

View File

@ -1892,7 +1892,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
component: AnyComponent(Text(
text: strings.Common_Cancel,
font: Font.regular(17.0),
color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
color: useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
)),
environment: {},
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
@ -2178,7 +2178,7 @@ public final class EmojiPagerContentComponent: Component {
public enum AnimationType {
case generic
case groupExpanded(id: AnyHashable)
case groupInstalled(id: AnyHashable)
case groupInstalled(id: AnyHashable, scrollToGroup: Bool)
case groupRemoved(id: AnyHashable)
}
@ -2239,7 +2239,7 @@ public final class EmojiPagerContentComponent: Component {
public let openStickerSettings: (() -> Void)?
public let openFeatured: (() -> Void)?
public let openSearch: () -> Void
public let addGroupAction: (AnyHashable, Bool) -> Void
public let addGroupAction: (AnyHashable, Bool, Bool) -> Void
public let clearGroup: (AnyHashable) -> Void
public let pushController: (ViewController) -> Void
public let presentController: (ViewController) -> Void
@ -2248,6 +2248,7 @@ public final class EmojiPagerContentComponent: Component {
public let requestUpdate: (Transition) -> Void
public let updateSearchQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void
public let updateScrollingToItemGroup: () -> Void
public let externalCancel: (() -> Void)?
public let chatPeerId: PeerId?
public let peekBehavior: EmojiContentPeekBehavior?
public let customLayout: CustomLayout?
@ -2262,7 +2263,7 @@ public final class EmojiPagerContentComponent: Component {
openStickerSettings: (() -> Void)?,
openFeatured: (() -> Void)?,
openSearch: @escaping () -> Void,
addGroupAction: @escaping (AnyHashable, Bool) -> Void,
addGroupAction: @escaping (AnyHashable, Bool, Bool) -> Void,
clearGroup: @escaping (AnyHashable) -> Void,
pushController: @escaping (ViewController) -> Void,
presentController: @escaping (ViewController) -> Void,
@ -2271,6 +2272,7 @@ public final class EmojiPagerContentComponent: Component {
requestUpdate: @escaping (Transition) -> Void,
updateSearchQuery: @escaping (SearchQuery?) -> Void,
updateScrollingToItemGroup: @escaping () -> Void,
externalCancel: (() -> Void)? = nil,
chatPeerId: PeerId?,
peekBehavior: EmojiContentPeekBehavior?,
customLayout: CustomLayout?,
@ -2293,6 +2295,7 @@ public final class EmojiPagerContentComponent: Component {
self.requestUpdate = requestUpdate
self.updateSearchQuery = updateSearchQuery
self.updateScrollingToItemGroup = updateScrollingToItemGroup
self.externalCancel = externalCancel
self.chatPeerId = chatPeerId
self.peekBehavior = peekBehavior
self.customLayout = customLayout
@ -2541,6 +2544,7 @@ public final class EmojiPagerContentComponent: Component {
public let displaySearchWithPlaceholder: String?
public let searchCategories: EmojiSearchCategories?
public let searchInitiallyHidden: Bool
public let searchAlwaysActive: Bool
public let searchIsPlaceholderOnly: Bool
public let emptySearchResults: EmptySearchResults?
public let enableLongPress: Bool
@ -2561,6 +2565,7 @@ public final class EmojiPagerContentComponent: Component {
displaySearchWithPlaceholder: String?,
searchCategories: EmojiSearchCategories?,
searchInitiallyHidden: Bool,
searchAlwaysActive: Bool,
searchIsPlaceholderOnly: Bool,
emptySearchResults: EmptySearchResults?,
enableLongPress: Bool,
@ -2580,6 +2585,7 @@ public final class EmojiPagerContentComponent: Component {
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
self.searchCategories = searchCategories
self.searchInitiallyHidden = searchInitiallyHidden
self.searchAlwaysActive = searchAlwaysActive
self.searchIsPlaceholderOnly = searchIsPlaceholderOnly
self.emptySearchResults = emptySearchResults
self.enableLongPress = enableLongPress
@ -2602,6 +2608,7 @@ public final class EmojiPagerContentComponent: Component {
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
searchCategories: self.searchCategories,
searchInitiallyHidden: self.searchInitiallyHidden,
searchAlwaysActive: self.searchAlwaysActive,
searchIsPlaceholderOnly: self.searchIsPlaceholderOnly,
emptySearchResults: emptySearchResults,
enableLongPress: self.enableLongPress,
@ -4296,7 +4303,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
public func scrollToItemGroup(id supergroupId: AnyHashable, subgroupId: Int32?) {
public func scrollToItemGroup(id supergroupId: AnyHashable, subgroupId: Int32?, animated: Bool) {
guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
return
}
@ -4332,6 +4339,9 @@ public final class EmojiPagerContentComponent: Component {
}
var scrollPosition = anchorFrame.minY + floor(-itemLayout.verticalGroupDefaultSpacing / 2.0) - pagerEnvironment.containerInsets.top
if !animated {
scrollPosition = floor(anchorFrame.midY - self.scrollView.bounds.height * 0.5)
}
if scrollPosition > self.scrollView.contentSize.height - self.scrollView.bounds.height {
scrollPosition = self.scrollView.contentSize.height - self.scrollView.bounds.height
}
@ -4339,6 +4349,26 @@ public final class EmojiPagerContentComponent: Component {
scrollPosition = 0.0
}
if !animated, let keyboardChildEnvironment = self.keyboardChildEnvironment, let inputInteraction = component.inputInteractionHolder.inputInteraction, inputInteraction.useOpaqueTheme {
let highlightLayer = SimpleLayer()
highlightLayer.backgroundColor = keyboardChildEnvironment.theme.list.itemAccentColor.withMultipliedAlpha(0.1).cgColor
highlightLayer.cornerRadius = 20.0
var highlightFrame = group.frame
if highlightFrame.origin.x < 4.0 {
highlightFrame.size.width += (4.0 - highlightFrame.origin.x)
highlightFrame.origin.x = 4.0
}
if highlightFrame.minX + highlightFrame.size.width > self.scrollView.bounds.width - 4.0 {
highlightFrame.size.width = self.scrollView.bounds.width - 4.0 - highlightFrame.minX
}
highlightLayer.frame = highlightFrame
self.scrollView.layer.insertSublayer(highlightLayer, at: 0)
highlightLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.8, removeOnCompletion: false, completion: { [weak highlightLayer] _ in
highlightLayer?.removeFromSuperlayer()
})
}
let offsetDirectionSign: Double = scrollPosition < self.scrollView.bounds.minY ? -1.0 : 1.0
var previousVisibleLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:]
@ -4462,7 +4492,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
let duration = 0.4
let duration: Double = animated ? 0.4 : 0.0
let timingFunction = kCAMediaTimingFunctionSpring
if let commonItemOffset = commonItemOffset {
@ -4779,41 +4809,43 @@ public final class EmojiPagerContentComponent: Component {
return
}
if case .ended = recognizer.state {
let locationInScrollView = recognizer.location(in: self.scrollView)
outer: for (id, groupHeader) in self.visibleGroupHeaders {
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.inputInteractionHolder.inputInteraction?.clearGroup(id)
return
} else {
if groupHeader.tapGesture(point: recognizer.location(in: groupHeader)) {
if self.scrollViewClippingView.bounds.contains(recognizer.location(in: self.scrollViewClippingView)) {
let locationInScrollView = recognizer.location(in: self.scrollView)
outer: for (id, groupHeader) in self.visibleGroupHeaders {
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.inputInteractionHolder.inputInteraction?.clearGroup(id)
return
} else {
if groupHeader.tapGesture(point: recognizer.location(in: groupHeader)) {
return
}
}
}
}
}
var foundItem = false
var foundExactItem = false
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self)), let itemLayer = self.visibleItemLayers[itemKey] {
foundExactItem = true
foundItem = true
if !itemLayer.displayPlaceholder {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, false)
}
}
if !foundExactItem {
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self), extendedHitRange: true), let itemLayer = self.visibleItemLayers[itemKey] {
var foundItem = false
var foundExactItem = false
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self)), let itemLayer = self.visibleItemLayers[itemKey] {
foundExactItem = true
foundItem = true
if !itemLayer.displayPlaceholder {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, false)
}
}
if !foundExactItem {
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self), extendedHitRange: true), let itemLayer = self.visibleItemLayers[itemKey] {
foundItem = true
if !itemLayer.displayPlaceholder {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, false)
}
}
}
let _ = foundItem
}
let _ = foundItem
}
}
@ -5006,7 +5038,7 @@ public final class EmojiPagerContentComponent: Component {
scrollView.layer.removeAllAnimations()
}
if self.isSearchActivated, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil {
if self.isSearchActivated, let component = self.component, !component.searchAlwaysActive, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil {
scrollView.isScrollEnabled = false
DispatchQueue.main.async {
scrollView.isScrollEnabled = true
@ -5118,8 +5150,10 @@ public final class EmojiPagerContentComponent: Component {
var transitionHintExpandedGroupId: AnyHashable?
if let contentAnimation = contentAnimation {
switch contentAnimation.type {
case let .groupInstalled(groupId):
transitionHintInstalledGroupId = groupId
case let .groupInstalled(groupId, scrollToGroup):
if scrollToGroup {
transitionHintInstalledGroupId = groupId
}
case let .groupExpanded(groupId):
transitionHintExpandedGroupId = groupId
case let .groupRemoved(groupId):
@ -5155,7 +5189,7 @@ public final class EmojiPagerContentComponent: Component {
guard let strongSelf = self, let component = strongSelf.component else {
return
}
component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, false)
component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, false, true)
},
performItemAction: { [weak self] item, view, rect, layer in
guard let strongSelf = self, let component = strongSelf.component else {
@ -5337,7 +5371,7 @@ public final class EmojiPagerContentComponent: Component {
guard let strongSelf = self, let component = strongSelf.component else {
return
}
component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, isPremiumLocked)
component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, isPremiumLocked, true)
}
)),
environment: {},
@ -5967,6 +6001,10 @@ public final class EmojiPagerContentComponent: Component {
self.component = component
self.state = state
if component.searchAlwaysActive {
self.isSearchActivated = true
}
component.inputInteractionHolder.inputInteraction?.peekBehavior?.setGestureRecognizerEnabled(view: self, isEnabled: true, itemAtPoint: { [weak self] point in
guard let strongSelf = self else {
return nil
@ -6019,10 +6057,15 @@ public final class EmojiPagerContentComponent: Component {
var transitionHintInstalledGroupId: AnyHashable?
var transitionHintExpandedGroupId: AnyHashable?
var keepOffset = false
if let contentAnimation = contentAnimation {
switch contentAnimation.type {
case let .groupInstalled(groupId):
transitionHintInstalledGroupId = groupId
case let .groupInstalled(groupId, scrollToGroup):
if scrollToGroup {
transitionHintInstalledGroupId = groupId
} else {
keepOffset = true
}
case let .groupExpanded(groupId):
transitionHintExpandedGroupId = groupId
case let .groupRemoved(groupId):
@ -6258,7 +6301,7 @@ public final class EmojiPagerContentComponent: Component {
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: scrollView.isDragging || scrollView.isDecelerating)
var animatedScrollOffset: CGFloat = 0.0
if !anchorItems.isEmpty {
if !anchorItems.isEmpty && !keepOffset {
let sortedAnchorItems: [(ItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in
if lhs.value.minY != rhs.value.minY {
return lhs.value.minY < rhs.value.minY
@ -6390,20 +6433,24 @@ public final class EmojiPagerContentComponent: Component {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
}
}, deactivated: { [weak self] isFirstResponder in
guard let strongSelf = self else {
guard let strongSelf = self, let component = strongSelf.component else {
return
}
strongSelf.scrollToTop()
strongSelf.isSearchActivated = false
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false)
if strongSelf.component?.searchInitiallyHidden == false {
if !isFirstResponder {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.easeInOut(duration: 0.2))
}
if let externalCancel = component.inputInteractionHolder.inputInteraction?.externalCancel {
externalCancel()
} else {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
strongSelf.scrollToTop()
strongSelf.isSearchActivated = false
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false)
if strongSelf.component?.searchInitiallyHidden == false {
if !isFirstResponder {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.easeInOut(duration: 0.2))
}
} else {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
}
}
}, updateQuery: { [weak self] query in
guard let strongSelf = self else {
@ -7490,6 +7537,7 @@ public final class EmojiPagerContentComponent: Component {
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
searchCategories: searchCategories,
searchInitiallyHidden: searchInitiallyHidden,
searchAlwaysActive: false,
searchIsPlaceholderOnly: false,
emptySearchResults: nil,
enableLongPress: (isReactionSelection && !isQuickReactionSelection) || isStatusSelection,
@ -8010,6 +8058,7 @@ public final class EmojiPagerContentComponent: Component {
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
searchCategories: searchCategories,
searchInitiallyHidden: true,
searchAlwaysActive: false,
searchIsPlaceholderOnly: searchIsPlaceholderOnly,
emptySearchResults: nil,
enableLongPress: false,

View File

@ -0,0 +1,466 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramPresentationData
import TelegramCore
import Postbox
import AnimationCache
import MultiAnimationRenderer
import AccountContext
import AsyncDisplayKit
import ComponentDisplayAdapters
import PagerComponent
import SwiftSignalKit
public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode {
private struct Params: Equatable {
var size: CGSize
var leftInset: CGFloat
var rightInset: CGFloat
var bottomInset: CGFloat
var inputHeight: CGFloat
var deviceMetrics: DeviceMetrics
}
private let context: AccountContext
private var initialFocusId: ItemCollectionId?
private let hasPremiumForUse: Bool
private let hasPremiumForInstallation: Bool
private let parentInputInteraction: EmojiPagerContentComponent.InputInteraction
private var presentationData: PresentationData
private let keyboardView = ComponentView<Empty>()
private let panelHostView: PagerExternalTopPanelContainer
private let inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder
private var params: Params?
private var itemGroups: [EmojiPagerContentComponent.ItemGroup] = []
public var onCancel: (() -> Void)?
private let emojiSearchDisposable = MetaDisposable()
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
private var emojiSearchResultValue: (groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?
private var dataDisposable: Disposable?
public init(
context: AccountContext,
items: [FeaturedStickerPackItem],
initialFocusId: ItemCollectionId?,
hasPremiumForUse: Bool,
hasPremiumForInstallation: Bool,
parentInputInteraction: EmojiPagerContentComponent.InputInteraction
) {
self.context = context
self.initialFocusId = initialFocusId
self.hasPremiumForUse = hasPremiumForUse
self.hasPremiumForInstallation = hasPremiumForInstallation
self.parentInputInteraction = parentInputInteraction
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.panelHostView = PagerExternalTopPanelContainer()
self.inputInteractionHolder = EmojiPagerContentComponent.InputInteractionHolder()
super.init()
for groupItem in items {
var groupItems: [EmojiPagerContentComponent.Item] = []
for item in groupItem.topItems {
var tintMode: EmojiPagerContentComponent.Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.file)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
groupItems.append(resultItem)
}
//TODO:localize
self.itemGroups.append(EmojiPagerContentComponent.ItemGroup(
supergroupId: AnyHashable(groupItem.info.id),
groupId: AnyHashable(groupItem.info.id),
title: groupItem.info.title,
subtitle: nil,
actionButtonTitle: "Add \(groupItem.info.title)",
isFeatured: true,
isPremiumLocked: !self.hasPremiumForInstallation,
isEmbedded: false,
hasClear: false,
collapsedLineCount: 3,
displayPremiumBadges: false,
headerItem: nil,
items: groupItems
))
}
self.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] groupId, item, sourceView, sourceRect, sourceLayer, isPreview in
guard let self else {
return
}
self.parentInputInteraction.performItemAction(groupId, item, sourceView, sourceRect, sourceLayer, isPreview)
if self.hasPremiumForUse {
self.onCancel?()
}
},
deleteBackwards: {
},
openStickerSettings: {
},
openFeatured: {
},
openSearch: {
},
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
guard let self else {
return
}
self.parentInputInteraction.addGroupAction(groupId, isPremiumLocked, false)
if !isPremiumLocked {
if self.itemGroups.count == 1 {
self.onCancel?()
} else {
self.itemGroups.removeAll(where: { $0.groupId == groupId })
self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(EmojiPagerContentComponent.ContentAnimation(type: .groupRemoved(id: groupId))))
}
}
},
clearGroup: { _ in
},
pushController: { _ in
},
presentController: { _ in
},
presentGlobalOverlayController: { _ in
},
navigationController: {
return nil
},
requestUpdate: { _ in
},
updateSearchQuery: { [weak self] query in
guard let self else {
return
}
switch query {
case .none:
self.emojiSearchDisposable.set(nil)
self.emojiSearchResult.set(.single(nil))
case let .text(rawQuery, languageCode):
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isEmpty {
self.emojiSearchDisposable.set(nil)
self.emojiSearchResult.set(.single(nil))
} else {
let context = self.context
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
if !languageCode.lowercased().hasPrefix("en") {
signal = signal
|> mapToSignal { keywords in
return .single(keywords)
|> then(
context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
|> map { englishKeywords in
return keywords + englishKeywords
}
)
}
}
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
let resultSignal = signal
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
context.engine.stickers.availableReactions(),
hasPremium
)
|> take(1)
|> map { view, availableReactions, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in
var result: [(String, TelegramMediaFile?, String)] = []
var allEmoticons: [String: String] = [:]
for keyword in keywords {
for emoticon in keyword.emoticons {
allEmoticons[emoticon] = keyword.keyword
}
}
for entry in view.entries {
guard let item = entry.item as? StickerPackItem else {
continue
}
for attribute in item.file.attributes {
switch attribute {
case let .CustomEmoji(_, _, alt, _):
if !item.file.isPremiumEmoji || hasPremium {
if !alt.isEmpty, let keyword = allEmoticons[alt] {
result.append((alt, item.file, keyword))
} else if alt == query {
result.append((alt, item.file, alt))
}
}
default:
break
}
}
}
var items: [EmojiPagerContentComponent.Item] = []
var existingIds = Set<MediaId>()
for item in result {
if let itemFile = item.1 {
if existingIds.contains(itemFile.fileId) {
continue
}
existingIds.insert(itemFile.fileId)
let animationData = EntityKeyboardAnimationData(file: itemFile)
let item = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: itemFile, subgroupId: nil,
icon: .none,
tintMode: animationData.isTemplate ? .primary : .none
)
items.append(item)
}
}
return [EmojiPagerContentComponent.ItemGroup(
supergroupId: "search",
groupId: "search",
title: nil,
subtitle: nil,
actionButtonTitle: nil,
isFeatured: false,
isPremiumLocked: false,
isEmbedded: false,
hasClear: false,
collapsedLineCount: nil,
displayPremiumBadges: false,
headerItem: nil,
items: items
)]
}
}
self.emojiSearchDisposable.set((resultSignal
|> delay(0.15, queue: .mainQueue())
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
self.emojiSearchResult.set(.single((result, AnyHashable(query))))
}))
}
case let .category(value):
let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value)
|> mapToSignal { files -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
var items: [EmojiPagerContentComponent.Item] = []
var existingIds = Set<MediaId>()
for itemFile in files {
if existingIds.contains(itemFile.fileId) {
continue
}
existingIds.insert(itemFile.fileId)
let animationData = EntityKeyboardAnimationData(file: itemFile)
let item = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: itemFile, subgroupId: nil,
icon: .none,
tintMode: animationData.isTemplate ? .primary : .none
)
items.append(item)
}
return .single([EmojiPagerContentComponent.ItemGroup(
supergroupId: "search",
groupId: "search",
title: nil,
subtitle: nil,
actionButtonTitle: nil,
isFeatured: false,
isPremiumLocked: false,
isEmbedded: false,
hasClear: false,
collapsedLineCount: nil,
displayPremiumBadges: false,
headerItem: nil,
items: items
)])
}
self.emojiSearchDisposable.set((resultSignal
|> delay(0.15, queue: .mainQueue())
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
self.emojiSearchResult.set(.single((result, AnyHashable(value))))
}))
}
},
updateScrollingToItemGroup: {
},
externalCancel: { [weak self] in
guard let self else {
return
}
self.onCancel?()
},
chatPeerId: nil,
peekBehavior: nil,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
useOpaqueTheme: true,
hideBackground: false
)
self.dataDisposable = (
self.emojiSearchResult.get()
|> deliverOnMainQueue
).start(next: { [weak self] emojiSearchResult in
guard let self else {
return
}
self.emojiSearchResultValue = emojiSearchResult
self.update(transition: .immediate)
})
}
deinit {
self.emojiSearchDisposable.dispose()
self.dataDisposable?.dispose()
}
private func update(transition: Transition) {
if let params = self.params {
self.update(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, bottomInset: params.bottomInset, inputHeight: params.inputHeight, deviceMetrics: params.deviceMetrics, transition: transition)
}
}
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
self.update(size: size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: Transition(transition))
}
private func update(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: Transition) {
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
let params = Params(size: size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics)
self.params = params
//TODO:localize
var emojiContent = EmojiPagerContentComponent(
id: "emoji",
context: self.context,
avatarPeer: nil,
animationCache: self.context.animationCache,
animationRenderer: self.context.animationRenderer,
inputInteractionHolder: self.inputInteractionHolder,
panelItemGroups: [],
contentItemGroups: self.itemGroups,
itemLayoutType: .compact,
itemContentUniqueId: "main",
warpContentsOnEdges: false,
displaySearchWithPlaceholder: "Search Emoji",
searchCategories: nil,
searchInitiallyHidden: false,
searchAlwaysActive: true,
searchIsPlaceholderOnly: false,
emptySearchResults: nil,
enableLongPress: false,
selectedItems: Set()
)
if let emojiSearchResult = self.emojiSearchResultValue {
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
text: self.presentationData.strings.EmojiSearch_SearchEmojiEmptyResult,
iconFile: nil
)
}
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
}
let _ = self.keyboardView.update(
transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
component: AnyComponent(EntityKeyboardComponent(
theme: self.presentationData.theme,
strings: self.presentationData.strings,
isContentInFocus: true,
containerInsets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: bottomInset, right: rightInset),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
emojiContent: emojiContent,
stickerContent: nil,
maskContent: nil,
gifContent: nil,
hasRecentGifs: false,
availableGifSearchEmojies: [],
defaultToEmojiTab: true,
externalTopPanelContainer: self.panelHostView,
externalBottomPanelContainer: nil,
displayTopPanelBackground: true,
topPanelExtensionUpdated: { _, _ in },
hideInputUpdated: { _, _, _ in },
hideTopPanelUpdated: { _, _ in
},
switchToTextInput: {},
switchToGifSubject: { _ in },
reorderItems: { _, _ in },
makeSearchContainerNode: { _ in return nil },
contentIdUpdated: { _ in },
deviceMetrics: deviceMetrics,
hiddenInputHeight: 0.0,
inputHeight: 0.0,
displayBottomPanel: false,
isExpanded: false,
clipContentToTopPanel: false,
hidePanels: true
)),
environment: {},
containerSize: size
)
if let keyboardComponentView = self.keyboardView.view as? EntityKeyboardComponent.View {
if keyboardComponentView.superview == nil {
self.view.addSubview(keyboardComponentView)
}
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(), size: size))
if let initialFocusId = self.initialFocusId {
self.initialFocusId = nil
keyboardComponentView.scrollToItemGroup(contentId: "emoji", groupId: AnyHashable(initialFocusId), subgroupId: nil, animated: false)
}
}
}
}

View File

@ -286,9 +286,28 @@ final class EmojiSearchSearchBarComponent: Component {
if let _ = self.selectedItem, let categories = component.categories, let group = categories.groups.first(where: { $0.id == itemId }) {
component.searchTermUpdated(group.identifiers.joined(separator: ""))
if let itemComponentView = itemView.view.view {
var offset = self.scrollView.contentOffset.x
let maxDistance: CGFloat = 44.0
if itemComponentView.frame.maxX - offset > self.scrollView.bounds.width - maxDistance {
offset = itemComponentView.frame.maxX - (self.scrollView.bounds.width - maxDistance)
}
if itemComponentView.frame.minX - offset < maxDistance {
offset = itemComponentView.frame.minX - maxDistance
}
offset = max(0.0, min(offset, self.scrollView.contentSize.width - self.scrollView.bounds.width))
if offset != self.scrollView.contentOffset.x {
self.scrollView.setContentOffset(CGPoint(x: offset, y: 0.0), animated: true)
}
}
} else {
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: self.scrollView.bounds.size))
self.updateScrolling(transition: transition, fromScrolling: false)
//self.scrollView.setContentOffset(CGPoint(), animated: true)
component.searchTermUpdated(nil)
self.scrollView.setContentOffset(CGPoint(), animated: true)
}
break
@ -301,8 +320,13 @@ final class EmojiSearchSearchBarComponent: Component {
func clearSelection(dispatchEvent: Bool) {
if self.selectedItem != nil {
self.selectedItem = nil
self.state?.updated(transition: .immediate)
self.scrollView.setContentOffset(CGPoint(), animated: true)
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: self.scrollView.bounds.size))
self.updateScrolling(transition: transition, fromScrolling: false)
self.state?.updated(transition: transition)
if dispatchEvent {
self.component?.searchTermUpdated(nil)
}
@ -419,8 +443,18 @@ final class EmojiSearchSearchBarComponent: Component {
for (id, itemView) in self.visibleItemViews {
if !validItemIds.contains(id) {
removedItemIds.append(id)
itemView.view.view?.removeFromSuperview()
itemView.tintView.removeFromSuperview()
if let itemComponentView = itemView.view.view {
transition.attachAnimation(view: itemComponentView, id: "remove", completion: { [weak itemComponentView] _ in
itemComponentView?.removeFromSuperview()
})
}
let tintView = itemView.tintView
transition.attachAnimation(view: tintView, id: "remove", completion: { [weak tintView] _ in
tintView?.removeFromSuperview()
})
//itemView.view.view?.removeFromSuperview()
//itemView.tintView.removeFromSuperview()
}
}
for id in removedItemIds {
@ -523,10 +557,6 @@ final class EmojiSearchSearchBarComponent: Component {
self.isUserInteractionEnabled = false
self.textView.view?.isHidden = hasText
self.tintTextView.view?.isHidden = hasText
/*if self.scrollView.contentOffset.x != 0.0 {
self.scrollView.setContentOffset(CGPoint(), animated: true)
}*/
case .inactive:
self.isUserInteractionEnabled = true
self.textView.view?.isHidden = false

View File

@ -115,6 +115,7 @@ public final class EntityKeyboardComponent: Component {
public let displayBottomPanel: Bool
public let isExpanded: Bool
public let clipContentToTopPanel: Bool
public let hidePanels: Bool
public init(
theme: PresentationTheme,
@ -145,7 +146,8 @@ public final class EntityKeyboardComponent: Component {
inputHeight: CGFloat,
displayBottomPanel: Bool,
isExpanded: Bool,
clipContentToTopPanel: Bool
clipContentToTopPanel: Bool,
hidePanels: Bool = false
) {
self.theme = theme
self.strings = strings
@ -176,6 +178,7 @@ public final class EntityKeyboardComponent: Component {
self.displayBottomPanel = displayBottomPanel
self.isExpanded = isExpanded
self.clipContentToTopPanel = clipContentToTopPanel
self.hidePanels = hidePanels
}
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
@ -701,6 +704,8 @@ public final class EntityKeyboardComponent: Component {
let panelHideBehavior: PagerComponentPanelHideBehavior
if self.searchComponent != nil {
panelHideBehavior = .hide
} else if component.hidePanels {
panelHideBehavior = .disable
} else if component.isExpanded {
panelHideBehavior = .show
} else {
@ -914,6 +919,25 @@ public final class EntityKeyboardComponent: Component {
}
}
public func openCustomSearch(content: EntitySearchContainerNode) {
guard let component = self.component else {
return
}
if self.searchComponent != nil {
return
}
self.searchComponent = EntitySearchContentComponent(
makeContainerNode: {
return content
},
dismissSearch: { [weak self] in
self?.closeSearch()
}
)
component.hideInputUpdated(true, true, Transition(animation: .curve(duration: 0.3, curve: .spring)))
}
private func closeSearch() {
guard let component = self.component else {
return
@ -926,7 +950,7 @@ public final class EntityKeyboardComponent: Component {
component.hideInputUpdated(false, false, Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
private func scrollToItemGroup(contentId: String, groupId: AnyHashable, subgroupId: Int32?) {
public func scrollToItemGroup(contentId: String, groupId: AnyHashable, subgroupId: Int32?, animated: Bool = true) {
guard let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View else {
return
}
@ -938,7 +962,7 @@ public final class EntityKeyboardComponent: Component {
}
self.component?.emojiContent?.inputInteractionHolder.inputInteraction?.updateScrollingToItemGroup()
pagerContentView.scrollToItemGroup(id: groupId, subgroupId: subgroupId)
pagerContentView.scrollToItemGroup(id: groupId, subgroupId: subgroupId, animated: animated)
pagerView.collapseTopPanel()
}

View File

@ -289,7 +289,7 @@ public final class GifPagerContentComponent: Component {
self.itemSize = floor((itemHorizontalSpace - self.horizontalSpacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow))
let numRowsInGroup = (itemCount + (self.itemsPerRow - 1)) / self.itemsPerRow
self.contentSize = CGSize(width: width, height: self.containerInsets.top + self.containerInsets.bottom + CGFloat(numRowsInGroup) * self.itemSize + CGFloat(max(0, numRowsInGroup - 1)) * self.verticalSpacing)
self.contentSize = CGSize(width: width, height: self.searchInsets.top + self.searchHeight + self.containerInsets.top + self.containerInsets.bottom + CGFloat(numRowsInGroup) * self.itemSize + CGFloat(max(0, numRowsInGroup - 1)) * self.verticalSpacing)
}
func frame(at index: Int) -> CGRect {
@ -730,13 +730,18 @@ public final class GifPagerContentComponent: Component {
}
private func updateScrollingOffset(transition: Transition) {
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
let isInteracting = self.scrollView.isDragging || self.scrollView.isDecelerating
if let previousScrollingOffsetValue = self.previousScrollingOffset {
let currentBounds = scrollView.bounds
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
let currentBounds = self.scrollView.bounds
var offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
var offsetToBottomEdge = max(0.0, self.scrollView.contentSize.height - currentBounds.maxY)
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue
if self.scrollView.contentSize.height < self.scrollView.bounds.height * 2.0 {
offsetToTopEdge = 0.0
offsetToBottomEdge = self.scrollView.contentSize.height
}
let relativeOffset = self.scrollView.contentOffset.y - previousScrollingOffsetValue
self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate(
relativeOffset: relativeOffset,
absoluteOffsetToTopEdge: offsetToTopEdge,
@ -745,19 +750,21 @@ public final class GifPagerContentComponent: Component {
isInteracting: isInteracting,
transition: transition
))
self.previousScrollingOffset = scrollView.contentOffset.y
self.previousScrollingOffset = self.scrollView.contentOffset.y
}
self.previousScrollingOffset = scrollView.contentOffset.y
self.previousScrollingOffset = self.scrollView.contentOffset.y
}
private func snappedContentOffset(proposedOffset: CGFloat) -> CGFloat {
guard let pagerEnvironment = self.pagerEnvironment else {
guard let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
return proposedOffset
}
var proposedOffset = proposedOffset
let bounds = self.bounds
if proposedOffset + bounds.height > self.scrollView.contentSize.height - pagerEnvironment.containerInsets.bottom {
if proposedOffset <= itemLayout.searchInsets.top + itemLayout.searchHeight * 0.5 {
proposedOffset = 0.0
} else if proposedOffset + bounds.height > self.scrollView.contentSize.height - pagerEnvironment.containerInsets.bottom {
proposedOffset = self.scrollView.contentSize.height - bounds.height
}
if proposedOffset < pagerEnvironment.containerInsets.top {
@ -775,6 +782,7 @@ public final class GifPagerContentComponent: Component {
transition.setBounds(view: self.scrollView, bounds: currentBounds)
self.updateScrollingOffset(transition: transition)
self.updateVisibleItems(attemptSynchronousLoads: false, transition: transition, fromScrolling: true)
}
private func updateVisibleItems(attemptSynchronousLoads: Bool, transition: Transition, fromScrolling: Bool) {

View File

@ -928,7 +928,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
},
openSearch: {
},
addGroupAction: { groupId, isPremiumLocked in
addGroupAction: { groupId, isPremiumLocked, _ in
guard let collectionId = groupId.base as? ItemCollectionId else {
return
}

View File

@ -8662,27 +8662,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else {
return
}
let subjectFlags: TelegramChatBannedRightsFlags
let subjectFlags: [TelegramChatBannedRightsFlags]
switch subject {
case .stickers:
subjectFlags = .banSendStickers
case .mediaRecording, .premiumVoiceMessages:
subjectFlags = .banSendMedia
case .stickers:
subjectFlags = [.banSendStickers]
case .mediaRecording, .premiumVoiceMessages:
subjectFlags = [.banSendVoice, .banSendInstantVideos]
}
let bannedPermission: (Int32, Bool)?
var bannedPermission: (Int32, Bool)? = nil
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
bannedPermission = channel.hasBannedPermission(subjectFlags)
} else if let group = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
if group.hasBannedPermission(subjectFlags) {
bannedPermission = (Int32.max, false)
} else {
bannedPermission = nil
for subjectFlag in subjectFlags {
if let value = channel.hasBannedPermission(subjectFlag) {
bannedPermission = value
break
}
}
} else if let group = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
for subjectFlag in subjectFlags {
if group.hasBannedPermission(subjectFlag) {
bannedPermission = (Int32.max, false)
break
}
}
} else {
bannedPermission = nil
}
var displayToast = false
if let (untilDate, personal) = bannedPermission {
let banDescription: String
switch subject {
@ -8700,7 +8706,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else if personal {
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedMedia
} else {
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedMedia
banDescription = strongSelf.restrictedSendingContentsText()
displayToast = true
}
case .premiumVoiceMessages:
banDescription = ""
@ -8714,37 +8721,41 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch displayType {
case .tooltip:
var rect: CGRect?
let isStickers: Bool = subject == .stickers
switch subject {
case .stickers:
rect = strongSelf.chatDisplayNode.frameForStickersButton()
if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() {
rectValue.origin.y = actionRect.minY
rect = rectValue
}
case .mediaRecording, .premiumVoiceMessages:
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
}
if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers {
tooltipController.updateContent(.text(banDescription), animated: true, extendTimer: true)
} else if let rect = rect {
strongSelf.mediaRestrictedTooltipController?.dismiss()
let tooltipController = TooltipController(content: .text(banDescription), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize)
strongSelf.mediaRestrictedTooltipController = tooltipController
strongSelf.mediaRestrictedTooltipControllerMode = isStickers
tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
strongSelf.mediaRestrictedTooltipController = nil
if displayToast {
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: banDescription))
} else {
var rect: CGRect?
let isStickers: Bool = subject == .stickers
switch subject {
case .stickers:
rect = strongSelf.chatDisplayNode.frameForStickersButton()
if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() {
rectValue.origin.y = actionRect.minY
rect = rectValue
}
case .mediaRecording, .premiumVoiceMessages:
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
}
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self {
return (strongSelf.chatDisplayNode, rect)
if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers {
tooltipController.updateContent(.text(banDescription), animated: true, extendTimer: true)
} else if let rect = rect {
strongSelf.mediaRestrictedTooltipController?.dismiss()
let tooltipController = TooltipController(content: .text(banDescription), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize)
strongSelf.mediaRestrictedTooltipController = tooltipController
strongSelf.mediaRestrictedTooltipControllerMode = isStickers
tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
strongSelf.mediaRestrictedTooltipController = nil
}
}
return nil
}))
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self {
return (strongSelf.chatDisplayNode, rect)
}
return nil
}))
}
}
case .alert:
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: banDescription, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))

View File

@ -930,7 +930,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
return
}
//TODO:localize
controller.controllerInteraction?.displayUndo(.info(title: nil, text: "The admins of this group do not allow to send text messages."))
controller.controllerInteraction?.displayUndo(.info(title: nil, text: controller.restrictedSendingContentsText()))
} else {
strongSelf.ensureFocused()
}

View File

@ -14,6 +14,12 @@ import AnimationCache
import MultiAnimationRenderer
import TextFormat
import ChatControllerInteraction
import ContextUI
import SwiftSignalKit
import PremiumUI
import StickerPeekUI
import UndoUI
import Pasteboard
private enum EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
case symbol(String)
@ -119,6 +125,8 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
private let animationCache: AnimationCache
private let animationRenderer: MultiAnimationRenderer
private weak var peekController: PeekController?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.animationCache = chatPresentationContext.animationCache
self.animationRenderer = chatPresentationContext.animationRenderer
@ -161,6 +169,281 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
self.addSubnode(self.clippingNode)
self.clippingNode.addSubnode(self.listView)
let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
guard let self else {
return nil
}
return self.peekContentAtPoint(point: point)
}, present: { [weak self] content, sourceView, sourceRect in
guard let strongSelf = self else {
return nil
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
/*controller.visibilityUpdated = { [weak self] visible in
self?.previewingStickersPromise.set(visible)
self?.requestDisableStickerAnimations?(visible)
self?.simulateUpdateLayout(isVisible: !visible)
}*/
strongSelf.peekController = controller
strongSelf.interfaceInteraction?.presentController(controller, nil)
return controller
}, updateContent: { [weak self] content in
guard let strongSelf = self else {
return
}
let _ = strongSelf
})
self.view.addGestureRecognizer(peekRecognizer)
}
private func peekContentAtPoint(point: CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? {
guard let presentationInterfaceState = self.presentationInterfaceState else {
return nil
}
guard let chatPeerId = presentationInterfaceState.renderedPeer?.peer?.id else {
return nil
}
var maybeFile: TelegramMediaFile?
var maybeItemLayer: CALayer?
self.listView.forEachItemNode { itemNode in
if let itemNode = itemNode as? EmojisChatInputPanelItemNode, let item = itemNode.item {
let localPoint = self.view.convert(point, to: itemNode.view)
if itemNode.view.bounds.contains(localPoint) {
maybeFile = item.file
maybeItemLayer = itemNode.layer
}
}
}
guard let file = maybeFile else {
return nil
}
guard let itemLayer = maybeItemLayer else {
return nil
}
let _ = chatPeerId
let _ = file
let _ = itemLayer
var collectionId: ItemCollectionId?
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id)
default:
break
}
}
}
var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
if let collectionId {
bubbleUpEmojiOrStickersets.append(collectionId)
}
let context = self.context
let accountPeerId = context.account.peerId
let _ = bubbleUpEmojiOrStickersets
let _ = context
let _ = accountPeerId
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId))
|> map { peer -> Bool in
var hasPremium = false
if case let .user(user) = peer, user.isPremium {
hasPremium = true
}
return hasPremium
}
|> deliverOnMainQueue
|> map { [weak self, weak itemLayer] hasPremium -> (UIView, CGRect, PeekControllerContent)? in
guard let strongSelf = self, let itemLayer = itemLayer else {
return nil
}
let _ = strongSelf
let _ = itemLayer
var menuItems: [ContextMenuItem] = []
menuItems.removeAll()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = presentationData
var isLocked = false
if !hasPremium {
isLocked = file.isPremiumEmoji
if isLocked && chatPeerId == context.account.peerId {
isLocked = false
}
}
if let interaction = strongSelf.interfaceInteraction {
let _ = interaction
let sendEmoji: (TelegramMediaFile) -> Void = { file in
guard let self else {
return
}
guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else {
return
}
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
switch attribute {
case let .CustomEmoji(_, _, displayText, stickerPackReference):
text = displayText
var packId: ItemCollectionId?
if case let .id(id, _) = stickerPackReference {
packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id)
}
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: packId, fileId: file.fileId.id, file: file)
break loop
default:
break
}
}
if let emojiAttribute {
controller.controllerInteraction?.sendEmoji(text, emojiAttribute, true)
}
}
let setStatus: (TelegramMediaFile) -> Void = { file in
guard let self else {
return
}
guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else {
return
}
let _ = self.context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).start()
var animateInAsReplacement = false
animateInAsReplacement = false
/*if let currentUndoOverlayController = strongSelf.currentUndoOverlayController {
currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation()
strongSelf.currentUndoOverlayController = nil
animateInAsReplacement = true
}*/
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: self.context, file: file, title: nil, text: "Your emoji status has been updated.", undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false })
//strongSelf.currentUndoOverlayController = controller
controller.controllerInteraction?.presentController(undoController, nil)
}
let copyEmoji: (TelegramMediaFile) -> Void = { file in
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
switch attribute {
case let .CustomEmoji(_, _, displayText, _):
text = displayText
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file)
break loop
default:
break
}
}
if let _ = emojiAttribute {
storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))])
}
}
//TODO:localize
menuItems.append(.action(ContextMenuActionItem(text: "Send Emoji", icon: { theme in
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) {
return generateImage(image.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size))
}
})
} else {
return nil
}
}, action: { _, f in
sendEmoji(file)
f(.default)
})))
//TODO:localize
menuItems.append(.action(ContextMenuActionItem(text: "Set as Status", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Smile"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
guard let strongSelf = self else {
return
}
if hasPremium {
setStatus(file)
} else {
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)
}
strongSelf.interfaceInteraction?.getNavigationController()?.pushViewController(controller)
}
})))
//TODO:localize
menuItems.append(.action(ContextMenuActionItem(text: "Copy Emoji", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
copyEmoji(file)
f(.default)
})))
}
if menuItems.isEmpty {
return nil
}
let content = StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in
guard let self else {
return
}
guard let interfaceInteraction = self.interfaceInteraction else {
return
}
let _ = self
let _ = interfaceInteraction
let controller = PremiumIntroScreen(context: context, source: .stickers)
//let _ = controller
interfaceInteraction.getNavigationController()?.pushViewController(controller)
})
let _ = content
//return nil
return (strongSelf.view, itemLayer.convert(itemLayer.bounds, to: strongSelf.view.layer), content)
}
}
func updateResults(_ results: [(String, TelegramMediaFile?, String)]) {

View File

@ -17,7 +17,7 @@ final class EmojisChatInputPanelItem: ListViewItem {
fileprivate let theme: PresentationTheme
fileprivate let symbol: String
fileprivate let text: String
fileprivate let file: TelegramMediaFile?
let file: TelegramMediaFile?
fileprivate let animationCache: AnimationCache
fileprivate let animationRenderer: MultiAnimationRenderer
private let emojiSelected: (String, TelegramMediaFile?) -> Void
@ -94,6 +94,8 @@ final class EmojisChatInputPanelItemNode: ListViewItemNode {
private let symbolNode: TextNode
private var emojiView: EmojiTextAttachmentView?
var item: EmojisChatInputPanelItem?
init() {
self.symbolNode = TextNode()
self.symbolNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
@ -123,6 +125,8 @@ final class EmojisChatInputPanelItemNode: ListViewItemNode {
return (nodeLayout, { _ in
if let strongSelf = self {
strongSelf.item = item
let _ = symbolApply()
strongSelf.symbolNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((EmojisChatInputPanelItemNode.itemSize.width - symbolLayout.size.width) / 2.0), y: 0.0), size: symbolLayout.size)