Refactoring

This commit is contained in:
Ali 2021-09-20 23:07:38 +03:00
parent 6e5c503338
commit b778c66226
91 changed files with 290 additions and 3473 deletions

View File

@ -358,7 +358,7 @@ public final class CallListController: TelegramBaseController {
}
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: self, sourceNode: buttonNode.contentNode, keepInPlace: false, blurBackground: false)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: nil)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: self, sourceNode: buttonNode.contentNode, keepInPlace: false, blurBackground: false)), items: .single(ContextController.Items(items: items)), gesture: nil)
self.presentInGlobalOverlay(contextController)
}

View File

@ -839,12 +839,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
case let .groupReference(groupId, _, _, _, _):
let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId, controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
chatListController.navigationPresentation = .master
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId, chatListController: strongSelf) |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId, chatListController: strongSelf) |> map { ContextController.Items(items: $0) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
case let .peer(_, peer, _, _, _, _, _, _, promoInfo, _, _, _):
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(items: $0) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
}
@ -868,7 +868,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(items: $0) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
@ -1095,7 +1095,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})))
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode, keepInPlace: keepInPlace)), items: .single(ContextController.Items(items: items)), reactionItems: [], recognizer: nil, gesture: gesture)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode, keepInPlace: keepInPlace)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
})
}
@ -2842,7 +2842,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListTabBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), reactionItems: [], recognizer: nil, gesture: gesture)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListTabBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
})
}

View File

@ -737,7 +737,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return items
}
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], recognizer: nil, gesture: gesture)
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, recognizer: nil, gesture: gesture)
self.presentInGlobalOverlay?(controller, nil)
}
@ -797,7 +797,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
switch previewData {
case let .gallery(gallery):
gallery.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
strongSelf.presentInGlobalOverlay?(contextController, nil)
case .instantPage:
break

View File

@ -53,8 +53,8 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
var hideAuthor = false
var messageText: String
if let message = message {
if let messageMain = messageMainPeer(message) {
peer = messageMain
if let messageMain = messageMainPeer(EngineMessage(message)) {
peer = messageMain._asPeer()
} else {
peer = chatPeer.chatMainPeer
}
@ -261,12 +261,12 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
}
default:
hideAuthor = true
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) {
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: true) {
messageText = text
}
}
case _ as TelegramMediaExpiredContent:
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) {
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: true) {
messageText = text
}
case let poll as TelegramMediaPoll:

View File

@ -175,7 +175,7 @@ final class ContactsControllerNode: ASDisplayNode {
}
let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(peer.id), subject: nil, botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: contactContextMenuItems(context: self.context, peerId: peer.id, contactsController: contactsController) |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: contactContextMenuItems(context: self.context, peerId: peer.id, contactsController: contactsController) |> map { ContextController.Items(items: $0) }, gesture: gesture)
contactsController.presentInGlobalOverlay(contextController)
}

View File

@ -15,7 +15,6 @@ swift_library(
"//submodules/Display:Display",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TextSelectionNode:TextSelectionNode",
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
"//submodules/AppBundle:AppBundle",
],
visibility = [

View File

@ -4,7 +4,6 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
import TextSelectionNode
import ReactionSelectionNode
import TelegramCore
import SwiftSignalKit
@ -119,7 +118,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
private let source: ContextContentSource
private var items: Signal<ContextController.Items, NoError>
private let beginDismiss: (ContextMenuActionResult) -> Void
private let reactionSelected: (ReactionContextItem.Reaction) -> Void
private let beganAnimatingOut: () -> Void
private let attemptTransitionControllerIntoNavigation: () -> Void
fileprivate var dismissedForCancel: (() -> Void)?
@ -150,14 +148,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
private var contentAreaInScreenSpace: CGRect?
private let contentContainerNode: ContextContentContainerNode
private var actionsContainerNode: ContextActionsContainerNode
private var reactionContextNode: ReactionContextNode?
private var reactionContextNodeIsAnimatingOut = false
private var didCompleteAnimationIn = false
private var initialContinueGesturePoint: CGPoint?
private var didMoveFromInitialGesturePoint = false
private var highlightedActionNode: ContextActionNodeProtocol?
private var highlightedReaction: ReactionContextItem.Reaction?
private let hapticFeedback = HapticFeedback()
@ -168,12 +163,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
private let blurBackground: Bool
init(account: Account, controller: ContextController, presentationData: PresentationData, source: ContextContentSource, items: Signal<ContextController.Items, NoError>, reactionItems: [ReactionContextItem], beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, gesture: ContextGesture?, reactionSelected: @escaping (ReactionContextItem.Reaction) -> Void, beganAnimatingOut: @escaping () -> Void, attemptTransitionControllerIntoNavigation: @escaping () -> Void) {
init(account: Account, controller: ContextController, presentationData: PresentationData, source: ContextContentSource, items: Signal<ContextController.Items, NoError>, beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, gesture: ContextGesture?, beganAnimatingOut: @escaping () -> Void, attemptTransitionControllerIntoNavigation: @escaping () -> Void) {
self.presentationData = presentationData
self.source = source
self.items = items
self.beginDismiss = beginDismiss
self.reactionSelected = reactionSelected
self.beganAnimatingOut = beganAnimatingOut
self.attemptTransitionControllerIntoNavigation = attemptTransitionControllerIntoNavigation
self.gesture = gesture
@ -237,13 +231,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
feedbackTap?()
}, blurBackground: blurBackground)
if !reactionItems.isEmpty {
let reactionContextNode = ReactionContextNode(account: account, theme: presentationData.theme, items: reactionItems)
self.reactionContextNode = reactionContextNode
} else {
self.reactionContextNode = nil
}
super.init()
feedbackTap = { [weak self] in
@ -265,7 +252,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.scrollNode.addSubnode(self.dismissAccessibilityArea)
self.scrollNode.addSubnode(self.actionsContainerNode)
self.reactionContextNode.flatMap(self.addSubnode)
if let recognizer = recognizer {
recognizer.externalUpdated = { [weak self, weak recognizer] view, point in
@ -298,24 +284,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
strongSelf.hapticFeedback.tap()
}
}
if let reactionContextNode = strongSelf.reactionContextNode {
let highlightedReaction = reactionContextNode.reaction(at: strongSelf.view.convert(localPoint, to: reactionContextNode.view)).flatMap { value -> ReactionContextItem.Reaction? in
switch value {
case .like:
return .like
case .unlike:
return .unlike
}
}
if strongSelf.highlightedReaction != highlightedReaction {
strongSelf.highlightedReaction = highlightedReaction
reactionContextNode.setHighlightedReaction(highlightedReaction)
if let _ = highlightedReaction {
strongSelf.hapticFeedback.tap()
}
}
}
}
}
}
@ -330,20 +298,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
strongSelf.highlightedActionNode = nil
highlightedActionNode.performAction()
}
if let _ = strongSelf.reactionContextNode {
if let reaction = strongSelf.highlightedReaction {
strongSelf.reactionSelected(reaction)
}
}
} else {
if let highlightedActionNode = strongSelf.highlightedActionNode {
strongSelf.highlightedActionNode = nil
highlightedActionNode.setIsHighlighted(false)
}
if let reactionContextNode = strongSelf.reactionContextNode, let _ = strongSelf.highlightedReaction {
strongSelf.highlightedReaction = nil
reactionContextNode.setHighlightedReaction(nil)
}
}
}
}
@ -382,24 +341,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
strongSelf.hapticFeedback.tap()
}
}
if let reactionContextNode = strongSelf.reactionContextNode {
let highlightedReaction = reactionContextNode.reaction(at: strongSelf.view.convert(localPoint, to: reactionContextNode.view)).flatMap { value -> ReactionContextItem.Reaction? in
switch value {
case .like:
return .like
case .unlike:
return .unlike
}
}
if strongSelf.highlightedReaction != highlightedReaction {
strongSelf.highlightedReaction = highlightedReaction
reactionContextNode.setHighlightedReaction(highlightedReaction)
if let _ = highlightedReaction {
strongSelf.hapticFeedback.tap()
}
}
}
}
}
}
@ -414,39 +355,16 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
strongSelf.highlightedActionNode = nil
highlightedActionNode.performAction()
}
if let _ = strongSelf.reactionContextNode {
if let reaction = strongSelf.highlightedReaction {
strongSelf.reactionSelected(reaction)
}
}
} else {
if let highlightedActionNode = strongSelf.highlightedActionNode {
strongSelf.highlightedActionNode = nil
highlightedActionNode.setIsHighlighted(false)
}
if let reactionContextNode = strongSelf.reactionContextNode, let _ = strongSelf.highlightedReaction {
strongSelf.highlightedReaction = nil
reactionContextNode.setHighlightedReaction(nil)
}
}
}
}
}
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.reactionSelected = { [weak self] reaction in
guard let _ = self else {
return
}
switch reaction {
case .like:
reactionSelected(.like)
case .unlike:
reactionSelected(.unlike)
}
}
}
self.itemsDisposable.set((items
|> deliverOnMainQueue).start(next: { [weak self] items in
self?.setItems(items: items, minHeight: nil, previousActionsTransition: .scale)
@ -527,26 +445,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
strongSelf.updateLayout(layout: validLayout, transition: .animated(duration: 0.2 * animationDurationFactor, curve: .easeInOut), previousActionsContainerNode: nil)
}
}
takenViewInfo.contentContainingNode.updateDistractionFreeMode = { [weak self] value in
guard let strongSelf = self, let reactionContextNode = strongSelf.reactionContextNode else {
return
}
if value {
if !reactionContextNode.alpha.isZero {
reactionContextNode.alpha = 0.0
reactionContextNode.allowsGroupOpacity = true
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3 * animationDurationFactor, completion: { [weak reactionContextNode] _ in
reactionContextNode?.allowsGroupOpacity = false
})
}
} else if reactionContextNode.alpha != 1.0 {
reactionContextNode.alpha = 1.0
reactionContextNode.allowsGroupOpacity = true
reactionContextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3 * animationDurationFactor, completion: { [weak reactionContextNode] _ in
reactionContextNode?.allowsGroupOpacity = false
})
}
}
self.contentAreaInScreenSpace = takenViewInfo.contentAreaInScreenSpace
self.contentContainerNode.addSubnode(takenViewInfo.contentContainingNode.contentNode)
@ -695,10 +593,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
localContentSourceFrame = localSourceFrame
}
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateIn(from: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size))
}
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y + actionsOffset)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: actionsDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
let contentContainerOffset = CGPoint(x: localContentSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localContentSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY)
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: contentDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { [weak self] _ in
@ -968,10 +862,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
contentParentNode.updateAbsoluteRect?(self.contentContainerNode.frame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y + contentContainerOffset.y), self.bounds.size)
contentParentNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: -contentContainerOffset.y), transitionCurve, transitionDuration)
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size), animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
}
contentParentNode.willUpdateIsExtractedToContextPreview?(false, .animated(duration: 0.2, curve: .easeInOut))
} else {
if let snapshotView = contentParentNode.contentNode.view.snapshotContentTree(keepTransform: true) {
@ -990,10 +880,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
contentParentNode.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
contentParentNode.willUpdateIsExtractedToContextPreview?(false, .animated(duration: 0.2, curve: .easeInOut))
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
}
}
case let .controller(source):
guard let maybeContentNode = self.contentContainerNode.contentNode, case let .controller(controller) = maybeContentNode else {
@ -1132,42 +1018,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
completedContentNode = true
intermediateCompletion()
})
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
}
}
}
}
func animateOutToReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: @escaping () -> Void) {
guard let reactionContextNode = self.reactionContextNode else {
self.animateOut(result: .default, completion: completion)
return
}
var contentCompleted = false
var reactionCompleted = false
let intermediateCompletion: () -> Void = {
if contentCompleted && reactionCompleted {
completion()
}
}
self.reactionContextNodeIsAnimatingOut = true
self.animateOut(result: .default, completion: {
contentCompleted = true
intermediateCompletion()
})
reactionContextNode.animateOutToReaction(value: value, targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: hideNode, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.reactionContextNode?.removeFromSupernode()
strongSelf.reactionContextNode = nil
reactionCompleted = true
intermediateCompletion()
})
}
func getActionsMinHeight() -> ContextController.ActionsHeight? {
if !self.actionsContainerNode.bounds.height.isZero {
@ -1281,10 +1134,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0
var contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
if let _ = self.reactionContextNode {
contentTopInset += 34.0
}
let contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
let actionsBottomInset: CGFloat = 11.0
if let contentNode = self.contentContainerNode.contentNode {
@ -1506,12 +1357,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y)
contentParentNode.updateAbsoluteRect?(absoluteContentRect, layout.size)
if let reactionContextNode = self.reactionContextNode {
let insets = layout.insets(options: [.statusBar])
transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size))
reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX + contentParentNode.contentRect.minX, y: absoluteContentRect.minY + contentParentNode.contentRect.minY), size: contentParentNode.contentRect.size), transition: transition)
}
}
case let .controller(contentParentNode):
var projectedFrame: CGRect = convertFrame(contentParentNode.sourceNode.bounds, from: contentParentNode.sourceNode.view, to: self.view)
@ -1642,14 +1487,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY)
}
}
let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y)
if let reactionContextNode = self.reactionContextNode {
let insets = layout.insets(options: [.statusBar])
transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size))
reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX, y: absoluteContentRect.minY), size: contentSize), transition: transition)
}
}
}
}
@ -1734,11 +1571,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
if !self.bounds.contains(point) {
return nil
}
if let reactionContextNode = self.reactionContextNode {
if let result = reactionContextNode.hitTest(self.view.convert(point, to: reactionContextNode.view), with: event) {
return result
}
}
let mappedPoint = self.view.convert(point, to: self.scrollNode.view)
if let maybeContentNode = self.contentContainerNode.contentNode {
switch maybeContentNode {
@ -1918,7 +1750,6 @@ public final class ContextController: ViewController, StandalonePresentableContr
private var presentationData: PresentationData
private let source: ContextContentSource
private var items: Signal<ContextController.Items, NoError>
private var reactionItems: [ReactionContextItem]
private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
@ -1934,8 +1765,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
private var controllerNode: ContextControllerNode {
return self.displayNode as! ContextControllerNode
}
public var reactionSelected: ((ReactionContextItem.Reaction) -> Void)?
public var dismissed: (() -> Void)?
public var dismissedForCancel: (() -> Void)? {
didSet {
@ -1948,12 +1778,11 @@ public final class ContextController: ViewController, StandalonePresentableContr
private var shouldBeDismissedDisposable: Disposable?
public init(account: Account, presentationData: PresentationData, source: ContextContentSource, items: Signal<ContextController.Items, NoError>, reactionItems: [ReactionContextItem], recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil) {
public init(account: Account, presentationData: PresentationData, source: ContextContentSource, items: Signal<ContextController.Items, NoError>, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil) {
self.account = account
self.presentationData = presentationData
self.source = source
self.items = items
self.reactionItems = reactionItems
self.recognizer = recognizer
self.gesture = gesture
@ -2004,14 +1833,9 @@ public final class ContextController: ViewController, StandalonePresentableContr
}
override public func loadDisplayNode() {
self.displayNode = ContextControllerNode(account: self.account, controller: self, presentationData: self.presentationData, source: self.source, items: self.items, reactionItems: self.reactionItems, beginDismiss: { [weak self] result in
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, reactionSelected: { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.reactionSelected?(value)
}, beganAnimatingOut: { [weak self] in
}, recognizer: self.recognizer, gesture: self.gesture, beganAnimatingOut: { [weak self] in
self?.statusBar.statusBarStyle = .Ignore
}, attemptTransitionControllerIntoNavigation: { [weak self] in
guard let strongSelf = self else {
@ -2096,15 +1920,4 @@ public final class ContextController: ViewController, StandalonePresentableContr
override public func dismiss(completion: (() -> Void)? = nil) {
self.dismiss(result: .default, completion: completion)
}
public func dismissWithReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: (() -> Void)?) {
if !self.wasDismissed {
self.wasDismissed = true
self.controllerNode.animateOutToReaction(value: value, targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: hideNode, completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
})
self.dismissed?()
}
}
}

View File

@ -4,7 +4,6 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
import TextSelectionNode
import ReactionSelectionNode
import TelegramCore
import SwiftSignalKit

View File

@ -97,9 +97,9 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati
if let action = media as? TelegramMediaAction {
switch action.action {
case let .photoUpdated(image):
if let peer = messageMainPeer(message), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")])
let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in
if let peer = messageMainPeer(EngineMessage(message)), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer._asPeer(), message.timestamp, nil, message.id, image.immediateThumbnailData, "action")])
let galleryController = AvatarGalleryController(context: context, peer: peer._asPeer(), sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in
})
return .chatAvatars(galleryController, image)

View File

@ -954,7 +954,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
var generalMessageContentKind: MessageContentKind?
for message in messages {
let currentKind = messageContentKind(contentSettings: strongSelf.context.currentContentSettings.with { $0 }, message: message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: strongSelf.context.account.peerId)
let currentKind = messageContentKind(contentSettings: strongSelf.context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: strongSelf.context.account.peerId)
if generalMessageContentKind == nil || generalMessageContentKind == currentKind {
generalMessageContentKind = currentKind
} else {
@ -1103,7 +1103,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
var messageContentKinds = Set<MessageContentKindKey>()
for message in messages {
let currentKind = messageContentKind(contentSettings: strongSelf.context.currentContentSettings.with { $0 }, message: message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: strongSelf.context.account.peerId)
let currentKind = messageContentKind(contentSettings: strongSelf.context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: strongSelf.context.account.peerId)
if beganContentKindScanning && currentKind != generalMessageContentKind {
generalMessageContentKind = nil
} else if !beganContentKindScanning || currentKind == generalMessageContentKind {

View File

@ -2032,7 +2032,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
self.isShowingContextMenuPromise.set(true)
controller.presentInGlobalOverlay(contextController)

View File

@ -300,7 +300,7 @@ public final class SecretMediaPreviewController: ViewController {
}, transition: .immediate)
} else {
let contentNode = SecretMediaPreviewFooterContentNode()
let peerTitle = messageMainPeer(message).flatMap(EnginePeer.init)?.compactDisplayTitle ?? ""
let peerTitle = messageMainPeer(EngineMessage(message))?.compactDisplayTitle ?? ""
let text: String
if let file = media as? TelegramMediaFile {
if file.isAnimated {

View File

@ -418,7 +418,7 @@ public final class InviteLinkInviteController: ViewController {
})
})))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController)
}, copyLink: { [weak self] invite in
UIPasteboard.general.string = invite.link

View File

@ -550,7 +550,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, createLink: {
let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: nil, completion: { invite in
@ -714,7 +714,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, blurBackground: true)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, blurBackground: true)), items: .single(ContextController.Items(items: items)), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, openAdmin: { admin in
let controller = inviteLinkListController(context: context, peerId: peerId, admin: admin)

View File

@ -565,7 +565,7 @@ public final class InviteLinkViewController: ViewController {
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController)
})

View File

@ -1050,7 +1050,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
})
})))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, manageInviteLinks: {
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)

View File

@ -13,7 +13,6 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",

View File

