mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 17:30:12 +00:00
UI improvements
This commit is contained in:
parent
55d076a494
commit
3b42f14613
@ -150,7 +150,7 @@ public struct Transition {
|
|||||||
//view.center = CGPoint(x: frame.midX, y: frame.midY)
|
//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.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -149,6 +149,7 @@ public enum PagerComponentPanelHideBehavior {
|
|||||||
case hideOnScroll
|
case hideOnScroll
|
||||||
case show
|
case show
|
||||||
case hide
|
case hide
|
||||||
|
case disable
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class PagerComponentContentIcon: Equatable {
|
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
|
var topPanelHeight: CGFloat = 0.0
|
||||||
if let topPanel = component.topPanel {
|
if let topPanel = component.topPanel {
|
||||||
let effectiveTopPanelOffsetFraction = scrollingPanelOffsetFraction
|
let effectiveTopPanelOffsetFraction = scrollingPanelOffsetFraction
|
||||||
@ -616,7 +621,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
|
|
||||||
var topPanelVisibilityFraction: CGFloat = 1.0 - effectiveTopPanelOffsetFraction
|
var topPanelVisibilityFraction: CGFloat = 1.0 - effectiveTopPanelOffsetFraction
|
||||||
switch component.panelHideBehavior {
|
switch component.panelHideBehavior {
|
||||||
case .hide:
|
case .hide, .disable:
|
||||||
topPanelVisibilityFraction = 0.0
|
topPanelVisibilityFraction = 0.0
|
||||||
case .show:
|
case .show:
|
||||||
topPanelVisibilityFraction = 1.0
|
topPanelVisibilityFraction = 1.0
|
||||||
@ -631,12 +636,16 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
|
|
||||||
if case .hide = component.panelHideBehavior {
|
if case .hide = component.panelHideBehavior {
|
||||||
topPanelOffset = topPanelSize.height
|
topPanelOffset = topPanelSize.height
|
||||||
|
} else if case .disable = component.panelHideBehavior {
|
||||||
|
topPanelOffset = topPanelSize.height
|
||||||
}
|
}
|
||||||
|
|
||||||
if component.externalTopPanelContainer != nil {
|
if component.externalTopPanelContainer != nil {
|
||||||
var visibleTopPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
|
var visibleTopPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
|
||||||
if case .hide = component.panelHideBehavior {
|
if case .hide = component.panelHideBehavior {
|
||||||
visibleTopPanelHeight = 0.0
|
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)))
|
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
|
bottomPanelOffset = bottomPanelSize.height * scrollingPanelOffsetFraction
|
||||||
if case .hide = component.panelHideBehavior {
|
if case .hide = component.panelHideBehavior {
|
||||||
bottomPanelOffset = bottomPanelSize.height
|
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))
|
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
|
let effectiveTopPanelHeight: CGFloat
|
||||||
switch component.panelHideBehavior {
|
switch component.panelHideBehavior {
|
||||||
case .hide:
|
case .hide, .disable:
|
||||||
effectiveTopPanelHeight = 0.0
|
effectiveTopPanelHeight = 0.0
|
||||||
case .show, .hideOnScroll:
|
case .show, .hideOnScroll:
|
||||||
if component.externalTopPanelContainer != nil {
|
if component.externalTopPanelContainer != nil {
|
||||||
|
|||||||
@ -276,7 +276,7 @@ class StickerPickerScreen: ViewController {
|
|||||||
openFeatured: nil,
|
openFeatured: nil,
|
||||||
openSearch: {
|
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 {
|
guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -391,7 +391,7 @@ class StickerPickerScreen: ViewController {
|
|||||||
openStickerSettings: nil,
|
openStickerSettings: nil,
|
||||||
openFeatured: nil,
|
openFeatured: nil,
|
||||||
openSearch: {},
|
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 {
|
guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -481,7 +481,7 @@ class StickerPickerScreen: ViewController {
|
|||||||
openFeatured: nil,
|
openFeatured: nil,
|
||||||
openSearch: {
|
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 {
|
guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -783,6 +783,8 @@
|
|||||||
authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes];
|
authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes];
|
||||||
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector];
|
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
restartRequest = true;
|
||||||
} else if (rpcError.errorCode == 406) {
|
} else if (rpcError.errorCode == 406) {
|
||||||
if (_didReceiveSoftAuthResetError) {
|
if (_didReceiveSoftAuthResetError) {
|
||||||
_didReceiveSoftAuthResetError();
|
_didReceiveSoftAuthResetError();
|
||||||
|
|||||||
@ -1264,7 +1264,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
},
|
},
|
||||||
openSearch: {
|
openSearch: {
|
||||||
},
|
},
|
||||||
addGroupAction: { [weak self] groupId, isPremiumLocked in
|
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
|
||||||
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
|
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1284,7 +1284,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
|
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
|
||||||
if featuredEmojiPack.info.id == collectionId {
|
if featuredEmojiPack.info.id == collectionId {
|
||||||
if let strongSelf = self {
|
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()
|
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
|
||||||
|
|
||||||
|
|||||||
@ -481,7 +481,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
openFeatured: nil,
|
openFeatured: nil,
|
||||||
openSearch: {
|
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 {
|
guard let strongSelf = self, let controller = strongSelf.controller?(), let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -615,7 +615,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
openFeatured: nil,
|
openFeatured: nil,
|
||||||
openSearch: {
|
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 {
|
guard let strongSelf = self, let controller = strongSelf.controller?(), let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -641,25 +641,61 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
var premiumToastCounter = 0
|
var premiumToastCounter = 0
|
||||||
self.emojiInputInteraction = EmojiPagerContentComponent.InputInteraction(
|
self.emojiInputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||||
performItemAction: { [weak self, weak interfaceInteraction, weak controllerInteraction] groupId, item, _, _, _, _ in
|
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 {
|
guard let strongSelf = self, let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupId == AnyHashable("featuredTop"), let file = item.itemFile {
|
if groupId == AnyHashable("featuredTop"), let file = item.itemFile {
|
||||||
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
|
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)
|
|> 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 {
|
guard let controllerInteraction = controllerInteraction else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let view = views.views[viewKey] as? OrderedItemListView else {
|
guard let view = views.views[viewKey] as? OrderedItemListView else {
|
||||||
return
|
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 }) {
|
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,
|
context: context,
|
||||||
updatedPresentationData: controllerInteraction.updatedPresentationData,
|
updatedPresentationData: controllerInteraction.updatedPresentationData,
|
||||||
mode: .default,
|
mode: .default,
|
||||||
@ -675,7 +711,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
interfaceInteraction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
|
interfaceInteraction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
controllerInteraction.presentController(controller, nil)
|
controllerInteraction.presentController(controller, nil)*/
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -786,7 +822,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
},
|
},
|
||||||
openSearch: {
|
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 {
|
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -815,7 +851,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
|
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
|
||||||
if featuredEmojiPack.info.id == collectionId {
|
if featuredEmojiPack.info.id == collectionId {
|
||||||
if let strongSelf = self {
|
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()
|
let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
|
||||||
|
|
||||||
@ -1181,7 +1217,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
pagerView.openSearch()
|
pagerView.openSearch()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addGroupAction: { groupId, isPremiumLocked in
|
addGroupAction: { groupId, isPremiumLocked, _ in
|
||||||
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
|
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1479,7 +1515,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
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
|
loadMore: { [weak self] token in
|
||||||
guard let strongSelf = self, let gifContext = strongSelf.gifContext else {
|
guard let strongSelf = self, let gifContext = strongSelf.gifContext else {
|
||||||
@ -1697,7 +1733,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
strongSelf.reorderItems(category: category, items: items)
|
strongSelf.reorderItems(category: category, items: items)
|
||||||
},
|
},
|
||||||
makeSearchContainerNode: { [weak controllerInteraction] content in
|
makeSearchContainerNode: { [weak self, weak controllerInteraction] content in
|
||||||
guard let controllerInteraction = controllerInteraction else {
|
guard let controllerInteraction = controllerInteraction else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1711,7 +1747,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
return PaneSearchContainerNode(
|
let searchContainerNode = PaneSearchContainerNode(
|
||||||
context: context,
|
context: context,
|
||||||
theme: presentationData.theme,
|
theme: presentationData.theme,
|
||||||
strings: presentationData.strings,
|
strings: presentationData.strings,
|
||||||
@ -1722,6 +1758,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
cancel: {
|
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 },
|
contentIdUpdated: { _ in },
|
||||||
deviceMetrics: deviceMetrics,
|
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) {
|
private func openGifContextMenu(file: FileMediaReference, contextResult: (ChatContextResultCollection, ChatContextResult)?, sourceView: UIView, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) {
|
||||||
let file = item
|
|
||||||
|
|
||||||
let canSaveGif: Bool
|
let canSaveGif: Bool
|
||||||
if file.file.media.fileId.namespace == Namespaces.Media.CloudFile {
|
if file.media.fileId.namespace == Namespaces.Media.CloudFile {
|
||||||
canSaveGif = true
|
canSaveGif = true
|
||||||
} else {
|
} else {
|
||||||
canSaveGif = false
|
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
|
|> deliverOnMainQueue).start(next: { [weak self] isGifSaved in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -1875,7 +1917,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
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
|
let gallery = GalleryController(context: strongSelf.context, source: .standaloneMessage(message), streamSingleVideo: true, replaceRootController: { _, _ in
|
||||||
}, baseNavigationController: nil)
|
}, baseNavigationController: nil)
|
||||||
@ -1887,8 +1929,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
if isSaved {
|
if isSaved {
|
||||||
let _ = self?.controllerInteraction?.sendGif(file.file, sourceView, sourceRect, false, false)
|
let _ = self?.controllerInteraction?.sendGif(file, sourceView, sourceRect, false, false)
|
||||||
} else if let (collection, result) = file.contextResult {
|
} else if let (collection, result) = contextResult {
|
||||||
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, false)
|
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, false)
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
@ -1908,8 +1950,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
if isSaved {
|
if isSaved {
|
||||||
let _ = self?.controllerInteraction?.sendGif(file.file, sourceView, sourceRect, true, false)
|
let _ = self?.controllerInteraction?.sendGif(file, sourceView, sourceRect, true, false)
|
||||||
} else if let (collection, result) = file.contextResult {
|
} else if let (collection, result) = contextResult {
|
||||||
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, true)
|
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, true)
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
@ -1921,7 +1963,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.default)
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
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 {
|
} else if canSaveGif && !isGifSaved {
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Preview_SaveGif, icon: { theme in
|
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 context = strongSelf.context
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
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
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -2136,7 +2178,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi
|
|||||||
},
|
},
|
||||||
openSearch: {
|
openSearch: {
|
||||||
},
|
},
|
||||||
addGroupAction: { _, _ in
|
addGroupAction: { _, _, _ in
|
||||||
},
|
},
|
||||||
clearGroup: { [weak self] groupId in
|
clearGroup: { [weak self] groupId in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
|||||||
@ -395,7 +395,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
},
|
},
|
||||||
openSearch: {
|
openSearch: {
|
||||||
},
|
},
|
||||||
addGroupAction: { groupId, isPremiumLocked in
|
addGroupAction: { groupId, isPremiumLocked, _ in
|
||||||
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
|
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -410,7 +410,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
|
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
|
||||||
if featuredEmojiPack.info.id == collectionId {
|
if featuredEmojiPack.info.id == collectionId {
|
||||||
if let strongSelf = self {
|
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()
|
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
|
||||||
|
|
||||||
|
|||||||
@ -1892,7 +1892,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
component: AnyComponent(Text(
|
component: AnyComponent(Text(
|
||||||
text: strings.Common_Cancel,
|
text: strings.Common_Cancel,
|
||||||
font: Font.regular(17.0),
|
font: Font.regular(17.0),
|
||||||
color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
color: useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
|
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
|
||||||
@ -2178,7 +2178,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
public enum AnimationType {
|
public enum AnimationType {
|
||||||
case generic
|
case generic
|
||||||
case groupExpanded(id: AnyHashable)
|
case groupExpanded(id: AnyHashable)
|
||||||
case groupInstalled(id: AnyHashable)
|
case groupInstalled(id: AnyHashable, scrollToGroup: Bool)
|
||||||
case groupRemoved(id: AnyHashable)
|
case groupRemoved(id: AnyHashable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2239,7 +2239,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
public let openStickerSettings: (() -> Void)?
|
public let openStickerSettings: (() -> Void)?
|
||||||
public let openFeatured: (() -> Void)?
|
public let openFeatured: (() -> Void)?
|
||||||
public let openSearch: () -> 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 clearGroup: (AnyHashable) -> Void
|
||||||
public let pushController: (ViewController) -> Void
|
public let pushController: (ViewController) -> Void
|
||||||
public let presentController: (ViewController) -> Void
|
public let presentController: (ViewController) -> Void
|
||||||
@ -2248,6 +2248,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
public let requestUpdate: (Transition) -> Void
|
public let requestUpdate: (Transition) -> Void
|
||||||
public let updateSearchQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void
|
public let updateSearchQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void
|
||||||
public let updateScrollingToItemGroup: () -> Void
|
public let updateScrollingToItemGroup: () -> Void
|
||||||
|
public let externalCancel: (() -> Void)?
|
||||||
public let chatPeerId: PeerId?
|
public let chatPeerId: PeerId?
|
||||||
public let peekBehavior: EmojiContentPeekBehavior?
|
public let peekBehavior: EmojiContentPeekBehavior?
|
||||||
public let customLayout: CustomLayout?
|
public let customLayout: CustomLayout?
|
||||||
@ -2262,7 +2263,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
openStickerSettings: (() -> Void)?,
|
openStickerSettings: (() -> Void)?,
|
||||||
openFeatured: (() -> Void)?,
|
openFeatured: (() -> Void)?,
|
||||||
openSearch: @escaping () -> Void,
|
openSearch: @escaping () -> Void,
|
||||||
addGroupAction: @escaping (AnyHashable, Bool) -> Void,
|
addGroupAction: @escaping (AnyHashable, Bool, Bool) -> Void,
|
||||||
clearGroup: @escaping (AnyHashable) -> Void,
|
clearGroup: @escaping (AnyHashable) -> Void,
|
||||||
pushController: @escaping (ViewController) -> Void,
|
pushController: @escaping (ViewController) -> Void,
|
||||||
presentController: @escaping (ViewController) -> Void,
|
presentController: @escaping (ViewController) -> Void,
|
||||||
@ -2271,6 +2272,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
requestUpdate: @escaping (Transition) -> Void,
|
requestUpdate: @escaping (Transition) -> Void,
|
||||||
updateSearchQuery: @escaping (SearchQuery?) -> Void,
|
updateSearchQuery: @escaping (SearchQuery?) -> Void,
|
||||||
updateScrollingToItemGroup: @escaping () -> Void,
|
updateScrollingToItemGroup: @escaping () -> Void,
|
||||||
|
externalCancel: (() -> Void)? = nil,
|
||||||
chatPeerId: PeerId?,
|
chatPeerId: PeerId?,
|
||||||
peekBehavior: EmojiContentPeekBehavior?,
|
peekBehavior: EmojiContentPeekBehavior?,
|
||||||
customLayout: CustomLayout?,
|
customLayout: CustomLayout?,
|
||||||
@ -2293,6 +2295,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.requestUpdate = requestUpdate
|
self.requestUpdate = requestUpdate
|
||||||
self.updateSearchQuery = updateSearchQuery
|
self.updateSearchQuery = updateSearchQuery
|
||||||
self.updateScrollingToItemGroup = updateScrollingToItemGroup
|
self.updateScrollingToItemGroup = updateScrollingToItemGroup
|
||||||
|
self.externalCancel = externalCancel
|
||||||
self.chatPeerId = chatPeerId
|
self.chatPeerId = chatPeerId
|
||||||
self.peekBehavior = peekBehavior
|
self.peekBehavior = peekBehavior
|
||||||
self.customLayout = customLayout
|
self.customLayout = customLayout
|
||||||
@ -2541,6 +2544,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
public let displaySearchWithPlaceholder: String?
|
public let displaySearchWithPlaceholder: String?
|
||||||
public let searchCategories: EmojiSearchCategories?
|
public let searchCategories: EmojiSearchCategories?
|
||||||
public let searchInitiallyHidden: Bool
|
public let searchInitiallyHidden: Bool
|
||||||
|
public let searchAlwaysActive: Bool
|
||||||
public let searchIsPlaceholderOnly: Bool
|
public let searchIsPlaceholderOnly: Bool
|
||||||
public let emptySearchResults: EmptySearchResults?
|
public let emptySearchResults: EmptySearchResults?
|
||||||
public let enableLongPress: Bool
|
public let enableLongPress: Bool
|
||||||
@ -2561,6 +2565,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
displaySearchWithPlaceholder: String?,
|
displaySearchWithPlaceholder: String?,
|
||||||
searchCategories: EmojiSearchCategories?,
|
searchCategories: EmojiSearchCategories?,
|
||||||
searchInitiallyHidden: Bool,
|
searchInitiallyHidden: Bool,
|
||||||
|
searchAlwaysActive: Bool,
|
||||||
searchIsPlaceholderOnly: Bool,
|
searchIsPlaceholderOnly: Bool,
|
||||||
emptySearchResults: EmptySearchResults?,
|
emptySearchResults: EmptySearchResults?,
|
||||||
enableLongPress: Bool,
|
enableLongPress: Bool,
|
||||||
@ -2580,6 +2585,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
|
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
|
||||||
self.searchCategories = searchCategories
|
self.searchCategories = searchCategories
|
||||||
self.searchInitiallyHidden = searchInitiallyHidden
|
self.searchInitiallyHidden = searchInitiallyHidden
|
||||||
|
self.searchAlwaysActive = searchAlwaysActive
|
||||||
self.searchIsPlaceholderOnly = searchIsPlaceholderOnly
|
self.searchIsPlaceholderOnly = searchIsPlaceholderOnly
|
||||||
self.emptySearchResults = emptySearchResults
|
self.emptySearchResults = emptySearchResults
|
||||||
self.enableLongPress = enableLongPress
|
self.enableLongPress = enableLongPress
|
||||||
@ -2602,6 +2608,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
|
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
|
||||||
searchCategories: self.searchCategories,
|
searchCategories: self.searchCategories,
|
||||||
searchInitiallyHidden: self.searchInitiallyHidden,
|
searchInitiallyHidden: self.searchInitiallyHidden,
|
||||||
|
searchAlwaysActive: self.searchAlwaysActive,
|
||||||
searchIsPlaceholderOnly: self.searchIsPlaceholderOnly,
|
searchIsPlaceholderOnly: self.searchIsPlaceholderOnly,
|
||||||
emptySearchResults: emptySearchResults,
|
emptySearchResults: emptySearchResults,
|
||||||
enableLongPress: self.enableLongPress,
|
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 {
|
guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -4332,6 +4339,9 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var scrollPosition = anchorFrame.minY + floor(-itemLayout.verticalGroupDefaultSpacing / 2.0) - pagerEnvironment.containerInsets.top
|
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 {
|
if scrollPosition > self.scrollView.contentSize.height - self.scrollView.bounds.height {
|
||||||
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
|
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
|
let offsetDirectionSign: Double = scrollPosition < self.scrollView.bounds.minY ? -1.0 : 1.0
|
||||||
|
|
||||||
var previousVisibleLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:]
|
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
|
let timingFunction = kCAMediaTimingFunctionSpring
|
||||||
|
|
||||||
if let commonItemOffset = commonItemOffset {
|
if let commonItemOffset = commonItemOffset {
|
||||||
@ -4779,41 +4809,43 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
let locationInScrollView = recognizer.location(in: self.scrollView)
|
if self.scrollViewClippingView.bounds.contains(recognizer.location(in: self.scrollViewClippingView)) {
|
||||||
outer: for (id, groupHeader) in self.visibleGroupHeaders {
|
let locationInScrollView = recognizer.location(in: self.scrollView)
|
||||||
if groupHeader.frame.insetBy(dx: -10.0, dy: -6.0).contains(locationInScrollView) {
|
outer: for (id, groupHeader) in self.visibleGroupHeaders {
|
||||||
let groupHeaderPoint = self.scrollView.convert(locationInScrollView, to: groupHeader)
|
if groupHeader.frame.insetBy(dx: -10.0, dy: -6.0).contains(locationInScrollView) {
|
||||||
if let clearIconLayer = groupHeader.clearIconLayer, clearIconLayer.frame.insetBy(dx: -4.0, dy: -4.0).contains(groupHeaderPoint) {
|
let groupHeaderPoint = self.scrollView.convert(locationInScrollView, to: groupHeader)
|
||||||
component.inputInteractionHolder.inputInteraction?.clearGroup(id)
|
if let clearIconLayer = groupHeader.clearIconLayer, clearIconLayer.frame.insetBy(dx: -4.0, dy: -4.0).contains(groupHeaderPoint) {
|
||||||
return
|
component.inputInteractionHolder.inputInteraction?.clearGroup(id)
|
||||||
} else {
|
|
||||||
if groupHeader.tapGesture(point: recognizer.location(in: groupHeader)) {
|
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
if groupHeader.tapGesture(point: recognizer.location(in: groupHeader)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
var foundItem = false
|
||||||
var foundItem = false
|
var foundExactItem = false
|
||||||
var foundExactItem = false
|
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self)), let itemLayer = self.visibleItemLayers[itemKey] {
|
||||||
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self)), let itemLayer = self.visibleItemLayers[itemKey] {
|
foundExactItem = true
|
||||||
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
|
foundItem = true
|
||||||
if !itemLayer.displayPlaceholder {
|
if !itemLayer.displayPlaceholder {
|
||||||
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, false)
|
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()
|
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
|
scrollView.isScrollEnabled = false
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
scrollView.isScrollEnabled = true
|
scrollView.isScrollEnabled = true
|
||||||
@ -5118,8 +5150,10 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
var transitionHintExpandedGroupId: AnyHashable?
|
var transitionHintExpandedGroupId: AnyHashable?
|
||||||
if let contentAnimation = contentAnimation {
|
if let contentAnimation = contentAnimation {
|
||||||
switch contentAnimation.type {
|
switch contentAnimation.type {
|
||||||
case let .groupInstalled(groupId):
|
case let .groupInstalled(groupId, scrollToGroup):
|
||||||
transitionHintInstalledGroupId = groupId
|
if scrollToGroup {
|
||||||
|
transitionHintInstalledGroupId = groupId
|
||||||
|
}
|
||||||
case let .groupExpanded(groupId):
|
case let .groupExpanded(groupId):
|
||||||
transitionHintExpandedGroupId = groupId
|
transitionHintExpandedGroupId = groupId
|
||||||
case let .groupRemoved(groupId):
|
case let .groupRemoved(groupId):
|
||||||
@ -5155,7 +5189,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
guard let strongSelf = self, let component = strongSelf.component else {
|
guard let strongSelf = self, let component = strongSelf.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, false)
|
component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, false, true)
|
||||||
},
|
},
|
||||||
performItemAction: { [weak self] item, view, rect, layer in
|
performItemAction: { [weak self] item, view, rect, layer in
|
||||||
guard let strongSelf = self, let component = strongSelf.component else {
|
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 {
|
guard let strongSelf = self, let component = strongSelf.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, isPremiumLocked)
|
component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, isPremiumLocked, true)
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -5967,6 +6001,10 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
if component.searchAlwaysActive {
|
||||||
|
self.isSearchActivated = true
|
||||||
|
}
|
||||||
|
|
||||||
component.inputInteractionHolder.inputInteraction?.peekBehavior?.setGestureRecognizerEnabled(view: self, isEnabled: true, itemAtPoint: { [weak self] point in
|
component.inputInteractionHolder.inputInteraction?.peekBehavior?.setGestureRecognizerEnabled(view: self, isEnabled: true, itemAtPoint: { [weak self] point in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return nil
|
return nil
|
||||||
@ -6019,10 +6057,15 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
var transitionHintInstalledGroupId: AnyHashable?
|
var transitionHintInstalledGroupId: AnyHashable?
|
||||||
var transitionHintExpandedGroupId: AnyHashable?
|
var transitionHintExpandedGroupId: AnyHashable?
|
||||||
|
var keepOffset = false
|
||||||
if let contentAnimation = contentAnimation {
|
if let contentAnimation = contentAnimation {
|
||||||
switch contentAnimation.type {
|
switch contentAnimation.type {
|
||||||
case let .groupInstalled(groupId):
|
case let .groupInstalled(groupId, scrollToGroup):
|
||||||
transitionHintInstalledGroupId = groupId
|
if scrollToGroup {
|
||||||
|
transitionHintInstalledGroupId = groupId
|
||||||
|
} else {
|
||||||
|
keepOffset = true
|
||||||
|
}
|
||||||
case let .groupExpanded(groupId):
|
case let .groupExpanded(groupId):
|
||||||
transitionHintExpandedGroupId = groupId
|
transitionHintExpandedGroupId = groupId
|
||||||
case let .groupRemoved(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)
|
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: scrollView.isDragging || scrollView.isDecelerating)
|
||||||
|
|
||||||
var animatedScrollOffset: CGFloat = 0.0
|
var animatedScrollOffset: CGFloat = 0.0
|
||||||
if !anchorItems.isEmpty {
|
if !anchorItems.isEmpty && !keepOffset {
|
||||||
let sortedAnchorItems: [(ItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in
|
let sortedAnchorItems: [(ItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in
|
||||||
if lhs.value.minY != rhs.value.minY {
|
if lhs.value.minY != rhs.value.minY {
|
||||||
return 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)
|
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
||||||
}
|
}
|
||||||
}, deactivated: { [weak self] isFirstResponder in
|
}, deactivated: { [weak self] isFirstResponder in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self, let component = strongSelf.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.scrollToTop()
|
if let externalCancel = component.inputInteractionHolder.inputInteraction?.externalCancel {
|
||||||
|
externalCancel()
|
||||||
strongSelf.isSearchActivated = false
|
|
||||||
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false)
|
|
||||||
if strongSelf.component?.searchInitiallyHidden == false {
|
|
||||||
if !isFirstResponder {
|
|
||||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.easeInOut(duration: 0.2))
|
|
||||||
}
|
|
||||||
} else {
|
} 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
|
}, updateQuery: { [weak self] query in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -7490,6 +7537,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
|
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
|
||||||
searchCategories: searchCategories,
|
searchCategories: searchCategories,
|
||||||
searchInitiallyHidden: searchInitiallyHidden,
|
searchInitiallyHidden: searchInitiallyHidden,
|
||||||
|
searchAlwaysActive: false,
|
||||||
searchIsPlaceholderOnly: false,
|
searchIsPlaceholderOnly: false,
|
||||||
emptySearchResults: nil,
|
emptySearchResults: nil,
|
||||||
enableLongPress: (isReactionSelection && !isQuickReactionSelection) || isStatusSelection,
|
enableLongPress: (isReactionSelection && !isQuickReactionSelection) || isStatusSelection,
|
||||||
@ -8010,6 +8058,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
|
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
|
||||||
searchCategories: searchCategories,
|
searchCategories: searchCategories,
|
||||||
searchInitiallyHidden: true,
|
searchInitiallyHidden: true,
|
||||||
|
searchAlwaysActive: false,
|
||||||
searchIsPlaceholderOnly: searchIsPlaceholderOnly,
|
searchIsPlaceholderOnly: searchIsPlaceholderOnly,
|
||||||
emptySearchResults: nil,
|
emptySearchResults: nil,
|
||||||
enableLongPress: false,
|
enableLongPress: false,
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 }) {
|
if let _ = self.selectedItem, let categories = component.categories, let group = categories.groups.first(where: { $0.id == itemId }) {
|
||||||
component.searchTermUpdated(group.identifiers.joined(separator: ""))
|
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 {
|
} 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)
|
component.searchTermUpdated(nil)
|
||||||
self.scrollView.setContentOffset(CGPoint(), animated: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -301,8 +320,13 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
func clearSelection(dispatchEvent: Bool) {
|
func clearSelection(dispatchEvent: Bool) {
|
||||||
if self.selectedItem != nil {
|
if self.selectedItem != nil {
|
||||||
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 {
|
if dispatchEvent {
|
||||||
self.component?.searchTermUpdated(nil)
|
self.component?.searchTermUpdated(nil)
|
||||||
}
|
}
|
||||||
@ -419,8 +443,18 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
for (id, itemView) in self.visibleItemViews {
|
for (id, itemView) in self.visibleItemViews {
|
||||||
if !validItemIds.contains(id) {
|
if !validItemIds.contains(id) {
|
||||||
removedItemIds.append(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 {
|
for id in removedItemIds {
|
||||||
@ -523,10 +557,6 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
self.textView.view?.isHidden = hasText
|
self.textView.view?.isHidden = hasText
|
||||||
self.tintTextView.view?.isHidden = hasText
|
self.tintTextView.view?.isHidden = hasText
|
||||||
|
|
||||||
/*if self.scrollView.contentOffset.x != 0.0 {
|
|
||||||
self.scrollView.setContentOffset(CGPoint(), animated: true)
|
|
||||||
}*/
|
|
||||||
case .inactive:
|
case .inactive:
|
||||||
self.isUserInteractionEnabled = true
|
self.isUserInteractionEnabled = true
|
||||||
self.textView.view?.isHidden = false
|
self.textView.view?.isHidden = false
|
||||||
|
|||||||
@ -115,6 +115,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
public let displayBottomPanel: Bool
|
public let displayBottomPanel: Bool
|
||||||
public let isExpanded: Bool
|
public let isExpanded: Bool
|
||||||
public let clipContentToTopPanel: Bool
|
public let clipContentToTopPanel: Bool
|
||||||
|
public let hidePanels: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
@ -145,7 +146,8 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
inputHeight: CGFloat,
|
inputHeight: CGFloat,
|
||||||
displayBottomPanel: Bool,
|
displayBottomPanel: Bool,
|
||||||
isExpanded: Bool,
|
isExpanded: Bool,
|
||||||
clipContentToTopPanel: Bool
|
clipContentToTopPanel: Bool,
|
||||||
|
hidePanels: Bool = false
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
@ -176,6 +178,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
self.displayBottomPanel = displayBottomPanel
|
self.displayBottomPanel = displayBottomPanel
|
||||||
self.isExpanded = isExpanded
|
self.isExpanded = isExpanded
|
||||||
self.clipContentToTopPanel = clipContentToTopPanel
|
self.clipContentToTopPanel = clipContentToTopPanel
|
||||||
|
self.hidePanels = hidePanels
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
||||||
@ -701,6 +704,8 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
let panelHideBehavior: PagerComponentPanelHideBehavior
|
let panelHideBehavior: PagerComponentPanelHideBehavior
|
||||||
if self.searchComponent != nil {
|
if self.searchComponent != nil {
|
||||||
panelHideBehavior = .hide
|
panelHideBehavior = .hide
|
||||||
|
} else if component.hidePanels {
|
||||||
|
panelHideBehavior = .disable
|
||||||
} else if component.isExpanded {
|
} else if component.isExpanded {
|
||||||
panelHideBehavior = .show
|
panelHideBehavior = .show
|
||||||
} else {
|
} 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() {
|
private func closeSearch() {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
@ -926,7 +950,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
component.hideInputUpdated(false, false, Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
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 {
|
guard let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -938,7 +962,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
}
|
}
|
||||||
self.component?.emojiContent?.inputInteractionHolder.inputInteraction?.updateScrollingToItemGroup()
|
self.component?.emojiContent?.inputInteractionHolder.inputInteraction?.updateScrollingToItemGroup()
|
||||||
|
|
||||||
pagerContentView.scrollToItemGroup(id: groupId, subgroupId: subgroupId)
|
pagerContentView.scrollToItemGroup(id: groupId, subgroupId: subgroupId, animated: animated)
|
||||||
pagerView.collapseTopPanel()
|
pagerView.collapseTopPanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -289,7 +289,7 @@ public final class GifPagerContentComponent: Component {
|
|||||||
self.itemSize = floor((itemHorizontalSpace - self.horizontalSpacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow))
|
self.itemSize = floor((itemHorizontalSpace - self.horizontalSpacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow))
|
||||||
|
|
||||||
let numRowsInGroup = (itemCount + (self.itemsPerRow - 1)) / self.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 {
|
func frame(at index: Int) -> CGRect {
|
||||||
@ -730,13 +730,18 @@ public final class GifPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateScrollingOffset(transition: Transition) {
|
private func updateScrollingOffset(transition: Transition) {
|
||||||
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
|
let isInteracting = self.scrollView.isDragging || self.scrollView.isDecelerating
|
||||||
if let previousScrollingOffsetValue = self.previousScrollingOffset {
|
if let previousScrollingOffsetValue = self.previousScrollingOffset {
|
||||||
let currentBounds = scrollView.bounds
|
let currentBounds = self.scrollView.bounds
|
||||||
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
var offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
||||||
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
|
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(
|
self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate(
|
||||||
relativeOffset: relativeOffset,
|
relativeOffset: relativeOffset,
|
||||||
absoluteOffsetToTopEdge: offsetToTopEdge,
|
absoluteOffsetToTopEdge: offsetToTopEdge,
|
||||||
@ -745,19 +750,21 @@ public final class GifPagerContentComponent: Component {
|
|||||||
isInteracting: isInteracting,
|
isInteracting: isInteracting,
|
||||||
transition: transition
|
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 {
|
private func snappedContentOffset(proposedOffset: CGFloat) -> CGFloat {
|
||||||
guard let pagerEnvironment = self.pagerEnvironment else {
|
guard let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
|
||||||
return proposedOffset
|
return proposedOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
var proposedOffset = proposedOffset
|
var proposedOffset = proposedOffset
|
||||||
let bounds = self.bounds
|
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
|
proposedOffset = self.scrollView.contentSize.height - bounds.height
|
||||||
}
|
}
|
||||||
if proposedOffset < pagerEnvironment.containerInsets.top {
|
if proposedOffset < pagerEnvironment.containerInsets.top {
|
||||||
@ -775,6 +782,7 @@ public final class GifPagerContentComponent: Component {
|
|||||||
transition.setBounds(view: self.scrollView, bounds: currentBounds)
|
transition.setBounds(view: self.scrollView, bounds: currentBounds)
|
||||||
|
|
||||||
self.updateScrollingOffset(transition: transition)
|
self.updateScrollingOffset(transition: transition)
|
||||||
|
self.updateVisibleItems(attemptSynchronousLoads: false, transition: transition, fromScrolling: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateVisibleItems(attemptSynchronousLoads: Bool, transition: Transition, fromScrolling: Bool) {
|
private func updateVisibleItems(attemptSynchronousLoads: Bool, transition: Transition, fromScrolling: Bool) {
|
||||||
|
|||||||
@ -928,7 +928,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
},
|
},
|
||||||
openSearch: {
|
openSearch: {
|
||||||
},
|
},
|
||||||
addGroupAction: { groupId, isPremiumLocked in
|
addGroupAction: { groupId, isPremiumLocked, _ in
|
||||||
guard let collectionId = groupId.base as? ItemCollectionId else {
|
guard let collectionId = groupId.base as? ItemCollectionId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8662,27 +8662,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let subjectFlags: TelegramChatBannedRightsFlags
|
let subjectFlags: [TelegramChatBannedRightsFlags]
|
||||||
switch subject {
|
switch subject {
|
||||||
case .stickers:
|
case .stickers:
|
||||||
subjectFlags = .banSendStickers
|
subjectFlags = [.banSendStickers]
|
||||||
case .mediaRecording, .premiumVoiceMessages:
|
case .mediaRecording, .premiumVoiceMessages:
|
||||||
subjectFlags = .banSendMedia
|
subjectFlags = [.banSendVoice, .banSendInstantVideos]
|
||||||
}
|
}
|
||||||
|
|
||||||
let bannedPermission: (Int32, Bool)?
|
var bannedPermission: (Int32, Bool)? = nil
|
||||||
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||||
bannedPermission = channel.hasBannedPermission(subjectFlags)
|
for subjectFlag in subjectFlags {
|
||||||
} else if let group = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
|
if let value = channel.hasBannedPermission(subjectFlag) {
|
||||||
if group.hasBannedPermission(subjectFlags) {
|
bannedPermission = value
|
||||||
bannedPermission = (Int32.max, false)
|
break
|
||||||
} else {
|
}
|
||||||
bannedPermission = nil
|
}
|
||||||
|
} 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 {
|
if let (untilDate, personal) = bannedPermission {
|
||||||
let banDescription: String
|
let banDescription: String
|
||||||
switch subject {
|
switch subject {
|
||||||
@ -8700,7 +8706,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
} else if personal {
|
} else if personal {
|
||||||
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedMedia
|
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedMedia
|
||||||
} else {
|
} else {
|
||||||
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedMedia
|
banDescription = strongSelf.restrictedSendingContentsText()
|
||||||
|
displayToast = true
|
||||||
}
|
}
|
||||||
case .premiumVoiceMessages:
|
case .premiumVoiceMessages:
|
||||||
banDescription = ""
|
banDescription = ""
|
||||||
@ -8714,37 +8721,41 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
switch displayType {
|
switch displayType {
|
||||||
case .tooltip:
|
case .tooltip:
|
||||||
var rect: CGRect?
|
if displayToast {
|
||||||
let isStickers: Bool = subject == .stickers
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: banDescription))
|
||||||
switch subject {
|
} else {
|
||||||
case .stickers:
|
var rect: CGRect?
|
||||||
rect = strongSelf.chatDisplayNode.frameForStickersButton()
|
let isStickers: Bool = subject == .stickers
|
||||||
if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() {
|
switch subject {
|
||||||
rectValue.origin.y = actionRect.minY
|
case .stickers:
|
||||||
rect = rectValue
|
rect = strongSelf.chatDisplayNode.frameForStickersButton()
|
||||||
}
|
if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() {
|
||||||
case .mediaRecording, .premiumVoiceMessages:
|
rectValue.origin.y = actionRect.minY
|
||||||
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
rect = rectValue
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
case .mediaRecording, .premiumVoiceMessages:
|
||||||
|
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
||||||
}
|
}
|
||||||
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
|
||||||
if let strongSelf = self {
|
if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers {
|
||||||
return (strongSelf.chatDisplayNode, rect)
|
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:
|
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))
|
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))
|
||||||
|
|||||||
@ -930,7 +930,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
//TODO:localize
|
//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 {
|
} else {
|
||||||
strongSelf.ensureFocused()
|
strongSelf.ensureFocused()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,12 @@ import AnimationCache
|
|||||||
import MultiAnimationRenderer
|
import MultiAnimationRenderer
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import ChatControllerInteraction
|
import ChatControllerInteraction
|
||||||
|
import ContextUI
|
||||||
|
import SwiftSignalKit
|
||||||
|
import PremiumUI
|
||||||
|
import StickerPeekUI
|
||||||
|
import UndoUI
|
||||||
|
import Pasteboard
|
||||||
|
|
||||||
private enum EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
|
private enum EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
|
||||||
case symbol(String)
|
case symbol(String)
|
||||||
@ -119,6 +125,8 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
private let animationCache: AnimationCache
|
private let animationCache: AnimationCache
|
||||||
private let animationRenderer: MultiAnimationRenderer
|
private let animationRenderer: MultiAnimationRenderer
|
||||||
|
|
||||||
|
private weak var peekController: PeekController?
|
||||||
|
|
||||||
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
|
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
|
||||||
self.animationCache = chatPresentationContext.animationCache
|
self.animationCache = chatPresentationContext.animationCache
|
||||||
self.animationRenderer = chatPresentationContext.animationRenderer
|
self.animationRenderer = chatPresentationContext.animationRenderer
|
||||||
@ -161,6 +169,281 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
|
|
||||||
self.addSubnode(self.clippingNode)
|
self.addSubnode(self.clippingNode)
|
||||||
self.clippingNode.addSubnode(self.listView)
|
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)]) {
|
func updateResults(_ results: [(String, TelegramMediaFile?, String)]) {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ final class EmojisChatInputPanelItem: ListViewItem {
|
|||||||
fileprivate let theme: PresentationTheme
|
fileprivate let theme: PresentationTheme
|
||||||
fileprivate let symbol: String
|
fileprivate let symbol: String
|
||||||
fileprivate let text: String
|
fileprivate let text: String
|
||||||
fileprivate let file: TelegramMediaFile?
|
let file: TelegramMediaFile?
|
||||||
fileprivate let animationCache: AnimationCache
|
fileprivate let animationCache: AnimationCache
|
||||||
fileprivate let animationRenderer: MultiAnimationRenderer
|
fileprivate let animationRenderer: MultiAnimationRenderer
|
||||||
private let emojiSelected: (String, TelegramMediaFile?) -> Void
|
private let emojiSelected: (String, TelegramMediaFile?) -> Void
|
||||||
@ -94,6 +94,8 @@ final class EmojisChatInputPanelItemNode: ListViewItemNode {
|
|||||||
private let symbolNode: TextNode
|
private let symbolNode: TextNode
|
||||||
private var emojiView: EmojiTextAttachmentView?
|
private var emojiView: EmojiTextAttachmentView?
|
||||||
|
|
||||||
|
var item: EmojisChatInputPanelItem?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.symbolNode = TextNode()
|
self.symbolNode = TextNode()
|
||||||
self.symbolNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
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
|
return (nodeLayout, { _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.item = item
|
||||||
|
|
||||||
let _ = symbolApply()
|
let _ = symbolApply()
|
||||||
strongSelf.symbolNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((EmojisChatInputPanelItemNode.itemSize.width - symbolLayout.size.width) / 2.0), y: 0.0), size: symbolLayout.size)
|
strongSelf.symbolNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((EmojisChatInputPanelItemNode.itemSize.width - symbolLayout.size.width) / 2.0), y: 0.0), size: symbolLayout.size)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user