mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Reaction and status improvements
This commit is contained in:
parent
5e196704e0
commit
2495a30c33
@ -172,6 +172,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
private var didSetupTabs = false
|
||||
|
||||
private weak var emojiStatusSelectionController: ViewController?
|
||||
|
||||
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.isNodeLoaded {
|
||||
self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition)
|
||||
@ -847,7 +849,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
|
||||
private func openStatusSetup(sourceView: UIView) {
|
||||
self.present(EmojiStatusSelectionController(
|
||||
self.emojiStatusSelectionController?.dismiss()
|
||||
let controller = EmojiStatusSelectionController(
|
||||
context: self.context,
|
||||
mode: .statusSelection,
|
||||
sourceView: sourceView,
|
||||
@ -866,7 +869,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
destinationItemView: { [weak sourceView] in
|
||||
return sourceView
|
||||
}
|
||||
), in: .window(.root))
|
||||
)
|
||||
self.emojiStatusSelectionController = controller
|
||||
self.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
@ -1899,6 +1904,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if let emojiStatusSelectionController = self.emojiStatusSelectionController {
|
||||
self.emojiStatusSelectionController = nil
|
||||
emojiStatusSelectionController.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override public func viewWillDisappear(_ animated: Bool) {
|
||||
|
@ -459,7 +459,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let onlineNode: PeerOnlineMarkerNode
|
||||
let pinnedIconNode: ASImageNode
|
||||
var secretIconNode: ASImageNode?
|
||||
var credibilityIconNode: ASImageNode?
|
||||
var credibilityIconView: ComponentHostView<Empty>?
|
||||
let mutedIconNode: ASImageNode
|
||||
|
||||
@ -1071,7 +1070,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var currentMentionBadgeImage: UIImage?
|
||||
var currentPinnedIconImage: UIImage?
|
||||
var currentMutedIconImage: UIImage?
|
||||
var currentCredibilityIconImage: UIImage?
|
||||
var currentCredibilityIconContent: EmojiStatusComponent.Content?
|
||||
var currentSecretIconImage: UIImage?
|
||||
|
||||
@ -1523,19 +1521,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if let peer = messages.last?.author {
|
||||
if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||
currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
|
||||
} else if peer.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if peer.isFake {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if peer.isVerified {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||
currentCredibilityIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||
currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
}
|
||||
@ -1544,19 +1537,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
|
||||
if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||
currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
|
||||
} else if peer.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if peer.isFake {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if peer.isVerified {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||
currentCredibilityIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||
currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
}
|
||||
@ -1564,13 +1552,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let currentSecretIconImage = currentSecretIconImage {
|
||||
titleIconsWidth += currentSecretIconImage.size.width + 2.0
|
||||
}
|
||||
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
||||
if let currentCredibilityIconContent = currentCredibilityIconContent {
|
||||
if titleIconsWidth.isZero {
|
||||
titleIconsWidth += 4.0
|
||||
} else {
|
||||
titleIconsWidth += 2.0
|
||||
}
|
||||
titleIconsWidth += currentCredibilityIconImage.size.width
|
||||
switch currentCredibilityIconContent {
|
||||
case .fake, .scam:
|
||||
titleIconsWidth += 14.0
|
||||
default:
|
||||
titleIconsWidth += 8.0
|
||||
}
|
||||
}
|
||||
|
||||
let layoutOffset: CGFloat = 0.0
|
||||
@ -2077,32 +2070,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
containerSize: CGSize(width: 20.0, height: 20.0)
|
||||
)
|
||||
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0) - UIScreenPixel), size: iconSize))
|
||||
nextTitleIconOrigin += credibilityIconView.bounds.width + 4.0
|
||||
} else if let credibilityIconView = strongSelf.credibilityIconView {
|
||||
strongSelf.credibilityIconView = nil
|
||||
credibilityIconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
||||
let iconNode: ASImageNode
|
||||
if let current = strongSelf.credibilityIconNode {
|
||||
iconNode = current
|
||||
} else {
|
||||
iconNode = ASImageNode()
|
||||
iconNode.isLayerBacked = true
|
||||
iconNode.displaysAsynchronously = false
|
||||
iconNode.displayWithoutProcessing = true
|
||||
strongSelf.contextContainer.addSubnode(iconNode)
|
||||
strongSelf.credibilityIconNode = iconNode
|
||||
iconNode.isHidden = true
|
||||
}
|
||||
iconNode.image = currentCredibilityIconImage
|
||||
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.midY - currentCredibilityIconImage.size.height / 2.0) - UIScreenPixel), size: currentCredibilityIconImage.size))
|
||||
nextTitleIconOrigin += currentCredibilityIconImage.size.width + 4.0
|
||||
} else if let credibilityIconNode = strongSelf.credibilityIconNode {
|
||||
strongSelf.credibilityIconNode = nil
|
||||
credibilityIconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let currentMutedIconImage = currentMutedIconImage {
|
||||
strongSelf.mutedIconNode.image = currentMutedIconImage
|
||||
strongSelf.mutedIconNode.isHidden = false
|
||||
@ -2359,10 +2332,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if let credibilityIconView = self.credibilityIconView {
|
||||
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: credibilityIconView.frame.origin.y), size: credibilityIconView.bounds.size))
|
||||
nextTitleIconOrigin += credibilityIconView.bounds.size.width + 5.0
|
||||
} else if let credibilityIconNode = self.credibilityIconNode {
|
||||
transition.updateFrame(node: credibilityIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: credibilityIconNode.frame.origin.y), size: credibilityIconNode.bounds.size))
|
||||
nextTitleIconOrigin += credibilityIconNode.bounds.size.width + 5.0
|
||||
nextTitleIconOrigin += credibilityIconView.bounds.size.width + 4.0
|
||||
}
|
||||
|
||||
let mutedIconFrame = self.mutedIconNode.frame
|
||||
|
@ -97,7 +97,7 @@ public final class ReactionIconView: PortalSourceView {
|
||||
case .builtin:
|
||||
iconSize = CGSize(width: floor(size.width * 2.0), height: floor(size.height * 2.0))
|
||||
case .custom:
|
||||
iconSize = size
|
||||
iconSize = CGSize(width: floor(size.width * 1.25), height: floor(size.height * 1.25))
|
||||
}
|
||||
|
||||
transition.updateFrame(layer: animationLayer, frame: CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
@ -125,9 +125,9 @@ public final class ReactionIconView: PortalSourceView {
|
||||
let iconSize: CGSize
|
||||
switch reaction {
|
||||
case .builtin:
|
||||
iconSize = CGSize(width: floor(size.width * 1.5), height: floor(size.height * 1.5))
|
||||
iconSize = CGSize(width: floor(size.width * 2.0), height: floor(size.height * 2.0))
|
||||
case .custom:
|
||||
iconSize = size
|
||||
iconSize = CGSize(width: floor(size.width * 1.25), height: floor(size.height * 1.25))
|
||||
}
|
||||
|
||||
let animationLayer = InlineStickerItemLayer(
|
||||
@ -653,8 +653,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
public var activateAfterCompletion: Bool = false {
|
||||
didSet {
|
||||
if self.activateAfterCompletion {
|
||||
self.contextGesture?.activatedAfterCompletion = { [weak self] in
|
||||
self?.pressed()
|
||||
self.contextGesture?.activatedAfterCompletion = { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.buttonNode.bounds.contains(point) {
|
||||
strongSelf.pressed()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.contextGesture?.activatedAfterCompletion = nil
|
||||
@ -692,8 +697,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
}
|
||||
|
||||
if self.activateAfterCompletion {
|
||||
self.contextGesture?.activatedAfterCompletion = { [weak self] in
|
||||
self?.pressed()
|
||||
self.contextGesture?.activatedAfterCompletion = { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.buttonNode.bounds.contains(point) {
|
||||
strongSelf.pressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ swift_library(
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -12,6 +12,7 @@ import TextNodeWithEntities
|
||||
import EntityKeyboard
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import UndoUI
|
||||
|
||||
private let animationDurationFactor: Double = 1.0
|
||||
|
||||
@ -1531,7 +1532,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
controller.reactionSelected?(reaction, isLarge)
|
||||
}
|
||||
reactionContextNode.premiumReactionsSelected = { [weak self] in
|
||||
reactionContextNode.premiumReactionsSelected = { [weak self] _ in
|
||||
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else {
|
||||
return
|
||||
}
|
||||
@ -2140,6 +2141,22 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
return nil
|
||||
}
|
||||
|
||||
if let controller = self.getController() as? ContextController {
|
||||
var innerResult: UIView?
|
||||
controller.forEachController { c in
|
||||
if let c = c as? UndoOverlayController {
|
||||
if let result = c.view.hitTest(self.view.convert(point, to: c.view), with: event) {
|
||||
innerResult = result
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
if let innerResult = innerResult {
|
||||
return innerResult
|
||||
}
|
||||
}
|
||||
|
||||
if let presentationNode = self.presentationNode {
|
||||
return presentationNode.hitTest(self.view.convert(point, to: presentationNode.view), with: event)
|
||||
}
|
||||
@ -2583,7 +2600,18 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
self.displayNode = ContextControllerNode(account: self.account, controller: self, presentationData: self.presentationData, source: self.source, items: self.items, beginDismiss: { [weak self] result in
|
||||
self?.dismiss(result: result, completion: nil)
|
||||
}, recognizer: self.recognizer, gesture: self.gesture, beganAnimatingOut: { [weak self] in
|
||||
self?.statusBar.statusBarStyle = .Ignore
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
strongSelf.forEachController { c in
|
||||
if let c = c as? UndoOverlayController {
|
||||
c.dismiss()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}, attemptTransitionControllerIntoNavigation: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -7,6 +7,7 @@ import TextSelectionNode
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import ReactionSelectionNode
|
||||
import UndoUI
|
||||
|
||||
private extension ContextControllerTakeViewInfo.ContainingItem {
|
||||
var contentRect: CGRect {
|
||||
@ -189,6 +190,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
private let contentRectDebugNode: ASDisplayNode
|
||||
private let actionsStackNode: ContextControllerActionsStackNode
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var animatingOutState: AnimatingOutState?
|
||||
|
||||
private var strings: PresentationStrings?
|
||||
@ -201,6 +203,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
private var overscrollMode: OverscrollMode = .unrestricted
|
||||
|
||||
private weak var currentUndoController: ViewController?
|
||||
|
||||
init(
|
||||
getController: @escaping () -> ContextControllerProtocol?,
|
||||
requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void,
|
||||
@ -437,6 +441,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
transition: ContainedViewLayoutTransition,
|
||||
stateTransition: ContextControllerPresentationNodeStateTransition?
|
||||
) {
|
||||
self.validLayout = layout
|
||||
|
||||
let contentActionsSpacing: CGFloat = 7.0
|
||||
let actionsEdgeInset: CGFloat
|
||||
let actionsSideInset: CGFloat = 6.0
|
||||
@ -540,11 +546,37 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
controller.reactionSelected?(reaction, isLarge)
|
||||
}
|
||||
reactionContextNode.premiumReactionsSelected = { [weak self] in
|
||||
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else {
|
||||
let context = reactionItems.context
|
||||
reactionContextNode.premiumReactionsSelected = { [weak self] file in
|
||||
guard let strongSelf = self, let validLayout = strongSelf.validLayout, let controller = strongSelf.getController() as? ContextController else {
|
||||
return
|
||||
}
|
||||
controller.premiumReactionsSelected?()
|
||||
|
||||
if let file = file, let reactionContextNode = strongSelf.reactionContextNode {
|
||||
let position: UndoOverlayController.Position
|
||||
let insets = validLayout.insets(options: .statusBar)
|
||||
if reactionContextNode.hasSpaceInTheBottom(insets: insets, height: 100.0) {
|
||||
position = .bottom
|
||||
} else {
|
||||
position = .top
|
||||
}
|
||||
|
||||
var animateInAsReplacement = false
|
||||
if let currentUndoController = strongSelf.currentUndoController {
|
||||
currentUndoController.dismiss()
|
||||
animateInAsReplacement = true
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Subscribe to **Telegram Premium** to unlock this reaction.", undoText: "More", customAction: { [weak controller] in
|
||||
controller?.premiumReactionsSelected?()
|
||||
}), elevatedLayout: false, position: position, animateInAsReplacement: animateInAsReplacement, action: { _ in true })
|
||||
strongSelf.currentUndoController = undoController
|
||||
controller.present(undoController, in: .current)
|
||||
} else {
|
||||
controller.premiumReactionsSelected?()
|
||||
}
|
||||
}
|
||||
}
|
||||
contentTopInset += reactionContextNode.contentHeight + 18.0
|
||||
|
@ -68,7 +68,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
|
||||
public var activated: ((ContextGesture, CGPoint) -> Void)?
|
||||
public var externalUpdated: ((UIView?, CGPoint) -> Void)?
|
||||
public var externalEnded: (((UIView?, CGPoint)?) -> Void)?
|
||||
public var activatedAfterCompletion: (() -> Void)?
|
||||
public var activatedAfterCompletion: ((CGPoint) -> Void)?
|
||||
public var cancelGesturesOnActivation: (() -> Void)?
|
||||
|
||||
override public init(target: Any?, action: Selector?) {
|
||||
@ -208,7 +208,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
|
||||
self.currentProgress = 0.0
|
||||
self.activationProgress?(0.0, .ended(self.currentProgress))
|
||||
if self.wasActivated {
|
||||
self.activatedAfterCompletion?()
|
||||
self.activatedAfterCompletion?(touch.location(in: self.view))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +160,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var customReactionSource: (view: UIView, rect: CGRect, layer: CALayer, item: ReactionItem)?
|
||||
|
||||
public var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)?
|
||||
public var premiumReactionsSelected: (() -> Void)?
|
||||
public var premiumReactionsSelected: ((TelegramMediaFile?) -> Void)?
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
private var standaloneReactionAnimation: StandaloneReactionAnimation?
|
||||
@ -196,6 +196,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var availableReactions: AvailableReactions?
|
||||
private var availableReactionsDisposable: Disposable?
|
||||
|
||||
private var hasPremium: Bool?
|
||||
private var hasPremiumDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
@ -277,8 +280,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.contentContainer.view.addSubview(expandItemView)
|
||||
self.contentTintContainer.view.addSubview(expandItemView.tintView)
|
||||
|
||||
self.canBeExpanded = true
|
||||
} else {
|
||||
self.expandItemView = nil
|
||||
}
|
||||
@ -292,12 +293,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.addSubnode(self.contentContainer)
|
||||
self.addSubnode(self.previewingItemContainer)
|
||||
|
||||
if self.canBeExpanded {
|
||||
let horizontalExpandRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.horizontalExpandGesture(_:)))
|
||||
self.view.addGestureRecognizer(horizontalExpandRecognizer)
|
||||
self.horizontalExpandRecognizer = horizontalExpandRecognizer
|
||||
}
|
||||
|
||||
self.availableReactionsDisposable = (context.engine.stickers.availableReactions()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] availableReactions in
|
||||
@ -306,11 +301,62 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
strongSelf.availableReactions = availableReactions
|
||||
})
|
||||
|
||||
self.hasPremiumDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.hasPremium = peer?.isPremium ?? false
|
||||
})
|
||||
|
||||
if let getEmojiContent = getEmojiContent {
|
||||
self.emojiContentDisposable = (getEmojiContent(self.animationCache, self.animationRenderer)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] emojiContent in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.emojiContent = emojiContent
|
||||
if !strongSelf.canBeExpanded {
|
||||
strongSelf.canBeExpanded = true
|
||||
|
||||
let horizontalExpandRecognizer = UIPanGestureRecognizer(target: strongSelf, action: #selector(strongSelf.horizontalExpandGesture(_:)))
|
||||
strongSelf.view.addGestureRecognizer(horizontalExpandRecognizer)
|
||||
strongSelf.horizontalExpandRecognizer = horizontalExpandRecognizer
|
||||
}
|
||||
strongSelf.updateEmojiContent(emojiContent)
|
||||
|
||||
if let reactionSelectionComponentHost = strongSelf.reactionSelectionComponentHost, let componentView = reactionSelectionComponentHost.view {
|
||||
var emojiTransition: Transition = .immediate
|
||||
if let scheduledEmojiContentAnimationHint = strongSelf.scheduledEmojiContentAnimationHint {
|
||||
strongSelf.scheduledEmojiContentAnimationHint = nil
|
||||
let contentAnimation = scheduledEmojiContentAnimationHint
|
||||
emojiTransition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
|
||||
}
|
||||
|
||||
let _ = reactionSelectionComponentHost.update(
|
||||
transition: emojiTransition,
|
||||
component: AnyComponent(EmojiStatusSelectionComponent(
|
||||
theme: strongSelf.presentationData.theme,
|
||||
strings: strongSelf.presentationData.strings,
|
||||
deviceMetrics: DeviceMetrics.iPhone13,
|
||||
emojiContent: emojiContent,
|
||||
backgroundColor: .clear,
|
||||
separatorColor: strongSelf.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: componentView.bounds.width, height: 300.0)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.emojiContentDisposable?.dispose()
|
||||
self.availableReactionsDisposable?.dispose()
|
||||
self.hasPremiumDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
@ -339,13 +385,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
var compressionFactor: CGFloat = max(0.0, min(1.0, self.horizontalExpandDistance / maxCompressionDistance))
|
||||
compressionFactor = compressionFactor * compressionFactor
|
||||
|
||||
self.extensionDistance = 20.0 * compressionFactor
|
||||
self.visibleExtensionDistance = self.extensionDistance
|
||||
|
||||
self.requestLayout(.immediate)
|
||||
if compressionFactor >= 0.95 {
|
||||
self.horizontalExpandStartLocation = nil
|
||||
self.expand()
|
||||
} else {
|
||||
self.extensionDistance = 20.0 * compressionFactor
|
||||
self.visibleExtensionDistance = self.extensionDistance
|
||||
|
||||
self.requestLayout(.immediate)
|
||||
}
|
||||
}
|
||||
case .cancelled, .ended:
|
||||
if self.horizontalExpandDistance != 0.0 {
|
||||
if let _ = self.horizontalExpandStartLocation, self.horizontalExpandDistance != 0.0 {
|
||||
if self.horizontalExpandDistance >= 90.0 {
|
||||
self.expand()
|
||||
} else {
|
||||
@ -620,6 +671,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
if self.getEmojiContent != nil && i == visibleItemCount - 1 {
|
||||
transition.updateSublayerTransformScale(node: itemNode, scale: 0.001 * (1.0 - compressionFactor) + 1.0 * compressionFactor)
|
||||
|
||||
let alphaFraction = min(compressionFactor, 0.2) / 0.2
|
||||
transition.updateAlpha(node: itemNode, alpha: alphaFraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -745,7 +799,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
itemSpacing: itemSpacing
|
||||
)
|
||||
|
||||
if (self.isExpanded || self.reactionSelectionComponentHost != nil), let getEmojiContent = self.getEmojiContent {
|
||||
if (self.isExpanded || self.reactionSelectionComponentHost != nil), let _ = self.getEmojiContent {
|
||||
let reactionSelectionComponentHost: ComponentView<Empty>
|
||||
var componentTransition = Transition(transition)
|
||||
if let current = self.reactionSelectionComponentHost {
|
||||
@ -756,57 +810,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.reactionSelectionComponentHost = reactionSelectionComponentHost
|
||||
}
|
||||
|
||||
var emojiContent: EmojiPagerContentComponent?
|
||||
if let current = self.emojiContent {
|
||||
emojiContent = current
|
||||
} else {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let _ = (getEmojiContent(self.animationCache, self.animationRenderer) |> take(1)).start(next: { value in
|
||||
emojiContent = value
|
||||
semaphore.signal()
|
||||
})
|
||||
|
||||
semaphore.wait()
|
||||
self.emojiContent = emojiContent
|
||||
if let emojiContent = emojiContent {
|
||||
self.updateEmojiContent(emojiContent)
|
||||
}
|
||||
|
||||
self.emojiContentDisposable = (getEmojiContent(self.animationCache, self.animationRenderer)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] emojiContent in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.emojiContent = emojiContent
|
||||
strongSelf.updateEmojiContent(emojiContent)
|
||||
|
||||
if let reactionSelectionComponentHost = strongSelf.reactionSelectionComponentHost, let componentView = reactionSelectionComponentHost.view {
|
||||
var emojiTransition: Transition = .immediate
|
||||
if let scheduledEmojiContentAnimationHint = strongSelf.scheduledEmojiContentAnimationHint {
|
||||
strongSelf.scheduledEmojiContentAnimationHint = nil
|
||||
let contentAnimation = scheduledEmojiContentAnimationHint
|
||||
emojiTransition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
|
||||
}
|
||||
|
||||
let _ = reactionSelectionComponentHost.update(
|
||||
transition: emojiTransition,
|
||||
component: AnyComponent(EmojiStatusSelectionComponent(
|
||||
theme: strongSelf.presentationData.theme,
|
||||
strings: strongSelf.presentationData.strings,
|
||||
deviceMetrics: DeviceMetrics.iPhone13,
|
||||
emojiContent: emojiContent,
|
||||
backgroundColor: .clear,
|
||||
separatorColor: strongSelf.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: componentView.bounds.width, height: 300.0)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if let emojiContent = emojiContent {
|
||||
if let emojiContent = self.emojiContent {
|
||||
self.updateEmojiContent(emojiContent)
|
||||
|
||||
if let scheduledEmojiContentAnimationHint = self.scheduledEmojiContentAnimationHint {
|
||||
@ -848,6 +852,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
guard let placeholder = itemNode.currentFrameImage else {
|
||||
continue
|
||||
}
|
||||
if itemNode.alpha.isZero {
|
||||
continue
|
||||
}
|
||||
initialPositionAndFrame[itemNode.item.stillAnimation.fileId] = (
|
||||
position: itemNode.frame.center,
|
||||
frameIndex: itemNode.currentFrameIndex,
|
||||
@ -974,8 +981,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
isCustom: false
|
||||
)
|
||||
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(updateReaction, isLongPress)
|
||||
if case .custom = reactionItem.updateMessageReaction, let hasPremium = strongSelf.hasPremium, !hasPremium {
|
||||
strongSelf.premiumReactionsSelected?(reactionItem.stillAnimation)
|
||||
} else {
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(updateReaction, isLongPress)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
@ -992,7 +1003,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
isCustom: true
|
||||
)
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, isLongPress)
|
||||
if case .custom = reactionItem.updateMessageReaction, let hasPremium = strongSelf.hasPremium, !hasPremium {
|
||||
strongSelf.premiumReactionsSelected?(reactionItem.stillAnimation)
|
||||
} else {
|
||||
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, isLongPress)
|
||||
}
|
||||
}
|
||||
},
|
||||
deleteBackwards: {
|
||||
@ -1007,7 +1022,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
if isPremiumLocked {
|
||||
strongSelf.premiumReactionsSelected?()
|
||||
strongSelf.premiumReactionsSelected?(nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1594,9 +1609,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
} else if let reaction = self.reaction(at: point) {
|
||||
switch reaction {
|
||||
case let .reaction(reactionItem):
|
||||
self.reactionSelected?(reactionItem.updateMessageReaction, false)
|
||||
if case .custom = reactionItem.updateMessageReaction, let hasPremium = self.hasPremium, !hasPremium {
|
||||
self.premiumReactionsSelected?(reactionItem.stillAnimation)
|
||||
} else {
|
||||
self.reactionSelected?(reactionItem.updateMessageReaction, false)
|
||||
}
|
||||
case .premium:
|
||||
self.premiumReactionsSelected?()
|
||||
self.premiumReactionsSelected?(nil)
|
||||
}
|
||||
}
|
||||
default:
|
||||
@ -1604,6 +1623,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public func hasSpaceInTheBottom(insets: UIEdgeInsets, height: CGFloat) -> Bool {
|
||||
if self.backgroundNode.frame.maxY < self.bounds.height - insets.bottom - height {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func expand() {
|
||||
if self.hapticFeedback == nil {
|
||||
self.hapticFeedback = HapticFeedback()
|
||||
@ -1720,7 +1747,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
public func performReactionSelection(reaction: ReactionItem.Reaction, isLarge: Bool) {
|
||||
for (_, itemNode) in self.visibleItemNodes {
|
||||
if let itemNode = itemNode as? ReactionNode, itemNode.item.reaction == reaction {
|
||||
self.reactionSelected?(itemNode.item.updateMessageReaction, isLarge)
|
||||
if case .custom = itemNode.item.updateMessageReaction, let hasPremium = self.hasPremium, !hasPremium {
|
||||
self.premiumReactionsSelected?(itemNode.item.stillAnimation)
|
||||
} else {
|
||||
self.reactionSelected?(itemNode.item.updateMessageReaction, isLarge)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -361,7 +361,8 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
),
|
||||
context: context,
|
||||
attemptSynchronousLoad: attemptSynchronousLoads,
|
||||
|
@ -61,7 +61,7 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
|
||||
for attribute in currentMessage.attributes {
|
||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
for updatedReaction in reactions {
|
||||
if !attribute.reactions.contains(where: { $0.value == updatedReaction.reaction }) {
|
||||
if !attribute.reactions.contains(where: { $0.value == updatedReaction.reaction && $0.isSelected }) {
|
||||
let recentReactionItem: RecentReactionItem
|
||||
switch updatedReaction {
|
||||
case let .builtin(value):
|
||||
|
@ -106,7 +106,7 @@ public final class EmojiStatusSelectionComponent: Component {
|
||||
let topPanelHeight: CGFloat = 42.0
|
||||
|
||||
let keyboardSize = self.keyboardView.update(
|
||||
transition: transition,
|
||||
transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
@ -670,7 +670,52 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
}
|
||||
|
||||
if let item = item, let destinationView = controller.destinationItemView() {
|
||||
self.animateOutToStatus(groupId: groupId, item: item, customEffectFile: nil, destinationView: destinationView)
|
||||
var emojiString: String?
|
||||
if let itemFile = item.itemFile {
|
||||
attributeLoop: for attribute in itemFile.attributes {
|
||||
switch attribute {
|
||||
case let .CustomEmoji(_, alt, _):
|
||||
emojiString = alt
|
||||
break attributeLoop
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let _ = (context.engine.stickers.availableReactions()
|
||||
|> take(1)
|
||||
|> mapToSignal { availableReactions -> Signal<String?, NoError> in
|
||||
guard let emojiString = emojiString, let availableReactions = availableReactions else {
|
||||
return .single(nil)
|
||||
}
|
||||
for reaction in availableReactions.reactions {
|
||||
if case let .builtin(value) = reaction.value, value == emojiString {
|
||||
if let aroundAnimation = reaction.aroundAnimation {
|
||||
return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource)
|
||||
|> take(1)
|
||||
|> map { data -> String? in
|
||||
if data.complete {
|
||||
return data.path
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] filePath in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.animateOutToStatus(groupId: groupId, item: item, customEffectFile: filePath, destinationView: destinationView)
|
||||
})
|
||||
} else {
|
||||
controller.dismiss()
|
||||
}
|
||||
|
@ -179,6 +179,16 @@ private final class WarpView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
public struct EmojiComponentReactionItem {
|
||||
public var reaction: MessageReaction.Reaction
|
||||
public var file: TelegramMediaFile
|
||||
|
||||
public init(reaction: MessageReaction.Reaction, file: TelegramMediaFile) {
|
||||
self.reaction = reaction
|
||||
self.file = file
|
||||
}
|
||||
}
|
||||
|
||||
public final class EntityKeyboardAnimationData: Equatable {
|
||||
public enum Id: Hashable {
|
||||
case file(MediaId)
|
||||
@ -1506,6 +1516,14 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
public final class SynchronousLoadBehavior {
|
||||
public let isDisabled: Bool
|
||||
|
||||
public init(isDisabled: Bool) {
|
||||
self.isDisabled = isDisabled
|
||||
}
|
||||
}
|
||||
|
||||
public struct CustomLayout: Equatable {
|
||||
public var itemsPerRow: Int
|
||||
public var itemSize: CGFloat
|
||||
@ -1633,21 +1651,30 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
public final class Item: Equatable {
|
||||
public enum Icon: Equatable {
|
||||
case none
|
||||
case locked
|
||||
case premium
|
||||
}
|
||||
|
||||
public let animationData: EntityKeyboardAnimationData?
|
||||
public let content: ItemContent
|
||||
public let itemFile: TelegramMediaFile?
|
||||
public let subgroupId: Int32?
|
||||
public let icon: Icon
|
||||
|
||||
public init(
|
||||
animationData: EntityKeyboardAnimationData?,
|
||||
content: ItemContent,
|
||||
itemFile: TelegramMediaFile?,
|
||||
subgroupId: Int32?
|
||||
subgroupId: Int32?,
|
||||
icon: Icon
|
||||
) {
|
||||
self.animationData = animationData
|
||||
self.content = content
|
||||
self.itemFile = itemFile
|
||||
self.subgroupId = subgroupId
|
||||
self.icon = icon
|
||||
}
|
||||
|
||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||
@ -1666,6 +1693,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if lhs.subgroupId != rhs.subgroupId {
|
||||
return false
|
||||
}
|
||||
if lhs.icon != rhs.icon {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -3953,6 +3983,15 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var badge: ItemLayer.Badge?
|
||||
if itemGroup.displayPremiumBadges, let file = item.itemFile, file.isPremiumSticker {
|
||||
badge = .premium
|
||||
} else {
|
||||
switch item.icon {
|
||||
case .none:
|
||||
break
|
||||
case .locked:
|
||||
badge = .locked
|
||||
case .premium:
|
||||
badge = .premium
|
||||
}
|
||||
}
|
||||
itemLayer.update(transition: transition, size: itemFrame.size, badge: badge, blurredBadgeColor: UIColor(white: 0.0, alpha: 0.1), blurredBadgeBackgroundColor: keyboardChildEnvironment.theme.list.plainBackgroundColor)
|
||||
|
||||
@ -4649,7 +4688,14 @@ public final class EmojiPagerContentComponent: Component {
|
||||
previousAbsoluteItemPositions[id] = position.offsetBy(dx: 0.0, dy: animatedScrollOffset)
|
||||
}
|
||||
|
||||
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: !(scrollView.isDragging || scrollView.isDecelerating), previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame)
|
||||
var attemptSynchronousLoads = !(scrollView.isDragging || scrollView.isDecelerating)
|
||||
if let synchronousLoadBehavior = transition.userData(SynchronousLoadBehavior.self) {
|
||||
if synchronousLoadBehavior.isDisabled {
|
||||
attemptSynchronousLoads = false
|
||||
}
|
||||
}
|
||||
|
||||
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: attemptSynchronousLoads, previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
@ -4680,7 +4726,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return hasPremium
|
||||
}
|
||||
|
||||
public static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool, isStatusSelection: Bool, isReactionSelection: Bool, isQuickReactionSelection: Bool = false, topReactionItems: [AvailableReactions.Reaction], areUnicodeEmojiEnabled: Bool, areCustomEmojiEnabled: Bool, chatPeerId: EnginePeer.Id?, selectedItems: Set<MediaId> = Set()) -> Signal<EmojiPagerContentComponent, NoError> {
|
||||
public static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool, isStatusSelection: Bool, isReactionSelection: Bool, isQuickReactionSelection: Bool = false, topReactionItems: [EmojiComponentReactionItem], areUnicodeEmojiEnabled: Bool, areCustomEmojiEnabled: Bool, chatPeerId: EnginePeer.Id?, selectedItems: Set<MediaId> = Set()) -> Signal<EmojiPagerContentComponent, NoError> {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||
|
||||
@ -4751,7 +4797,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationData: nil,
|
||||
content: .icon(.premiumStar),
|
||||
itemFile: nil,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
let groupId = "recent"
|
||||
@ -4782,7 +4829,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
@ -4809,7 +4857,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
@ -4831,42 +4880,38 @@ public final class EmojiPagerContentComponent: Component {
|
||||
switch topReaction.content {
|
||||
case let .builtin(value):
|
||||
if let reaction = availableReactions?.reactions.first(where: { $0.value == .builtin(value) }) {
|
||||
topReactionItems.append(reaction)
|
||||
topReactionItems.append(EmojiComponentReactionItem(reaction: .builtin(value), file: reaction.selectAnimation))
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case let .custom(file):
|
||||
topReactionItems.append(AvailableReactions.Reaction(
|
||||
isEnabled: true,
|
||||
isPremium: false,
|
||||
value: .custom(file.fileId.id),
|
||||
title: "",
|
||||
staticIcon: file,
|
||||
appearAnimation: file,
|
||||
selectAnimation: file,
|
||||
activateAnimation: file,
|
||||
effectAnimation: file,
|
||||
aroundAnimation: file,
|
||||
centerAnimation: file
|
||||
))
|
||||
topReactionItems.append(EmojiComponentReactionItem(reaction: .custom(file.fileId.id), file: file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for reactionItem in topReactionItems {
|
||||
if existingIds.contains(reactionItem.value) {
|
||||
if existingIds.contains(reactionItem.reaction) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(reactionItem.value)
|
||||
existingIds.insert(reactionItem.reaction)
|
||||
|
||||
let animationFile = reactionItem.selectAnimation
|
||||
let icon: EmojiPagerContentComponent.Item.Icon
|
||||
if !hasPremium, case .custom = reactionItem.reaction {
|
||||
icon = .locked
|
||||
} else {
|
||||
icon = .none
|
||||
}
|
||||
|
||||
let animationFile = reactionItem.file
|
||||
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: animationFile,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: icon
|
||||
)
|
||||
|
||||
let groupId = "recent"
|
||||
@ -4888,11 +4933,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
let maxRecentLineCount: Int
|
||||
#if DEBUG
|
||||
maxRecentLineCount = 5
|
||||
#else
|
||||
maxRecentLineCount = 10
|
||||
#endif
|
||||
if hasPremium {
|
||||
maxRecentLineCount = 10
|
||||
} else {
|
||||
maxRecentLineCount = 5
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let popularTitle = hasRecent ? "Recently Used" : "Popular"
|
||||
@ -4907,21 +4952,43 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
existingIds.insert(reactionItem.value)
|
||||
|
||||
let icon: EmojiPagerContentComponent.Item.Icon
|
||||
if !hasPremium, case .custom = reactionItem.value {
|
||||
icon = .locked
|
||||
} else {
|
||||
icon = .none
|
||||
}
|
||||
|
||||
let animationFile = reactionItem.selectAnimation
|
||||
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: animationFile,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: icon
|
||||
)
|
||||
|
||||
let groupId = "popular"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
if hasPremium {
|
||||
let groupId = "popular"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent && !isQuickReactionSelection, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent && !isQuickReactionSelection, headerItem: nil, items: [resultItem]))
|
||||
let groupId = "recent"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
|
||||
if itemGroups[groupIndex].items.count >= maxRecentLineCount * 8 {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4934,6 +5001,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
let animationFile: TelegramMediaFile
|
||||
let icon: EmojiPagerContentComponent.Item.Icon
|
||||
|
||||
switch item.content {
|
||||
case let .builtin(value):
|
||||
if existingIds.contains(.builtin(value)) {
|
||||
@ -4949,12 +5018,20 @@ public final class EmojiPagerContentComponent: Component {
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
icon = .none
|
||||
case let .custom(file):
|
||||
if existingIds.contains(.custom(file.fileId.id)) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(.custom(file.fileId.id))
|
||||
animationFile = file
|
||||
|
||||
if !hasPremium {
|
||||
icon = .locked
|
||||
} else {
|
||||
icon = .none
|
||||
}
|
||||
}
|
||||
|
||||
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
|
||||
@ -4962,7 +5039,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: animationFile,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: icon
|
||||
)
|
||||
|
||||
let groupId = "popular"
|
||||
@ -5003,14 +5081,16 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
case let .text(text):
|
||||
resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: nil,
|
||||
content: .staticEmoji(text),
|
||||
itemFile: nil,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
}
|
||||
|
||||
@ -5032,7 +5112,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationData: nil,
|
||||
content: .staticEmoji(emojiString),
|
||||
itemFile: nil,
|
||||
subgroupId: subgroupId.rawValue
|
||||
subgroupId: subgroupId.rawValue,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
@ -5055,12 +5136,19 @@ public final class EmojiPagerContentComponent: Component {
|
||||
guard let item = entry.item as? StickerPackItem else {
|
||||
continue
|
||||
}
|
||||
|
||||
var icon: EmojiPagerContentComponent.Item.Icon = .none
|
||||
if isReactionSelection, !hasPremium {
|
||||
icon = .locked
|
||||
}
|
||||
|
||||
let animationData = EntityKeyboardAnimationData(file: item.file)
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: icon
|
||||
)
|
||||
|
||||
let supergroupId = entry.index.collectionId
|
||||
@ -5119,7 +5207,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
let supergroupId = featuredEmojiPack.info.id
|
||||
|
@ -114,7 +114,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
animationData: component.item,
|
||||
content: .animation(component.item),
|
||||
itemFile: nil,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
),
|
||||
context: component.context,
|
||||
attemptSynchronousLoad: false,
|
||||
|
@ -1053,31 +1053,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty {
|
||||
actions.reactionItems = topReactions.map(ReactionContextItem.reaction)
|
||||
/*if hasPremiumPlaceholder && !premiumConfiguration.isPremiumDisabled {
|
||||
actions.reactionItems.append(.premium)
|
||||
}*/
|
||||
|
||||
if !actions.reactionItems.isEmpty {
|
||||
let reactionItems: [AvailableReactions.Reaction] = actions.reactionItems.compactMap { item -> AvailableReactions.Reaction? in
|
||||
let reactionItems: [EmojiComponentReactionItem] = actions.reactionItems.compactMap { item -> EmojiComponentReactionItem? in
|
||||
switch item {
|
||||
case let .reaction(reaction):
|
||||
if let largeApplicationAnimation = reaction.largeApplicationAnimation {
|
||||
return AvailableReactions.Reaction(
|
||||
isEnabled: true,
|
||||
isPremium: false,
|
||||
value: reaction.reaction.rawValue,
|
||||
title: "",
|
||||
staticIcon: reaction.listAnimation,
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
selectAnimation: reaction.stillAnimation,
|
||||
activateAnimation: reaction.largeListAnimation,
|
||||
effectAnimation: largeApplicationAnimation,
|
||||
aroundAnimation: reaction.applicationAnimation,
|
||||
centerAnimation: reaction.listAnimation
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
@ -200,7 +200,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
let supergroupId = "featuredTop"
|
||||
@ -236,7 +237,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
let groupId = "saved"
|
||||
@ -263,7 +265,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.media,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
let groupId = "recent"
|
||||
@ -313,7 +316,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
let groupId = "premium"
|
||||
@ -345,7 +349,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
let groupId = "peerSpecific"
|
||||
@ -367,7 +372,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
let groupId = entry.index.collectionId
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
@ -419,7 +425,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: item.file,
|
||||
subgroupId: nil
|
||||
subgroupId: nil,
|
||||
icon: .none
|
||||
)
|
||||
|
||||
let supergroupId = featuredStickerPack.info.id
|
||||
|
@ -1769,6 +1769,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
private let cachedFaq = Promise<ResolvedUrl?>(nil)
|
||||
|
||||
private weak var copyProtectionTooltipController: TooltipController?
|
||||
weak var emojiStatusSelectionController: ViewController?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
var ready: Promise<Bool> {
|
||||
@ -3103,7 +3104,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
let animationCache = context.animationCache
|
||||
let animationRenderer = context.animationRenderer
|
||||
|
||||
strongSelf.controller?.present(EmojiStatusSelectionController(
|
||||
strongSelf.emojiStatusSelectionController?.dismiss()
|
||||
let emojiStatusSelectionController = EmojiStatusSelectionController(
|
||||
context: strongSelf.context,
|
||||
mode: .statusSelection,
|
||||
sourceView: sourceView,
|
||||
@ -3122,7 +3124,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
destinationItemView: { [weak sourceView] in
|
||||
return sourceView
|
||||
}
|
||||
), in: .window(.root))
|
||||
)
|
||||
strongSelf.emojiStatusSelectionController = emojiStatusSelectionController
|
||||
strongSelf.controller?.present(emojiStatusSelectionController, in: .window(.root))
|
||||
}
|
||||
} else {
|
||||
screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext)
|
||||
@ -8307,6 +8311,11 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
self.dismissAllTooltips()
|
||||
|
||||
if let emojiStatusSelectionController = self.controllerNode.emojiStatusSelectionController {
|
||||
self.controllerNode.emojiStatusSelectionController = nil
|
||||
emojiStatusSelectionController.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
|
@ -51,6 +51,11 @@ public enum UndoOverlayAction {
|
||||
}
|
||||
|
||||
public final class UndoOverlayController: ViewController {
|
||||
public enum Position {
|
||||
case top
|
||||
case bottom
|
||||
}
|
||||
|
||||
private let presentationData: PresentationData
|
||||
public var content: UndoOverlayContent {
|
||||
didSet {
|
||||
@ -58,6 +63,7 @@ public final class UndoOverlayController: ViewController {
|
||||
}
|
||||
}
|
||||
private let elevatedLayout: Bool
|
||||
private let position: Position
|
||||
private let animateInAsReplacement: Bool
|
||||
private var action: (UndoOverlayAction) -> Bool
|
||||
|
||||
@ -66,10 +72,11 @@ public final class UndoOverlayController: ViewController {
|
||||
|
||||
public var keepOnParentDismissal = false
|
||||
|
||||
public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, animateInAsReplacement: Bool = false, action: @escaping (UndoOverlayAction) -> Bool) {
|
||||
public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, position: Position = .bottom, animateInAsReplacement: Bool = false, action: @escaping (UndoOverlayAction) -> Bool) {
|
||||
self.presentationData = presentationData
|
||||
self.content = content
|
||||
self.elevatedLayout = elevatedLayout
|
||||
self.position = position
|
||||
self.animateInAsReplacement = animateInAsReplacement
|
||||
self.action = action
|
||||
|
||||
@ -83,7 +90,7 @@ public final class UndoOverlayController: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, action: { [weak self] value in
|
||||
self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, placementPosition: self.position, action: { [weak self] value in
|
||||
return self?.action(value) ?? false
|
||||
}, dismiss: { [weak self] in
|
||||
self?.dismiss()
|
||||
|
@ -20,6 +20,7 @@ import AccountContext
|
||||
|
||||
final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private let elevatedLayout: Bool
|
||||
private let placementPosition: UndoOverlayController.Position
|
||||
private var statusNode: RadialStatusNode?
|
||||
private let timerTextNode: ImmediateTextNode
|
||||
private let avatarNode: AvatarNode?
|
||||
@ -56,8 +57,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
private var fetchResourceDisposable: Disposable?
|
||||
|
||||
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
|
||||
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
|
||||
self.elevatedLayout = elevatedLayout
|
||||
self.placementPosition = placementPosition
|
||||
self.content = content
|
||||
|
||||
self.action = action
|
||||
@ -1099,12 +1101,23 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
contentHeight = max(49.0, contentHeight)
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
if self.elevatedLayout {
|
||||
insets.bottom += 49.0
|
||||
switch self.placementPosition {
|
||||
case .top:
|
||||
insets.top = layout.statusBarHeight ?? 0.0
|
||||
case .bottom:
|
||||
if self.elevatedLayout {
|
||||
insets.bottom += 49.0
|
||||
}
|
||||
}
|
||||
|
||||
var panelFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
||||
var panelWrapperFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
||||
|
||||
if case .top = self.placementPosition {
|
||||
panelFrame.origin.y = insets.top
|
||||
panelWrapperFrame.origin.y = insets.top
|
||||
}
|
||||
|
||||
let panelFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
||||
let panelWrapperFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
||||
transition.updateFrame(node: self.panelNode, frame: panelFrame)
|
||||
transition.updateFrame(node: self.panelWrapperNode, frame: panelWrapperFrame)
|
||||
self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
|
||||
|
Loading…
x
Reference in New Issue
Block a user