@ -3,7 +3,6 @@ import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import MapKit
import TelegramPresentationData
@ -27,14 +26,15 @@ import TelegramStringFormatting
private let maxUsersDisplayedLimit: Int32 = 5
private struct PeerNearbyEntry {
let peer: (Peer, CachedPeerData?)
let peer: EnginePeer
let memberCount: Int32?
let expires: Int32
let distance: Int32
}
private func arePeersNearbyEqual(_ lhs: PeerNearbyEntry?, _ rhs: PeerNearbyEntry?) -> Bool {
if let lhs = lhs, let rhs = rhs {
return lhs.peer.0.isEqual(rhs.peer.0) && lhs.expires == rhs.expires && lhs.distance == rhs.distance
return lhs.peer == rhs.peer && lhs.expires == rhs.expires && lhs.distance == rhs.distance
} else {
return (lhs != nil) == (rhs != nil)
}
@ -45,7 +45,7 @@ private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNear
return false
}
for i in 0 ..< lhs.count {
if !lhs[i].peer.0.isEqual(rhs[i].peer.0) || lhs[i].expires != rhs[i].expires || lhs[i].distance != rhs[i].distance {
if lhs[i].peer != rhs[i].peer || lhs[i].expires != rhs[i].expires || lhs[i].distance != rhs[i].distance {
return false
}
}
@ -55,13 +55,13 @@ private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNear
private final class PeersNearbyControllerArguments {
let context: AccountContext
let toggleVisibility: (Bool) -> Void
let openProfile: (Peer, Int32) -> Void
let openChat: (Peer) -> Void
let openProfile: (EnginePeer, Int32) -> Void
let openChat: (EnginePeer) -> Void
let openCreateGroup: (Double, Double, String?) -> Void
let contextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
let contextAction: (EnginePeer, ASDisplayNode, ContextGesture?) -> Void
let expandUsers: () -> Void
init(context: AccountContext, toggleVisibility: @escaping (Bool) -> Void, openProfile: @escaping (Peer, Int32) -> Void, openChat: @escaping (Peer) -> Void, openCreateGroup: @escaping (Double, Double, String?) -> Void, contextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, expandUsers: @escaping () -> Void) {
init(context: AccountContext, toggleVisibility: @escaping (Bool) -> Void, openProfile: @escaping (EnginePeer, Int32) -> Void, openChat: @escaping (EnginePeer) -> Void, openCreateGroup: @escaping (Double, Double, String?) -> Void, contextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void, expandUsers: @escaping () -> Void) {
self.context = context
self.toggleVisibility = toggleVisibility
self.openProfile = openProfile
@ -226,13 +226,13 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
})
case let .user(_, _, strings, dateTimeFormat, nameDisplayOrder, peer):
var text = strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string
let isSelfPeer = peer.peer.0.id == arguments.context.account.peerId
let isSelfPeer = peer.peer.id == arguments.context.account.peerId
if isSelfPeer {
text = strings.PeopleNearby_VisibleUntil(humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: peer.expires).string).string
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer.peer.0), aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: !isSelfPeer, sectionId: self.section, action: {
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: !isSelfPeer, sectionId: self.section, action: {
if !isSelfPeer {
arguments.openProfile(peer.peer.0, peer.distance)
arguments.openProfile(peer.peer, peer.distance)
}
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil, hasTopGroupInset: false, tag: nil)
case let .expand(theme, title):
@ -249,29 +249,29 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
})
case let .group(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted):
var text: ItemListPeerItemText
if let cachedData = peer.peer.1 as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount {
if let memberCount = peer.memberCount {
text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string), \(memberCount > 0 ? strings.Conversation_StatusMembers(memberCount) : strings.PeopleNearby_NoMembers)", .secondary)
} else {
text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string, .secondary)
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer.peer.0), aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: {
arguments.openChat(peer.peer.0)
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: {
arguments.openChat(peer.peer)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: { node, gesture in
arguments.contextAction(peer.peer.0, node, gesture)
arguments.contextAction(peer.peer, node, gesture)
}, hasTopGroupInset: false, tag: nil)
case let .channelsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .channel(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted):
var text: ItemListPeerItemText
if let cachedData = peer.peer.1 as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount {
if let memberCount = peer.memberCount {
text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string), \(strings.Conversation_StatusSubscribers(memberCount))", .secondary)
} else {
text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string, .secondary)
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer.peer.0), aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: {
arguments.openChat(peer.peer.0)
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: {
arguments.openChat(peer.peer)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: { node, gesture in
arguments.contextAction(peer.peer.0, node, gesture)
arguments.contextAction(peer.peer, node, gesture)
}, hasTopGroupInset: false, tag: nil)
}
}
@ -282,12 +282,12 @@ private struct PeersNearbyData: Equatable {
let longitude: Double
let address: String?
let visible: Bool
let accountPeerId: PeerId
let accountPeerId: EnginePeer.Id
let users: [PeerNearbyEntry]
let groups: [PeerNearbyEntry]
let channels: [PeerNearbyEntry]
init(latitude: Double, longitude: Double, address: String?, visible: Bool, accountPeerId: PeerId, users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
init(latitude: Double, longitude: Double, address: String?, visible: Bool, accountPeerId: EnginePeer.Id, users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
self.latitude = latitude
self.longitude = longitude
self.address = address
@ -314,7 +314,7 @@ private func peersNearbyControllerEntries(data: PeersNearbyData?, state: PeersNe
if let data = data, !data.users.isEmpty {
var index: Int32 = 0
var users = data.users.filter { $0.peer.0.id != data.accountPeerId }
var users = data.users.filter { $0.peer.id != data.accountPeerId }
var effectiveExpanded = expanded
if users.count > maxUsersDisplayedLimit && !expanded {
users = Array(users.prefix(Int(maxUsersDisplayedLimit)))
@ -332,7 +332,7 @@ private func peersNearbyControllerEntries(data: PeersNearbyData?, state: PeersNe
}
}
var highlightedPeerId: PeerId?
var highlightedPeerId: EnginePeer.Id?
if let chatLocation = chatLocation, case let .peer(peerId) = chatLocation {
highlightedPeerId = peerId
}
@ -342,7 +342,7 @@ private func peersNearbyControllerEntries(data: PeersNearbyData?, state: PeersNe
if let data = data, !data.groups.isEmpty {
var i: Int32 = 0
for group in data.groups {
entries.append(.group(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, group, highlightedPeerId == group.peer.0.id))
entries.append(.group(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, group, highlightedPeerId == group.peer.id))
i += 1
}
}
@ -350,7 +350,7 @@ private func peersNearbyControllerEntries(data: PeersNearbyData?, state: PeersNe
if let data = data, !data.channels.isEmpty {
var i: Int32 = 0
for channel in data.channels {
entries.append(.channel(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, channel, highlightedPeerId == channel.peer.0.id))
entries.append(.channel(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, channel, highlightedPeerId == channel.peer.id))
i += 1
}
}
@ -386,7 +386,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
}
}
private func peerNearbyContextMenuItems(context: AccountContext, peerId: PeerId, present: @escaping (ViewController) -> Void) -> Signal<[ContextMenuItem], NoError> {
private func peerNearbyContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, present: @escaping (ViewController) -> Void) -> Signal<[ContextMenuItem], NoError> {
return context.account.postbox.transaction { _ -> [ContextMenuItem] in
let items: [ContextMenuItem] = []
@ -409,8 +409,8 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
var replaceTopControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
var navigateToProfileImpl: ((Peer, Int32) -> Void)?
var navigateToChatImpl: ((Peer) -> Void)?
var navigateToProfileImpl: ((EnginePeer, Int32) -> Void)?
var navigateToChatImpl: ((EnginePeer) -> Void)?
let actionsDisposable = DisposableSet()
let checkCreationAvailabilityDisposable = MetaDisposable()
@ -494,7 +494,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
chatController.canReadHistory.set(false)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: peerNearbyContextMenuItems(context: context, peerId: peer.id, present: { c in
presentControllerImpl?(c, nil)
}) |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
}) |> map { ContextController.Items(items: $0) }, gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, expandUsers: {
expandedPromise.set(true)
@ -533,16 +533,16 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
case let .peer(id, expires, distance):
if let peer = transaction.getPeer(id) {
if id.namespace == Namespaces.Peer.CloudUser {
users.append(PeerNearbyEntry(peer: (peer, nil), expires: expires, distance: distance))
users.append(PeerNearbyEntry(peer: EnginePeer(peer), memberCount: nil, expires: expires, distance: distance))
} else {
let cachedData = transaction.getPeerCachedData(peerId: id) as? CachedChannelData
groups.append(PeerNearbyEntry(peer: (peer, cachedData), expires: expires, distance: distance))
groups.append(PeerNearbyEntry(peer: EnginePeer(peer), memberCount: cachedData?.participantsSummary.memberCount, expires: expires, distance: distance))
}
}
case let .selfPeer(expires):
visible = true
if let peer = transaction.getPeer(context.account.peerId) {
users.append(PeerNearbyEntry(peer: (peer, nil), expires: expires, distance: 0))
users.append(PeerNearbyEntry(peer: EnginePeer(peer), memberCount: nil, expires: expires, distance: 0))
}
}
}
@ -598,7 +598,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
controller?.clearItemNodesHighlight(animated: true)
}
navigateToProfileImpl = { [weak controller] peer, distance in
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false) {
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false) {
navigationController.pushViewController(controller)
}
}

View File

@ -1,24 +0,0 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ReactionSelectionNode",
module_name = "ReactionSelectionNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/TelegramPresentationData:TelegramPresentationData",
],
visibility = [
"//visibility:public",
],
)

View File

@ -1,323 +0,0 @@
/*import Foundation
import AsyncDisplayKit
import AnimatedStickerNode
import Display
import Postbox
import TelegramCore
import TelegramPresentationData
import AppBundle
private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(foreground.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setShadow(offset: CGSize(), blur: 1.0, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
final class ReactionAttachedNode: ASDisplayNode {
private let account: Account
private let theme: PresentationTheme
private let reactions: [ReactionGestureItem]
private let backgroundNode: ASImageNode
private let backgroundShadowNode: ASImageNode
private let bubbleNodes: [(ASImageNode, ASImageNode)]
private var reactionNodes: [ReactionNode] = []
private var hasSelectedNode = false
private let hapticFeedback = HapticFeedback()
private var shadowBlur: CGFloat = 8.0
private var minimizedReactionSize: CGFloat = 30.0
private var maximizedReactionSize: CGFloat = 60.0
private var smallCircleSize: CGFloat = 8.0
public init(account: Account, theme: PresentationTheme, reactions: [ReactionGestureItem]) {
self.account = account
self.theme = theme
self.reactions = reactions
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundShadowNode = ASImageNode()
self.backgroundShadowNode.displaysAsynchronously = false
self.backgroundShadowNode.displayWithoutProcessing = true
self.bubbleNodes = (0 ..< 2).map { i -> (ASImageNode, ASImageNode) in
let imageNode = ASImageNode()
imageNode.displaysAsynchronously = false
imageNode.displayWithoutProcessing = true
let shadowNode = ASImageNode()
shadowNode.displaysAsynchronously = false
shadowNode.displayWithoutProcessing = true
return (imageNode, shadowNode)
}
super.init()
self.bubbleNodes.forEach { _, shadow in
self.addSubnode(shadow)
}
self.addSubnode(self.backgroundShadowNode)
self.bubbleNodes.forEach { foreground, _ in
self.addSubnode(foreground)
}
self.addSubnode(self.backgroundNode)
}
func updateLayout(constrainedSize: CGSize, startingPoint: CGPoint, offsetFromStart: CGFloat, isInitial: Bool) {
let initialAnchorX = startingPoint.x
if isInitial && self.reactionNodes.isEmpty {
let availableContentWidth = constrainedSize.width
var minimizedReactionSize = (availableContentWidth - self.maximizedReactionSize) / (CGFloat(self.reactions.count - 1) + CGFloat(self.reactions.count + 1) * 0.2)
minimizedReactionSize = max(16.0, floor(minimizedReactionSize))
minimizedReactionSize = min(30.0, minimizedReactionSize)
self.minimizedReactionSize = minimizedReactionSize
self.shadowBlur = floor(minimizedReactionSize * 0.26)
self.smallCircleSize = 8.0
let backgroundHeight = floor(minimizedReactionSize * 1.4)
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur)
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur)
for i in 0 ..< self.bubbleNodes.count {
self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
}
self.reactionNodes = self.reactions.map { reaction -> ReactionNode in
return ReactionNode(account: self.account, theme: self.theme, reaction: reaction, maximizedReactionSize: self.maximizedReactionSize, loadFirstFrame: true)
}
self.reactionNodes.forEach(self.addSubnode(_:))
}
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.4)
let reactionSpacing: CGFloat = floor(self.minimizedReactionSize * 0.2)
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
let contentWidth: CGFloat = CGFloat(self.reactionNodes.count - 1) * (minimizedReactionSize) + maximizedReactionSize + CGFloat(self.reactionNodes.count + 1) * reactionSpacing
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
backgroundFrame = backgroundFrame.offsetBy(dx: initialAnchorX - contentWidth + backgroundHeight / 2.0, dy: startingPoint.y - backgroundHeight - 16.0)
backgroundFrame.origin.x = max(0.0, backgroundFrame.minX)
backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX)
self.backgroundNode.frame = backgroundFrame
self.backgroundShadowNode.frame = backgroundFrame
let anchorMinX = backgroundFrame.minX + shadowBlur + backgroundHeight / 2.0
let anchorMaxX = backgroundFrame.maxX - shadowBlur - backgroundHeight / 2.0
let anchorX = max(anchorMinX, min(anchorMaxX, offsetFromStart))
var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSpacing
if offsetFromStart > backgroundFrame.maxX - shadowBlur || offsetFromStart < backgroundFrame.minX {
self.hasSelectedNode = false
} else {
self.hasSelectedNode = true
}
var maximizedIndex = Int(((anchorX - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count))
maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex))
for iterationIndex in 0 ..< self.reactionNodes.count {
var i = iterationIndex
let isMaximized = i == maximizedIndex
let reactionSize: CGFloat
if isMaximized {
reactionSize = maximizedReactionSize
} else {
reactionSize = minimizedReactionSize
}
let transition: ContainedViewLayoutTransition
if isInitial {
transition = .immediate
} else {
transition = .animated(duration: 0.18, curve: .easeInOut)
}
if self.reactionNodes[i].isMaximized != isMaximized {
self.reactionNodes[i].isMaximized = isMaximized
self.reactionNodes[i].updateIsAnimating(isMaximized, animated: !isInitial)
if isMaximized && !isInitial {
self.hapticFeedback.tap()
}
}
var reactionFrame = CGRect(origin: CGPoint(x: reactionX, y: backgroundFrame.maxY - shadowBlur - minimizedReactionVerticalInset - reactionSize), size: CGSize(width: reactionSize, height: reactionSize))
if isMaximized {
reactionFrame.origin.x -= 9.0
reactionFrame.size.width += 18.0
}
self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 18.0), transition: transition, displayText: isMaximized)
transition.updateFrame(node: self.reactionNodes[i], frame: reactionFrame, beginWithCurrentState: true)
reactionX += reactionSize + reactionSpacing
}
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - self.smallCircleSize - shadowBlur, y: backgroundFrame.maxY - shadowBlur - self.smallCircleSize - shadowBlur), size: CGSize(width: self.smallCircleSize * 2.0 + shadowBlur * 2.0, height: self.smallCircleSize * 2.0 + shadowBlur * 2.0))
self.bubbleNodes[1].0.frame = mainBubbleFrame
self.bubbleNodes[1].1.frame = mainBubbleFrame
let secondaryBubbleFrame = CGRect(origin: CGPoint(x: mainBubbleFrame.midX - 10.0 - (self.smallCircleSize + shadowBlur * 2.0) / 2.0, y: mainBubbleFrame.midY + 10.0 - (self.smallCircleSize + shadowBlur * 2.0) / 2.0), size: CGSize(width: self.smallCircleSize + shadowBlur * 2.0, height: self.smallCircleSize + shadowBlur * 2.0))
self.bubbleNodes[0].0.frame = secondaryBubbleFrame
self.bubbleNodes[0].1.frame = secondaryBubbleFrame
}
func animateIn() {
self.bubbleNodes[1].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[1].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[0].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[0].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
let backgroundOffset = CGPoint(x: -(self.backgroundNode.frame.width - shadowBlur) / 2.0 + 42.0, y: (self.backgroundNode.frame.height - shadowBlur) / 2.0)
let damping: CGFloat = 100.0
for i in 0 ..< self.reactionNodes.count {
let animationOffset: Double = 1.0 - Double(i) / Double(self.reactionNodes.count - 1)
var nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.minX + shadowBlur) / 2.0 - 42.0, y: self.reactionNodes[i].frame.minY - self.backgroundNode.frame.maxY - shadowBlur)
nodeOffset.x = -nodeOffset.x
nodeOffset.y = 30.0
self.reactionNodes[i].layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5 + animationOffset * 0.28, initialVelocity: 0.0, damping: damping)
self.reactionNodes[i].layer.animateSpring(from: NSValue(cgPoint: nodeOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
}
self.backgroundNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
self.backgroundNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
self.backgroundShadowNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
self.backgroundShadowNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
}
func animateOut(into targetNode: ASImageNode?, hideTarget: Bool, completion: @escaping () -> Void) {
self.hapticFeedback.prepareTap()
var completedContainer = false
var completedTarget = true
let intermediateCompletion: () -> Void = {
if completedContainer && completedTarget {
completion()
}
}
if let targetNode = targetNode {
for i in 0 ..< self.reactionNodes.count {
if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized {
if let snapshotView = self.reactionNodes[i].view.snapshotContentTree() {
let targetSnapshotView = UIImageView()
targetSnapshotView.image = targetNode.image
targetSnapshotView.frame = self.view.convert(targetNode.bounds, from: targetNode.view)
self.reactionNodes[i].isHidden = true
self.view.addSubview(targetSnapshotView)
self.view.addSubview(snapshotView)
completedTarget = false
let targetPosition = self.view.convert(targetNode.bounds.center, from: targetNode.view)
let duration: Double = 0.3
if hideTarget {
targetNode.isHidden = true
}
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
targetSnapshotView.layer.animateScale(from: snapshotView.bounds.width / targetSnapshotView.bounds.width, to: 0.5, duration: 0.3, removeOnCompletion: false)
let sourcePoint = snapshotView.center
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - 30.0)
let x1 = sourcePoint.x
let y1 = sourcePoint.y
let x2 = midPoint.x
let y2 = midPoint.y
let x3 = targetPosition.x
let y3 = targetPosition.y
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
var keyframes: [AnyObject] = []
for i in 0 ..< 10 {
let k = CGFloat(i) / CGFloat(10 - 1)
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
let y = a * x * x + b * x + c
keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y)))
}
snapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.hapticFeedback.tap()
}
completedTarget = true
if hideTarget {
targetNode.isHidden = false
targetNode.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0)
}
intermediateCompletion()
})
targetSnapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false)
snapshotView.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / snapshotView.bounds.width, duration: 0.3, removeOnCompletion: false)
}
break
}
}
}
self.backgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completedContainer = true
intermediateCompletion()
})
for (node, shadow) in self.bubbleNodes {
node.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
shadow.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
shadow.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
for i in 0 ..< self.reactionNodes.count {
self.reactionNodes[i].layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.reactionNodes[i].layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
}
func selectedReaction() -> ReactionGestureItem? {
if !self.hasSelectedNode {
return nil
}
for i in 0 ..< self.reactionNodes.count {
if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized {
return self.reactionNodes[i].reaction
}
}
return nil
}
}
*/

View File

