Reaction and status improvements

This commit is contained in:
Ali 2022-08-29 03:36:53 +04:00
parent 5e196704e0
commit 2495a30c33
18 changed files with 446 additions and 211 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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