@ -1,523 +0,0 @@
import Foundation
import AsyncDisplayKit
import Display
import AnimatedStickerNode
import TelegramCore
import TelegramPresentationData
public final class ReactionContextItem {
public enum Reaction {
case like
case unlike
}
public let reaction: ReactionContextItem.Reaction
public init(reaction: ReactionContextItem.Reaction) {
self.reaction = reaction
}
}
private let largeCircleSize: CGFloat = 16.0
private let smallCircleSize: CGFloat = 8.0
private func generateBackgroundImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(foreground.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(shadowBlur + diameter / 2.0), topCapHeight: Int(shadowBlur + diameter / 2.0))
}
private func generateBackgroundShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter * 2.0 + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(shadow.cgColor)
context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur + diameter, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.fill(CGRect(origin: CGPoint(x: shadowBlur + diameter / 2.0, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur + diameter, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.fill(CGRect(origin: CGPoint(x: shadowBlur + diameter / 2.0, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(foreground.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(shadow.cgColor)
context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setShadow(offset: CGSize(), blur: 1.0, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
public final class ReactionContextNode: ASDisplayNode {
private let theme: PresentationTheme
private let items: [ReactionContextItem]
private let backgroundNode: ASImageNode
private let backgroundShadowNode: ASImageNode
private let backgroundContainerNode: ASDisplayNode
private let largeCircleNode: ASImageNode
private let largeCircleShadowNode: ASImageNode
private let smallCircleNode: ASImageNode
private let smallCircleShadowNode: ASImageNode
private let contentContainer: ASDisplayNode
private var itemNodes: [ReactionNode] = []
private let disclosureButton: HighlightTrackingButtonNode
private var isExpanded: Bool = true
private var highlightedReaction: ReactionContextItem.Reaction?
private var validLayout: (CGSize, UIEdgeInsets, CGRect)?
public var reactionSelected: ((ReactionGestureItem) -> Void)?
private let hapticFeedback = HapticFeedback()
public init(account: Account, theme: PresentationTheme, items: [ReactionContextItem]) {
self.theme = theme
self.items = items
let shadowBlur: CGFloat = 5.0
self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundShadowNode = ASImageNode()
self.backgroundShadowNode.displayWithoutProcessing = true
self.backgroundShadowNode.displaysAsynchronously = false
self.backgroundContainerNode = ASDisplayNode()
self.backgroundContainerNode.allowsGroupOpacity = true
self.largeCircleNode = ASImageNode()
self.largeCircleNode.displayWithoutProcessing = true
self.largeCircleNode.displaysAsynchronously = false
self.largeCircleShadowNode = ASImageNode()
self.largeCircleShadowNode.displayWithoutProcessing = true
self.largeCircleShadowNode.displaysAsynchronously = false
self.smallCircleNode = ASImageNode()
self.smallCircleNode.displayWithoutProcessing = true
self.smallCircleNode.displaysAsynchronously = false
self.smallCircleShadowNode = ASImageNode()
self.smallCircleShadowNode.displayWithoutProcessing = true
self.smallCircleShadowNode.displaysAsynchronously = false
self.backgroundNode.image = generateBackgroundImage(foreground: theme.contextMenu.backgroundColor.withAlphaComponent(1.0), diameter: 52.0, shadowBlur: shadowBlur)
self.backgroundShadowNode.image = generateBackgroundShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: 52.0, shadowBlur: shadowBlur)
self.largeCircleNode.image = generateBubbleImage(foreground: theme.contextMenu.backgroundColor.withAlphaComponent(1.0), diameter: largeCircleSize, shadowBlur: shadowBlur)
self.smallCircleNode.image = generateBubbleImage(foreground: theme.contextMenu.backgroundColor.withAlphaComponent(1.0), diameter: smallCircleSize, shadowBlur: shadowBlur)
self.largeCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: largeCircleSize, shadowBlur: shadowBlur)
self.smallCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: smallCircleSize, shadowBlur: shadowBlur)
self.contentContainer = ASDisplayNode()
self.contentContainer.clipsToBounds = true
self.disclosureButton = HighlightTrackingButtonNode()
self.disclosureButton.hitTestSlop = UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -6.0)
let buttonImage = generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.contextMenu.dimColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setStrokeColor(UIColor.clear.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setLineJoin(.round)
context.beginPath()
context.move(to: CGPoint(x: 8.0, y: size.height / 2.0 + 3.0))
context.addLine(to: CGPoint(x: size.width / 2.0, y: 11.0))
context.addLine(to: CGPoint(x: size.width - 8.0, y: size.height / 2.0 + 3.0))
context.strokePath()
})
self.disclosureButton.setImage(buttonImage, for: [])
super.init()
self.addSubnode(self.smallCircleShadowNode)
self.addSubnode(self.largeCircleShadowNode)
self.addSubnode(self.backgroundShadowNode)
self.backgroundContainerNode.addSubnode(self.smallCircleNode)
self.backgroundContainerNode.addSubnode(self.largeCircleNode)
self.backgroundContainerNode.addSubnode(self.backgroundNode)
self.addSubnode(self.backgroundContainerNode)
self.contentContainer.addSubnode(self.disclosureButton)
self.itemNodes = self.items.map { item in
let reactionItem: ReactionGestureItem
switch item.reaction {
case .like:
reactionItem = .like
case .unlike:
reactionItem = .unlike
}
return ReactionNode(account: account, theme: theme, reaction: reactionItem, maximizedReactionSize: 30.0, loadFirstFrame: true)
}
self.itemNodes.forEach(self.contentContainer.addSubnode)
self.addSubnode(self.contentContainer)
self.disclosureButton.addTarget(self, action: #selector(self.disclosurePressed), forControlEvents: .touchUpInside)
self.disclosureButton.highligthedChanged = { [weak self] highlighted in
if highlighted {
self?.disclosureButton.layer.animateScale(from: 1.0, to: 0.8, duration: 0.15, removeOnCompletion: false)
} else {
self?.disclosureButton.layer.animateScale(from: 0.8, to: 1.0, duration: 0.25)
}
}
}
override public func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
public func updateLayout(size: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, transition: ContainedViewLayoutTransition) {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: transition, animateInFromAnchorRect: nil, animateOutToAnchorRect: nil)
}
private func calculateBackgroundFrame(containerSize: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, contentSize: CGSize) -> (CGRect, Bool) {
var contentSize = contentSize
contentSize.width = max(52.0, contentSize.width)
contentSize.height = 52.0
let sideInset: CGFloat = 12.0
let backgroundOffset: CGPoint = CGPoint(x: 22.0, y: -7.0)
var rect: CGRect
let isLeftAligned: Bool
if anchorRect.maxX < containerSize.width - backgroundOffset.x - sideInset {
rect = CGRect(origin: CGPoint(x: anchorRect.maxX - contentSize.width + backgroundOffset.x, y: anchorRect.minY - contentSize.height + backgroundOffset.y), size: contentSize)
isLeftAligned = true
} else {
rect = CGRect(origin: CGPoint(x: anchorRect.minX - backgroundOffset.x - 4.0, y: anchorRect.minY - contentSize.height + backgroundOffset.y), size: contentSize)
isLeftAligned = false
}
rect.origin.x = max(sideInset, rect.origin.x)
rect.origin.y = max(insets.top + sideInset, rect.origin.y)
rect.origin.x = min(containerSize.width - contentSize.width - sideInset, rect.origin.x)
return (rect, isLeftAligned)
}
private func updateLayout(size: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, transition: ContainedViewLayoutTransition, animateInFromAnchorRect: CGRect?, animateOutToAnchorRect: CGRect?, animateReactionHighlight: Bool = false) {
self.validLayout = (size, insets, anchorRect)
let sideInset: CGFloat = 12.0
let itemSpacing: CGFloat = 6.0
let minimizedItemSize: CGFloat = 30.0
let maximizedItemSize: CGFloat = 30.0 - 18.0
let shadowBlur: CGFloat = 5.0
let verticalInset: CGFloat = 13.0
let rowHeight: CGFloat = 30.0
let rowSpacing: CGFloat = itemSpacing
let columnCount = min(6, self.items.count)
let contentWidth = CGFloat(columnCount) * minimizedItemSize + (CGFloat(columnCount) - 1.0) * itemSpacing + sideInset * 2.0
let rowCount = self.items.count / columnCount + (self.items.count % columnCount == 0 ? 0 : 1)
let expandedRowCount = self.isExpanded ? rowCount : 1
let contentHeight = verticalInset * 2.0 + rowHeight * CGFloat(expandedRowCount) + CGFloat(expandedRowCount - 1) * rowSpacing
let (backgroundFrame, isLeftAligned) = self.calculateBackgroundFrame(containerSize: size, insets: insets, anchorRect: anchorRect, contentSize: CGSize(width: contentWidth, height: contentHeight))
transition.updateFrame(node: self.contentContainer, frame: backgroundFrame)
for i in 0 ..< self.items.count {
let rowIndex = i / columnCount
let columnIndex = i % columnCount
let row = CGFloat(rowIndex)
let column = CGFloat(columnIndex)
let itemSize: CGFloat = minimizedItemSize
let itemOffset: CGFloat = 0.0
let itemFrame = CGRect(origin: CGPoint(x: sideInset + column * (minimizedItemSize + itemSpacing) - itemOffset, y: verticalInset + row * (rowHeight + rowSpacing) + floor((rowHeight - minimizedItemSize) / 2.0) - itemOffset), size: CGSize(width: itemSize, height: itemSize))
transition.updateFrame(node: self.itemNodes[i], frame: itemFrame, beginWithCurrentState: true)
self.itemNodes[i].updateLayout(size: CGSize(width: itemSize, height: itemSize), scale: itemSize / (maximizedItemSize + 18.0), transition: transition, displayText: false)
self.itemNodes[i].updateIsAnimating(false, animated: false)
if rowIndex != 0 || columnIndex == columnCount - 1 {
if self.isExpanded {
if self.itemNodes[i].alpha.isZero {
self.itemNodes[i].alpha = 1.0
if transition.isAnimated {
let delayOffset: Double = 1.0 - Double(columnIndex) / Double(columnCount - 1)
self.itemNodes[i].layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4 + delayOffset * 0.32, initialVelocity: 0.0, damping: 95.0)
self.itemNodes[i].layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.05)
}
}
} else {
self.itemNodes[i].alpha = 0.0
}
} else {
self.itemNodes[i].alpha = 1.0
}
if rowIndex == 0 && columnIndex == columnCount - 1 {
transition.updateFrame(node: self.disclosureButton, frame: itemFrame)
if self.isExpanded {
if self.disclosureButton.alpha.isEqual(to: 1.0) {
self.disclosureButton.alpha = 0.0
if transition.isAnimated {
self.disclosureButton.layer.animateScale(from: 0.8, to: 0.1, duration: 0.2, removeOnCompletion: false)
self.disclosureButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
self?.disclosureButton.layer.removeAnimation(forKey: "scale")
})
}
}
} else {
self.disclosureButton.alpha = 1.0
}
}
}
let isInOverflow = backgroundFrame.maxY > anchorRect.minY
let backgroundAlpha: CGFloat = isInOverflow ? 1.0 : 0.8
let shadowAlpha: CGFloat = isInOverflow ? 1.0 : 0.0
transition.updateAlpha(node: self.backgroundContainerNode, alpha: backgroundAlpha)
transition.updateAlpha(node: self.backgroundShadowNode, alpha: shadowAlpha)
transition.updateAlpha(node: self.largeCircleShadowNode, alpha: shadowAlpha)
transition.updateAlpha(node: self.smallCircleShadowNode, alpha: shadowAlpha)
transition.updateFrame(node: self.backgroundContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur))
transition.updateFrame(node: self.backgroundShadowNode, frame: backgroundFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur))
let largeCircleFrame: CGRect
let smallCircleFrame: CGRect
if isLeftAligned {
largeCircleFrame = CGRect(origin: CGPoint(x: backgroundFrame.midX - floor(largeCircleSize / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.maxX - 3.0, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize))
} else {
largeCircleFrame = CGRect(origin: CGPoint(x: backgroundFrame.midX - floor(largeCircleSize / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.minX + 3.0 - smallCircleSize, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize))
}
transition.updateFrame(node: self.largeCircleNode, frame: largeCircleFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur))
transition.updateFrame(node: self.largeCircleShadowNode, frame: largeCircleFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur))
transition.updateFrame(node: self.smallCircleNode, frame: smallCircleFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur))
transition.updateFrame(node: self.smallCircleShadowNode, frame: smallCircleFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur))
if let animateInFromAnchorRect = animateInFromAnchorRect {
let springDuration: Double = 0.42
let springDamping: CGFloat = 104.0
let sourceBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: insets, anchorRect: animateInFromAnchorRect, contentSize: CGSize(width: contentWidth, height: contentHeight)).0
self.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.minX - backgroundFrame.minX, y: sourceBackgroundFrame.minY - backgroundFrame.minY)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
} else if let animateOutToAnchorRect = animateOutToAnchorRect {
let targetBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: insets, anchorRect: animateOutToAnchorRect, contentSize: CGSize(width: contentWidth, height: contentHeight)).0
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: targetBackgroundFrame.minX - backgroundFrame.minX, y: targetBackgroundFrame.minY - backgroundFrame.minY), duration: 0.2, removeOnCompletion: false, additive: true)
}
}
public func animateIn(from sourceAnchorRect: CGRect) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
if let (size, insets, anchorRect) = self.validLayout {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .immediate, animateInFromAnchorRect: sourceAnchorRect, animateOutToAnchorRect: nil)
}
let smallCircleDuration: Double = 0.5
let largeCircleDuration: Double = 0.5
let largeCircleDelay: Double = 0.08
let mainCircleDuration: Double = 0.5
let mainCircleDelay: Double = 0.1
self.smallCircleNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: smallCircleDuration)
self.smallCircleShadowNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: smallCircleDuration)
self.largeCircleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: largeCircleDelay)
self.largeCircleNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay)
self.largeCircleShadowNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay)
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: mainCircleDelay)
self.backgroundNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
self.backgroundShadowNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
if let itemNode = self.itemNodes.first {
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: mainCircleDelay)
itemNode.didAppear()
itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay, completion: { _ in
})
}
}
public func animateOut(to targetAnchorRect: CGRect?, animatingOutToReaction: Bool) {
self.backgroundNode.layer.animateAlpha(from: self.backgroundNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateAlpha(from: self.backgroundShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.largeCircleNode.layer.animateAlpha(from: self.largeCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.largeCircleShadowNode.layer.animateAlpha(from: self.largeCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.smallCircleNode.layer.animateAlpha(from: self.smallCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.smallCircleShadowNode.layer.animateAlpha(from: self.smallCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
for itemNode in self.itemNodes {
itemNode.layer.animateAlpha(from: itemNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
self.disclosureButton.layer.animateAlpha(from: self.disclosureButton.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
if let targetAnchorRect = targetAnchorRect, let (size, insets, anchorRect) = self.validLayout {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .immediate, animateInFromAnchorRect: nil, animateOutToAnchorRect: targetAnchorRect)
}
}
public func animateOutToReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: @escaping () -> Void) {
for itemNode in self.itemNodes {
switch itemNode.reaction {
case .like:
if let snapshotView = itemNode.view.snapshotContentTree(keepTransform: true), let targetSnapshotView = targetFilledNode.view.snapshotContentTree() {
targetSnapshotView.frame = self.view.convert(targetFilledNode.bounds, from: targetFilledNode.view)
itemNode.isHidden = true
self.view.addSubview(targetSnapshotView)
self.view.addSubview(snapshotView)
snapshotView.frame = itemNode.view.convert(itemNode.view.bounds, to: self.view)
var completedTarget = false
let intermediateCompletion: () -> Void = {
if completedTarget {
completion()
}
}
let targetPosition = self.view.convert(targetFilledNode.bounds.center, from: targetFilledNode.view)
let duration: Double = 0.3
if hideNode {
targetFilledNode.isHidden = true
}
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
targetSnapshotView.layer.animateScale(from: snapshotView.bounds.width / targetSnapshotView.bounds.width, to: 0.5, duration: 0.3, removeOnCompletion: false)
let sourcePoint = snapshotView.center
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - 30.0)
let x1 = sourcePoint.x
let y1 = sourcePoint.y
let x2 = midPoint.x
let y2 = midPoint.y
let x3 = targetPosition.x
let y3 = targetPosition.y
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
var keyframes: [AnyObject] = []
for i in 0 ..< 10 {
let k = CGFloat(i) / CGFloat(10 - 1)
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
let y = a * x * x + b * x + c
keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y)))
}
snapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.hapticFeedback.tap()
}
completedTarget = true
if hideNode {
targetFilledNode.isHidden = false
targetFilledNode.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0)
targetEmptyNode.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0)
}
intermediateCompletion()
})
targetSnapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false)
snapshotView.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / snapshotView.bounds.width, duration: 0.3, removeOnCompletion: false)
return
}
default:
break
}
}
completion()
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let contentPoint = self.contentContainer.view.convert(point, from: self.view)
if !self.disclosureButton.alpha.isZero {
if let result = self.disclosureButton.hitTest(self.disclosureButton.view.convert(point, from: self.view), with: event) {
return result
}
}
for itemNode in self.itemNodes {
if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) {
return self.view
}
}
return nil
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
let point = recognizer.location(in: self.view)
if let reaction = self.reaction(at: point) {
self.reactionSelected?(reaction)
}
}
}
public func reaction(at point: CGPoint) -> ReactionGestureItem? {
let contentPoint = self.contentContainer.view.convert(point, from: self.view)
for itemNode in self.itemNodes {
if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) {
return itemNode.reaction
}
}
for itemNode in self.itemNodes {
if !itemNode.alpha.isZero && itemNode.frame.insetBy(dx: -8.0, dy: -8.0).contains(contentPoint) {
return itemNode.reaction
}
}
return nil
}
public func setHighlightedReaction(_ value: ReactionContextItem.Reaction?) {
self.highlightedReaction = value
if let (size, insets, anchorRect) = self.validLayout {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.18, curve: .easeInOut), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
}
}
@objc private func disclosurePressed() {
self.isExpanded = true
if let (size, insets, anchorRect) = self.validLayout {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.3, curve: .spring), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
}
}
}

View File

@ -1,8 +0,0 @@
import Foundation
import Postbox
import TelegramCore
public enum ReactionGestureItem {
case like
case unlike
}

View File

@ -1,487 +0,0 @@
import Foundation
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import TelegramPresentationData
import AppBundle
import AnimatedStickerNode
import TelegramAnimatedStickerNode
private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(foreground.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setShadow(offset: CGSize(), blur: 1.0, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
private let font = Font.medium(13.0)
final class ReactionNode: ASDisplayNode {
let reaction: ReactionGestureItem
private let textBackgroundNode: ASImageNode
private let textNode: ImmediateTextNode
private let animationNode: AnimatedStickerNode
private let imageNode: ASImageNode
private let additionalImageNode: ASImageNode
var isMaximized: Bool?
private let intrinsicSize: CGSize
private let intrinsicOffset: CGPoint
init(account: Account, theme: PresentationTheme, reaction: ReactionGestureItem, maximizedReactionSize: CGFloat, loadFirstFrame: Bool) {
self.reaction = reaction
self.textBackgroundNode = ASImageNode()
self.textBackgroundNode.displaysAsynchronously = false
self.textBackgroundNode.displayWithoutProcessing = true
self.textBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: theme.chat.serviceMessage.components.withDefaultWallpaper.dateFillFloating.withAlphaComponent(0.8))
self.textBackgroundNode.alpha = 0.0
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.isUserInteractionEnabled = false
let reactionText: String = ""
self.textNode.attributedText = NSAttributedString(string: reactionText, font: font, textColor: theme.chat.serviceMessage.dateTextColor.withWallpaper)
let textSize = self.textNode.updateLayout(CGSize(width: 200.0, height: 100.0))
let textBackgroundSize = CGSize(width: textSize.width + 12.0, height: 20.0)
let textBackgroundFrame = CGRect(origin: CGPoint(), size: textBackgroundSize)
let textFrame = CGRect(origin: CGPoint(x: floor((textBackgroundFrame.width - textSize.width) / 2.0), y: floor((textBackgroundFrame.height - textSize.height) / 2.0)), size: textSize)
self.textBackgroundNode.frame = textBackgroundFrame
self.textNode.frame = textFrame
self.textNode.alpha = 0.0
self.animationNode = AnimatedStickerNode()
self.animationNode.automaticallyLoadFirstFrame = loadFirstFrame
self.animationNode.playToCompletionOnStop = true
var intrinsicSize = CGSize(width: maximizedReactionSize + 14.0, height: maximizedReactionSize + 14.0)
self.imageNode = ASImageNode()
self.additionalImageNode = ASImageNode()
switch reaction {
case .like:
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
self.imageNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Reactions/ContextHeartFilled"), color: UIColor(rgb: 0xfe1512))
case .unlike:
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
self.imageNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Reactions/ContextHeartBrokenL"), color: UIColor(rgb: 0xfe1512))
self.additionalImageNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Reactions/ContextHeartBrokenR"), color: UIColor(rgb: 0xfe1512))
}
if let image = self.imageNode.image {
intrinsicSize = image.size
}
self.intrinsicSize = intrinsicSize
super.init()
//self.backgroundColor = .gray
//self.textBackgroundNode.addSubnode(self.textNode)
//self.addSubnode(self.textBackgroundNode)
//self.addSubnode(self.animationNode)
self.addSubnode(self.imageNode)
self.addSubnode(self.additionalImageNode)
self.animationNode.updateLayout(size: self.intrinsicSize)
self.animationNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicSize)
switch reaction {
case .like:
self.imageNode.frame = CGRect(origin: CGPoint(x: -5.0, y: -5.0), size: self.intrinsicSize)
case .unlike:
self.imageNode.frame = CGRect(origin: CGPoint(x: -6.0, y: -5.0), size: self.intrinsicSize)
self.additionalImageNode.frame = CGRect(origin: CGPoint(x: -3.0, y: -5.0), size: self.intrinsicSize)
}
}
func updateLayout(size: CGSize, scale: CGFloat, transition: ContainedViewLayoutTransition, displayText: Bool) {
/*transition.updatePosition(node: self.animationNode, position: CGPoint(x: size.width / 2.0 + self.intrinsicOffset.x * scale, y: size.height / 2.0 + self.intrinsicOffset.y * scale), beginWithCurrentState: true)
transition.updateTransformScale(node: self.animationNode, scale: scale, beginWithCurrentState: true)
transition.updatePosition(node: self.imageNode, position: CGPoint(x: size.width / 2.0 + self.intrinsicOffset.x * scale, y: size.height / 2.0 + self.intrinsicOffset.y * scale), beginWithCurrentState: true)
transition.updateTransformScale(node: self.imageNode, scale: scale, beginWithCurrentState: true)
transition.updatePosition(node: self.textBackgroundNode, position: CGPoint(x: size.width / 2.0, y: displayText ? -24.0 : (size.height / 2.0)), beginWithCurrentState: true)
transition.updateTransformScale(node: self.textBackgroundNode, scale: displayText ? 1.0 : 0.1, beginWithCurrentState: true)
transition.updateAlpha(node: self.textBackgroundNode, alpha: displayText ? 1.0 : 0.0, beginWithCurrentState: true)
transition.updateAlpha(node: self.textNode, alpha: displayText ? 1.0 : 0.0, beginWithCurrentState: true)*/
}
func updateIsAnimating(_ isAnimating: Bool, animated: Bool) {
if isAnimating {
self.animationNode.visibility = true
} else {
self.animationNode.visibility = false
}
}
func didAppear() {
switch self.reaction {
case .like:
self.imageNode.layer.animateScale(from: 1.0, to: 1.08, duration: 0.12, delay: 0.22, removeOnCompletion: false, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.imageNode.layer.animateScale(from: 1.08, to: 1.0, duration: 0.12)
})
case .unlike:
self.imageNode.layer.animatePosition(from: CGPoint(x: -2.5, y: 0.0), to: CGPoint(), duration: 0.2, delay: 0.15, additive: true)
self.additionalImageNode.layer.animatePosition(from: CGPoint(x: 2.5, y: 0.0), to: CGPoint(), duration: 0.2, delay: 0.15, additive: true)
}
}
}
final class ReactionSelectionNode: ASDisplayNode {
private let account: Account
private let theme: PresentationTheme
private let reactions: [ReactionGestureItem]
private let backgroundNode: ASImageNode
private let backgroundShadowNode: ASImageNode
private let bubbleNodes: [(ASImageNode, ASImageNode)]
private var reactionNodes: [ReactionNode] = []
private var hasSelectedNode = false
private let hapticFeedback = HapticFeedback()
private var shadowBlur: CGFloat = 8.0
private var minimizedReactionSize: CGFloat = 28.0
private var smallCircleSize: CGFloat = 14.0
private var isRightAligned: Bool = false
public init(account: Account, theme: PresentationTheme, reactions: [ReactionGestureItem]) {
self.account = account
self.theme = theme
self.reactions = reactions
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundShadowNode = ASImageNode()
self.backgroundShadowNode.displaysAsynchronously = false
self.backgroundShadowNode.displayWithoutProcessing = true
self.bubbleNodes = (0 ..< 2).map { i -> (ASImageNode, ASImageNode) in
let imageNode = ASImageNode()
imageNode.displaysAsynchronously = false
imageNode.displayWithoutProcessing = true
let shadowNode = ASImageNode()
shadowNode.displaysAsynchronously = false
shadowNode.displayWithoutProcessing = true
return (imageNode, shadowNode)
}
super.init()
self.bubbleNodes.forEach { _, shadow in
//self.addSubnode(shadow)
}
self.addSubnode(self.backgroundShadowNode)
self.bubbleNodes.forEach { foreground, _ in
//self.addSubnode(foreground)
}
self.addSubnode(self.backgroundNode)
}
func updateLayout(constrainedSize: CGSize, startingPoint: CGPoint, offsetFromStart: CGFloat, isInitial: Bool, touchPoint: CGPoint) {
let initialAnchorX = startingPoint.x
var isRightAligned = false
if initialAnchorX > constrainedSize.width / 2.0 {
isRightAligned = true
}
let reactionSideInset: CGFloat = 10.0
let reactionSpacing: CGFloat = 6.0
let minReactionSpacing: CGFloat = 2.0
let minimizedReactionSize = self.minimizedReactionSize
let contentWidth: CGFloat = CGFloat(self.reactions.count) * (minimizedReactionSize) + CGFloat(self.reactions.count - 1) * reactionSpacing + reactionSideInset * 2.0
let spaceForMaximizedReaction = CGFloat(self.reactions.count - 1) * reactionSpacing - CGFloat(self.reactions.count - 1) * minReactionSpacing
let maximizedReactionSize: CGFloat = minimizedReactionSize + spaceForMaximizedReaction
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.8)
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
if constrainedSize.width > 500.0 {
backgroundFrame = backgroundFrame.offsetBy(dx: constrainedSize.width - contentWidth - 44.0, dy: startingPoint.y - backgroundHeight - 12.0)
} else {
backgroundFrame = backgroundFrame.offsetBy(dx: floor((constrainedSize.width - contentWidth) / 2.0), dy: startingPoint.y - backgroundHeight - 12.0)
}
backgroundFrame.origin.x = max(0.0, backgroundFrame.minX)
backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX)
let anchorMinX = backgroundFrame.minX + shadowBlur + backgroundHeight / 2.0
let anchorMaxX = backgroundFrame.maxX - shadowBlur - backgroundHeight / 2.0
let anchorX = max(anchorMinX, min(anchorMaxX, offsetFromStart))
var maximizedIndex = -1
/*if let reaction = self.reactions.last, case .reply = reaction {
maximizedIndex = self.reactions.count - 1
}*/
if backgroundFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: 0.0, dy: 10.0).contains(touchPoint) {
maximizedIndex = Int(((touchPoint.x - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count))
maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex))
}
let interReactionSpacing: CGFloat
if maximizedIndex != -1 {
interReactionSpacing = minReactionSpacing
} else {
interReactionSpacing = reactionSpacing
}
if isInitial && self.reactionNodes.isEmpty {
self.shadowBlur = floor(minimizedReactionSize * 0.26)
self.smallCircleSize = 14.0
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur)
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur)
for i in 0 ..< self.bubbleNodes.count {
self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
}
self.reactionNodes = self.reactions.map { reaction -> ReactionNode in
return ReactionNode(account: self.account, theme: self.theme, reaction: reaction, maximizedReactionSize: maximizedReactionSize - 12.0, loadFirstFrame: true)
}
self.reactionNodes.forEach(self.addSubnode(_:))
}
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
/*if maximizedIndex == -1 {
backgroundFrame.size.width -= maximizedReactionSize - minimizedReactionSize
backgroundFrame.origin.x += maximizedReactionSize - minimizedReactionSize
}*/
self.isRightAligned = isRightAligned
let backgroundTransition: ContainedViewLayoutTransition
if isInitial {
backgroundTransition = .immediate
} else {
backgroundTransition = .animated(duration: 0.18, curve: .easeInOut)
}
backgroundTransition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
backgroundTransition.updateFrame(node: self.backgroundShadowNode, frame: backgroundFrame)
var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSideInset
if maximizedIndex != -1 {
self.hasSelectedNode = false
} else {
self.hasSelectedNode = true
}
for iterationIndex in 0 ..< self.reactionNodes.count {
var i = iterationIndex
let isMaximized = i == maximizedIndex
if !isRightAligned {
i = self.reactionNodes.count - 1 - i
}
let reactionSize: CGFloat
if isMaximized {
reactionSize = maximizedReactionSize
} else {
reactionSize = minimizedReactionSize
}
let transition: ContainedViewLayoutTransition
if isInitial {
transition = .immediate
} else {
transition = .animated(duration: 0.18, curve: .easeInOut)
}
if self.reactionNodes[i].isMaximized != isMaximized {
self.reactionNodes[i].isMaximized = isMaximized
self.reactionNodes[i].updateIsAnimating(isMaximized, animated: !isInitial)
if isMaximized && !isInitial {
self.hapticFeedback.tap()
}
}
var reactionFrame = CGRect(origin: CGPoint(x: reactionX, y: backgroundFrame.maxY - shadowBlur - minimizedReactionVerticalInset - reactionSize), size: CGSize(width: reactionSize, height: reactionSize))
if isMaximized {
reactionFrame.origin.x -= 7.0
reactionFrame.size.width += 14.0
}
self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 14.0), transition: transition, displayText: isMaximized)
transition.updateFrame(node: self.reactionNodes[i], frame: reactionFrame, beginWithCurrentState: true)
reactionX += reactionSize + interReactionSpacing
}
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - self.smallCircleSize - shadowBlur, y: backgroundFrame.maxY - shadowBlur - self.smallCircleSize - shadowBlur), size: CGSize(width: self.smallCircleSize * 2.0 + shadowBlur * 2.0, height: self.smallCircleSize * 2.0 + shadowBlur * 2.0))
self.bubbleNodes[1].0.frame = mainBubbleFrame
self.bubbleNodes[1].1.frame = mainBubbleFrame
let secondaryBubbleFrame = CGRect(origin: CGPoint(x: mainBubbleFrame.midX - 10.0 - (self.smallCircleSize + shadowBlur * 2.0) / 2.0, y: mainBubbleFrame.midY + 10.0 - (self.smallCircleSize + shadowBlur * 2.0) / 2.0), size: CGSize(width: self.smallCircleSize + shadowBlur * 2.0, height: self.smallCircleSize + shadowBlur * 2.0))
self.bubbleNodes[0].0.frame = secondaryBubbleFrame
self.bubbleNodes[0].1.frame = secondaryBubbleFrame
}
func animateIn() {
self.bubbleNodes[1].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[1].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[0].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[0].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
let backgroundOffset: CGPoint
if self.isRightAligned {
backgroundOffset = CGPoint(x: (self.backgroundNode.frame.width - shadowBlur) / 2.0 - 42.0, y: 10.0)
} else {
backgroundOffset = CGPoint(x: -(self.backgroundNode.frame.width - shadowBlur) / 2.0 + 42.0, y: 10.0)
}
let damping: CGFloat = 100.0
for i in 0 ..< self.reactionNodes.count {
let animationOffset: Double = 1.0 - Double(i) / Double(self.reactionNodes.count - 1)
var nodeOffset: CGPoint
if self.isRightAligned {
nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.maxX - shadowBlur) / 2.0 - 42.0, y: 10.0)
} else {
nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.minX + shadowBlur) / 2.0 - 42.0, y: 10.0)
}
nodeOffset.x = 0.0
nodeOffset.y = 30.0
self.reactionNodes[i].layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: animationOffset * 0.1)
self.reactionNodes[i].layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, delay: animationOffset * 0.1, initialVelocity: 0.0, damping: damping)
//self.reactionNodes[i].layer.animateSpring(from: NSValue(cgPoint: nodeOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, delay: animationOffset * 0.1, initialVelocity: 0.0, damping: damping, additive: true)
}
self.backgroundNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
self.backgroundNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
self.backgroundShadowNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
self.backgroundShadowNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
}
func animateOut(into targetNode: ASDisplayNode?, hideTarget: Bool, completion: @escaping () -> Void) {
self.hapticFeedback.prepareTap()
var completedContainer = false
var completedTarget = true
let intermediateCompletion: () -> Void = {
if completedContainer && completedTarget {
completion()
}
}
if let targetNode = targetNode {
for i in 0 ..< self.reactionNodes.count {
if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized {
targetNode.recursivelyEnsureDisplaySynchronously(true)
if let snapshotView = self.reactionNodes[i].view.snapshotContentTree(), let targetSnapshotView = targetNode.view.snapshotContentTree() {
targetSnapshotView.frame = self.view.convert(targetNode.bounds, from: targetNode.view)
self.reactionNodes[i].isHidden = true
self.view.addSubview(targetSnapshotView)
self.view.addSubview(snapshotView)
completedTarget = false
let targetPosition = self.view.convert(targetNode.bounds.center, from: targetNode.view)
let duration: Double = 0.3
if hideTarget {
targetNode.isHidden = true
}
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
targetSnapshotView.layer.animateScale(from: snapshotView.bounds.width / targetSnapshotView.bounds.width, to: 0.5, duration: 0.3, removeOnCompletion: false)
let sourcePoint = snapshotView.center
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - 30.0)
let x1 = sourcePoint.x
let y1 = sourcePoint.y
let x2 = midPoint.x
let y2 = midPoint.y
let x3 = targetPosition.x
let y3 = targetPosition.y
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
var keyframes: [AnyObject] = []
for i in 0 ..< 10 {
let k = CGFloat(i) / CGFloat(10 - 1)
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
let y = a * x * x + b * x + c
keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y)))
}
snapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.hapticFeedback.tap()
}
completedTarget = true
if hideTarget {
targetNode.isHidden = false
targetNode.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0)
}
intermediateCompletion()
})
targetSnapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false)
snapshotView.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / snapshotView.bounds.width, duration: 0.3, removeOnCompletion: false)
}
break
}
}
}
//self.backgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completedContainer = true
intermediateCompletion()
})
for (node, shadow) in self.bubbleNodes {
node.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
shadow.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
shadow.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
for i in 0 ..< self.reactionNodes.count {
self.reactionNodes[i].layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.reactionNodes[i].layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
}
func selectedReaction() -> ReactionGestureItem? {
for i in 0 ..< self.reactionNodes.count {
if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized {
return self.reactionNodes[i].reaction
}
}
return nil
}
}

View File

@ -1,85 +0,0 @@
import Foundation
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import TelegramPresentationData
public final class ReactionSelectionParentNode: ASDisplayNode {
private let account: Account
private let theme: PresentationTheme
private var currentNode: ReactionSelectionNode?
private var currentLocation: (CGPoint, CGFloat, CGPoint)?
private var validLayout: (size: CGSize, insets: UIEdgeInsets)?
public init(account: Account, theme: PresentationTheme) {
self.account = account
self.theme = theme
super.init()
}
func displayReactions(_ reactions: [ReactionGestureItem], at point: CGPoint, touchPoint: CGPoint) {
if let currentNode = self.currentNode {
currentNode.removeFromSupernode()
self.currentNode = nil
}
let reactionNode = ReactionSelectionNode(account: self.account, theme: self.theme, reactions: reactions)
self.addSubnode(reactionNode)
self.currentNode = reactionNode
self.currentLocation = (point, point.x, touchPoint)
if let (size, insets) = self.validLayout {
self.update(size: size, insets: insets, isInitial: true)
reactionNode.animateIn()
}
}
func selectedReaction() -> ReactionGestureItem? {
if let currentNode = self.currentNode {
return currentNode.selectedReaction()
}
return nil
}
func dismissReactions(into targetNode: ASDisplayNode?, hideTarget: Bool) {
if let currentNode = self.currentNode {
currentNode.animateOut(into: targetNode, hideTarget: hideTarget, completion: { [weak currentNode] in
currentNode?.removeFromSupernode()
})
self.currentNode = nil
}
}
func updateReactionsAnchor(point: CGPoint, touchPoint: CGPoint) {
if let (currentPoint, _, _) = self.currentLocation {
self.currentLocation = (currentPoint, point.x, touchPoint)
if let (size, insets) = self.validLayout {
self.update(size: size, insets: insets, isInitial: false)
}
}
}
public func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, insets)
self.update(size: size, insets: insets, isInitial: false)
}
private func update(size: CGSize, insets: UIEdgeInsets, isInitial: Bool) {
if let currentNode = self.currentNode, let (point, offset, touchPoint) = self.currentLocation {
currentNode.updateLayout(constrainedSize: size, startingPoint: CGPoint(x: size.width - 32.0, y: point.y), offsetFromStart: offset, isInitial: isInitial, touchPoint: touchPoint)
currentNode.frame = CGRect(origin: CGPoint(), size: size)
}
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return nil
}
}

View File

@ -1,193 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
private var validatedGesture = false
private var firstLocation: CGPoint = CGPoint()
private var currentLocation: CGPoint = CGPoint()
private var currentReactions: [ReactionGestureItem] = []
private var isActivated = false
private var isAwaitingCompletion = false
private weak var currentContainer: ReactionSelectionParentNode?
private var activationTimer: Timer?
public var availableReactions: (() -> [ReactionGestureItem])?
public var getReactionContainer: (() -> ReactionSelectionParentNode?)?
public var getAnchorPoint: (() -> CGPoint?)?
public var shouldElevateAnchorPoint: (() -> Bool)?
public var began: (() -> Void)?
public var updateOffset: ((CGFloat, Bool) -> Void)?
public var completed: ((ReactionGestureItem?) -> Void)?
public var displayReply: ((CGFloat) -> Void)?
public var activateReply: (() -> Void)?
private var currentAnchorPoint: CGPoint?
private var currentAnchorStartPoint: CGPoint?
override public init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.maximumNumberOfTouches = 1
}
override public func reset() {
super.reset()
self.validatedGesture = false
self.currentReactions = []
self.isActivated = false
self.isAwaitingCompletion = false
self.activationTimer?.invalidate()
self.activationTimer = nil
}
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let availableReactions = self.availableReactions?(), !availableReactions.isEmpty {
self.currentReactions = availableReactions
let touch = touches.first!
self.firstLocation = touch.location(in: nil)
self.currentLocation = self.firstLocation
} else {
self.state = .failed
}
}
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
if self.isAwaitingCompletion {
return
}
guard let _ = self.view else {
return
}
guard let location = touches.first?.location(in: nil) else {
return
}
self.currentLocation = location
var translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y)
let absTranslationX: CGFloat = abs(translation.x)
let absTranslationY: CGFloat = abs(translation.y)
var updatedOffset = false
if !self.validatedGesture {
if translation.x > 0.0 {
self.state = .failed
} else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 {
self.state = .failed
} else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX {
self.validatedGesture = true
self.firstLocation = location
translation = CGPoint()
self.began?()
self.updateOffset?(0.0, false)
updatedOffset = true
self.activationTimer?.invalidate()
final class TimerTarget: NSObject {
let f: () -> Void
init(_ f: @escaping () -> Void) {
self.f = f
}
@objc func event() {
self.f()
}
}
let elevate = self.shouldElevateAnchorPoint?() ?? false
let activationTimer = Timer(timeInterval: elevate ? 0.15 : 0.01, target: TimerTarget { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.activationTimer = nil
if strongSelf.validatedGesture {
let location = strongSelf.currentLocation
if !strongSelf.currentReactions.isEmpty, let reactionContainer = strongSelf.getReactionContainer?(), let _ = strongSelf.getAnchorPoint?() {
strongSelf.currentContainer = reactionContainer
//let reactionContainerLocation = reactionContainer.view.convert(localAnchorPoint, from: strongSelf.view)
let elevate = strongSelf.shouldElevateAnchorPoint?() ?? false
let reactionContainerLocation = reactionContainer.view.convert(location, from: nil).offsetBy(dx: 0.0, dy: elevate ? -44.0 : 22.0)
let reactionContainerTouchPoint = reactionContainer.view.convert(location, from: nil)
strongSelf.currentAnchorPoint = reactionContainerLocation
strongSelf.currentAnchorStartPoint = location
reactionContainer.displayReactions(strongSelf.currentReactions, at: reactionContainerLocation, touchPoint: reactionContainerTouchPoint)
}
}
}, selector: #selector(TimerTarget.event), userInfo: nil, repeats: false)
self.activationTimer = activationTimer
RunLoop.main.add(activationTimer, forMode: .common)
}
}
if self.validatedGesture {
if !updatedOffset {
self.updateOffset?(-min(0.0, translation.x), false)
}
if !self.isActivated {
if absTranslationX > 40.0 {
self.isActivated = true
self.displayReply?(-min(0.0, translation.x))
}
} else {
if let reactionContainer = self.currentContainer, let currentAnchorPoint = self.currentAnchorPoint, let currentAnchorStartPoint = self.currentAnchorStartPoint {
let anchorPoint = CGPoint(x: currentAnchorPoint.x + location.x - currentAnchorStartPoint.x, y: currentAnchorPoint.y)
let reactionContainerLocation = anchorPoint
let reactionContainerTouchPoint = reactionContainer.view.convert(location, from: nil)
reactionContainer.updateReactionsAnchor(point: reactionContainerLocation, touchPoint: reactionContainerTouchPoint)
}
}
super.touchesMoved(touches, with: event)
}
}
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
if self.isAwaitingCompletion {
return
}
guard let location = touches.first?.location(in: nil) else {
return
}
if self.validatedGesture {
let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y)
if let reaction = self.currentContainer?.selectedReaction() {
self.isAwaitingCompletion = true
self.completed?(reaction)
} else {
if translation.x < -40.0 {
self.currentContainer?.dismissReactions(into: nil, hideTarget: false)
self.activateReply?()
self.state = .ended
} else {
self.currentContainer?.dismissReactions(into: nil, hideTarget: false)
self.completed?(nil)
self.state = .cancelled
super.touchesEnded(touches, with: event)
}
}
} else {
self.currentContainer?.dismissReactions(into: nil, hideTarget: false)
self.state = .cancelled
super.touchesEnded(touches, with: event)
}
}
public func complete(into targetNode: ASDisplayNode?, hideTarget: Bool) {
if self.isAwaitingCompletion {
self.currentContainer?.dismissReactions(into: targetNode, hideTarget: hideTarget)
self.state = .ended
}
}
public func cancel() {
self.state = .cancelled
}
}

View File

@ -11,7 +11,6 @@ swift_library(
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",

View File

@ -804,7 +804,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
}, colorContextAction: { isCurrent, reference, accentColor, node, gesture in
@ -1041,7 +1041,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
}
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
})

View File

@ -13,7 +13,6 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AccountContext:AccountContext",
"//submodules/StickerResources:StickerResources",

View File

@ -1,7 +1,6 @@
import Foundation
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import StickerResources

View File

@ -523,7 +523,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
})
})))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(items: items)), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
return controller

View File

@ -241,7 +241,7 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
let contentKind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: item.message, strings: item.presentationData.strings, nameDisplayOrder: .firstLast, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId)
let contentKind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(item.message), strings: item.presentationData.strings, nameDisplayOrder: .firstLast, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId)
var text = !item.message.text.isEmpty ? item.message.text : stringForMediaKind(contentKind, strings: item.presentationData.strings).0
text = foldLineBreaks(text)

View File

@ -555,7 +555,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
return
}
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuSpeedItems()
let contextController = ContextController(account: self.context.account, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
self.presentInGlobalOverlay?(contextController)
}

View File

@ -1791,7 +1791,7 @@ public final class VoiceChatController: ViewController {
dismissPromise.set(true)
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(source), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(source), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
contextController.useComplexItemsTransitionAnimation = true
strongSelf.controller?.presentInGlobalOverlay(contextController)
}, getPeerVideo: { [weak self] endpointId, position in
@ -2458,7 +2458,7 @@ public final class VoiceChatController: ViewController {
private func openSettingsMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems()
if let controller = self.controller {
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: self.darkTheme), source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceNode: self.optionsButton.referenceNode)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: self.darkTheme), source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceNode: self.optionsButton.referenceNode)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
}

View File

@ -1020,7 +1020,6 @@ public class Account {
self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())

View File

@ -258,10 +258,6 @@ struct AccountMutableState {
self.addOperation(.UpdateMessagePoll(id, poll, results))
}
/*mutating func updateMessageReactions(_ messageId: MessageId, reactions: Api.MessageReactions) {
self.addOperation(.UpdateMessageReactions(messageId, reactions))
}*/
mutating func updateMedia(_ id: MediaId, media: Media?) {
self.addOperation(.UpdateMedia(id, media))
}

View File

@ -1,101 +0,0 @@
import Foundation
import Postbox
import TelegramApi
/*extension ReactionsMessageAttribute {
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
switch reactions {
case let .messageReactions(flags, results):
let min = (flags & (1 << 0)) != 0
var reactions = results.map { result -> MessageReaction in
switch result {
case let .reactionCount(flags, reaction, count):
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
}
}
if min {
var currentSelectedReaction: String?
for reaction in self.reactions {
if reaction.isSelected {
currentSelectedReaction = reaction.value
break
}
}
if let currentSelectedReaction = currentSelectedReaction {
for i in 0 ..< reactions.count {
if reactions[i].value == currentSelectedReaction {
reactions[i].isSelected = true
}
}
}
}
return ReactionsMessageAttribute(reactions: reactions)
}
}
}*/
public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsMessageAttribute? {
var current: ReactionsMessageAttribute?
var pending: PendingReactionsMessageAttribute?
for attribute in attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
current = attribute
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
pending = attribute
}
}
if let pending = pending {
var reactions = current?.reactions ?? []
if let value = pending.value {
var found = false
for i in 0 ..< reactions.count {
if reactions[i].value == value {
found = true
if !reactions[i].isSelected {
reactions[i].isSelected = true
reactions[i].count += 1
}
}
}
if !found {
reactions.append(MessageReaction(value: value, count: 1, isSelected: true))
}
}
for i in (0 ..< reactions.count).reversed() {
if reactions[i].isSelected, pending.value != reactions[i].value {
if reactions[i].count == 1 {
reactions.remove(at: i)
} else {
reactions[i].isSelected = false
reactions[i].count -= 1
}
}
}
if !reactions.isEmpty {
return ReactionsMessageAttribute(reactions: reactions)
} else {
return nil
}
} else if let current = current {
return current
} else {
return nil
}
}
/*extension ReactionsMessageAttribute {
convenience init(apiReactions: Api.MessageReactions) {
switch apiReactions {
case let .messageReactions(_, results):
self.init(reactions: results.map { result in
switch result {
case let .reactionCount(flags, reaction, count):
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
}
})
}
}
}
*/

View File

@ -546,10 +546,6 @@ extension StoreMessage {
attributes.append(ContentRequiresValidationMessageAttribute())
}
/*if let reactions = reactions {
attributes.append(ReactionsMessageAttribute(apiReactions: reactions))
}*/
if let replies = replies {
let recentRepliersPeerIds: [PeerId]?
switch replies {

View File

@ -648,7 +648,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
if !hasHiddenForwardMedia {
var sourceId: PeerId? = nil
var sourceMessageId: MessageId? = nil
if let peer = messageMainPeer(sourceMessage) as? TelegramChannel, case .broadcast = peer.info {
if case let .channel(peer) = messageMainPeer(EngineMessage(sourceMessage)), case .broadcast = peer.info {
sourceId = peer.id
sourceMessageId = sourceMessage.id
}

View File

@ -1381,8 +1381,6 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.updateLangPack(langCode: langCode, difference: difference)
case let .updateMessagePoll(_, pollId, poll, results):
updatedState.updateMessagePoll(MediaId(namespace: Namespaces.Media.CloudPoll, id: pollId), poll: poll, results: results)
/*case let .updateMessageReactions(peer, msgId, reactions):
updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions)*/
case let .updateFolderPeers(folderPeers, _, _):
for folderPeer in folderPeers {
switch folderPeer {
@ -2685,26 +2683,6 @@ func replayFinalState(
updatedPoll = updatedPoll.withUpdatedResults(TelegramMediaPollResults(apiResults: results), min: resultsMin)
updateMessageMedia(transaction: transaction, id: pollId, media: updatedPoll)
}
/*case let .UpdateMessageReactions(messageId, reactions):
transaction.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
var attributes = currentMessage.attributes
var found = false
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ReactionsMessageAttribute {
attributes[j] = attribute.withUpdatedResults(reactions)
found = true
break loop
}
}
if !found {
attributes.append(ReactionsMessageAttribute(apiReactions: reactions))
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})*/
case let .UpdateMedia(id, media):
if let media = media as? TelegramMediaWebpage {
updatedWebpages[id] = media

View File

@ -812,89 +812,6 @@ public final class AccountViewTracker {
}
}
public func updateReactionsForMessageIds(messageIds: Set<MessageId>) {
/*self.queue.async {
var addedMessageIds: [MessageId] = []
let timestamp = Int32(CFAbsoluteTimeGetCurrent())
for messageId in messageIds {
let messageTimestamp = self.updatedReactionsMessageIdsAndTimestamps[messageId]
if messageTimestamp == nil || messageTimestamp! < timestamp - 5 * 60 {
self.updatedReactionsMessageIdsAndTimestamps[messageId] = timestamp
addedMessageIds.append(messageId)
}
}
if !addedMessageIds.isEmpty {
for (peerId, messageIds) in messagesIdsGroupedByPeerId(Set(addedMessageIds)) {
let disposableId = self.nextUpdatedReactionsDisposableId
self.nextUpdatedReactionsDisposableId += 1
if let account = self.account {
let signal = (account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.getMessagesReactions(peer: inputPeer, id: messageIds.map { $0.id }))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Void, NoError> in
guard let updates = updates else {
return .complete()
}
return account.postbox.transaction { transaction -> Void in
let updateList: [Api.Update]
switch updates {
case let .updates(updates, _, _, _, _):
updateList = updates
case let .updatesCombined(updates, _, _, _, _, _):
updateList = updates
case let .updateShort(update, _):
updateList = [update]
default:
updateList = []
}
for update in updateList {
switch update {
case let .updateMessageReactions(peer, msgId, reactions):
transaction.updateMessage(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), update: { currentMessage in
let updatedReactions = ReactionsMessageAttribute(apiReactions: reactions)
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ReactionsMessageAttribute {
if updatedReactions.reactions == attribute.reactions {
return .skip
}
attributes[j] = updatedReactions
break loop
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
default:
break
}
}
}
}
} else {
return .complete()
}
}
|> switchToLatest)
|> afterDisposed { [weak self] in
self?.queue.async {
self?.updatedReactionsDisposables.set(nil, forKey: disposableId)
}
}
self.updatedReactionsDisposables.set(signal.start(), forKey: disposableId)
}
}
}
}*/
}
public func updateSeenLiveLocationForMessageIds(messageIds: Set<MessageId>) {
self.queue.async {
var addedMessageIds: [MessageId] = []

View File

@ -1,227 +0,0 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public func updateMessageReactionsInteractively(postbox: Postbox, messageId: MessageId, reaction: String?) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction())
transaction.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
var attributes = currentMessage.attributes
loop: for j in 0 ..< attributes.count {
if let _ = attributes[j] as? PendingReactionsMessageAttribute {
attributes.remove(at: j)
break loop
}
}
attributes.append(PendingReactionsMessageAttribute(value: reaction))
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
|> ignoreValues
}
private enum RequestUpdateMessageReactionError {
case generic
}
private func requestUpdateMessageReaction(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal<Never, RequestUpdateMessageReactionError> {
return .complete()
/*return postbox.transaction { transaction -> (Peer, String?)? in
guard let peer = transaction.getPeer(messageId.peerId) else {
return nil
}
guard let message = transaction.getMessage(messageId) else {
return nil
}
var value: String?
for attribute in message.attributes {
if let attribute = attribute as? PendingReactionsMessageAttribute {
value = attribute.value
break
}
}
return (peer, value)
}
|> castError(RequestUpdateMessageReactionError.self)
|> mapToSignal { peerAndValue in
guard let (peer, value) = peerAndValue else {
return .fail(.generic)
}
guard let inputPeer = apiInputPeer(peer) else {
return .fail(.generic)
}
if messageId.namespace != Namespaces.Message.Cloud {
return .fail(.generic)
}
return network.request(Api.functions.messages.sendReaction(flags: value == nil ? 0 : 1, peer: inputPeer, msgId: messageId.id, reaction: value))
|> mapError { _ -> RequestUpdateMessageReactionError in
return .generic
}
|> mapToSignal { result -> Signal<Never, RequestUpdateMessageReactionError> in
return postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction())
transaction.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
let reactions = mergedMessageReactions(attributes: currentMessage.attributes)
var attributes = currentMessage.attributes
for j in (0 ..< attributes.count).reversed() {
if attributes[j] is PendingReactionsMessageAttribute || attributes[j] is ReactionsMessageAttribute {
attributes.remove(at: j)
}
}
if let reactions = reactions {
attributes.append(reactions)
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
stateManager.addUpdates(result)
}
|> castError(RequestUpdateMessageReactionError.self)
|> ignoreValues
}
}*/
}
private final class ManagedApplyPendingMessageReactionsActionsHelper {
var operationDisposables: [MessageId: Disposable] = [:]
func update(entries: [PendingMessageActionsEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) {
var disposeOperations: [Disposable] = []
var beginOperations: [(PendingMessageActionsEntry, MetaDisposable)] = []
var hasRunningOperationForPeerId = Set<PeerId>()
var validIds = Set<MessageId>()
for entry in entries {
if !hasRunningOperationForPeerId.contains(entry.id.peerId) {
hasRunningOperationForPeerId.insert(entry.id.peerId)
validIds.insert(entry.id)
if self.operationDisposables[entry.id] == nil {
let disposable = MetaDisposable()
beginOperations.append((entry, disposable))
self.operationDisposables[entry.id] = disposable
}
}
}
var removeMergedIds: [MessageId] = []
for (id, disposable) in self.operationDisposables {
if !validIds.contains(id) {
removeMergedIds.append(id)
disposeOperations.append(disposable)
}
}
for id in removeMergedIds {
self.operationDisposables.removeValue(forKey: id)
}
return (disposeOperations, beginOperations)
}
func reset() -> [Disposable] {
let disposables = Array(self.operationDisposables.values)
self.operationDisposables.removeAll()
return disposables
}
}
private func withTakenAction(postbox: Postbox, type: PendingMessageActionType, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal<Never, NoError>) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Signal<Never, NoError> in
var result: PendingMessageActionsEntry?
if let action = transaction.getPendingMessageAction(type: type, id: id) as? UpdateMessageReactionsAction {
result = PendingMessageActionsEntry(id: id, action: action)
}
return f(transaction, result)
}
|> switchToLatest
}
func managedApplyPendingMessageReactionsActions(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
return Signal { _ in
let helper = Atomic<ManagedApplyPendingMessageReactionsActionsHelper>(value: ManagedApplyPendingMessageReactionsActionsHelper())
let actionsKey = PostboxViewKey.pendingMessageActions(type: .updateReaction)
let disposable = postbox.combinedView(keys: [actionsKey]).start(next: { view in
var entries: [PendingMessageActionsEntry] = []
if let v = view.views[actionsKey] as? PendingMessageActionsView {
entries = v.entries
}
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) in
return helper.update(entries: entries)
}
for disposable in disposeOperations {
disposable.dispose()
}
for (entry, disposable) in beginOperations {
let signal = withTakenAction(postbox: postbox, type: .updateReaction, id: entry.id, { transaction, entry -> Signal<Never, NoError> in
if let entry = entry {
if let _ = entry.action as? UpdateMessageReactionsAction {
return synchronizeMessageReactions(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, id: entry.id)
} else {
assertionFailure()
}
}
return .complete()
})
|> then(
postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .updateReaction, id: entry.id, action: nil)
}
|> ignoreValues
)
disposable.set(signal.start())
}
})
return ActionDisposable {
let disposables = helper.with { helper -> [Disposable] in
return helper.reset()
}
for disposable in disposables {
disposable.dispose()
}
disposable.dispose()
}
}
}
private func synchronizeMessageReactions(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, id: MessageId) -> Signal<Never, NoError> {
return requestUpdateMessageReaction(postbox: postbox, network: network, stateManager: stateManager, messageId: id)
|> `catch` { _ -> Signal<Never, NoError> in
return postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .updateReaction, id: id, action: nil)
transaction.updateMessage(id, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
var attributes = currentMessage.attributes
loop: for j in 0 ..< attributes.count {
if let _ = attributes[j] as? PendingReactionsMessageAttribute {
attributes.remove(at: j)
break loop
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
|> ignoreValues
}
}

View File

@ -1,42 +1,13 @@
import Postbox
public struct MessageReaction: Equatable, PostboxCoding {
public var value: String
public var count: Int32
public var isSelected: Bool
public init(value: String, count: Int32, isSelected: Bool) {
self.value = value
self.count = count
self.isSelected = isSelected
}
public init(decoder: PostboxDecoder) {
self.value = decoder.decodeStringForKey("v", orElse: "")
self.count = decoder.decodeInt32ForKey("c", orElse: 0)
self.isSelected = decoder.decodeInt32ForKey("s", orElse: 0) != 0
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.value, forKey: "v")
encoder.encodeInt32(self.count, forKey: "c")
encoder.encodeInt32(self.isSelected ? 1 : 0, forKey: "s")
}
}
public final class ReactionsMessageAttribute: MessageAttribute {
public let reactions: [MessageReaction]
public init(reactions: [MessageReaction]) {
self.reactions = reactions
public init() {
}
required public init(decoder: PostboxDecoder) {
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("r")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObjectArray(self.reactions, forKey: "r")
}
}

View File

@ -2,4 +2,84 @@ import Postbox
public enum EngineMedia {
public typealias Id = MediaId
case image(TelegramMediaImage)
case file(TelegramMediaFile)
case geo(TelegramMediaMap)
case contact(TelegramMediaContact)
case action(TelegramMediaAction)
case dice(TelegramMediaDice)
case expiredContent(TelegramMediaExpiredContent)
case game(TelegramMediaGame)
case invoice(TelegramMediaInvoice)
case poll(TelegramMediaPoll)
case unsupported(TelegramMediaUnsupported)
case webFile(TelegramMediaWebFile)
case webpage(TelegramMediaWebpage)
}
public extension EngineMedia {
init(_ media: Media) {
switch media {
case let image as TelegramMediaImage:
self = .image(image)
case let file as TelegramMediaFile:
self = .file(file)
case let geo as TelegramMediaMap:
self = .geo(geo)
case let contact as TelegramMediaContact:
self = .contact(contact)
case let action as TelegramMediaAction:
self = .action(action)
case let dice as TelegramMediaDice:
self = .dice(dice)
case let expiredContent as TelegramMediaExpiredContent:
self = .expiredContent(expiredContent)
case let game as TelegramMediaGame:
self = .game(game)
case let invoice as TelegramMediaInvoice:
self = .invoice(invoice)
case let poll as TelegramMediaPoll:
self = .poll(poll)
case let unsupported as TelegramMediaUnsupported:
self = .unsupported(unsupported)
case let webFile as TelegramMediaWebFile:
self = .webFile(webFile)
case let webpage as TelegramMediaWebpage:
self = .webpage(webpage)
default:
preconditionFailure()
}
}
func _asMedia() -> Media {
switch self {
case let .image(image):
return image
case let .file(file):
return file
case let .geo(geo):
return geo
case let .contact(contact):
return contact
case let .action(action):
return action
case let .dice(dice):
return dice
case let .expiredContent(expiredContent):
return expiredContent
case let .game(game):
return game
case let .invoice(invoice):
return invoice
case let .poll(poll):
return poll
case let .unsupported(unsupported):
return unsupported
case let .webFile(webFile):
return webFile
case let .webpage(webpage):
return webpage
}
}
}

View File

@ -194,12 +194,12 @@ public func peerDebugDisplayTitles(_ peers: [Peer]) -> String {
}
}
public func messageMainPeer(_ message: Message) -> Peer? {
public func messageMainPeer(_ message: EngineMessage) -> EnginePeer? {
if let peer = message.peers[message.id.peerId] {
if let peer = peer as? TelegramSecretChat {
return message.peers[peer.regularPeerId]
return message.peers[peer.regularPeerId].flatMap(EnginePeer.init)
} else {
return peer
return EnginePeer(peer)
}
} else {
return nil

View File

@ -10,7 +10,6 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AccountContext:AccountContext",

View File

@ -1,6 +1,5 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
import DeviceAccess
import AccountContext

View File

@ -11,7 +11,6 @@ swift_library(
],
deps = [
"//submodules/TelegramCore:TelegramCore",
"//submodules/Postbox:Postbox",
"//submodules/Display:Display",
"//submodules/PlatformRestrictionMatching:PlatformRestrictionMatching",
"//submodules/LocalizedPeerData:LocalizedPeerData",

View File

@ -1,5 +1,4 @@
import Foundation
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
@ -88,7 +87,7 @@ public enum MessageContentKind: Equatable {
}
}
public func messageContentKind(contentSettings: ContentSettings, message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: PeerId) -> MessageContentKind {
public func messageContentKind(contentSettings: ContentSettings, message: EngineMessage, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id) -> MessageContentKind {
for attribute in message.attributes {
if let attribute = attribute as? RestrictedContentMessageAttribute {
if let text = attribute.platformText(platform: "ios", contentSettings: contentSettings) {
@ -98,25 +97,25 @@ public func messageContentKind(contentSettings: ContentSettings, message: Messag
}
}
for media in message.media {
if let kind = mediaContentKind(media, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId) {
if let kind = mediaContentKind(EngineMedia(media), message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId) {
return kind
}
}
return .text(message.text)
}
public func mediaContentKind(_ media: Media, message: Message? = nil, strings: PresentationStrings? = nil, nameDisplayOrder: PresentationPersonNameOrder? = nil, dateTimeFormat: PresentationDateTimeFormat? = nil, accountPeerId: PeerId? = nil) -> MessageContentKind? {
public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil, strings: PresentationStrings? = nil, nameDisplayOrder: PresentationPersonNameOrder? = nil, dateTimeFormat: PresentationDateTimeFormat? = nil, accountPeerId: EnginePeer.Id? = nil) -> MessageContentKind? {
switch media {
case let expiredMedia as TelegramMediaExpiredContent:
case let .expiredContent(expiredMedia):
switch expiredMedia.data {
case .image:
return .expiredImage
case .file:
return .expiredVideo
}
case _ as TelegramMediaImage:
case .image:
return .image
case let file as TelegramMediaFile:
case let .file(file):
var fileName: String = ""
for attribute in file.attributes {
switch attribute {
@ -154,27 +153,27 @@ public func mediaContentKind(_ media: Media, message: Message? = nil, strings: P
return .sticker("")
}
return .file(fileName)
case _ as TelegramMediaContact:
case .contact:
return .contact
case let game as TelegramMediaGame:
case let .game(game):
return .game(game.title)
case let location as TelegramMediaMap:
case let .geo(location):
if location.liveBroadcastingTimeout != nil {
return .liveLocation
} else {
return .location
}
case _ as TelegramMediaAction:
case .action:
if let message = message, let strings = strings, let nameDisplayOrder = nameDisplayOrder, let accountPeerId = accountPeerId {
return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat ?? PresentationDateTimeFormat(timeFormat: .military, dateFormat: .dayFirst, dateSeparator: ".", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), message: message, accountPeerId: accountPeerId, forChatList: false) ?? "")
} else {
return nil
}
case let poll as TelegramMediaPoll:
case let .poll(poll):
return .poll(poll.text)
case let dice as TelegramMediaDice:
case let .dice(dice):
return .dice(dice.emoji)
case let invoice as TelegramMediaInvoice:
case let .invoice(invoice):
return .invoice(invoice.title)
default:
return nil
@ -230,7 +229,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
}
}
public func descriptionStringForMessage(contentSettings: ContentSettings, message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: PeerId) -> (String, Bool) {
public func descriptionStringForMessage(contentSettings: ContentSettings, message: EngineMessage, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id) -> (String, Bool) {
let contentKind = messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
if !message.text.isEmpty && ![.expiredImage, .expiredVideo].contains(contentKind.key) {
return (foldLineBreaks(message.text), false)

View File

@ -1,6 +1,5 @@
import Foundation
import UIKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
@ -12,11 +11,11 @@ import Markdown
private let titleFont = Font.regular(13.0)
private let titleBoldFont = Font.bold(13.0)
private func peerMentionAttributes(primaryTextColor: UIColor, peerId: PeerId) -> MarkdownAttributeSet {
private func peerMentionAttributes(primaryTextColor: UIColor, peerId: EnginePeer.Id) -> MarkdownAttributeSet {
return MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [TelegramTextAttributes.PeerMention: TelegramPeerMention(peerId: peerId, mention: "")])
}
private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, PeerId?)]) -> [Int: MarkdownAttributeSet] {
private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, EnginePeer.Id?)]) -> [Int: MarkdownAttributeSet] {
var result: [Int: MarkdownAttributeSet] = [:]
for (index, peerId) in peerIds {
if let peerId = peerId {
@ -26,11 +25,11 @@ private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, P
return result
}
public func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId, forChatList: Bool) -> String? {
public func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id, forChatList: Bool) -> String? {
return universalServiceMessageString(presentationData: nil, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: forChatList)?.string
}
public func universalServiceMessageString(presentationData: (PresentationTheme, TelegramWallpaper)?, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId, forChatList: Bool) -> NSAttributedString? {
public func universalServiceMessageString(presentationData: (PresentationTheme, TelegramWallpaper)?, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id, forChatList: Bool) -> NSAttributedString? {
var attributedString: NSAttributedString?
let primaryTextColor: UIColor
@ -45,7 +44,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
for media in message.media {
if let action = media as? TelegramMediaAction {
let authorName = message.author.flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
let authorName = message.author?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
var isChannel = false
if message.id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
@ -71,7 +70,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedChat(authorName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, peerId)]))
}
} else {
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
var attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
let resultTitleString: PresentationStrings.FormattedString
if peerIds.count == 1 {
attributePeerIds.append((1, peerIds.first))
@ -90,7 +89,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_LeftChat(authorName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
}
} else {
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
var attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
if peerIds.count == 1 {
attributePeerIds.append((1, peerIds.first))
}
@ -153,10 +152,10 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case deleted
}
var pinnedMessage: Message?
var pinnedMessage: EngineMessage?
for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
pinnedMessage = message
pinnedMessage = EngineMessage(message)
}
}
@ -263,7 +262,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case let .messageAutoremoveTimeoutUpdated(timeout):
let authorString: String
if let author = messageMainPeer(message) {
authorString = EnginePeer(author).compactDisplayTitle
authorString = author.compactDisplayTitle
} else {
authorString = ""
}
@ -325,8 +324,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
break
case .historyScreenshot:
let text: String
if message.effectivelyIncoming(accountPeerId) {
text = strings.Notification_SecretChatMessageScreenshot(message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "").string
if message._asMessage().effectivelyIncoming(accountPeerId) {
text = strings.Notification_SecretChatMessageScreenshot(message.author?.compactDisplayTitle ?? "").string
} else {
text = strings.Notification_SecretChatMessageScreenshotSelf
}
@ -372,10 +371,10 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
argumentAttributes[1] = MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [:])
attributedString = addAttributesToStringWithRanges(formatWithArgumentRanges(baseString, ranges, [authorName, gameTitle ?? ""]), body: bodyAttributes, argumentAttributes: argumentAttributes)
case let .paymentSent(currency, totalAmount):
var invoiceMessage: Message?
var invoiceMessage: EngineMessage?
for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
invoiceMessage = message
invoiceMessage = EngineMessage(message)
}
}
@ -391,7 +390,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
if let invoiceTitle = invoiceTitle {
let botString: String
if let peer = messageMainPeer(message) {
botString = EnginePeer(peer).compactDisplayTitle
botString = peer.compactDisplayTitle
} else {
botString = ""
}
@ -441,7 +440,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
if let scheduleDate = scheduleDate {
if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
let titleString: PresentationStrings.FormattedString
if let channel = message.author as? TelegramChannel, case .broadcast = channel.info {
if case let .channel(channel) = message.author, case .broadcast = channel.info {
titleString = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: scheduleDate, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat(dateFormatString: { strings.Notification_LiveStreamScheduled($0) }, tomorrowFormatString: { strings.Notification_LiveStreamScheduledTomorrow($0) }, todayFormatString: { strings.Notification_LiveStreamScheduledToday($0) }))
} else {
titleString = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: scheduleDate, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat(dateFormatString: { strings.Notification_VoiceChatScheduledChannel($0) }, tomorrowFormatString: { strings.Notification_VoiceChatScheduledTomorrowChannel($0) }, todayFormatString: { strings.Notification_VoiceChatScheduledTodayChannel($0) }))
@ -449,34 +448,34 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = NSAttributedString(string: titleString.string, font: titleFont, textColor: primaryTextColor)
} else {
let titleString = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: scheduleDate, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat(dateFormatString: { strings.Notification_VoiceChatScheduled(authorName, $0) }, tomorrowFormatString: { strings.Notification_VoiceChatScheduledTomorrow(authorName, $0) }, todayFormatString: { strings.Notification_VoiceChatScheduledToday(authorName, $0) }))
let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
let attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
attributedString = addAttributesToStringWithRanges(titleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
}
} else if let duration = duration {
if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
let titleString: String
if let channel = message.author as? TelegramChannel, case .broadcast = channel.info {
if case let .channel(channel) = message.author, case .broadcast = channel.info {
titleString = strings.Notification_LiveStreamEnded(callDurationString(strings: strings, value: duration)).string
} else {
titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).string
}
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
} else {
let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
let attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
let titleString = strings.Notification_VoiceChatEndedGroup(authorName, callDurationString(strings: strings, value: duration))
attributedString = addAttributesToStringWithRanges(titleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
}
} else {
if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
let titleString: String
if let channel = message.author as? TelegramChannel, case .broadcast = channel.info {
if case let .channel(channel) = message.author, case .broadcast = channel.info {
titleString = strings.Notification_LiveStreamStarted
} else {
titleString = strings.Notification_VoiceChatStartedChannel
}
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
} else {
let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
let attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
let titleString = strings.Notification_VoiceChatStarted(authorName)
attributedString = addAttributesToStringWithRanges(titleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
}
@ -529,7 +528,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReached(message.peers[fromId].flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "", distanceString, message.peers[toId].flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "")._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, fromId), (2, toId)]))
}
case let .inviteToGroupPhoneCall(_, _, peerIds):
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
var attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
let resultTitleString: PresentationStrings.FormattedString
if peerIds.count == 1 {
if peerIds[0] == accountPeerId {
@ -551,7 +550,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} else if message.author?.id == accountPeerId {
attributedString = NSAttributedString(string: strings.Notification_YouDisabledTheme, font: titleFont, textColor: primaryTextColor)
} else {
let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
let attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
let resultTitleString = strings.Notification_DisabledTheme(authorName)
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
}

View File

@ -188,7 +188,6 @@ swift_library(
"//submodules/WatchBridge:WatchBridge",
"//submodules/ShareItems:ShareItems",
"//submodules/ShareItems/Impl:ShareItemsImpl",
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
"//submodules/SettingsUI:SettingsUI",
"//submodules/UrlHandling:UrlHandling",
"//submodules/HexColor:HexColor",

View File

@ -44,7 +44,6 @@ import PeerAvatarGalleryUI
import PeerInfoUI
import RaiseToListen
import UrlHandling
import ReactionSelectionNode
import AvatarNode
import AppBundle
import LocalizedPeerData
@ -934,35 +933,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, !actions.items.isEmpty else {
return
}
var reactionItems: [ReactionContextItem] = []
/*var hasLike = false
let hearts: [String] = ["", "❤️"]
for attribute in messages[0].attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
for reaction in attribute.reactions {
if hearts.contains(reaction.value) {
if reaction.isSelected {
hasLike = true
}
}
}
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
if let value = attribute.value, hearts.contains(value) {
hasLike = true
}
}
}
if hasLike {
reactionItems.append(ReactionContextItem(reaction: .unlike))
} else {
reactionItems.append(ReactionContextItem(reaction: .like))
}*/
if Namespaces.Message.allScheduled.contains(message.id.namespace) || message.id.peerId.namespace == Namespaces.Peer.SecretChat {
reactionItems = []
}
var tip: ContextController.Tip?
@ -979,47 +949,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
actions.tip = tip
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: selectAll)), items: .single(actions), reactionItems: reactionItems, recognizer: recognizer, gesture: gesture)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: selectAll)), items: .single(actions), recognizer: recognizer, gesture: gesture)
strongSelf.currentContextController = controller
controller.reactionSelected = { [weak controller] value in
guard let strongSelf = self, let message = updatedMessages.first else {
return
}
let hearts: [String] = ["", "❤️"]
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
if item.message.id == message.id {
switch value {
case .like:
itemNode.awaitingAppliedReaction = (hearts[0], { [weak itemNode] in
guard let controller = controller else {
return
}
if let itemNode = itemNode, let (targetEmptyNode, targetFilledNode) = itemNode.targetReactionNode(value: hearts[0]) {
controller.dismissWithReaction(value: hearts[0], targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: true, completion: {
})
} else {
controller.dismiss()
}
})
case .unlike:
itemNode.awaitingAppliedReaction = (nil, {
guard let controller = controller else {
return
}
controller.dismiss()
})
}
}
}
}
switch value {
case .like:
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: message.id, reaction: hearts[0]).start()
case .unlike:
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: message.id, reaction: nil).start()
}
}
strongSelf.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss()
@ -1734,8 +1666,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return self?.navigationController as? NavigationController
}, chatControllerNode: { [weak self] in
return self?.chatDisplayNode
}, reactionContainerNode: { [weak self] in
return self?.chatDisplayNode.reactionContainerNode
}, presentGlobalOverlayController: { [weak self] controller, arguments in
self?.presentInGlobalOverlay(controller, with: arguments)
}, callPeer: { [weak self] peerId, isVideo in
@ -2162,28 +2092,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if canReplyInChat(strongSelf.presentationInterfaceState) {
return .reply
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
/*var hasLike = false
let hearts: [String] = ["", "❤️"]
for attribute in message.attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
for reaction in attribute.reactions {
if hearts.contains(reaction.value) {
if reaction.isSelected {
hasLike = true
}
}
}
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
if let value = attribute.value, hearts.contains(value) {
hasLike = true
}
}
}
if hasLike {
return .unlike
} else {
return .like
}*/
}
}
return .none
@ -2235,7 +2143,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})))
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: true)), items: .single(ContextController.Items(items: actions)), reactionItems: [], recognizer: nil)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: true)), items: .single(ContextController.Items(items: actions)), recognizer: nil)
strongSelf.currentContextController = controller
strongSelf.forEachController({ controller in
if let controller = controller as? TooltipScreen {
@ -2312,7 +2220,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
f(.dismissWithoutContent)
})))
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: topMessage, selectAll: true)), items: .single(ContextController.Items(items: actions)), reactionItems: [], recognizer: nil)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: topMessage, selectAll: true)), items: .single(ContextController.Items(items: actions)), recognizer: nil)
strongSelf.currentContextController = controller
strongSelf.forEachController({ controller in
if let controller = controller as? TooltipScreen {
@ -2604,18 +2512,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .speak:
speakText(text.string)
}
}, updateMessageLike: { [weak self] messageId, isLiked in
guard let strongSelf = self else {
return
}
let heart = ""
if isLiked {
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, reaction: heart).start()
} else {
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, reaction: nil).start()
}
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { [weak self] _ in
guard let strongSelf = self else {
return
@ -2755,7 +2651,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return items
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
})
}, openMessageReplies: { [weak self] messageId, isChannelPost, displayModalProgress in
@ -3034,7 +2930,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return items
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
@ -5841,7 +5737,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return items
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [])
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(items: $0) })
contextController.dismissedForCancel = { [weak chatController] in
if let selectedMessageIds = (chatController as? ChatControllerImpl)?.selectedMessageIds {
var forwardMessageIds = strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
@ -7349,7 +7245,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: .pinnedMessages(id: pinnedMessage.message.id), botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(ContextController.Items(items: items)), gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}, joinGroupCall: { [weak self] activeCall in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {

View File

@ -8,7 +8,6 @@ import Display
import TelegramUIPreferences
import AccountContext
import TextSelectionNode
import ReactionSelectionNode
import ContextUI
import ChatInterfaceState
import UndoUI
@ -45,8 +44,6 @@ struct ChatInterfacePollActionState: Equatable {
public enum ChatControllerInteractionSwipeAction {
case none
case reply
case like
case unlike
}
public final class ChatControllerInteraction {
@ -83,7 +80,6 @@ public final class ChatControllerInteraction {
let presentController: (ViewController, Any?) -> Void
let navigationController: () -> NavigationController?
let chatControllerNode: () -> ASDisplayNode?
let reactionContainerNode: () -> ReactionSelectionParentNode?
let presentGlobalOverlayController: (ViewController, Any?) -> Void
let callPeer: (PeerId, Bool) -> Void
let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void
@ -104,8 +100,6 @@ public final class ChatControllerInteraction {
let sendScheduledMessagesNow: ([MessageId]) -> Void
let editScheduledMessagesTime: ([MessageId]) -> Void
let performTextSelectionAction: (UInt32, NSAttributedString, TextSelectionAction) -> Void
let updateMessageLike: (MessageId, Bool) -> Void
let openMessageReactions: (MessageId) -> Void
let displayImportedMessageTooltip: (ASDisplayNode) -> Void
let displaySwipeToReplyHint: () -> Void
let dismissReplyMarkupMessage: (Message) -> Void
@ -180,7 +174,6 @@ public final class ChatControllerInteraction {
presentController: @escaping (ViewController, Any?) -> Void,
navigationController: @escaping () -> NavigationController?,
chatControllerNode: @escaping () -> ASDisplayNode?,
reactionContainerNode: @escaping () -> ReactionSelectionParentNode?,
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
callPeer: @escaping (PeerId, Bool) -> Void,
longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void,
@ -201,8 +194,6 @@ public final class ChatControllerInteraction {
sendScheduledMessagesNow: @escaping ([MessageId]) -> Void,
editScheduledMessagesTime: @escaping ([MessageId]) -> Void,
performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void,
updateMessageLike: @escaping (MessageId, Bool) -> Void,
openMessageReactions: @escaping (MessageId) -> Void,
displayImportedMessageTooltip: @escaping (ASDisplayNode) -> Void,
displaySwipeToReplyHint: @escaping () -> Void,
dismissReplyMarkupMessage: @escaping (Message) -> Void,
@ -263,7 +254,6 @@ public final class ChatControllerInteraction {
self.presentController = presentController
self.navigationController = navigationController
self.chatControllerNode = chatControllerNode
self.reactionContainerNode = reactionContainerNode
self.presentGlobalOverlayController = presentGlobalOverlayController
self.callPeer = callPeer
self.longTap = longTap
@ -287,8 +277,6 @@ public final class ChatControllerInteraction {
self.sendScheduledMessagesNow = sendScheduledMessagesNow
self.editScheduledMessagesTime = editScheduledMessagesTime
self.performTextSelectionAction = performTextSelectionAction
self.updateMessageLike = updateMessageLike
self.openMessageReactions = openMessageReactions
self.displayImportedMessageTooltip = displayImportedMessageTooltip
self.displaySwipeToReplyHint = displaySwipeToReplyHint
self.dismissReplyMarkupMessage = dismissReplyMarkupMessage
@ -324,8 +312,6 @@ public final class ChatControllerInteraction {
return nil
}, chatControllerNode: {
return nil
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in
return .none
@ -342,8 +328,6 @@ public final class ChatControllerInteraction {
}, sendScheduledMessagesNow: { _ in
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in

View File

@ -10,7 +10,6 @@ import TelegramUIPreferences
import TextFormat
import AccountContext
import TelegramNotices
import ReactionSelectionNode
import TelegramUniversalVideoContent
import ChatInterfaceState
import FastBlur
@ -83,7 +82,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
let backgroundNode: WallpaperBackgroundNode
let historyNode: ChatHistoryListNode
var blurredHistoryNode: ASImageNode?
let reactionContainerNode: ReactionSelectionParentNode
let historyNodeContainer: ASDisplayNode
let loadingNode: ChatLoadingNode
private var emptyNode: ChatEmptyNode?
@ -336,8 +334,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
onTransitionEventImpl?(transition)
})
self.reactionContainerNode = ReactionSelectionParentNode(account: context.account, theme: chatPresentationInterfaceState.theme)
self.loadingNode = ChatLoadingNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners)
if case let .color(color) = self.chatPresentationInterfaceState.chatWallpaper, UIColor(rgb: color).isEqual(self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) {
@ -1178,9 +1174,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
transition.updateFrame(node: emptyNode, frame: contentBounds)
}
transition.updateFrame(node: self.reactionContainerNode, frame: contentBounds)
self.reactionContainerNode.updateLayout(size: contentBounds.size, insets: UIEdgeInsets(), transition: transition)
var contentBottomInset: CGFloat = inputPanelsHeight + 4.0
if let scrollContainerNode = self.scrollContainerNode {
@ -1852,10 +1845,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} else {
completion(.immediate)
}
if self.reactionContainerNode.supernode == nil {
self.addSubnode(self.reactionContainerNode)
}
}
func updateAutomaticMediaDownloadSettings(_ settings: MediaAutoDownloadSettings) {

View File

@ -470,7 +470,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private let messageProcessingManager = ChatMessageThrottledProcessingManager()
private let adSeenProcessingManager = ChatMessageThrottledProcessingManager()
private let messageReactionsProcessingManager = ChatMessageThrottledProcessingManager()
private let seenLiveLocationProcessingManager = ChatMessageThrottledProcessingManager()
private let unsupportedMessageProcessingManager = ChatMessageThrottledProcessingManager()
private let refreshMediaProcessingManager = ChatMessageThrottledProcessingManager()
@ -631,9 +630,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
}
}
self.messageReactionsProcessingManager.process = { [weak context] messageIds in
context?.account.viewTracker.updateReactionsForMessageIds(messageIds: messageIds)
}
self.seenLiveLocationProcessingManager.process = { [weak context] messageIds in
context?.account.viewTracker.updateSeenLiveLocationForMessageIds(messageIds: messageIds)
}
@ -1503,7 +1499,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
var messageIdsWithViewCount: [MessageId] = []
var messageIdsWithAds: [MessageId] = []
var messageIdsWithUpdateableReactions: [MessageId] = []
var messageIdsWithLiveLocation: [MessageId] = []
var messageIdsWithUnsupportedMedia: [MessageId] = []
var messageIdsWithRefreshMedia: [MessageId] = []
@ -1541,7 +1536,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
contentRequiredValidation = true
}
}
var isAction = false
for media in message.media {
if let _ = media as? TelegramMediaUnsupported {
contentRequiredValidation = true
@ -1550,8 +1544,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if message.timestamp + liveBroadcastingTimeout > timestamp {
messageIdsWithLiveLocation.append(message.id)
}
} else if let _ = media as? TelegramMediaAction {
isAction = true
} else if let telegramFile = media as? TelegramMediaFile {
if telegramFile.isAnimatedSticker, (message.id.peerId.namespace == Namespaces.Peer.SecretChat || !telegramFile.previewRepresentations.isEmpty), let size = telegramFile.size, size > 0 && size <= 128 * 1024 {
if message.id.peerId.namespace == Namespaces.Peer.SecretChat {
@ -1572,9 +1564,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
}
}
if !isAction && message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
messageIdsWithUpdateableReactions.append(message.id)
}
if contentRequiredValidation {
messageIdsWithUnsupportedMedia.append(message.id)
}
@ -1703,9 +1692,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if !messageIdsWithAds.isEmpty {
self.adSeenProcessingManager.add(messageIdsWithAds)
}
if !messageIdsWithUpdateableReactions.isEmpty {
self.messageReactionsProcessingManager.add(messageIdsWithUpdateableReactions)
}
if !messageIdsWithLiveLocation.isEmpty {
self.seenLiveLocationProcessingManager.add(messageIdsWithLiveLocation)
}

View File

@ -1465,7 +1465,7 @@ final class ChatMediaInputNode: ChatInputNode {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: sourceNode, sourceRect: sourceRect)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: sourceNode, sourceRect: sourceRect)), items: .single(ContextController.Items(items: items)), gesture: gesture)
strongSelf.controllerInteraction.presentGlobalOverlayController(contextController, nil)
})
}

View File

@ -19,7 +19,7 @@ import GalleryUI
import WallpaperBackgroundNode
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false)
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false)
}
class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {

View File

@ -299,13 +299,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.messageAccessibilityArea.focused = { [weak self] in
self?.accessibilityElementDidBecomeFocused()
}
self.dateAndStatusNode.openReactions = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.openMessageReactions(item.message.id)
}
}
deinit {
@ -352,7 +345,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
guard let strongSelf = self else {
return
}
//strongSelf.reactionRecognizer?.cancel()
if let action = strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) {
switch action {
case let .action(f):
@ -875,27 +868,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular)
var isReplyThread = false
if case .replyThread = item.chatLocation {
isReplyThread = true
}
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
@ -1836,10 +1816,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
break
case .reply:
item.controllerInteraction.setupReply(item.message.id)
case .like:
item.controllerInteraction.updateMessageLike(item.message.id, true)
case .unlike:
item.controllerInteraction.updateMessageLike(item.message.id, true)
}
}
}

View File

@ -329,20 +329,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings)
var webpageGalleryMediaCount: Int?
for media in message.media {
@ -506,7 +493,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
edited: edited,
viewCount: viewCount,
dateReplies: dateReplies,
dateReactions: dateReactions,
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
dateText: dateText
)
@ -626,7 +612,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
let textConstrainedSize = CGSize(width: constrainedSize.width - insets.left - insets.right, height: constrainedSize.height - insets.top - insets.bottom)
if let textStatusType = textStatusType {
statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, textStatusType, textConstrainedSize, dateReactions, dateReplies, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, message.isSelfExpiring)
statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, textStatusType, textConstrainedSize, dateReplies, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, message.isSelfExpiring)
}
var updatedAdditionalImageBadge: ChatMessageInteractiveMediaBadge?
@ -1120,11 +1106,4 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? {
return self.contentImageNode?.playMediaWithSound()
}
func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
if !self.statusNode.isHidden {
return self.statusNode.reactionNode(value: value)
}
return nil
}
}

View File

@ -210,10 +210,6 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
}
func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
return nil
}
func getStatusNode() -> ASDisplayNode? {
return nil
}

View File

@ -16,7 +16,6 @@ import MosaicLayout
import TextSelectionNode
import PlatformRestrictionMatching
import Emoji
import ReactionSelectionNode
import PersistentStringHash
import GridMessageSelectionNode
import AppBundle
@ -438,7 +437,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
private var appliedForwardInfo: (Peer?, String?)?
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
private var reactionRecognizer: ReactionSwipeGestureRecognizer?
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
@ -799,7 +797,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
guard let strongSelf = self else {
return
}
strongSelf.reactionRecognizer?.cancel()
if let action = strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) {
switch action {
case let .action(f):
@ -828,234 +826,35 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.tapRecognizer = recognizer
self.view.addGestureRecognizer(recognizer)
self.view.isExclusiveTouch = true
if true {
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
replyRecognizer.shouldBegin = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
if strongSelf.selectionNode != nil {
return false
}
for media in item.content.firstMessage.media {
if let _ = media as? TelegramMediaExpiredContent {
return false
}
else if let media = media as? TelegramMediaAction {
if case .phoneCall(_, _, _, _) = media.action {
} else {
return false
}
}
}
let action = item.controllerInteraction.canSetupReply(item.message)
strongSelf.currentSwipeAction = action
if case .none = action {
return false
} else {
return true
}
}
return false
}
self.view.addGestureRecognizer(replyRecognizer)
} else {
/*let reactionRecognizer = ReactionSwipeGestureRecognizer(target: nil, action: nil)
self.reactionRecognizer = reactionRecognizer
reactionRecognizer.availableReactions = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item, !item.presentationData.isPreview && !Namespaces.Message.allScheduled.contains(item.message.id.namespace) else {
return []
}
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
replyRecognizer.shouldBegin = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
if strongSelf.selectionNode != nil {
return []
return false
}
for media in item.content.firstMessage.media {
if let _ = media as? TelegramMediaExpiredContent {
return []
return false
}
else if let media = media as? TelegramMediaAction {
if case .phoneCall = media.action {
if case .phoneCall(_, _, _, _) = media.action {
} else {
return []
return false
}
}
}
let reactions: [(String, String, String)] = [
("😔", "Sad", "sad"),
("😳", "Surprised", "surprised"),
("😂", "Fun", "lol"),
("👍", "Like", "thumbsup"),
("", "Love", "heart"),
]
var reactionItems: [ReactionGestureItem] = []
for (value, text, name) in reactions.reversed() {
if let path = getAppBundle().path(forResource: name, ofType: "tgs") {
reactionItems.append(.reaction(value: value, text: text, path: path))
}
}
if item.controllerInteraction.canSetupReply(item.message) {
//reactionItems.append(.reply)
}
return reactionItems
}
reactionRecognizer.getReactionContainer = { [weak self] in
return self?.item?.controllerInteraction.reactionContainerNode()
}
reactionRecognizer.getAnchorPoint = { [weak self] in
guard let strongSelf = self else {
return nil
}
return CGPoint(x: strongSelf.backgroundNode.frame.maxX, y: strongSelf.backgroundNode.frame.minY)
}
reactionRecognizer.shouldElevateAnchorPoint = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
let action = item.controllerInteraction.canSetupReply(item.message)
strongSelf.currentSwipeAction = action
if case .none = action {
return false
}
return item.controllerInteraction.canSetupReply(item.message)
}
reactionRecognizer.began = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.cancelInteractiveKeyboardGestures()
}
reactionRecognizer.updateOffset = { [weak self] offset, animated in
guard let strongSelf = self else {
return
}
var bounds = strongSelf.bounds
bounds.origin.x = offset
strongSelf.bounds = bounds
var shadowBounds = strongSelf.shadowNode.bounds
shadowBounds.origin.x = offset
strongSelf.shadowNode.bounds = shadowBounds
if animated {
strongSelf.layer.animateBoundsOriginXAdditive(from: -offset, to: 0.0, duration: 0.1, mediaTimingFunction: CAMediaTimingFunction(name: .easeOut))
strongSelf.shadowNode.layer.animateBoundsOriginXAdditive(from: -offset, to: 0.0, duration: 0.1, mediaTimingFunction: CAMediaTimingFunction(name: .easeOut))
}
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
swipeToReplyNode.alpha = max(0.0, min(1.0, abs(offset / 40.0)))
}
}
reactionRecognizer.activateReply = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
var bounds = strongSelf.bounds
let offset = bounds.origin.x
bounds.origin.x = 0.0
strongSelf.bounds = bounds
var shadowBounds = strongSelf.shadowNode.bounds
let shadowOffset = shadowBounds.origin.x
shadowBounds.origin.x = 0.0
strongSelf.shadowNode.bounds = shadowBounds
if !offset.isZero {
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if !shadowOffset.isZero {
strongSelf.shadowNode.layer.animateBoundsOriginXAdditive(from: shadowOffset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
strongSelf.swipeToReplyNode = nil
swipeToReplyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak swipeToReplyNode] _ in
swipeToReplyNode?.removeFromSupernode()
})
swipeToReplyNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
item.controllerInteraction.setupReply(item.message.id)
}
reactionRecognizer.displayReply = { [weak self] offset in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
if !item.controllerInteraction.canSetupReply(item.message) {
return
}
if strongSelf.swipeToReplyFeedback == nil {
strongSelf.swipeToReplyFeedback = HapticFeedback()
}
strongSelf.swipeToReplyFeedback?.tap()
if strongSelf.swipeToReplyNode == nil {
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonFillColor, wallpaper: item.presentationData.theme.wallpaper), strokeColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonStrokeColor, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper))
strongSelf.swipeToReplyNode = swipeToReplyNode
strongSelf.insertSubnode(swipeToReplyNode, belowSubnode: strongSelf.messageAccessibilityArea)
swipeToReplyNode.frame = CGRect(origin: CGPoint(x: strongSelf.bounds.size.width, y: floor((strongSelf.contentSize.height - 33.0) / 2.0)), size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
swipeToReplyNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
}
}
reactionRecognizer.completed = { [weak self] reaction in
guard let strongSelf = self else {
return
}
if let item = strongSelf.item, let reaction = reaction {
switch reaction {
case let .reaction(value, _, _):
var resolvedValue: String?
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), reactionsAttribute.reactions.contains(where: { $0.value == value }) {
resolvedValue = nil
} else {
resolvedValue = value
}
strongSelf.awaitingAppliedReaction = (resolvedValue, {})
item.controllerInteraction.updateMessageReaction(item.message.id, resolvedValue)
case .reply:
strongSelf.reactionRecognizer?.complete(into: nil, hideTarget: false)
var bounds = strongSelf.bounds
let offset = bounds.origin.x
bounds.origin.x = 0.0
strongSelf.bounds = bounds
var shadowBounds = strongSelf.shadowNode.bounds
let shadowOffset = shadowBounds.origin.x
shadowBounds.origin.x = 0.0
strongSelf.shadowNode.bounds = shadowBounds
if !offset.isZero {
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if !shadowOffset.isZero {
strongSelf.shadowNode.layer.animateBoundsOriginXAdditive(from: shadowOffset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
strongSelf.swipeToReplyNode = nil
swipeToReplyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak swipeToReplyNode] _ in
swipeToReplyNode?.removeFromSupernode()
})
swipeToReplyNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
item.controllerInteraction.setupReply(item.message.id)
}
} else {
strongSelf.reactionRecognizer?.complete(into: nil, hideTarget: false)
var bounds = strongSelf.bounds
let offset = bounds.origin.x
bounds.origin.x = 0.0
strongSelf.bounds = bounds
var shadowBounds = strongSelf.shadowNode.bounds
let shadowOffset = shadowBounds.origin.x
shadowBounds.origin.x = 0.0
strongSelf.shadowNode.bounds = shadowBounds
if !offset.isZero {
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if !shadowOffset.isZero {
strongSelf.shadowNode.layer.animateBoundsOriginXAdditive(from: shadowOffset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
strongSelf.swipeToReplyNode = nil
swipeToReplyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak swipeToReplyNode] _ in
swipeToReplyNode?.removeFromSupernode()
})
swipeToReplyNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
return true
}
}
self.view.addGestureRecognizer(reactionRecognizer)*/
return false
}
self.view.addGestureRecognizer(replyRecognizer)
}
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
@ -1110,7 +909,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode),
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)),
mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction], Int, Bool, Bool) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode),
mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, Int, Bool, Bool) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode),
layoutConstants: ChatMessageItemLayoutConstants,
currentItem: ChatMessageItem?,
currentForwardInfo: (Peer?, String?)?,
@ -1678,26 +1477,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateFormat: MessageTimestampStatusFormat
if let subject = item.associatedData.subject, case .forwardedMessages = subject {
dateFormat = .minimal
} else {
dateFormat = .regular
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat)
let statusType: ChatMessageDateAndStatusType
if message.effectivelyIncoming(item.context.account.peerId) {
@ -1717,7 +1503,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
isReplyThread = true
}
mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, message.isSelfExpiring)
mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReplies, message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, message.isSelfExpiring)
}
}
@ -2889,45 +2675,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
strongSelf.updateSearchTextHighlightState()
/*if let (awaitingAppliedReaction, f) = strongSelf.awaitingAppliedReaction {
var bounds = strongSelf.bounds
let offset = bounds.origin.x
bounds.origin.x = 0.0
strongSelf.bounds = bounds
var shadowBounds = strongSelf.shadowNode.bounds
let shadowOffset = shadowBounds.origin.x
shadowBounds.origin.x = 0.0
strongSelf.shadowNode.bounds = shadowBounds
if !offset.isZero {
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if !shadowOffset.isZero {
strongSelf.shadowNode.layer.animateBoundsOriginXAdditive(from: shadowOffset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
strongSelf.swipeToReplyNode = nil
swipeToReplyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak swipeToReplyNode] _ in
swipeToReplyNode?.removeFromSupernode()
})
swipeToReplyNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
strongSelf.awaitingAppliedReaction = nil
/*var targetNode: ASDisplayNode?
var hideTarget = false
if let awaitingAppliedReaction = awaitingAppliedReaction {
for contentNode in strongSelf.contentNodes {
if let (reactionNode, count) = contentNode.reactionTargetNode(value: awaitingAppliedReaction) {
targetNode = reactionNode
hideTarget = count == 1
break
}
}
}
strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)*/
f()
}*/
}
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
@ -3766,10 +3513,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
break
case .reply:
item.controllerInteraction.setupReply(item.message.id)
case .like:
item.controllerInteraction.updateMessageLike(item.message.id, true)
case .unlike:
item.controllerInteraction.updateMessageLike(item.message.id, false)
}
}
}
@ -3854,15 +3597,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.mainContextSourceNode.contentNode.addSubnode(accessoryItemNode)
}
override func targetReactionNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
for contentNode in self.contentNodes {
if let (emptyNode, filledNode) = contentNode.reactionTargetNode(value: value) {
return (emptyNode, filledNode)
}
}
return nil
}
private var backgroundMaskMode: Bool {
let hasWallpaper = self.item?.presentationData.theme.wallpaper.hasWallpaper ?? false
let isPreview = self.item?.presentationData.isPreview ?? false

View File

@ -167,7 +167,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: 0)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let statusText: String
if let callDuration = callDuration, callDuration > 1 {
@ -265,8 +265,4 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
return .none
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
return nil
}
}

View File

@ -166,20 +166,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let statusType: ChatMessageDateAndStatusType?
switch position {
@ -208,7 +195,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
}
@ -376,11 +363,4 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
let _ = item.controllerInteraction.openMessage(item.message, .default)
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionNode(value: value)
}
return nil
}
}

View File

@ -9,8 +9,6 @@ import TelegramPresentationData
import AccountContext
import AppBundle
private let reactionCountFont = Font.semibold(11.0)
private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
if let _ = layer.animation(forKey: "clockFrameAnimation") {
return
@ -42,112 +40,6 @@ enum ChatMessageDateAndStatusType: Equatable {
case FreeOutgoing(ChatMessageDateAndStatusOutgoingType)
}
private let reactionFont = Font.regular(12.0)
private final class StatusReactionNode: ASDisplayNode {
let emptyImageNode: ASImageNode
let selectedImageNode: ASImageNode
private var theme: PresentationTheme?
private var isSelected: Bool?
override init() {
self.emptyImageNode = ASImageNode()
self.emptyImageNode.displaysAsynchronously = false
self.selectedImageNode = ASImageNode()
self.selectedImageNode.displaysAsynchronously = false
super.init()
self.addSubnode(self.emptyImageNode)
self.addSubnode(self.selectedImageNode)
}
func update(type: ChatMessageDateAndStatusType, isSelected: Bool, count: Int, theme: PresentationTheme, wallpaper: TelegramWallpaper, animated: Bool) {
if self.theme !== theme {
self.theme = theme
let emptyImage: UIImage?
let selectedImage: UIImage?
switch type {
case .BubbleIncoming:
emptyImage = PresentationResourcesChat.chatMessageLike(theme, incoming: true, isSelected: false)
selectedImage = PresentationResourcesChat.chatMessageLike(theme, incoming: true, isSelected: true)
case .BubbleOutgoing:
emptyImage = PresentationResourcesChat.chatMessageLike(theme, incoming: false, isSelected: false)
selectedImage = PresentationResourcesChat.chatMessageLike(theme, incoming: false, isSelected: true)
case .ImageIncoming, .ImageOutgoing:
emptyImage = PresentationResourcesChat.chatMessageMediaLike(theme, isSelected: false)
selectedImage = PresentationResourcesChat.chatMessageMediaLike(theme, isSelected: true)
case .FreeIncoming, .FreeOutgoing:
emptyImage = PresentationResourcesChat.chatMessageFreeLike(theme, wallpaper: wallpaper, isSelected: false)
selectedImage = PresentationResourcesChat.chatMessageFreeLike(theme, wallpaper: wallpaper, isSelected: true)
}
if let emptyImage = emptyImage, let selectedImage = selectedImage {
self.emptyImageNode.image = emptyImage
self.emptyImageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: emptyImage.size)
self.selectedImageNode.image = selectedImage
self.selectedImageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: selectedImage.size)
}
}
if self.isSelected != isSelected {
let wasSelected = self.isSelected
self.isSelected = isSelected
self.emptyImageNode.isHidden = isSelected && count <= 1
self.selectedImageNode.isHidden = !isSelected
if let wasSelected = wasSelected, wasSelected, !isSelected {
if let image = self.selectedImageNode.image {
let leftImage = generateImage(image.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
image.draw(in: CGRect(origin: CGPoint(), size: size))
UIGraphicsPopContext()
context.clear(CGRect(origin: CGPoint(x: size.width / 2.0, y: 0.0), size: CGSize(width: size.width / 2.0, height: size.height)))
})
let rightImage = generateImage(image.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
image.draw(in: CGRect(origin: CGPoint(), size: size))
UIGraphicsPopContext()
context.clear(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width / 2.0, height: size.height)))
})
if let leftImage = leftImage, let rightImage = rightImage {
let leftView = UIImageView()
leftView.image = leftImage
leftView.frame = self.selectedImageNode.frame
let rightView = UIImageView()
rightView.image = rightImage
rightView.frame = self.selectedImageNode.frame
self.view.addSubview(leftView)
self.view.addSubview(rightView)
let duration: Double = 0.3
leftView.layer.animateRotation(from: 0.0, to: -CGFloat.pi * 0.7, duration: duration, removeOnCompletion: false)
rightView.layer.animateRotation(from: 0.0, to: CGFloat.pi * 0.7, duration: duration, removeOnCompletion: false)
leftView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -6.0, y: 8.0), duration: duration, removeOnCompletion: false, additive: true)
rightView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 6.0, y: 8.0), duration: duration, removeOnCompletion: false, additive: true)
leftView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak leftView] _ in
leftView?.removeFromSuperview()
})
rightView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak rightView] _ in
rightView?.removeFromSuperview()
})
}
}
}
}
}
}
class ChatMessageDateAndStatusNode: ASDisplayNode {
private var backgroundNode: ASImageNode?
private var blurredBackgroundNode: NavigationBackgroundNode?
@ -157,9 +49,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
private var clockMinNode: ASImageNode?
private let dateNode: TextNode
private var impressionIcon: ASImageNode?
private var reactionNodes: [StatusReactionNode] = []
private var reactionCountNode: TextNode?
private var reactionButtonNode: HighlightTrackingButtonNode?
private var repliesIcon: ASImageNode?
private var selfExpiringIcon: ASImageNode?
private var replyCountNode: TextNode?
@ -169,8 +58,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
private var layoutSize: CGSize?
private var tapGestureRecognizer: UITapGestureRecognizer?
var openReactions: (() -> Void)?
var openReplies: (() -> Void)?
var pressed: (() -> Void)? {
didSet {
@ -203,7 +91,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}
}
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int, _ isPinned: Bool, _ hasAutoremove: Bool) -> (CGSize, (Bool) -> Void) {
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ replies: Int, _ isPinned: Bool, _ hasAutoremove: Bool) -> (CGSize, (Bool) -> Void) {
let dateLayout = TextNode.asyncLayout(self.dateNode)
var checkReadNode = self.checkReadNode
@ -217,13 +105,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
let currentType = self.type
let currentTheme = self.theme
let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode)
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
let previousLayoutSize = self.layoutSize
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replyCount, isPinned, hasAutoremove in
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, replyCount, isPinned, hasAutoremove in
let dateColor: UIColor
var backgroundImage: UIImage?
var blurredBackgroundColor: (UIColor, Bool)?
@ -526,35 +413,10 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if blurredBackgroundColor != nil {
backgroundInsets = UIEdgeInsets(top: 2.0, left: 7.0, bottom: 2.0, right: 7.0)
}
let reactionSize: CGFloat = 14.0
var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
let reactionSpacing: CGFloat = -4.0
let reactionTrailingSpacing: CGFloat = 4.0
var reactionInset: CGFloat = 0.0
if !reactions.isEmpty {
reactionInset = -1.0 + CGFloat(reactions.count) * reactionSize + CGFloat(reactions.count - 1) * reactionSpacing + reactionTrailingSpacing
var count = 0
for reaction in reactions {
count += Int(reaction.count)
}
let countString: String
if count > 1000000 {
countString = "\(count / 1000000)M"
} else if count > 1000 {
countString = "\(count / 1000)K"
} else {
countString = "\(count)"
}
let layoutAndApply = makeReactionCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
reactionInset += max(10.0, layoutAndApply.0.size.width) + 2.0
reactionCountLayoutAndApply = layoutAndApply
}
if replyCount > 0 {
let countString: String
@ -737,80 +599,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}
var reactionOffset: CGFloat = leftInset - reactionInset + backgroundInsets.left
for i in 0 ..< reactions.count {
let node: StatusReactionNode
if strongSelf.reactionNodes.count > i {
node = strongSelf.reactionNodes[i]
} else {
node = StatusReactionNode()
if strongSelf.reactionNodes.count > i {
let previousNode = strongSelf.reactionNodes[i]
if animated {
previousNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousNode] _ in
previousNode?.removeFromSupernode()
})
} else {
previousNode.removeFromSupernode()
}
strongSelf.reactionNodes[i] = node
} else {
strongSelf.reactionNodes.append(node)
}
}
node.update(type: type, isSelected: reactions[i].isSelected, count: Int(reactions[i].count), theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, animated: false)
if node.supernode == nil {
strongSelf.addSubnode(node)
if animated {
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset + 1.0), size: CGSize(width: reactionSize, height: reactionSize))
reactionOffset += reactionSize + reactionSpacing
}
if !reactions.isEmpty {
reactionOffset += reactionTrailingSpacing
}
for _ in reactions.count ..< strongSelf.reactionNodes.count {
let node = strongSelf.reactionNodes.removeLast()
if animated {
if let previousLayoutSize = previousLayoutSize {
node.frame = node.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0)
}
node.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
node?.removeFromSupernode()
})
} else {
node.removeFromSupernode()
}
}
if let (layout, apply) = reactionCountLayoutAndApply {
let node = apply()
if strongSelf.reactionCountNode !== node {
strongSelf.reactionCountNode?.removeFromSupernode()
strongSelf.addSubnode(node)
strongSelf.reactionCountNode = node
if animated {
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1.0, y: backgroundInsets.top + 1.0 + offset), size: layout.size)
reactionOffset += 1.0 + layout.size.width
} else if let reactionCountNode = strongSelf.reactionCountNode {
strongSelf.reactionCountNode = nil
if animated {
if let previousLayoutSize = previousLayoutSize {
reactionCountNode.frame = reactionCountNode.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0)
}
reactionCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionCountNode] _ in
reactionCountNode?.removeFromSupernode()
})
} else {
reactionCountNode.removeFromSupernode()
}
}
if let currentRepliesIcon = currentRepliesIcon {
currentRepliesIcon.displaysAsynchronously = !presentationData.isPreview
@ -865,50 +653,22 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
replyCountNode.removeFromSupernode()
}
}
/*if !strongSelf.reactionNodes.isEmpty {
if strongSelf.reactionButtonNode == nil {
let reactionButtonNode = HighlightTrackingButtonNode()
strongSelf.reactionButtonNode = reactionButtonNode
strongSelf.addSubnode(reactionButtonNode)
reactionButtonNode.addTarget(strongSelf, action: #selector(strongSelf.reactionButtonPressed), forControlEvents: .touchUpInside)
reactionButtonNode.highligthedChanged = { [weak strongSelf] highlighted in
guard let strongSelf = strongSelf else {
return
}
if highlighted {
for itemNode in strongSelf.reactionNodes {
itemNode.alpha = 0.4
}
} else {
for itemNode in strongSelf.reactionNodes {
itemNode.alpha = 1.0
itemNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.3)
}
}
}
}
strongSelf.reactionButtonNode?.frame = CGRect(origin: CGPoint(x: leftInset - reactionInset + backgroundInsets.left - 5.0, y: backgroundInsets.top + 1.0 + offset - 5.0), size: CGSize(width: reactionOffset + 5.0 * 2.0, height: 20.0))
} else if let reactionButtonNode = strongSelf.reactionButtonNode {
strongSelf.reactionButtonNode = nil
reactionButtonNode.removeFromSupernode()
}*/
}
})
}
}
static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int, _ isPinned: Bool, _ hasAutoremove: Bool) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) {
static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ replies: Int, _ isPinned: Bool, _ hasAutoremove: Bool) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) {
let currentLayout = node?.asyncLayout()
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies, isPinned, hasAutoremove in
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, replies, isPinned, hasAutoremove in
let resultNode: ChatMessageDateAndStatusNode
let resultSizeAndApply: (CGSize, (Bool) -> Void)
if let node = node, let currentLayout = currentLayout {
resultNode = node
resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies, isPinned, hasAutoremove)
resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, replies, isPinned, hasAutoremove)
} else {
resultNode = ChatMessageDateAndStatusNode()
resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies, isPinned, hasAutoremove)
resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, replies, isPinned, hasAutoremove)
}
return (resultSizeAndApply.0, { animated in
@ -918,23 +678,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}
}
func reactionNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
for node in self.reactionNodes {
return (node.emptyImageNode, node.selectedImageNode)
}
return nil
}
@objc private func reactionButtonPressed() {
self.openReactions?()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let reactionButtonNode = self.reactionButtonNode {
if reactionButtonNode.frame.contains(point) {
return reactionButtonNode.view
}
}
if self.pressed != nil {
if self.bounds.contains(point) {
return self.view

View File

@ -160,8 +160,4 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.interactiveFileNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
return self.interactiveFileNode.reactionTargetNode(value: value)
}
}

View File

@ -139,8 +139,4 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
}
return self.contentNode.transitionNode(media: media)
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
return self.contentNode.reactionTargetNode(value: value)
}
}

View File

@ -980,10 +980,6 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
break
case .reply:
item.controllerInteraction.setupReply(item.message.id)
case .like:
item.controllerInteraction.updateMessageLike(item.message.id, true)
case .unlike:
item.controllerInteraction.updateMessageLike(item.message.id, true)
}
}
}

View File

@ -320,22 +320,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
edited = true
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings)
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, reactionCount: dateReactionCount)
let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies, isPinned && !associatedData.isInPinnedListMode, message.isSelfExpiring)
let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReplies, isPinned && !associatedData.isInPinnedListMode, message.isSelfExpiring)
statusSize = size
statusApply = apply
}
@ -1137,13 +1124,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
self.playerUpdateTimer?.invalidate()
self.playerUpdateTimer = nil
}
func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionNode(value: value)
}
return nil
}
}

View File

@ -274,20 +274,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular)
let maxDateAndStatusWidth: CGFloat
if case .bubble = statusDisplayType {
@ -301,7 +288,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
isReplyThread = true
}
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
var displayVideoFrame = videoFrame
displayVideoFrame.size.width *= imageScale

View File

@ -69,7 +69,6 @@ struct ChatMessageDateAndStatus {
var edited: Bool
var viewCount: Int?
var dateReplies: Int
var dateReactions: [MessageReaction]
var isPinned: Bool
var dateText: String
}
@ -468,7 +467,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
var statusApply: ((Bool) -> Void)?
if let dateAndStatus = dateAndStatus {
let (size, apply) = statusLayout(context, presentationData, dateAndStatus.edited, dateAndStatus.viewCount, dateAndStatus.dateText, dateAndStatus.type, CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateAndStatus.dateReactions, dateAndStatus.dateReplies, dateAndStatus.isPinned, message.isSelfExpiring)
let (size, apply) = statusLayout(context, presentationData, dateAndStatus.edited, dateAndStatus.viewCount, dateAndStatus.dateText, dateAndStatus.type, CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateAndStatus.dateReplies, dateAndStatus.isPinned, message.isSelfExpiring)
statusSize = size
statusApply = apply
}

View File

@ -135,8 +135,4 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
}
return self.contentNode.transitionNode(media: media)
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
return self.contentNode.reactionTargetNode(value: value)
}
}

View File

@ -195,26 +195,13 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
if let selectedMedia = selectedMedia {
if selectedMedia.liveBroadcastingTimeout != nil {
edited = false
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let statusType: ChatMessageDateAndStatusType?
switch position {
@ -257,7 +244,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
}
@ -498,11 +485,4 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
}
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionNode(value: value)
}
return nil
}
}

View File

@ -167,20 +167,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let statusType: ChatMessageDateAndStatusType?
switch preparePosition {
@ -213,7 +200,6 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
edited: edited,
viewCount: viewCount,
dateReplies: dateReplies,
dateReactions: dateReactions,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
dateText: dateText
)
@ -399,11 +385,4 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
return false
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
/*if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionNode(value: value)
}*/
return nil
}
}

View File

@ -112,31 +112,31 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
var isReminder = false
var isScheduled = false
var title: String?
if let firstMessage = item.messages.first, let peer = messageMainPeer(firstMessage) {
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
title = EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
if let firstMessage = item.messages.first, let peer = messageMainPeer(EngineMessage(firstMessage)) {
if case let .channel(channel) = peer, case .broadcast = channel.info {
title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
} else if let author = firstMessage.author {
if firstMessage.id.peerId.isReplies, let _ = firstMessage.sourceReference, let effectiveAuthor = firstMessage.forwardInfo?.author {
title = EnginePeer(effectiveAuthor).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
title = EnginePeer(effectiveAuthor).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
} else if author.id != peer.id {
if author.id == item.context.account.peerId {
title = presentationData.strings.DialogList_You + "@" + EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
title = presentationData.strings.DialogList_You + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
} else {
title = EnginePeer(author).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
title = EnginePeer(author).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
}
} else {
title = EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
for attribute in firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
if let sourcePeer = firstMessage.peers[attribute.messageId.peerId] {
title = EnginePeer(sourcePeer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
title = EnginePeer(sourcePeer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
}
break
}
}
}
} else {
title = EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
}
if let _ = title, firstMessage.flags.contains(.WasScheduled) {
@ -148,9 +148,9 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
}
var avatarPeer = peer
if firstMessage.id.peerId.isReplies, let author = firstMessage.forwardInfo?.author {
avatarPeer = author
avatarPeer = EnginePeer(author)
}
self.avatarNode.setPeer(context: item.context, theme: presentationData.theme, peer: EnginePeer(avatarPeer), overrideImage: peer.id == item.context.account.peerId ? .savedMessagesIcon : nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor)
self.avatarNode.setPeer(context: item.context, theme: presentationData.theme, peer: avatarPeer, overrideImage: peer.id == item.context.account.peerId ? .savedMessagesIcon : nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor)
}
var updatedMedia: Media?
@ -180,7 +180,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
if message.containsSecretMedia {
imageDimensions = nil
}
messageText = descriptionStringForMessage(contentSettings: item.context.currentContentSettings.with { $0 }, message: message, strings: item.strings, nameDisplayOrder: item.nameDisplayOrder, dateTimeFormat: item.dateTimeFormat, accountPeerId: item.context.account.peerId).0
messageText = descriptionStringForMessage(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: item.strings, nameDisplayOrder: item.nameDisplayOrder, dateTimeFormat: item.dateTimeFormat, accountPeerId: item.context.account.peerId).0
} else if item.messages.count > 1, let peer = item.messages[0].peers[item.messages[0].id.peerId] {
var displayAuthor = true
if let channel = peer as? TelegramChannel {
@ -205,9 +205,9 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
messageText = presentationData.strings.PUSH_MESSAGE_FWDS_TEXT(Int32(item.messages.count))
}
} else if item.messages[0].groupingKey != nil {
var kind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: item.messages[0], strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId).key
var kind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(item.messages[0]), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId).key
for i in 1 ..< item.messages.count {
let nextKind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: item.messages[i], strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId)
let nextKind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(item.messages[i]), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId)
if kind != nextKind.key {
kind = .text
break

View File

@ -1036,20 +1036,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let statusType: ChatMessageDateAndStatusType?
switch position {
@ -1078,7 +1065,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
}
@ -1744,13 +1731,6 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
if !self.statusNode.isHidden {
return self.statusNode.reactionNode(value: value)
}
return nil
}
func updatePollTooltipMessageState(animated: Bool) {
guard let item = self.item else {
return

View File

@ -64,7 +64,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
}
}
let (textString, isMedia) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: message, strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: context.account.peerId)
let (textString, isMedia) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: context.account.peerId)
let placeholderColor: UIColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
let titleColor: UIColor

View File

@ -67,20 +67,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let statusType: ChatMessageDateAndStatusType?
switch position {
@ -109,7 +96,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, item.message.isSelfExpiring)
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
}
@ -252,11 +239,4 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
if !self.statusNode.isHidden {
return self.statusNode.reactionNode(value: value)
}
return nil
}
}

View File

@ -140,13 +140,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.messageAccessibilityArea.focused = { [weak self] in
self?.accessibilityElementDidBecomeFocused()
}
self.dateAndStatusNode.openReactions = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.openMessageReactions(item.message.id)
}
}
required init?(coder aDecoder: NSCoder) {
@ -191,7 +184,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
guard let strongSelf = self else {
return
}
//strongSelf.reactionRecognizer?.cancel()
if let action = strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) {
switch action {
case let .action(f):
@ -478,27 +471,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular)
var isReplyThread = false
if case .replyThread = item.chatLocation {
isReplyThread = true
}
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
@ -1153,10 +1133,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
break
case .reply:
item.controllerInteraction.setupReply(item.message.id)
case .like:
item.controllerInteraction.updateMessageLike(item.message.id, true)
case .unlike:
item.controllerInteraction.updateMessageLike(item.message.id, true)
}
}
}

View File

@ -73,10 +73,6 @@ extension ChatMessageSwipeToReplyNode.Action {
self = .reply
case .reply:
self = .reply
case .like:
self = .like
case .unlike:
self = .unlike
}
} else {
self = .reply

View File

@ -65,13 +65,6 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
self?.item?.controllerInteraction.openUrl(url, false, false, nil)
}
self.statusNode.openReactions = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.openMessageReactions(item.message.id)
}
}
required init?(coder aDecoder: NSCoder) {
@ -126,26 +119,13 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var dateReactions: [MessageReaction] = []
var dateReactionCount = 0
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
for reaction in reactionsAttribute.reactions {
if reaction.isSelected {
dateReactions.insert(reaction, at: 0)
} else {
dateReactions.append(reaction)
}
dateReactionCount += Int(reaction.count)
}
}
let dateFormat: MessageTimestampStatusFormat
if let subject = item.associatedData.subject, case .forwardedMessages = subject {
dateFormat = .minimal
} else {
dateFormat = .regular
}
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, reactionCount: dateReactionCount)
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat)
let statusType: ChatMessageDateAndStatusType?
var displayStatus = false
@ -184,7 +164,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
}
@ -636,13 +616,6 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
if !self.statusNode.isHidden {
return self.statusNode.reactionNode(value: value)
}
return nil
}
override func getStatusNode() -> ASDisplayNode? {
return self.statusNode
}

View File

@ -526,8 +526,4 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
let contentNodeFrame = self.contentNode.frame
self.contentNode.updateTouchesAtPoint(point.flatMap { $0.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY) })
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
return self.contentNode.reactionTargetNode(value: value)
}
}

View File

@ -451,7 +451,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
}
let (titleLayout, titleApply) = makeTitleLayout(CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), titleStrings)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: foldLineBreaks(descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId).0), font: Font.regular(15.0), textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: foldLineBreaks(descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId).0), font: Font.regular(15.0), textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
Queue.mainQueue().async {
if let strongSelf = self {

View File

@ -298,8 +298,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
return self?.getNavigationController()
}, chatControllerNode: { [weak self] in
return self
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { [weak self] action, message in
if let strongSelf = self {
switch action {
@ -507,8 +505,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, sendScheduledMessagesNow: { _ in
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in

View File

@ -207,7 +207,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
if let message = messages.first {
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peerId), subject: .message(id: message.id, highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(items: [])), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(items: [])), gesture: gesture)
presentInGlobalOverlay(contextController)
} else {
gesture?.cancel()

View File

@ -115,8 +115,6 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
return nil
}, chatControllerNode: {
return nil
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in
return .none
@ -133,8 +131,6 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, sendScheduledMessagesNow: { _ in
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in

View File

@ -169,7 +169,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
if let currentEditMediaReference = self.currentEditMediaReference {
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
}
(text, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: effectiveMessage, strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, dateTimeFormat: self.dateTimeFormat, accountPeerId: self.context.account.peerId)
(text, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(effectiveMessage), strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, dateTimeFormat: self.dateTimeFormat, accountPeerId: self.context.account.peerId)
}
var updatedMediaReference: AnyMediaReference?
@ -242,7 +242,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
switch messageContentKind(contentSettings: self.context.currentContentSettings.with { $0 }, message: effectiveMessage, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: self.context.account.peerId) {
switch messageContentKind(contentSettings: self.context.currentContentSettings.with { $0 }, message: EngineMessage(effectiveMessage), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: self.context.account.peerId) {
case .text:
isMedia = false
default:

View File

@ -102,8 +102,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
return nil
}, chatControllerNode: {
return nil
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in
}, callPeer: { _, _ in
}, longTap: { _, _ in
@ -125,8 +123,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, sendScheduledMessagesNow: { _ in
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in

View File

@ -1866,7 +1866,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
})))
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], recognizer: nil, gesture: gesture)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
strongSelf.controller?.window?.presentInGlobalOverlay(controller)
})
}, activateMessagePinch: { _ in
@ -2006,7 +2006,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
switch previewData {
case let .gallery(gallery):
gallery.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController)
case .instantPage:
break
@ -2090,8 +2090,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return self?.controller?.navigationController as? NavigationController
}, chatControllerNode: {
return nil
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in
}, longTap: { [weak self] content, _ in
guard let strongSelf = self else {
@ -2160,8 +2158,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, sendScheduledMessagesNow: { _ in
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
@ -2230,7 +2226,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.chatInterfaceInteraction.openPeer(peer.id, .default, nil)
}))
]
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
@ -2829,7 +2825,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, synchronousLoad: true)
galleryController.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController)
}
@ -3455,7 +3451,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.view.endEditing(true)
if let sourceNode = self.headerNode.buttonNodes[.mute]?.referenceNode {
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
@ -3771,7 +3767,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if let sourceNode = self.headerNode.buttonNodes[.more]?.referenceNode {
let items = mainItemsImpl?() ?? .single([])
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
@ -4440,7 +4436,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller {
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
@ -4536,7 +4532,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller {
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
@ -5609,7 +5605,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let contextController = ContextController(account: accountContext.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node)), items: accountContextMenuItems(context: accountContext, logout: { [weak self] in
self?.logoutAccount(id: id)
}) |> map { ContextController.Items(items: $0) }, reactionItems: [], gesture: gesture)
}) |> map { ContextController.Items(items: $0) }, gesture: gesture)
self.controller?.presentInGlobalOverlay(contextController)
} else {
gesture?.cancel()
@ -6908,7 +6904,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
})))
}
let controller = ContextController(account: primary.0.account, presentationData: self.presentationData, source: .extracted(SettingsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), reactionItems: [], recognizer: nil, gesture: gesture)
let controller = ContextController(account: primary.0.account, presentationData: self.presentationData, source: .extracted(SettingsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
}

View File

@ -106,7 +106,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
authorName = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
if let message = message {
(text, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId)
(text, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId)
}
var updatedMediaReference: AnyMediaReference?
@ -172,7 +172,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
let isMedia: Bool
if let message = message {
switch messageContentKind(contentSettings: context.currentContentSettings.with { $0 }, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId) {
switch messageContentKind(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId) {
case .text:
isMedia = false
default:

View File

@ -1254,8 +1254,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return nil
}, chatControllerNode: {
return nil
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in
return .none
@ -1272,8 +1270,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, sendScheduledMessagesNow: { _ in
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in

View File

@ -28,7 +28,7 @@ private func dateStringForDay(strings: PresentationStrings, dateTimeFormat: Pres
}
}
func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular, reactionCount: Int) -> String {
func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular) -> String {
if message.adAttribute != nil {
return strings.Message_SponsoredLabel
}

View File

@ -128,7 +128,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
if let contentText = content.text {
text = contentText
} else {
if let file = content.file, let mediaKind = mediaContentKind(file) {
if let file = content.file, let mediaKind = mediaContentKind(EngineMedia(file)) {
if content.type == "telegram_background" {
text = strings.Message_Wallpaper
} else if content.type == "telegram_theme" {