mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Quotes
This commit is contained in:
parent
b17138d501
commit
627009f32e
@ -517,21 +517,42 @@ public enum ChatControllerSubject: Equatable {
|
||||
case id(EngineMessage.Id)
|
||||
case timestamp(Int32)
|
||||
}
|
||||
|
||||
public struct ReplyOptions: Equatable {
|
||||
public var hasQuote: Bool
|
||||
|
||||
public init(hasQuote: Bool) {
|
||||
self.hasQuote = hasQuote
|
||||
}
|
||||
}
|
||||
|
||||
public struct ForwardOptions: Equatable {
|
||||
public let hideNames: Bool
|
||||
public let hideCaptions: Bool
|
||||
public var hideNames: Bool
|
||||
public var hideCaptions: Bool
|
||||
|
||||
public init(hideNames: Bool, hideCaptions: Bool) {
|
||||
public var replyOptions: ReplyOptions?
|
||||
|
||||
public init(hideNames: Bool, hideCaptions: Bool, replyOptions: ReplyOptions?) {
|
||||
self.hideNames = hideNames
|
||||
self.hideCaptions = hideCaptions
|
||||
self.replyOptions = replyOptions
|
||||
}
|
||||
}
|
||||
|
||||
public struct MessageOptionsInfo: Equatable {
|
||||
public enum Kind {
|
||||
public struct ReplyQuote: Equatable {
|
||||
public let messageId: EngineMessage.Id
|
||||
public let text: String
|
||||
|
||||
public init(messageId: EngineMessage.Id, text: String) {
|
||||
self.messageId = messageId
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
|
||||
public enum Kind: Equatable {
|
||||
case forward
|
||||
case reply
|
||||
case reply(initialQuote: ReplyQuote?)
|
||||
}
|
||||
|
||||
public let kind: Kind
|
||||
@ -541,7 +562,15 @@ public enum ChatControllerSubject: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
case message(id: MessageSubject, highlight: Bool, timecode: Double?)
|
||||
public struct MessageHighlight: Equatable {
|
||||
public var quote: String?
|
||||
|
||||
public init(quote: String? = nil) {
|
||||
self.quote = quote
|
||||
}
|
||||
}
|
||||
|
||||
case message(id: MessageSubject, highlight: MessageHighlight?, timecode: Double?)
|
||||
case scheduledMessages
|
||||
case pinnedMessages(id: EngineMessage.Id?)
|
||||
case messageOptions(peerIds: [EnginePeer.Id], ids: [EngineMessage.Id], info: MessageOptionsInfo, options: Signal<ForwardOptions, NoError>)
|
||||
|
@ -1150,7 +1150,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
if case let .channel(channel) = actualPeer, channel.flags.contains(.isForum), let threadId {
|
||||
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .never).startStandalone()
|
||||
} else {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: .message(id: .id(messageId), highlight: true, timecode: nil), purposefulAction: {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), purposefulAction: {
|
||||
if deactivateOnAction {
|
||||
self?.deactivateSearch(animated: false)
|
||||
}
|
||||
@ -1399,7 +1399,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
} else {
|
||||
var subject: ChatControllerSubject?
|
||||
if case let .search(messageId) = source, let id = messageId {
|
||||
subject = .message(id: .id(id), highlight: false, timecode: nil)
|
||||
subject = .message(id: .id(id), highlight: nil, timecode: nil)
|
||||
}
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
|
@ -403,6 +403,18 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
self.targetSelectionIndex = 1
|
||||
}
|
||||
icon = UIImage(bundleImageName: "Chat/Context Menu/Tip")
|
||||
case .quoteSelection:
|
||||
//TODO:localize
|
||||
var rawText = "Hold on a word, then move cursor to select more| text to quote."
|
||||
if let range = rawText.range(of: "|") {
|
||||
rawText.removeSubrange(range)
|
||||
self.text = rawText
|
||||
self.targetSelectionIndex = NSRange(range, in: rawText).lowerBound
|
||||
} else {
|
||||
self.text = rawText
|
||||
self.targetSelectionIndex = 1
|
||||
}
|
||||
icon = UIImage(bundleImageName: "Chat/Context Menu/Tip")
|
||||
case .messageViewsPrivacy:
|
||||
self.text = self.presentationData.strings.ChatContextMenu_MessageViewsPrivacyTip
|
||||
self.targetSelectionIndex = nil
|
||||
|
@ -537,6 +537,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self.contentReady.set(.single(true))
|
||||
case let .controller(source):
|
||||
self.contentReady.set(source.controller.ready.get())
|
||||
//TODO:
|
||||
//self.contentReady.set(.single(true))
|
||||
}
|
||||
|
||||
self.initializeContent()
|
||||
@ -764,51 +766,64 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
)
|
||||
self.presentationNode = presentationNode
|
||||
self.addSubnode(presentationNode)
|
||||
/*let takenViewInfo = source.takeView()
|
||||
|
||||
if let takenViewInfo = takenViewInfo, let parentSupernode = takenViewInfo.contentContainingNode.supernode {
|
||||
self.contentContainerNode.contentNode = .extracted(node: takenViewInfo.contentContainingNode, keepInPlace: source.keepInPlace)
|
||||
if source.keepInPlace || takenViewInfo.maskView != nil {
|
||||
self.clippingNode.view.mask = takenViewInfo.maskView
|
||||
self.clippingNode.addSubnode(self.contentContainerNode)
|
||||
} else {
|
||||
self.scrollNode.addSubnode(self.contentContainerNode)
|
||||
}
|
||||
let contentParentNode = takenViewInfo.contentContainingNode
|
||||
takenViewInfo.contentContainingNode.layoutUpdated = { [weak contentParentNode, weak self] size in
|
||||
guard let strongSelf = self, let contentParentNode = contentParentNode, let parentSupernode = contentParentNode.supernode else {
|
||||
return
|
||||
}
|
||||
if strongSelf.isAnimatingOut {
|
||||
return
|
||||
}
|
||||
strongSelf.originalProjectedContentViewFrame = (convertFrame(contentParentNode.frame, from: parentSupernode.view, to: strongSelf.view), convertFrame(contentParentNode.contentRect, from: contentParentNode.view, to: strongSelf.view))
|
||||
if let validLayout = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(layout: validLayout, transition: .animated(duration: 0.2 * animationDurationFactor, curve: .easeInOut), previousActionsContainerNode: nil)
|
||||
}
|
||||
}
|
||||
|
||||
self.contentAreaInScreenSpace = takenViewInfo.contentAreaInScreenSpace
|
||||
self.contentContainerNode.addSubnode(takenViewInfo.contentContainingNode.contentNode)
|
||||
takenViewInfo.contentContainingNode.isExtractedToContextPreview = true
|
||||
takenViewInfo.contentContainingNode.isExtractedToContextPreviewUpdated?(true)
|
||||
|
||||
self.originalProjectedContentViewFrame = (convertFrame(takenViewInfo.contentContainingNode.frame, from: parentSupernode.view, to: self.view), convertFrame(takenViewInfo.contentContainingNode.contentRect, from: takenViewInfo.contentContainingNode.view, to: self.view))
|
||||
}*/
|
||||
case let .controller(source):
|
||||
let transitionInfo = source.transitionInfo()
|
||||
if let transitionInfo = transitionInfo, let (sourceView, sourceNodeRect) = transitionInfo.sourceNode() {
|
||||
let contentParentNode = ContextControllerContentNode(sourceView: sourceView, controller: source.controller, tapped: { [weak self] in
|
||||
self?.attemptTransitionControllerIntoNavigation()
|
||||
})
|
||||
self.contentContainerNode.contentNode = .controller(contentParentNode)
|
||||
self.scrollNode.addSubnode(self.contentContainerNode)
|
||||
self.contentContainerNode.clipsToBounds = true
|
||||
self.contentContainerNode.cornerRadius = 14.0
|
||||
self.contentContainerNode.addSubnode(contentParentNode)
|
||||
|
||||
let projectedFrame = convertFrame(sourceNodeRect, from: sourceView, to: self.view)
|
||||
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
|
||||
if "".isEmpty {
|
||||
let transitionInfo = source.transitionInfo()
|
||||
if let transitionInfo = transitionInfo, let (sourceView, sourceNodeRect) = transitionInfo.sourceNode() {
|
||||
let contentParentNode = ContextControllerContentNode(sourceView: sourceView, controller: source.controller, tapped: { [weak self] in
|
||||
self?.attemptTransitionControllerIntoNavigation()
|
||||
})
|
||||
self.contentContainerNode.contentNode = .controller(contentParentNode)
|
||||
self.scrollNode.addSubnode(self.contentContainerNode)
|
||||
self.contentContainerNode.clipsToBounds = true
|
||||
self.contentContainerNode.cornerRadius = 14.0
|
||||
self.contentContainerNode.addSubnode(contentParentNode)
|
||||
|
||||
let projectedFrame = convertFrame(sourceNodeRect, from: sourceView, to: self.view)
|
||||
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
|
||||
}
|
||||
} else {
|
||||
let presentationNode = ContextControllerExtractedPresentationNode(
|
||||
getController: { [weak self] in
|
||||
return self?.getController()
|
||||
},
|
||||
requestUpdate: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let validLayout = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(
|
||||
layout: validLayout,
|
||||
transition: transition,
|
||||
previousActionsContainerNode: nil
|
||||
)
|
||||
}
|
||||
},
|
||||
requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let controller = strongSelf.getController() {
|
||||
controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition)
|
||||
}
|
||||
},
|
||||
requestDismiss: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.dismissedForCancel?()
|
||||
strongSelf.beginDismiss(result)
|
||||
},
|
||||
requestAnimateOut: { [weak self] result, completion in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.animateOut(result: result, completion: completion)
|
||||
},
|
||||
source: .controller(source)
|
||||
)
|
||||
self.presentationNode = presentationNode
|
||||
self.addSubnode(presentationNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2407,6 +2422,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
|
||||
public enum Tip: Equatable {
|
||||
case textSelection
|
||||
case quoteSelection
|
||||
case messageViewsPrivacy
|
||||
case messageCopyProtection(isChannel: Bool)
|
||||
case animatedEmoji(text: String?, arguments: TextNodeWithEntities.Arguments?, file: TelegramMediaFile?, action: (() -> Void)?)
|
||||
@ -2420,6 +2436,12 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .quoteSelection:
|
||||
if case .quoteSelection = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .messageViewsPrivacy:
|
||||
if case .messageViewsPrivacy = rhs {
|
||||
return true
|
||||
|
@ -115,9 +115,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
case location(ContextLocationContentSource)
|
||||
case reference(ContextReferenceContentSource)
|
||||
case extracted(ContextExtractedContentSource)
|
||||
case controller(ContextControllerContentSource)
|
||||
}
|
||||
|
||||
private final class ContentNode: ASDisplayNode {
|
||||
private final class ItemContentNode: ASDisplayNode {
|
||||
let offsetContainerNode: ASDisplayNode
|
||||
var containingItem: ContextControllerTakeViewInfo.ContainingItem
|
||||
|
||||
@ -160,6 +161,28 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
}
|
||||
|
||||
private final class ControllerContentNode: ASDisplayNode {
|
||||
let controller: ViewController
|
||||
|
||||
init(controller: ViewController) {
|
||||
self.controller = controller
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.controller.displayNode)
|
||||
}
|
||||
|
||||
func update(presentationData: PresentationData, size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
return self.view
|
||||
}
|
||||
}
|
||||
|
||||
private final class AnimatingOutState {
|
||||
var currentContentScreenFrame: CGRect
|
||||
|
||||
@ -170,6 +193,12 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
}
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
private var didSetReady: Bool = false
|
||||
var ready: Signal<Bool, NoError> {
|
||||
return self._ready.get()
|
||||
}
|
||||
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let requestUpdate: (ContainedViewLayoutTransition) -> Void
|
||||
private let requestUpdateOverlayWantsToBeBelowKeyboard: (ContainedViewLayoutTransition) -> Void
|
||||
@ -187,7 +216,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
private var reactionContextNode: ReactionContextNode?
|
||||
private var reactionContextNodeIsAnimatingOut: Bool = false
|
||||
|
||||
private var contentNode: ContentNode?
|
||||
private var itemContentNode: ItemContentNode?
|
||||
private var controllerContentNode: ControllerContentNode?
|
||||
private let contentRectDebugNode: ASDisplayNode
|
||||
|
||||
private var actionsContainerNode: ASDisplayNode
|
||||
@ -310,7 +340,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
}
|
||||
|
||||
if case let .extracted(source) = self.source, !source.ignoreContentTouches, let contentNode = self.contentNode {
|
||||
if case let .extracted(source) = self.source, !source.ignoreContentTouches, let contentNode = self.itemContentNode {
|
||||
let contentPoint = self.view.convert(point, to: contentNode.containingItem.contentView)
|
||||
if let result = contentNode.containingItem.customHitTest?(contentPoint) {
|
||||
return result
|
||||
@ -321,6 +351,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
return contentNode.containingItem.contentView
|
||||
}
|
||||
}
|
||||
} else if case .controller = self.source, let contentNode = self.controllerContentNode {
|
||||
let contentPoint = self.view.convert(point, to: contentNode.view)
|
||||
let _ = contentPoint
|
||||
//TODO:
|
||||
}
|
||||
|
||||
return self.scrollNode.hitTest(self.view.convert(point, to: self.scrollNode.view), with: event)
|
||||
@ -457,7 +491,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
switch self.source {
|
||||
case .location, .reference:
|
||||
return nil
|
||||
case .extracted:
|
||||
case .extracted, .controller:
|
||||
return self.actionsStackNode.view.convert(CGPoint(), to: self.view).y
|
||||
}
|
||||
}
|
||||
@ -487,7 +521,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
let topInset: CGFloat = layout.insets(options: .statusBar).top + 8.0
|
||||
let bottomInset: CGFloat = 10.0
|
||||
|
||||
let contentNode: ContentNode?
|
||||
let itemContentNode: ItemContentNode?
|
||||
let controllerContentNode: ControllerContentNode?
|
||||
var contentTransition = transition
|
||||
|
||||
if self.strings !== presentationData.strings {
|
||||
@ -505,7 +540,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
transition: .immediate
|
||||
)
|
||||
actionsEdgeInset = 16.0
|
||||
case .extracted:
|
||||
case .extracted, .controller:
|
||||
self.backgroundNode.updateColor(
|
||||
color: presentationData.theme.contextMenu.dimColor,
|
||||
enableBlur: true,
|
||||
@ -524,25 +559,36 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
transition.updateFrame(view: self.scroller, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
if let current = self.contentNode {
|
||||
contentNode = current
|
||||
if let current = self.itemContentNode {
|
||||
itemContentNode = current
|
||||
} else {
|
||||
switch self.source {
|
||||
case .location, .reference:
|
||||
contentNode = nil
|
||||
case .location, .reference, .controller:
|
||||
itemContentNode = nil
|
||||
case let .extracted(source):
|
||||
guard let takeInfo = source.takeView() else {
|
||||
return
|
||||
}
|
||||
let contentNodeValue = ContentNode(containingItem: takeInfo.containingItem)
|
||||
let contentNodeValue = ItemContentNode(containingItem: takeInfo.containingItem)
|
||||
contentNodeValue.animateClippingFromContentAreaInScreenSpace = takeInfo.contentAreaInScreenSpace
|
||||
self.scrollNode.insertSubnode(contentNodeValue, aboveSubnode: self.actionsContainerNode)
|
||||
self.contentNode = contentNodeValue
|
||||
contentNode = contentNodeValue
|
||||
self.itemContentNode = contentNodeValue
|
||||
itemContentNode = contentNodeValue
|
||||
contentTransition = .immediate
|
||||
}
|
||||
}
|
||||
|
||||
if let current = self.controllerContentNode {
|
||||
controllerContentNode = current
|
||||
} else {
|
||||
switch self.source {
|
||||
case let .controller(source):
|
||||
controllerContentNode = ControllerContentNode(controller: source.controller)
|
||||
case .location, .reference, .extracted:
|
||||
controllerContentNode = nil
|
||||
}
|
||||
}
|
||||
|
||||
var animateReactionsIn = false
|
||||
var contentTopInset: CGFloat = topInset
|
||||
var removedReactionContextNode: ReactionContextNode?
|
||||
@ -629,7 +675,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
removedReactionContextNode = reactionContextNode
|
||||
}
|
||||
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
switch stateTransition {
|
||||
case .animateIn, .animateOut:
|
||||
contentNode.storedGlobalFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view)
|
||||
@ -660,7 +706,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
return
|
||||
}
|
||||
case .extracted:
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
contentParentGlobalFrame = convertFrame(contentNode.containingItem.view.bounds, from: contentNode.containingItem.view, to: self.view)
|
||||
|
||||
let contentRectGlobalFrame = CGRect(origin: CGPoint(x: contentNode.containingItem.contentRect.minX, y: (contentNode.storedGlobalFrame?.maxY ?? 0.0) - contentNode.containingItem.contentRect.height), size: contentNode.containingItem.contentRect.size)
|
||||
@ -671,6 +717,15 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
} else {
|
||||
return
|
||||
}
|
||||
case .controller:
|
||||
//TODO
|
||||
if let contentNode = controllerContentNode {
|
||||
let _ = contentNode
|
||||
contentRect = CGRect(origin: CGPoint(x: layout.size.width * 0.5 - 100.0, y: layout.size.height * 0.5 - 100.0), size: CGSize(width: 200.0, height: 200.0))
|
||||
contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let keepInPlace: Bool
|
||||
@ -682,17 +737,29 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
case let .extracted(source):
|
||||
keepInPlace = source.keepInPlace
|
||||
actionsHorizontalAlignment = source.actionsHorizontalAlignment
|
||||
case .controller:
|
||||
//TODO:
|
||||
keepInPlace = false
|
||||
actionsHorizontalAlignment = .default
|
||||
}
|
||||
|
||||
var defaultScrollY: CGFloat = 0.0
|
||||
if self.animatingOutState == nil {
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
contentNode.update(
|
||||
presentationData: presentationData,
|
||||
size: contentNode.containingItem.view.bounds.size,
|
||||
transition: contentTransition
|
||||
)
|
||||
}
|
||||
if let contentNode = controllerContentNode {
|
||||
//TODO
|
||||
contentNode.update(
|
||||
presentationData: presentationData,
|
||||
size: CGSize(),
|
||||
transition: contentTransition
|
||||
)
|
||||
}
|
||||
|
||||
let actionsConstrainedHeight: CGFloat
|
||||
if let actionsPositionLock = self.actionsStackNode.topPositionLock {
|
||||
@ -709,7 +776,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
switch self.source {
|
||||
case .location, .reference:
|
||||
actionsStackPresentation = .inline
|
||||
case .extracted:
|
||||
case .extracted, .controller:
|
||||
actionsStackPresentation = .modal
|
||||
}
|
||||
|
||||
@ -841,6 +908,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
case .extracted:
|
||||
actionsFrame.origin.x = contentParentGlobalFrame.minX + contentRect.maxX - actionsSideInset - actionsSize.width - 1.0
|
||||
case .controller:
|
||||
//TODO:
|
||||
actionsFrame.origin.x = contentParentGlobalFrame.minX + contentRect.maxX - actionsSideInset - actionsSize.width - 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -871,13 +941,21 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
transition.updateFrame(node: self.actionsStackNode, frame: CGRect(origin: CGPoint(x: 0.0, y: combinedActionsFrame.height - actionsSize.height), size: actionsSize), beginWithCurrentState: true)
|
||||
transition.updateFrame(node: self.additionalActionsStackNode, frame: CGRect(origin: .zero, size: additionalActionsSize), beginWithCurrentState: true)
|
||||
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
var contentFrame = CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size)
|
||||
if case let .extracted(extracted) = self.source, extracted.centerVertically, contentFrame.midX > layout.size.width / 2.0 {
|
||||
contentFrame.origin.x = layout.size.width - contentFrame.maxX
|
||||
}
|
||||
contentTransition.updateFrame(node: contentNode, frame: contentFrame, beginWithCurrentState: true)
|
||||
}
|
||||
if let contentNode = controllerContentNode {
|
||||
//TODO:
|
||||
var contentFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: CGSize(width: 200.0, height: 200.0))
|
||||
if case let .extracted(extracted) = self.source, extracted.centerVertically, contentFrame.midX > layout.size.width / 2.0 {
|
||||
contentFrame.origin.x = layout.size.width - contentFrame.maxX
|
||||
}
|
||||
contentTransition.updateFrame(node: contentNode, frame: contentFrame, beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
let contentHeight: CGFloat
|
||||
if self.actionsStackNode.topPositionLock != nil || self.currentReactionsPositionLock != nil {
|
||||
@ -918,7 +996,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
switch stateTransition {
|
||||
case .animateIn:
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
contentNode.takeContainingNode()
|
||||
}
|
||||
|
||||
@ -931,7 +1009,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
let animationInContentYDistance: CGFloat
|
||||
let currentContentScreenFrame: CGRect
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
if let animateClippingFromContentAreaInScreenSpace = contentNode.animateClippingFromContentAreaInScreenSpace {
|
||||
self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(x: 0.0, y: animateClippingFromContentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: animateClippingFromContentAreaInScreenSpace.height)), to: CGRect(origin: CGPoint(), size: layout.size), duration: 0.2)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: animateClippingFromContentAreaInScreenSpace.minY, to: 0.0, duration: 0.2)
|
||||
@ -997,7 +1075,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
|
||||
let actionsVerticalTransitionDirection: CGFloat
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
if contentNode.frame.minY < self.actionsContainerNode.frame.minY {
|
||||
actionsVerticalTransitionDirection = -1.0
|
||||
} else {
|
||||
@ -1039,13 +1117,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
self.actionsStackNode.animateIn()
|
||||
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
contentNode.containingItem.isExtractedToContextPreview = true
|
||||
contentNode.containingItem.isExtractedToContextPreviewUpdated?(true)
|
||||
contentNode.containingItem.willUpdateIsExtractedToContextPreview?(true, transition)
|
||||
|
||||
contentNode.containingItem.layoutUpdated = { [weak self] _, animation in
|
||||
guard let strongSelf = self, let _ = strongSelf.contentNode else {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -1114,11 +1192,22 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
case let .controller(source):
|
||||
if let putBackInfo = source.transitionInfo() {
|
||||
let _ = putBackInfo
|
||||
self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
||||
|
||||
//TODO:
|
||||
currentContentScreenFrame = CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.animatingOutState = AnimatingOutState(
|
||||
@ -1134,13 +1223,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
animationInContentYDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY
|
||||
case .dismissWithoutContent:
|
||||
animationInContentYDistance = 0.0
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
let actionsVerticalTransitionDirection: CGFloat
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
if contentNode.frame.minY < self.actionsContainerNode.frame.minY {
|
||||
actionsVerticalTransitionDirection = -1.0
|
||||
} else {
|
||||
@ -1154,9 +1243,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
}
|
||||
|
||||
let completeWithActionStack = contentNode == nil
|
||||
let completeWithActionStack = itemContentNode == nil && controllerContentNode == nil
|
||||
|
||||
if let contentNode = contentNode {
|
||||
if let contentNode = itemContentNode {
|
||||
contentNode.containingItem.willUpdateIsExtractedToContextPreview?(false, transition)
|
||||
|
||||
var animationInContentXDistance: CGFloat = 0.0
|
||||
@ -1192,7 +1281,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
contentNode.containingItem.isExtractedToContextPreview = false
|
||||
contentNode.containingItem.isExtractedToContextPreviewUpdated?(false)
|
||||
|
||||
if let strongSelf = self, let contentNode = strongSelf.contentNode {
|
||||
if let strongSelf = self, let contentNode = strongSelf.itemContentNode {
|
||||
switch contentNode.containingItem {
|
||||
case let .node(containingNode):
|
||||
containingNode.addSubnode(containingNode.contentNode)
|
||||
@ -1206,6 +1295,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
)
|
||||
}
|
||||
if let controllerContentNode {
|
||||
let _ = controllerContentNode
|
||||
//TODO
|
||||
completion()
|
||||
}
|
||||
|
||||
self.actionsContainerNode.layer.animateAlpha(from: self.actionsContainerNode.alpha, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||
self.actionsContainerNode.layer.animate(
|
||||
|
@ -14,6 +14,8 @@ enum ContextControllerPresentationNodeStateTransition {
|
||||
}
|
||||
|
||||
protocol ContextControllerPresentationNode: ASDisplayNode {
|
||||
var ready: Signal<Bool, NoError> { get }
|
||||
|
||||
func replaceItems(items: ContextController.Items, animated: Bool)
|
||||
func pushItems(items: ContextController.Items)
|
||||
func popItems()
|
||||
|
@ -34,6 +34,8 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder,
|
||||
public var centerHorizontally = false
|
||||
public var dismissed: (() -> Void)?
|
||||
|
||||
public var dismissOnTap: ((UIView, CGPoint) -> Bool)?
|
||||
|
||||
public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false, blurred: Bool = false) {
|
||||
self.actions = actions
|
||||
self.catchTapsOutside = catchTapsOutside
|
||||
@ -55,6 +57,11 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder,
|
||||
self?.contextMenuNode.animateOut(bounce: (self?.presentationArguments as? ContextMenuControllerPresentationArguments)?.bounce ?? true, completion: {
|
||||
self?.presentingViewController?.dismiss(animated: false)
|
||||
})
|
||||
}, dismissOnTap: { [weak self] view, point in
|
||||
guard let self, let dismissOnTap = self.dismissOnTap else {
|
||||
return false
|
||||
}
|
||||
return dismissOnTap(view, point)
|
||||
}, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback, blurred: self.blurred)
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ private final class ContextMenuContentScrollNode: ASDisplayNode {
|
||||
final class ContextMenuNode: ASDisplayNode {
|
||||
private let actions: [ContextMenuAction]
|
||||
private let dismiss: () -> Void
|
||||
private let dismissOnTap: (UIView, CGPoint) -> Bool
|
||||
|
||||
private let containerNode: ContextMenuContainerNode
|
||||
private let scrollNode: ContextMenuContentScrollNode
|
||||
@ -145,9 +146,10 @@ final class ContextMenuNode: ASDisplayNode {
|
||||
|
||||
private let feedback: HapticFeedback?
|
||||
|
||||
init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, catchTapsOutside: Bool, hasHapticFeedback: Bool = false, blurred: Bool = false) {
|
||||
init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, dismissOnTap: @escaping (UIView, CGPoint) -> Bool, catchTapsOutside: Bool, hasHapticFeedback: Bool = false, blurred: Bool = false) {
|
||||
self.actions = actions
|
||||
self.dismiss = dismiss
|
||||
self.dismissOnTap = dismissOnTap
|
||||
self.catchTapsOutside = catchTapsOutside
|
||||
|
||||
self.containerNode = ContextMenuContainerNode(blurred: blurred)
|
||||
@ -259,6 +261,14 @@ final class ContextMenuNode: ASDisplayNode {
|
||||
}
|
||||
if event.type == .touches || eventIsPresses {
|
||||
if !self.containerNode.frame.contains(point) {
|
||||
if self.dismissOnTap(self.view, point) {
|
||||
self.dismiss()
|
||||
if self.catchTapsOutside {
|
||||
return self.view
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if !self.dismissedByTouchOutside {
|
||||
self.dismissedByTouchOutside = true
|
||||
self.dismiss()
|
||||
|
@ -516,7 +516,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if let navigationController = strongSelf.baseNavigationController() {
|
||||
strongSelf.beginCustomDismiss(true)
|
||||
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: true, timecode: nil)))
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil)))
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
strongSelf.completeCustomDismiss()
|
||||
|
@ -2522,7 +2522,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if let navigationController = strongSelf.baseNavigationController() {
|
||||
strongSelf.beginCustomDismiss(true)
|
||||
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: true, timecode: nil)))
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil)))
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
strongSelf.completeCustomDismiss()
|
||||
|
@ -67,7 +67,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.openMessageFromSearchDisposable.set((strongSelf.context.engine.peers.ensurePeerIsLocallyAvailable(peer: peer) |> deliverOnMainQueue).start(next: { actualPeer in
|
||||
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: message.id.peerId == actualPeer.id ? .message(id: .id(message.id), highlight: true, timecode: nil) : nil, keepStack: .always))
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: message.id.peerId == actualPeer.id ? .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) : nil, keepStack: .always))
|
||||
}
|
||||
}))
|
||||
strongSelf.controllerNode.listNode.clearHighlightAnimated(true)
|
||||
|
@ -977,7 +977,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
}
|
||||
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: true, timecode: nil)))
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil)))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -266,7 +266,7 @@ public func messageStatsController(context: AccountContext, messageId: EngineMes
|
||||
return
|
||||
}
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {}, peekData: nil))
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {}, peekData: nil))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(InlineBotMessageAttribute.self, f: { InlineBotMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TextEntitiesMessageAttribute.self, f: { TextEntitiesMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ReplyMessageAttribute.self, f: { ReplyMessageAttribute(decoder: $0) })
|
||||
declareEncodable(QuotedReplyMessageAttribute.self, f: { QuotedReplyMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ReplyStoryAttribute.self, f: { ReplyStoryAttribute(decoder: $0) })
|
||||
declareEncodable(ReplyThreadMessageAttribute.self, f: { ReplyThreadMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ReactionsMessageAttribute.self, f: { ReactionsMessageAttribute(decoder: $0) })
|
||||
|
@ -568,7 +568,6 @@ extension StoreMessage {
|
||||
var threadMessageId: MessageId?
|
||||
switch replyTo {
|
||||
case let .messageReplyHeader(flags, replyToMsgId, replyToPeerId, replyHeader, replyToTopId, quoteText, quoteEntities):
|
||||
let _ = replyHeader
|
||||
let isForumTopic = (flags & (1 << 3)) != 0
|
||||
|
||||
var quote: EngineMessageReplyQuote?
|
||||
@ -608,6 +607,8 @@ extension StoreMessage {
|
||||
}
|
||||
}
|
||||
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote))
|
||||
} else if let replyHeader = replyHeader {
|
||||
attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote))
|
||||
}
|
||||
case let .messageReplyStoryHeader(userId, storyId):
|
||||
attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId)))
|
||||
@ -841,8 +842,6 @@ extension StoreMessage {
|
||||
var threadMessageId: MessageId?
|
||||
switch replyTo {
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyToTopId, quoteText, quoteEntities):
|
||||
let _ = replyHeader
|
||||
|
||||
var quote: EngineMessageReplyQuote?
|
||||
if let quoteText = quoteText {
|
||||
quote = EngineMessageReplyQuote(text: quoteText, entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []))
|
||||
@ -868,6 +867,8 @@ extension StoreMessage {
|
||||
break
|
||||
}
|
||||
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote))
|
||||
} else if let replyHeader = replyHeader {
|
||||
attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote))
|
||||
}
|
||||
case let .messageReplyStoryHeader(userId, storyId):
|
||||
attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId)))
|
||||
|
@ -369,7 +369,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
}
|
||||
switch message {
|
||||
case let .message(_, attributes, _, _, replyToMessageId, _, _, _, _):
|
||||
if let replyToMessageId = replyToMessageId, replyToMessageId.messageId.peerId != peerId, let replyMessage = transaction.getMessage(replyToMessageId.messageId) {
|
||||
if let replyToMessageId = replyToMessageId, (replyToMessageId.messageId.peerId != peerId && peerId.namespace == Namespaces.Peer.SecretChat), let replyMessage = transaction.getMessage(replyToMessageId.messageId) {
|
||||
var canBeForwarded = true
|
||||
if replyMessage.id.namespace != Namespaces.Message.Cloud {
|
||||
canBeForwarded = false
|
||||
@ -503,12 +503,17 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: peerAutoremoveTimeout, countdownBeginTime: nil))
|
||||
}
|
||||
|
||||
if let replyToMessageId = replyToMessageId, replyToMessageId.messageId.peerId == peerId {
|
||||
if let replyToMessageId = replyToMessageId {
|
||||
var threadMessageId: MessageId?
|
||||
var quote = replyToMessageId.quote
|
||||
if let replyMessage = transaction.getMessage(replyToMessageId.messageId) {
|
||||
threadMessageId = replyMessage.effectiveReplyThreadMessageId
|
||||
if quote == nil, replyToMessageId.messageId.peerId != peerId {
|
||||
let nsText = replyMessage.text as NSString
|
||||
quote = EngineMessageReplyQuote(text: replyMessage.text, entities: messageTextEntitiesInRange(entities: replyMessage.textEntitiesAttribute?.entities ?? [], range: NSRange(location: 0, length: nsText.length), onlyQuoteable: true))
|
||||
}
|
||||
}
|
||||
attributes.append(ReplyMessageAttribute(messageId: replyToMessageId.messageId, threadMessageId: threadMessageId, quote: replyToMessageId.quote))
|
||||
attributes.append(ReplyMessageAttribute(messageId: replyToMessageId.messageId, threadMessageId: threadMessageId, quote: quote))
|
||||
}
|
||||
if let replyToStoryId = replyToStoryId {
|
||||
attributes.append(ReplyStoryAttribute(storyId: replyToStoryId))
|
||||
|
@ -779,6 +779,7 @@ public final class PendingMessageManager {
|
||||
var hideSendersNames = false
|
||||
var hideCaptions = false
|
||||
var replyMessageId: Int32?
|
||||
var replyPeerId: PeerId?
|
||||
var replyQuote: EngineMessageReplyQuote?
|
||||
var replyToStoryId: StoryId?
|
||||
var scheduleTime: Int32?
|
||||
@ -789,6 +790,9 @@ public final class PendingMessageManager {
|
||||
for attribute in messages[0].0.attributes {
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||
replyMessageId = replyAttribute.messageId.id
|
||||
if peerId != replyAttribute.messageId.peerId {
|
||||
replyPeerId = replyAttribute.messageId.peerId
|
||||
}
|
||||
replyQuote = replyAttribute.quote
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyToStoryId = attribute.storyId
|
||||
@ -934,6 +938,14 @@ public final class PendingMessageManager {
|
||||
replyFlags |= 1 << 0
|
||||
}
|
||||
|
||||
var replyToPeerId: Api.InputPeer?
|
||||
if let replyPeerId = replyPeerId {
|
||||
replyToPeerId = transaction.getPeer(replyPeerId).flatMap(apiInputPeer)
|
||||
}
|
||||
if replyToPeerId != nil {
|
||||
replyFlags |= 1 << 1
|
||||
}
|
||||
|
||||
var quoteText: String?
|
||||
var quoteEntities: [Api.MessageEntity]?
|
||||
if let replyQuote = replyQuote {
|
||||
@ -956,7 +968,7 @@ public final class PendingMessageManager {
|
||||
}
|
||||
}
|
||||
|
||||
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: topMsgId, replyToPeerId: nil, quoteText: quoteText, quoteEntities: quoteEntities)
|
||||
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: topMsgId, replyToPeerId: replyToPeerId, quoteText: quoteText, quoteEntities: quoteEntities)
|
||||
} else if let replyToStoryId = replyToStoryId {
|
||||
if let inputUser = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputUser) {
|
||||
flags |= 1 << 0
|
||||
@ -1133,6 +1145,7 @@ public final class PendingMessageManager {
|
||||
var forwardSourceInfoAttribute: ForwardSourceInfoAttribute?
|
||||
var messageEntities: [Api.MessageEntity]?
|
||||
var replyMessageId: Int32?
|
||||
var replyPeerId: PeerId?
|
||||
var replyQuote: EngineMessageReplyQuote?
|
||||
var replyToStoryId: StoryId?
|
||||
var scheduleTime: Int32?
|
||||
@ -1144,6 +1157,9 @@ public final class PendingMessageManager {
|
||||
for attribute in message.attributes {
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||
replyMessageId = replyAttribute.messageId.id
|
||||
if peer.id != replyAttribute.messageId.peerId {
|
||||
replyPeerId = replyAttribute.messageId.peerId
|
||||
}
|
||||
replyQuote = replyAttribute.quote
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyToStoryId = attribute.storyId
|
||||
@ -1206,6 +1222,14 @@ public final class PendingMessageManager {
|
||||
replyFlags |= 1 << 0
|
||||
}
|
||||
|
||||
var replyToPeerId: Api.InputPeer?
|
||||
if let replyPeerId = replyPeerId {
|
||||
replyToPeerId = transaction.getPeer(replyPeerId).flatMap(apiInputPeer)
|
||||
}
|
||||
if replyToPeerId != nil {
|
||||
replyFlags |= 1 << 1
|
||||
}
|
||||
|
||||
var quoteText: String?
|
||||
var quoteEntities: [Api.MessageEntity]?
|
||||
if let replyQuote = replyQuote {
|
||||
@ -1228,7 +1252,7 @@ public final class PendingMessageManager {
|
||||
}
|
||||
}
|
||||
|
||||
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:)), replyToPeerId: nil, quoteText: quoteText, quoteEntities: quoteEntities)
|
||||
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:)), replyToPeerId: replyToPeerId, quoteText: quoteText, quoteEntities: quoteEntities)
|
||||
} else if let replyToStoryId = replyToStoryId {
|
||||
if let inputUser = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputUser) {
|
||||
flags |= 1 << 0
|
||||
@ -1251,6 +1275,14 @@ public final class PendingMessageManager {
|
||||
replyFlags |= 1 << 0
|
||||
}
|
||||
|
||||
var replyToPeerId: Api.InputPeer?
|
||||
if let replyPeerId = replyPeerId {
|
||||
replyToPeerId = transaction.getPeer(replyPeerId).flatMap(apiInputPeer)
|
||||
}
|
||||
if replyToPeerId != nil {
|
||||
replyFlags |= 1 << 1
|
||||
}
|
||||
|
||||
var quoteText: String?
|
||||
var quoteEntities: [Api.MessageEntity]?
|
||||
if let replyQuote = replyQuote {
|
||||
@ -1273,7 +1305,7 @@ public final class PendingMessageManager {
|
||||
}
|
||||
}
|
||||
|
||||
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:)), replyToPeerId: nil, quoteText: quoteText, quoteEntities: quoteEntities)
|
||||
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:)), replyToPeerId: replyToPeerId, quoteText: quoteText, quoteEntities: quoteEntities)
|
||||
} else if let replyToStoryId = replyToStoryId {
|
||||
if let inputUser = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputUser) {
|
||||
flags |= 1 << 0
|
||||
@ -1310,6 +1342,14 @@ public final class PendingMessageManager {
|
||||
replyFlags |= 1 << 0
|
||||
}
|
||||
|
||||
var replyToPeerId: Api.InputPeer?
|
||||
if let replyPeerId = replyPeerId {
|
||||
replyToPeerId = transaction.getPeer(replyPeerId).flatMap(apiInputPeer)
|
||||
}
|
||||
if replyToPeerId != nil {
|
||||
replyFlags |= 1 << 1
|
||||
}
|
||||
|
||||
var quoteText: String?
|
||||
var quoteEntities: [Api.MessageEntity]?
|
||||
if let replyQuote = replyQuote {
|
||||
@ -1332,7 +1372,7 @@ public final class PendingMessageManager {
|
||||
}
|
||||
}
|
||||
|
||||
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:)), replyToPeerId: nil, quoteText: quoteText, quoteEntities: quoteEntities)
|
||||
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: message.threadId.flatMap(Int32.init(clamping:)), replyToPeerId: replyToPeerId, quoteText: quoteText, quoteEntities: quoteEntities)
|
||||
} else if let replyToStoryId = replyToStoryId {
|
||||
if let inputUser = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputUser) {
|
||||
flags |= 1 << 0
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
public class ReplyMessageAttribute: MessageAttribute {
|
||||
public let messageId: MessageId
|
||||
@ -46,6 +47,57 @@ public class ReplyMessageAttribute: MessageAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
public class QuotedReplyMessageAttribute: MessageAttribute {
|
||||
public let peerId: PeerId?
|
||||
public let authorName: String?
|
||||
public let quote: EngineMessageReplyQuote?
|
||||
|
||||
public var associatedMessageIds: [MessageId] {
|
||||
return []
|
||||
}
|
||||
|
||||
public init(peerId: PeerId?, authorName: String?, quote: EngineMessageReplyQuote?) {
|
||||
self.peerId = peerId
|
||||
self.authorName = authorName
|
||||
self.quote = quote
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.peerId = decoder.decodeOptionalInt64ForKey("p").flatMap(PeerId.init)
|
||||
self.authorName = decoder.decodeOptionalStringForKey("a")
|
||||
self.quote = decoder.decodeCodable(EngineMessageReplyQuote.self, forKey: "qu")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
if let peerId = self.peerId {
|
||||
encoder.encodeInt64(peerId.toInt64(), forKey: "p")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "p")
|
||||
}
|
||||
|
||||
if let authorName = self.authorName {
|
||||
encoder.encodeString(authorName, forKey: "a")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "a")
|
||||
}
|
||||
|
||||
if let quote = self.quote {
|
||||
encoder.encodeCodable(quote, forKey: "qu")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "qu")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension QuotedReplyMessageAttribute {
|
||||
convenience init(apiHeader: Api.MessageFwdHeader, quote: EngineMessageReplyQuote?) {
|
||||
switch apiHeader {
|
||||
case let .messageFwdHeader(_, fromId, fromName, _, _, _, _, _, _):
|
||||
self.init(peerId: fromId?.peerId, authorName: fromName, quote: quote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ReplyStoryAttribute: MessageAttribute {
|
||||
public let storyId: StoryId
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
|
||||
public enum MessageTextEntityType: Equatable {
|
||||
@ -315,3 +316,22 @@ public class TextEntitiesMessageAttribute: MessageAttribute, Equatable {
|
||||
return lhs.entities == rhs.entities
|
||||
}
|
||||
}
|
||||
|
||||
public func messageTextEntitiesInRange(entities: [MessageTextEntity], range: NSRange, onlyQuoteable: Bool) -> [MessageTextEntity] {
|
||||
let range: Range<Int> = range.lowerBound ..< range.upperBound
|
||||
var result: [MessageTextEntity] = []
|
||||
loop: for entity in entities {
|
||||
if onlyQuoteable {
|
||||
switch entity.type {
|
||||
case .Bold, .Italic, .Strikethrough, .Underline, .Spoiler, .CustomEmoji:
|
||||
break
|
||||
default:
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
if entity.range.overlaps(range) {
|
||||
result.append(MessageTextEntity(range: entity.range.clamped(to: range), type: entity.type))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -546,8 +546,8 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
|
||||
|
||||
boundingRect.origin.y += self.defaultTextContainerInset.top
|
||||
|
||||
boundingRect.origin.x -= 9.0
|
||||
boundingRect.size.width += 9.0
|
||||
boundingRect.origin.x -= 4.0
|
||||
boundingRect.size.width += 4.0
|
||||
boundingRect.size.width += 18.0
|
||||
boundingRect.size.width = min(boundingRect.size.width, self.bounds.width - 18.0)
|
||||
|
||||
|
@ -51,6 +51,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
public let context: AccountContext
|
||||
public let type: ChatMessageReplyInfoType
|
||||
public let message: Message?
|
||||
public let replyForward: QuotedReplyMessageAttribute?
|
||||
public let quote: EngineMessageReplyQuote?
|
||||
public let story: StoryId?
|
||||
public let parentMessage: Message
|
||||
@ -65,6 +66,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
context: AccountContext,
|
||||
type: ChatMessageReplyInfoType,
|
||||
message: Message?,
|
||||
replyForward: QuotedReplyMessageAttribute?,
|
||||
quote: EngineMessageReplyQuote?,
|
||||
story: StoryId?,
|
||||
parentMessage: Message,
|
||||
@ -78,6 +80,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
self.context = context
|
||||
self.type = type
|
||||
self.message = message
|
||||
self.replyForward = replyForward
|
||||
self.quote = quote
|
||||
self.story = story
|
||||
self.parentMessage = parentMessage
|
||||
@ -150,10 +153,24 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
if message.id.peerId != arguments.parentMessage.id.peerId {
|
||||
//TODO:localize
|
||||
if let peer = message.peers[message.id.peerId], (peer is TelegramChannel || peer is TelegramGroup) {
|
||||
titleString += " in \(peer.debugDisplayTitle)"
|
||||
}
|
||||
}
|
||||
|
||||
let (textStringValue, isMediaValue, isTextValue) = descriptionStringForMessage(contentSettings: arguments.context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId)
|
||||
textString = textStringValue
|
||||
isMedia = isMediaValue
|
||||
isText = isTextValue
|
||||
} else if let replyForward = arguments.replyForward {
|
||||
titleString = replyForward.authorName ?? " "
|
||||
|
||||
//TODO:localize
|
||||
textString = NSAttributedString(string: replyForward.quote?.text ?? "Message")
|
||||
isMedia = false
|
||||
isText = true
|
||||
} else if let story = arguments.story {
|
||||
if let authorPeer = arguments.parentMessage.peers[story.peerId] {
|
||||
titleString = EnginePeer(authorPeer).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
@ -289,10 +306,27 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
if entities.count > 0 {
|
||||
messageText = stringWithAppliedEntities(trimToLineCount(text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||
messageText = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||
} else {
|
||||
messageText = NSAttributedString(string: text, font: textFont, textColor: textColor)
|
||||
}
|
||||
} else if let replyForward = arguments.replyForward, let quote = replyForward.quote {
|
||||
let entities = quote.entities.filter { entity in
|
||||
if case .Strikethrough = entity.type {
|
||||
return true
|
||||
} else if case .Spoiler = entity.type {
|
||||
return true
|
||||
} else if case .CustomEmoji = entity.type {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if entities.count > 0 {
|
||||
messageText = stringWithAppliedEntities(quote.text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: nil)
|
||||
} else {
|
||||
messageText = NSAttributedString(string: quote.text, font: textFont, textColor: textColor)
|
||||
}
|
||||
} else {
|
||||
messageText = NSAttributedString(string: textString.string, font: textFont, textColor: textColor)
|
||||
}
|
||||
@ -356,13 +390,15 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
|
||||
|
||||
var additionalTitleWidth: CGFloat = 0.0
|
||||
var maxTitleNumberOfLines = 1
|
||||
var maxTextNumberOfLines = 1
|
||||
var adjustedConstrainedTextSize = contrainedTextSize
|
||||
var textCutout: TextNodeCutout?
|
||||
var textCutoutWidth: CGFloat = 0.0
|
||||
if arguments.quote != nil {
|
||||
if arguments.quote != nil || arguments.replyForward?.quote != nil {
|
||||
additionalTitleWidth += 10.0
|
||||
if case .bubble = arguments.type {
|
||||
maxTitleNumberOfLines = 2
|
||||
maxTextNumberOfLines = 5
|
||||
if imageTextInset != 0.0 {
|
||||
adjustedConstrainedTextSize.width += imageTextInset
|
||||
@ -372,15 +408,20 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets))
|
||||
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: maxTitleNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets))
|
||||
if isExpiredStory || isStory {
|
||||
contrainedTextSize.width -= 26.0
|
||||
}
|
||||
|
||||
if titleLayout.numberOfLines > 1 {
|
||||
textCutout = nil
|
||||
}
|
||||
|
||||
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: maxTextNumberOfLines, truncationType: .end, constrainedSize: adjustedConstrainedTextSize, alignment: .natural, lineSpacing: 0.07, cutout: textCutout, insets: textInsets))
|
||||
|
||||
let imageSide: CGFloat
|
||||
imageSide = titleLayout.size.height + titleLayout.size.height - 14.0
|
||||
let titleLineHeight: CGFloat = titleLayout.linesRects().first?.height ?? 12.0
|
||||
imageSide = titleLineHeight * 2.0
|
||||
|
||||
var applyImage: (() -> TransformImageNode)?
|
||||
if let imageDimensions = imageDimensions {
|
||||
@ -567,7 +608,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
node.backgroundView.tintColor = mainColor
|
||||
node.backgroundView.frame = backgroundFrame
|
||||
|
||||
if arguments.quote != nil {
|
||||
if arguments.quote != nil || arguments.replyForward?.quote != nil {
|
||||
let quoteIconView: UIImageView
|
||||
if let current = node.quoteIconView {
|
||||
quoteIconView = current
|
||||
|
@ -564,8 +564,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
strongSelf.statusNode.pressed = nil
|
||||
}
|
||||
|
||||
if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind {
|
||||
strongSelf.updateIsExtractedToContextPreview(true)
|
||||
if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case let .reply(initialQuote) = info.kind {
|
||||
if strongSelf.textSelectionNode == nil {
|
||||
strongSelf.updateIsExtractedToContextPreview(true)
|
||||
if let initialQuote, item.message.id == initialQuote.messageId, let string = strongSelf.textNode.textNode.cachedLayout?.attributedString {
|
||||
let nsString = string.string as NSString
|
||||
let subRange = nsString.range(of: initialQuote.text)
|
||||
if subRange.location != NSNotFound {
|
||||
strongSelf.beginTextSelection(range: subRange, displayMenu: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -772,7 +781,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
override public func updateIsExtractedToContextPreview(_ value: Bool) {
|
||||
if value {
|
||||
if self.textSelectionNode == nil, let item = self.item, !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(), let rootNode = item.controllerInteraction.chatControllerNode() {
|
||||
if self.textSelectionNode == nil, let item = self.item, let rootNode = item.controllerInteraction.chatControllerNode() {
|
||||
let selectionColor: UIColor
|
||||
let knobColor: UIColor
|
||||
if item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
@ -786,7 +795,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: knobColor), strings: item.presentationData.strings, textNode: self.textNode.textNode, updateIsActive: { [weak self] value in
|
||||
self?.updateIsTextSelectionActive?(value)
|
||||
}, present: { [weak self] c, a in
|
||||
self?.item?.controllerInteraction.presentGlobalOverlayController(c, a)
|
||||
guard let self, let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
/*if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind {
|
||||
item.controllerInteraction.presentControllerInCurrent(c, a)
|
||||
} else {*/
|
||||
item.controllerInteraction.presentGlobalOverlayController(c, a)
|
||||
//}
|
||||
}, rootNode: { [weak rootNode] in
|
||||
return rootNode
|
||||
}, performAction: { [weak self] text, action in
|
||||
@ -805,7 +822,22 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
textSelectionNode.enableQuote = item.controllerInteraction.canSetupReply(item.message) == .reply
|
||||
|
||||
let enableCopy = !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected()
|
||||
textSelectionNode.enableCopy = enableCopy
|
||||
|
||||
var enableQuote = false
|
||||
var enableOtherActions = true
|
||||
if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind {
|
||||
enableQuote = true
|
||||
enableOtherActions = false
|
||||
} else if item.controllerInteraction.canSetupReply(item.message) == .reply {
|
||||
enableQuote = true
|
||||
enableOtherActions = false
|
||||
}
|
||||
textSelectionNode.enableQuote = enableQuote
|
||||
textSelectionNode.enableTranslate = enableOtherActions
|
||||
textSelectionNode.enableShare = enableOtherActions
|
||||
self.textSelectionNode = textSelectionNode
|
||||
self.addSubnode(textSelectionNode)
|
||||
self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode.textNode)
|
||||
@ -859,4 +891,40 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
transition.horizontal.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: -widthDifference, y: 0.0))
|
||||
}
|
||||
|
||||
public func beginTextSelection(range: NSRange?, displayMenu: Bool = true) {
|
||||
guard let textSelectionNode = self.textSelectionNode else {
|
||||
return
|
||||
}
|
||||
guard let string = self.textNode.textNode.cachedLayout?.attributedString else {
|
||||
return
|
||||
}
|
||||
let nsString = string.string as NSString
|
||||
let range = range ?? NSRange(location: 0, length: nsString.length)
|
||||
textSelectionNode.setSelection(range: range, displayMenu: displayMenu)
|
||||
}
|
||||
|
||||
public func getCurrentTextSelection() -> (text: String, entities: [MessageTextEntity])? {
|
||||
guard let textSelectionNode = self.textSelectionNode else {
|
||||
return nil
|
||||
}
|
||||
guard let range = textSelectionNode.getSelection() else {
|
||||
return nil
|
||||
}
|
||||
guard let item = self.item else {
|
||||
return nil
|
||||
}
|
||||
guard let string = self.textNode.textNode.cachedLayout?.attributedString else {
|
||||
return nil
|
||||
}
|
||||
let nsString = string.string as NSString
|
||||
let substring = nsString.substring(with: range)
|
||||
|
||||
var entities: [MessageTextEntity] = []
|
||||
if let textEntitiesAttribute = item.message.textEntitiesAttribute {
|
||||
entities = messageTextEntitiesInRange(entities: textEntitiesAttribute.entities, range: range, onlyQuoteable: true)
|
||||
}
|
||||
|
||||
return (substring, entities)
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import TelegramNotices
|
||||
|
||||
public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
private let messageDisposable = MetaDisposable()
|
||||
public let chatPeerId: EnginePeer.Id
|
||||
public let messageId: MessageId
|
||||
public let quote: EngineMessageReplyQuote?
|
||||
|
||||
@ -39,9 +40,12 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
public var theme: PresentationTheme
|
||||
public var strings: PresentationStrings
|
||||
|
||||
private var textIsOptions: Bool = false
|
||||
|
||||
private var validLayout: (size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState)?
|
||||
|
||||
public init(context: AccountContext, messageId: MessageId, quote: EngineMessageReplyQuote?, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) {
|
||||
public init(context: AccountContext, chatPeerId: EnginePeer.Id, messageId: MessageId, quote: EngineMessageReplyQuote?, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) {
|
||||
self.chatPeerId = chatPeerId
|
||||
self.messageId = messageId
|
||||
self.quote = quote
|
||||
|
||||
@ -145,7 +149,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
isMedia = false
|
||||
}
|
||||
|
||||
let textFont = Font.regular(14.0)
|
||||
let textFont = Font.regular(15.0)
|
||||
let messageText: NSAttributedString
|
||||
if isText, let message = message {
|
||||
let entities = (message.textEntitiesAttribute?.entities ?? []).filter { entity in
|
||||
@ -231,14 +235,23 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
var titleText: String
|
||||
if let quote = strongSelf.quote {
|
||||
//TODO:localize
|
||||
strongSelf.titleNode.attributedText = NSAttributedString(string: "Reply to quote by \(authorName)", font: Font.medium(14.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)
|
||||
titleText = "Reply to quote by \(authorName)"
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: quote.text, font: textFont, textColor: strongSelf.theme.chat.inputPanel.primaryTextColor)
|
||||
} else {
|
||||
strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.strings.Conversation_ReplyMessagePanelTitle(authorName).string, font: Font.medium(14.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)
|
||||
titleText = strongSelf.strings.Conversation_ReplyMessagePanelTitle(authorName).string
|
||||
strongSelf.textNode.attributedText = messageText
|
||||
}
|
||||
|
||||
if strongSelf.messageId.peerId != strongSelf.chatPeerId {
|
||||
if let peer = message?.peers[strongSelf.messageId.peerId], (peer is TelegramChannel || peer is TelegramGroup) {
|
||||
titleText += " in \(peer.debugDisplayTitle)"
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.titleNode.attributedText = NSAttributedString(string: titleText, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)
|
||||
|
||||
let headerString: String
|
||||
if let message = message, message.flags.contains(.Incoming), let author = message.author {
|
||||
@ -277,6 +290,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
} else {
|
||||
text = "Tap here for forwarding options"
|
||||
}
|
||||
strongSelf.textIsOptions = true
|
||||
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
@ -336,7 +350,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
|
||||
if let text = self.textNode.attributedText?.string {
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.primaryTextColor)
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.textIsOptions ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor)
|
||||
}
|
||||
self.textNode.spoilerColor = self.theme.chat.inputPanel.secondaryTextColor
|
||||
|
||||
|
@ -362,7 +362,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
let forwardOptions: Signal<ChatControllerSubject.ForwardOptions, NoError>
|
||||
forwardOptions = strongSelf.presentationInterfaceStatePromise.get()
|
||||
|> map { state -> ChatControllerSubject.ForwardOptions in
|
||||
return ChatControllerSubject.ForwardOptions(hideNames: state.interfaceState.forwardOptionsState?.hideNames ?? false, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false)
|
||||
return ChatControllerSubject.ForwardOptions(hideNames: state.interfaceState.forwardOptionsState?.hideNames ?? false, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false, replyOptions: nil)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
|
@ -2570,7 +2570,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
navigationController: navigationController,
|
||||
context: component.context,
|
||||
chatLocation: chatLocation,
|
||||
subject: .message(id: .id(message.id), highlight: true, timecode: nil),
|
||||
subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil),
|
||||
keepStack: .always
|
||||
))
|
||||
})
|
||||
@ -2673,7 +2673,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
navigationController: navigationController,
|
||||
context: component.context,
|
||||
chatLocation: chatLocation,
|
||||
subject: .message(id: .id(message.id), highlight: true, timecode: nil),
|
||||
subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil),
|
||||
keepStack: .always
|
||||
))
|
||||
})
|
||||
|
@ -4416,7 +4416,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
presentationData: presentationData,
|
||||
content: .sticker(context: context, file: animation, loop: false, title: nil, text: component.strings.Story_ToastReactionSent, undoText: component.strings.Story_ToastViewInChat, customAction: { [weak self] in
|
||||
if let messageId = messageIds.first, let self {
|
||||
self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: false, timecode: nil) })
|
||||
self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: nil, timecode: nil) })
|
||||
}
|
||||
}),
|
||||
elevatedLayout: false,
|
||||
|
@ -371,7 +371,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
animateInAsReplacement: false,
|
||||
action: { [weak view, weak self] action in
|
||||
if case .undo = action, let messageId {
|
||||
view?.navigateToPeer(peer: peer, chat: true, subject: isScheduled ? .scheduledMessages : .message(id: .id(messageId), highlight: false, timecode: nil))
|
||||
view?.navigateToPeer(peer: peer, chat: true, subject: isScheduled ? .scheduledMessages : .message(id: .id(messageId), highlight: nil, timecode: nil))
|
||||
}
|
||||
self?.tooltipScreen = nil
|
||||
view?.updateIsProgressPaused()
|
||||
@ -2721,7 +2721,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
return
|
||||
}
|
||||
if let navigationController = controller.navigationController as? NavigationController {
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: true, timecode: nil)))
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil)))
|
||||
}
|
||||
completion?()
|
||||
})
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Quote.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Quote.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "IconQuote.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
3
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Quote.imageset/IconQuote.svg
vendored
Normal file
3
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Quote.imageset/IconQuote.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3.165C4.53884 3.165 4.165 3.53884 4.165 4C4.165 4.46116 4.53884 4.835 5 4.835C5.46116 4.835 5.835 4.46116 5.835 4C5.835 3.53884 5.46116 3.165 5 3.165ZM7.165 4C7.165 4.9637 6.53535 5.78033 5.665 6.06095V8.5C5.665 8.86727 5.36727 9.165 5 9.165C4.63273 9.165 4.335 8.86727 4.335 8.5V6.06095C3.46465 5.78033 2.835 4.9637 2.835 4C2.835 2.8043 3.8043 1.835 5 1.835C6.1957 1.835 7.165 2.8043 7.165 4ZM8.335 7C8.335 6.63273 8.63273 6.335 9 6.335H19C19.3673 6.335 19.665 6.63273 19.665 7C19.665 7.36727 19.3673 7.665 19 7.665H9C8.63273 7.665 8.335 7.36727 8.335 7ZM19.665 15.5C19.665 15.1327 19.3673 14.835 19 14.835C18.6327 14.835 18.335 15.1327 18.335 15.5V17.939C17.4647 18.2197 16.835 19.0363 16.835 20C16.835 21.1957 17.8043 22.165 19 22.165C20.1957 22.165 21.165 21.1957 21.165 20C21.165 19.0363 20.5353 18.2197 19.665 17.939V15.5ZM4.335 12C4.335 11.6327 4.63273 11.335 5 11.335H19C19.3673 11.335 19.665 11.6327 19.665 12C19.665 12.3673 19.3673 12.665 19 12.665H5C4.63273 12.665 4.335 12.3673 4.335 12ZM5 16.335C4.63273 16.335 4.335 16.6327 4.335 17C4.335 17.3673 4.63273 17.665 5 17.665H15C15.3673 17.665 15.665 17.3673 15.665 17C15.665 16.6327 15.3673 16.335 15 16.335H5ZM18.165 20C18.165 19.5388 18.5388 19.165 19 19.165C19.4612 19.165 19.835 19.5388 19.835 20C19.835 20.4612 19.4612 20.835 19 20.835C18.5388 20.835 18.165 20.4612 18.165 20Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QuoteRemove.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QuoteRemove.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "IconQuoteRemove.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.47023 3.52977C4.21053 3.27008 3.78947 3.27008 3.52977 3.52977C3.27008 3.78947 3.27008 4.21053 3.52977 4.47023L19.5298 20.4702C19.7895 20.7299 20.2105 20.7299 20.4702 20.4702C20.7299 20.2105 20.7299 19.7895 20.4702 19.5298L18.6055 17.665H19C19.3673 17.665 19.665 17.3673 19.665 17C19.665 16.6327 19.3673 16.335 19 16.335H17.2755L13.6055 12.665H19C19.3673 12.665 19.665 12.3673 19.665 12C19.665 11.6327 19.3673 11.335 19 11.335H12.2755L8.60545 7.665H12.5C12.8673 7.665 13.165 7.36727 13.165 7C13.165 6.63273 12.8673 6.335 12.5 6.335H7.27545L4.47023 3.52977ZM4.33502 7C4.33502 6.93205 4.34521 6.86647 4.36415 6.80473L5.22443 7.665H5.00002C4.63275 7.665 4.33502 7.36727 4.33502 7ZM5.00002 11.335H8.89443L10.2244 12.665H5.00002C4.63275 12.665 4.33502 12.3673 4.33502 12C4.33502 11.6327 4.63275 11.335 5.00002 11.335ZM5.00002 16.335H13.8944L15.2244 17.665H5.00002C4.63275 17.665 4.33502 17.3673 4.33502 17C4.33502 16.6327 4.63275 16.335 5.00002 16.335ZM20.6406 5.16992C20.3281 4.85742 19.9492 4.70117 19.5039 4.70117C19.2578 4.70117 19.0313 4.75391 18.8242 4.85937C18.6172 4.96484 18.4531 5.11328 18.3321 5.30469C18.2071 5.5 18.1446 5.72852 18.1446 5.99023C18.1446 6.36523 18.2598 6.66602 18.4903 6.89258C18.7168 7.11914 19.0039 7.23242 19.3516 7.23242C19.5938 7.23242 19.7969 7.16797 19.961 7.03906C20.0355 6.98226 20.0992 6.91214 20.152 6.82871C20.1095 7.02421 20.042 7.2037 19.9492 7.36719C19.8008 7.62891 19.6016 7.83594 19.3516 7.98828C19.1016 8.14062 18.8145 8.22461 18.4903 8.24023C18.3809 8.24414 18.2891 8.2832 18.2149 8.35742C18.1406 8.43164 18.1035 8.52344 18.1035 8.63281C18.1035 8.76562 18.1524 8.86719 18.25 8.9375C18.3477 9.00781 18.4688 9.04297 18.6133 9.04297C19.0547 9.04297 19.4649 8.92773 19.8438 8.69727C20.2188 8.4707 20.5235 8.16016 20.7578 7.76562C20.9883 7.37109 21.1035 6.92773 21.1035 6.43555C21.1035 5.9082 20.9492 5.48633 20.6406 5.16992ZM17.0547 5.16992C16.7422 4.85742 16.3633 4.70117 15.918 4.70117C15.6719 4.70117 15.4453 4.75391 15.2383 4.85937C15.0313 4.96484 14.8653 5.11328 14.7403 5.30469C14.6153 5.5 14.5528 5.72852 14.5528 5.99023C14.5528 6.36523 14.668 6.66602 14.8985 6.89258C15.125 7.11914 15.4141 7.23242 15.7656 7.23242C16.0039 7.23242 16.2071 7.16797 16.375 7.03906C16.4496 6.98226 16.5133 6.91214 16.566 6.82871C16.5236 7.02421 16.456 7.2037 16.3633 7.36719C16.2149 7.63281 16.0156 7.83984 15.7656 7.98828C15.5156 8.14062 15.2305 8.22461 14.9102 8.24023C14.793 8.24414 14.6973 8.2832 14.6231 8.35742C14.5489 8.43164 14.5117 8.52344 14.5117 8.63281C14.5117 8.76562 14.5606 8.86719 14.6582 8.9375C14.7559 9.00781 14.8789 9.04297 15.0274 9.04297C15.4649 9.04297 15.8731 8.92773 16.252 8.69727C16.6309 8.4707 16.9375 8.16016 17.1719 7.76562C17.4024 7.37109 17.5176 6.92773 17.5176 6.43555C17.5176 5.9082 17.3633 5.48633 17.0547 5.16992Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QuoteSelected.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QuoteSelected.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "IconQuoteSelected.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.6406 5.16992C20.3281 4.85742 19.9492 4.70117 19.5039 4.70117C19.2578 4.70117 19.0313 4.75391 18.8242 4.85938C18.6172 4.96484 18.4531 5.11328 18.332 5.30469C18.207 5.5 18.1445 5.72852 18.1445 5.99023C18.1445 6.36523 18.2598 6.66602 18.4902 6.89258C18.7168 7.11914 19.0039 7.23242 19.3516 7.23242C19.5938 7.23242 19.7969 7.16797 19.9609 7.03906C20.0355 6.98226 20.0992 6.91214 20.1519 6.82871C20.1095 7.02421 20.0419 7.2037 19.9492 7.36719C19.8008 7.62891 19.6016 7.83594 19.3516 7.98828C19.1016 8.14062 18.8145 8.22461 18.4902 8.24023C18.3809 8.24414 18.2891 8.2832 18.2148 8.35742C18.1406 8.43164 18.1035 8.52344 18.1035 8.63281C18.1035 8.76562 18.1523 8.86719 18.25 8.9375C18.3477 9.00781 18.4688 9.04297 18.6133 9.04297C19.0547 9.04297 19.4648 8.92773 19.8438 8.69727C20.2188 8.4707 20.5234 8.16016 20.7578 7.76562C20.9883 7.37109 21.1035 6.92773 21.1035 6.43555C21.1035 5.9082 20.9492 5.48633 20.6406 5.16992ZM17.0547 5.16992C16.7422 4.85742 16.3633 4.70117 15.918 4.70117C15.6719 4.70117 15.4453 4.75391 15.2383 4.85938C15.0313 4.96484 14.8652 5.11328 14.7402 5.30469C14.6152 5.5 14.5527 5.72852 14.5527 5.99023C14.5527 6.36523 14.668 6.66602 14.8984 6.89258C15.125 7.11914 15.4141 7.23242 15.7656 7.23242C16.0039 7.23242 16.207 7.16797 16.375 7.03906C16.4496 6.98226 16.5132 6.91214 16.566 6.82871C16.5236 7.02421 16.456 7.2037 16.3633 7.36719C16.2148 7.63281 16.0156 7.83984 15.7656 7.98828C15.5156 8.14062 15.2305 8.22461 14.9102 8.24023C14.793 8.24414 14.6973 8.2832 14.623 8.35742C14.5488 8.43164 14.5117 8.52344 14.5117 8.63281C14.5117 8.76562 14.5605 8.86719 14.6582 8.9375C14.7559 9.00781 14.8789 9.04297 15.0273 9.04297C15.4648 9.04297 15.873 8.92773 16.252 8.69727C16.6309 8.4707 16.9375 8.16016 17.1719 7.76562C17.4023 7.37109 17.5176 6.92773 17.5176 6.43555C17.5176 5.9082 17.3633 5.48633 17.0547 5.16992ZM5 6.335C4.63273 6.335 4.335 6.63273 4.335 7C4.335 7.36727 4.63273 7.665 5 7.665H12.5C12.8673 7.665 13.165 7.36727 13.165 7C13.165 6.63273 12.8673 6.335 12.5 6.335H5ZM5 11.335C4.63273 11.335 4.335 11.6327 4.335 12C4.335 12.3673 4.63273 12.665 5 12.665H19C19.3673 12.665 19.665 12.3673 19.665 12C19.665 11.6327 19.3673 11.335 19 11.335H5ZM4.335 17C4.335 16.6327 4.63273 16.335 5 16.335H19C19.3673 16.335 19.665 16.6327 19.665 17C19.665 17.3673 19.3673 17.665 19 17.665H5C4.63273 17.665 4.335 17.3673 4.335 17Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -785,7 +785,7 @@ final class AuthorizedApplicationContext {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: true, timecode: nil)))
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil)))
|
||||
})
|
||||
}
|
||||
|
||||
@ -904,7 +904,7 @@ final class AuthorizedApplicationContext {
|
||||
chatLocation = .peer(peer)
|
||||
}
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) } : nil, activateInput: activateInput ? .text : nil))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) } : nil, activateInput: activateInput ? .text : nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
import ChatPresentationInterfaceState
|
||||
import ContextUI
|
||||
import ChatInterfaceState
|
||||
import PresentationDataUtils
|
||||
import ChatMessageTextBubbleContentNode
|
||||
|
||||
func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) {
|
||||
guard let peerId = selfController.chatLocation.peerId else {
|
||||
@ -22,7 +25,7 @@ func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: A
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
hideNames = true
|
||||
}
|
||||
return ChatControllerSubject.ForwardOptions(hideNames: hideNames, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false)
|
||||
return ChatControllerSubject.ForwardOptions(hideNames: hideNames, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false, replyOptions: nil)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
@ -220,6 +223,153 @@ func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: A
|
||||
selfController.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
private func generateChatReplyOptionItems(selfController: ChatControllerImpl, chatController: ChatControllerImpl) -> Signal<ContextController.Items, NoError> {
|
||||
guard let replySubject = selfController.presentationInterfaceState.interfaceState.replyMessageSubject else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
let messageIds: [EngineMessage.Id] = [replySubject.messageId]
|
||||
let messagesCount: Signal<Int, NoError> = .single(1)
|
||||
|
||||
let items = combineLatest(selfController.context.account.postbox.messagesAtIds(messageIds), messagesCount)
|
||||
|> deliverOnMainQueue
|
||||
|> map { [weak selfController, weak chatController] messages, messagesCount -> [ContextMenuItem] in
|
||||
guard let selfController, let chatController else {
|
||||
return []
|
||||
}
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if replySubject.quote != nil {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Quote Selected Part", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QuoteSelected"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak selfController, weak chatController] _, f in
|
||||
guard let selfController, let chatController else {
|
||||
return
|
||||
}
|
||||
|
||||
var messageItemNode: ChatMessageItemView?
|
||||
chatController.chatDisplayNode.historyNode.enumerateItemNodes { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, item.message.id == replySubject.messageId {
|
||||
messageItemNode = itemNode
|
||||
}
|
||||
return true
|
||||
}
|
||||
var targetContentNode: ChatMessageTextBubbleContentNode?
|
||||
if let messageItemNode = messageItemNode as? ChatMessageBubbleItemNode {
|
||||
for contentNode in messageItemNode.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
targetContentNode = contentNode
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
guard let contentNode = targetContentNode else {
|
||||
return
|
||||
}
|
||||
guard let textSelection = contentNode.getCurrentTextSelection() else {
|
||||
return
|
||||
}
|
||||
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: EngineMessageReplyQuote(text: textSelection.text, entities: textSelection.entities))).withoutSelectionState() }) })
|
||||
|
||||
f(.default)
|
||||
})))
|
||||
} else {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Select Specific Quote", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Quote"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController, weak chatController] c, _ in
|
||||
guard let selfController, let chatController else {
|
||||
return
|
||||
}
|
||||
var messageItemNode: ChatMessageItemView?
|
||||
chatController.chatDisplayNode.historyNode.enumerateItemNodes { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, item.message.id == replySubject.messageId {
|
||||
messageItemNode = itemNode
|
||||
}
|
||||
return true
|
||||
}
|
||||
if let messageItemNode = messageItemNode as? ChatMessageBubbleItemNode {
|
||||
for contentNode in messageItemNode.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.beginTextSelection(range: nil)
|
||||
|
||||
var subItems: [ContextMenuItem] = []
|
||||
|
||||
subItems.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Common_Back, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||
}, iconPosition: .left, action: { [weak selfController, weak chatController] c, _ in
|
||||
guard let selfController, let chatController else {
|
||||
return
|
||||
}
|
||||
c.setItems(generateChatReplyOptionItems(selfController: selfController, chatController: chatController), minHeight: nil, previousActionsTransition: .slide(forward: false))
|
||||
//c.popItems()
|
||||
})))
|
||||
subItems.append(.separator)
|
||||
|
||||
//TODO:localize
|
||||
subItems.append(.action(ContextMenuActionItem(text: "Quote Selected Part", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QuoteSelected"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak selfController, weak contentNode] _, f in
|
||||
guard let selfController, let contentNode else {
|
||||
return
|
||||
}
|
||||
guard let textSelection = contentNode.getCurrentTextSelection() else {
|
||||
return
|
||||
}
|
||||
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: EngineMessageReplyQuote(text: textSelection.text, entities: textSelection.entities))).withoutSelectionState() }) })
|
||||
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
//c.pushItems(items: .single(ContextController.Items(content: .list(subItems))))
|
||||
|
||||
let minHeight = c.getActionsMinHeight()
|
||||
c.immediateItemsTransitionAnimation = false
|
||||
c.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: minHeight, previousActionsTransition: .slide(forward: true))
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Reply in Another Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] c, f in
|
||||
f(.default)
|
||||
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
guard let replySubject = selfController.presentationInterfaceState.interfaceState.replyMessageSubject else {
|
||||
return
|
||||
}
|
||||
moveReplyMessageToAnotherChat(selfController: selfController, replySubject: replySubject)
|
||||
})))
|
||||
|
||||
if replySubject.quote != nil {
|
||||
items.append(.action(ContextMenuActionItem(text: "Remove Quote", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QuoteRemove"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in
|
||||
f(.default)
|
||||
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
var replySubject = replySubject
|
||||
replySubject.quote = nil
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) })
|
||||
})))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
var tip: ContextController.Tip?
|
||||
if "".isEmpty {
|
||||
tip = .quoteSelection
|
||||
}
|
||||
return items |> map { ContextController.Items(content: .list($0), tip: tip) }
|
||||
}
|
||||
|
||||
func presentChatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) {
|
||||
guard let peerId = selfController.chatLocation.peerId else {
|
||||
@ -229,51 +379,161 @@ func presentChatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||
return
|
||||
}
|
||||
|
||||
let replyOptionsSubject = Promise<ChatControllerSubject.ForwardOptions>()
|
||||
replyOptionsSubject.set(.single(ChatControllerSubject.ForwardOptions(hideNames: false, hideCaptions: false, replyOptions: ChatControllerSubject.ReplyOptions(hasQuote: replySubject.quote != nil))))
|
||||
|
||||
//let presentationData = selfController.presentationData
|
||||
|
||||
let chatController = selfController.context.sharedContext.makeChatController(context: selfController.context, chatLocation: .peer(id: peerId), subject: .messageOptions(peerIds: [replySubject.messageId.peerId], ids: [replySubject.messageId], info: ChatControllerSubject.MessageOptionsInfo(kind: .reply), options: .single(ChatControllerSubject.ForwardOptions(hideNames: false, hideCaptions: false))), botStart: nil, mode: .standard(previewing: true))
|
||||
var replyQuote: ChatControllerSubject.MessageOptionsInfo.ReplyQuote?
|
||||
if let quote = replySubject.quote {
|
||||
replyQuote = ChatControllerSubject.MessageOptionsInfo.ReplyQuote(messageId: replySubject.messageId, text: quote.text)
|
||||
}
|
||||
guard let chatController = selfController.context.sharedContext.makeChatController(context: selfController.context, chatLocation: .peer(id: peerId), subject: .messageOptions(peerIds: [replySubject.messageId.peerId], ids: [replySubject.messageId], info: ChatControllerSubject.MessageOptionsInfo(kind: .reply(initialQuote: replyQuote)), options: replyOptionsSubject.get()), botStart: nil, mode: .standard(previewing: true)) as? ChatControllerImpl else {
|
||||
return
|
||||
}
|
||||
chatController.canReadHistory.set(false)
|
||||
|
||||
let messageIds: [EngineMessage.Id] = [replySubject.messageId]
|
||||
let messagesCount: Signal<Int, NoError> = .single(1)
|
||||
|
||||
//let accountPeerId = selfController.context.account.peerId
|
||||
let items = combineLatest(selfController.context.account.postbox.messagesAtIds(messageIds), messagesCount)
|
||||
|> deliverOnMainQueue
|
||||
|> map { [weak selfController] messages, messagesCount -> [ContextMenuItem] in
|
||||
guard let selfController else {
|
||||
return []
|
||||
}
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Reply in Another Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] c, f in
|
||||
selfController?.interfaceInteraction?.forwardCurrentForwardMessages()
|
||||
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
return items
|
||||
}
|
||||
let items = generateChatReplyOptionItems(selfController: selfController, chatController: chatController)
|
||||
|
||||
selfController.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
||||
|
||||
selfController.canReadHistory.set(false)
|
||||
|
||||
let contextController = ContextController(presentationData: selfController.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(content: .list($0)) })
|
||||
let contextController = ContextController(presentationData: selfController.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items)
|
||||
contextController.dismissed = { [weak selfController] in
|
||||
selfController?.canReadHistory.set(true)
|
||||
}
|
||||
contextController.dismissedForCancel = { [weak selfController, weak chatController] in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
if let selectedMessageIds = (chatController as? ChatControllerImpl)?.selectedMessageIds {
|
||||
var forwardMessageIds = selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
|
||||
forwardMessageIds = forwardMessageIds.filter { selectedMessageIds.contains($0) }
|
||||
selfController.updateChatPresentationInterfaceState(interactive: false, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) })
|
||||
}
|
||||
contextController.dismissedForCancel = {
|
||||
}
|
||||
contextController.immediateItemsTransitionAnimation = true
|
||||
selfController.presentInGlobalOverlay(contextController)
|
||||
|
||||
chatController.performTextSelectionAction = { [weak selfController, weak contextController] message, canCopy, text, action in
|
||||
guard let selfController, let contextController else {
|
||||
return
|
||||
}
|
||||
|
||||
contextController.dismiss()
|
||||
|
||||
selfController.controllerInteraction?.performTextSelectionAction(message, canCopy, text, action)
|
||||
}
|
||||
}
|
||||
|
||||
func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubject: ChatInterfaceState.ReplyMessageSubject) {
|
||||
let _ = selfController.presentVoiceMessageDiscardAlert(action: { [weak selfController] in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
let filter: ChatListNodePeersFilter = [.onlyWriteable, .includeSavedMessages, .excludeDisabled, .doNotSearchMessages]
|
||||
var attemptSelectionImpl: ((EnginePeer) -> Void)?
|
||||
let controller = selfController.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(
|
||||
context: selfController.context,
|
||||
updatedPresentationData: selfController.updatedPresentationData,
|
||||
filter: filter,
|
||||
hasFilters: true,
|
||||
title: "Reply in...", //TODO:localize
|
||||
attemptSelection: { peer, _ in
|
||||
attemptSelectionImpl?(peer)
|
||||
},
|
||||
multipleSelection: false,
|
||||
forwardedMessageIds: [],
|
||||
selectForumThreads: true
|
||||
))
|
||||
let context = selfController.context
|
||||
attemptSelectionImpl = { [weak selfController, weak controller] peer in
|
||||
guard let selfController, let controller = controller else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(textAlertController(context: context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: presentationData.strings.Forward_ErrorDisabledForChat, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
controller.peerSelected = { [weak selfController, weak controller] peer, threadId in
|
||||
guard let selfController, let strongController = controller else {
|
||||
return
|
||||
}
|
||||
let peerId = peer.id
|
||||
//let accountPeerId = selfController.context.account.peerId
|
||||
|
||||
var isPinnedMessages = false
|
||||
if case .pinnedMessages = selfController.presentationInterfaceState.subject {
|
||||
isPinnedMessages = true
|
||||
}
|
||||
|
||||
if case .peer(peerId) = selfController.chatLocation, selfController.parentController == nil, !isPinnedMessages {
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) })
|
||||
selfController.updateItemNodesSearchTextHighlightStates()
|
||||
selfController.searchResultsController = nil
|
||||
strongController.dismiss()
|
||||
} else {
|
||||
if let navigationController = selfController.navigationController as? NavigationController {
|
||||
for controller in navigationController.viewControllers {
|
||||
if let maybeChat = controller as? ChatControllerImpl {
|
||||
if case .peer(peerId) = maybeChat.chatLocation {
|
||||
var isChatPinnedMessages = false
|
||||
if case .pinnedMessages = maybeChat.presentationInterfaceState.subject {
|
||||
isChatPinnedMessages = true
|
||||
}
|
||||
if !isChatPinnedMessages {
|
||||
maybeChat.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }) })
|
||||
selfController.dismiss()
|
||||
strongController.dismiss()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (ChatInterfaceState.update(engine: selfController.context.engine, peerId: peerId, threadId: threadId, { currentState in
|
||||
return currentState.withUpdatedReplyMessageSubject(replySubject)
|
||||
})
|
||||
|> deliverOnMainQueue).startStandalone(completed: { [weak selfController] in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
let proceed: (ChatController) -> Void = { [weak selfController] chatController in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withoutSelectionState() }) })
|
||||
|
||||
let navigationController: NavigationController?
|
||||
if let parentController = selfController.parentController {
|
||||
navigationController = (parentController.navigationController as? NavigationController)
|
||||
} else {
|
||||
navigationController = selfController.effectiveNavigationController
|
||||
}
|
||||
|
||||
if let navigationController = navigationController {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
if threadId != nil {
|
||||
viewControllers.insert(chatController, at: viewControllers.count - 2)
|
||||
} else {
|
||||
viewControllers.insert(chatController, at: viewControllers.count - 1)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
|
||||
selfController.controllerNavigationDisposable.set((chatController.ready.get()
|
||||
|> SwiftSignalKit.filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak navigationController] _ in
|
||||
viewControllers.removeAll(where: { $0 is PeerSelectionController })
|
||||
navigationController?.setViewControllers(viewControllers, animated: true)
|
||||
}))
|
||||
}
|
||||
}
|
||||
if let threadId = threadId {
|
||||
let _ = (selfController.context.sharedContext.chatControllerForForumThread(context: selfController.context, peerId: peerId, threadId: threadId)
|
||||
|> deliverOnMainQueue).startStandalone(next: { chatController in
|
||||
proceed(chatController)
|
||||
})
|
||||
} else {
|
||||
proceed(ChatControllerImpl(context: selfController.context, chatLocation: .peer(id: peerId)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
selfController.chatDisplayNode.dismissInput()
|
||||
selfController.effectiveNavigationController?.pushViewController(controller)
|
||||
})
|
||||
}
|
||||
|
@ -105,6 +105,7 @@ import PeerSelectionController
|
||||
import SaveToCameraRoll
|
||||
import ChatMessageDateAndStatusNode
|
||||
import ReplyAccessoryPanelNode
|
||||
import TextSelectionNode
|
||||
|
||||
public enum ChatControllerPeekActions {
|
||||
case standard
|
||||
@ -543,6 +544,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var avatarNode: ChatAvatarNavigationNode?
|
||||
var storyStats: PeerStoryStats?
|
||||
|
||||
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
|
||||
|
||||
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) {
|
||||
let _ = ChatControllerCount.modify { value in
|
||||
return value + 1
|
||||
@ -3860,6 +3863,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let performTextSelectionAction = strongSelf.performTextSelectionAction {
|
||||
performTextSelectionAction(message, canCopy, text, action)
|
||||
return
|
||||
}
|
||||
|
||||
switch action {
|
||||
case .copy:
|
||||
storeAttributedTextInPasteboard(text)
|
||||
@ -4575,7 +4584,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else {
|
||||
var subject: ChatControllerSubject?
|
||||
if let messageId = messageId {
|
||||
subject = .message(id: .id(messageId), highlight: true, timecode: nil)
|
||||
subject = .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil)
|
||||
}
|
||||
navigationData = .chat(textInputState: nil, subject: subject, peekData: nil)
|
||||
}
|
||||
@ -5200,8 +5209,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else if case let .messageOptions(peerIds, messageIds, info, options) = subject {
|
||||
let _ = info
|
||||
|
||||
displayedCountSignal = self.presentationInterfaceStatePromise.get()
|
||||
|> map { state -> Int? in
|
||||
if let selectionState = state.interfaceState.selectionState {
|
||||
@ -5216,73 +5223,80 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|> take(1)
|
||||
|
||||
let presentationData = self.presentationData
|
||||
subtitleTextSignal = combineLatest(peers, options, displayedCountSignal)
|
||||
|> map { peersView, options, count in
|
||||
let peers = peersView.peers.values
|
||||
if !peers.isEmpty {
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
if let peer = peer as? TelegramUser {
|
||||
let displayName = EnginePeer(peer).compactDisplayTitle
|
||||
if count == 1 {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_UserMessageForwardHidden(displayName).string
|
||||
|
||||
switch info.kind {
|
||||
case .forward:
|
||||
subtitleTextSignal = combineLatest(peers, options, displayedCountSignal)
|
||||
|> map { peersView, options, count in
|
||||
let peers = peersView.peers.values
|
||||
if !peers.isEmpty {
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
if let peer = peer as? TelegramUser {
|
||||
let displayName = EnginePeer(peer).compactDisplayTitle
|
||||
if count == 1 {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_UserMessageForwardHidden(displayName).string
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_UserMessageForwardVisible(displayName).string
|
||||
}
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_UserMessageForwardVisible(displayName).string
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_UserMessagesForwardHidden(displayName).string
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_UserMessagesForwardVisible(displayName).string
|
||||
}
|
||||
}
|
||||
} else if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
if count == 1 {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_ChannelMessageForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_ChannelMessageForwardVisible
|
||||
}
|
||||
} else {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_ChannelMessagesForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_ChannelMessagesForwardVisible
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_UserMessagesForwardHidden(displayName).string
|
||||
if count == 1 {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_GroupMessageForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_GroupMessageForwardVisible
|
||||
}
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_UserMessagesForwardVisible(displayName).string
|
||||
}
|
||||
}
|
||||
} else if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
if count == 1 {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_ChannelMessageForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_ChannelMessageForwardVisible
|
||||
}
|
||||
} else {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_ChannelMessagesForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_ChannelMessagesForwardVisible
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_GroupMessagesForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_GroupMessagesForwardVisible
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if count == 1 {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_GroupMessageForwardHidden
|
||||
return presentationData.strings.Conversation_ForwardOptions_RecipientsMessageForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_GroupMessageForwardVisible
|
||||
return presentationData.strings.Conversation_ForwardOptions_RecipientsMessageForwardVisible
|
||||
}
|
||||
} else {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_GroupMessagesForwardHidden
|
||||
return presentationData.strings.Conversation_ForwardOptions_RecipientsMessagesForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_GroupMessagesForwardVisible
|
||||
return presentationData.strings.Conversation_ForwardOptions_RecipientsMessagesForwardVisible
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if count == 1 {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_RecipientsMessageForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_RecipientsMessageForwardVisible
|
||||
}
|
||||
} else {
|
||||
if options.hideNames {
|
||||
return presentationData.strings.Conversation_ForwardOptions_RecipientsMessagesForwardHidden
|
||||
} else {
|
||||
return presentationData.strings.Conversation_ForwardOptions_RecipientsMessagesForwardVisible
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .reply:
|
||||
//TODO:localize
|
||||
subtitleTextSignal = .single("You can select a specific part to quote")
|
||||
}
|
||||
}
|
||||
|
||||
@ -5305,8 +5319,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
if case .messageOptions = presentationInterfaceState.subject {
|
||||
if displayedCount == 1 {
|
||||
if case let .messageOptions(_, _, info, _) = presentationInterfaceState.subject {
|
||||
if case let .reply(initialQuote) = info.kind {
|
||||
//TODO:localize
|
||||
if initialQuote != nil {
|
||||
strongSelf.chatTitleView?.titleContent = .custom("Reply to Quote", subtitleText, false)
|
||||
} else {
|
||||
strongSelf.chatTitleView?.titleContent = .custom("Reply to Message", subtitleText, false)
|
||||
}
|
||||
} else if displayedCount == 1 {
|
||||
strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false)
|
||||
} else {
|
||||
strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1)), subtitleText, false)
|
||||
@ -10508,7 +10529,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if let navigationController = strongSelf.effectiveNavigationController {
|
||||
let subject: ChatControllerSubject? = sourceMessageId.flatMap { ChatControllerSubject.message(id: .id($0), highlight: true, timecode: nil) }
|
||||
let subject: ChatControllerSubject? = sourceMessageId.flatMap { ChatControllerSubject.message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) }
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadResult), subject: subject, keepStack: .always))
|
||||
}
|
||||
}, activatePinnedListPreview: { [weak self] node, gesture in
|
||||
@ -11902,6 +11923,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
||||
}
|
||||
|
||||
override public func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
|
||||
switch self.presentationInterfaceState.mode {
|
||||
case let .standard(previewing):
|
||||
if previewing {
|
||||
if let subject = self.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind {
|
||||
return self.chatDisplayNode.preferredContentSizeForLayout(layout)
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.suspendNavigationBarLayout = true
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
@ -16412,7 +16447,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})))
|
||||
}
|
||||
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .message(id: .timestamp(timestamp), highlight: false, timecode: nil), botStart: nil, mode: .standard(previewing: true))
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .message(id: .timestamp(timestamp), highlight: nil, timecode: nil), botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
||||
@ -16545,9 +16580,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let subject: ChatControllerSubject?
|
||||
if let atMessageId = atMessageId {
|
||||
subject = .message(id: .id(atMessageId), highlight: true, timecode: nil)
|
||||
subject = .message(id: .id(atMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil)
|
||||
} else if let index = result.scrollToLowerBoundMessage {
|
||||
subject = .message(id: .id(index.id), highlight: false, timecode: nil)
|
||||
subject = .message(id: .id(index.id), highlight: nil, timecode: nil)
|
||||
} else {
|
||||
subject = nil
|
||||
}
|
||||
@ -16628,7 +16663,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else {
|
||||
navigateToLocation = .peer(peer)
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: navigateToLocation, subject: .message(id: .id(messageId), highlight: true, timecode: nil), keepStack: .always))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: navigateToLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), keepStack: .always))
|
||||
})
|
||||
} else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) {
|
||||
let _ = (self.context.engine.data.get(
|
||||
@ -16645,7 +16680,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
chatLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
|
||||
}
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: true, timecode: nil), keepStack: .always))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), keepStack: .always))
|
||||
}
|
||||
})
|
||||
} else if forceInCurrentChat {
|
||||
@ -16833,7 +16868,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if let navigationController = strongSelf.effectiveNavigationController {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) }))
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) }))
|
||||
}
|
||||
})
|
||||
completion?()
|
||||
@ -16851,7 +16886,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
if let navigationController = self.effectiveNavigationController {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) }))
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) }))
|
||||
}
|
||||
completion?()
|
||||
})
|
||||
|
@ -445,37 +445,42 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
var messageText = message.text
|
||||
var messageMedia = message.media
|
||||
var hasDice = false
|
||||
if hideNames {
|
||||
for media in message.media {
|
||||
if options.hideCaptions {
|
||||
if media is TelegramMediaImage || media is TelegramMediaFile {
|
||||
messageText = ""
|
||||
break
|
||||
|
||||
if case .forward = info.kind {
|
||||
if hideNames {
|
||||
for media in message.media {
|
||||
if options.hideCaptions {
|
||||
if media is TelegramMediaImage || media is TelegramMediaFile {
|
||||
messageText = ""
|
||||
break
|
||||
}
|
||||
}
|
||||
if let poll = media as? TelegramMediaPoll {
|
||||
var updatedMedia = message.media.filter { !($0 is TelegramMediaPoll) }
|
||||
updatedMedia.append(TelegramMediaPoll(pollId: poll.pollId, publicity: poll.publicity, kind: poll.kind, text: poll.text, options: poll.options, correctAnswers: poll.correctAnswers, results: TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil), isClosed: false, deadlineTimeout: nil))
|
||||
messageMedia = updatedMedia
|
||||
}
|
||||
if let _ = media as? TelegramMediaDice {
|
||||
hasDice = true
|
||||
}
|
||||
}
|
||||
if let poll = media as? TelegramMediaPoll {
|
||||
var updatedMedia = message.media.filter { !($0 is TelegramMediaPoll) }
|
||||
updatedMedia.append(TelegramMediaPoll(pollId: poll.pollId, publicity: poll.publicity, kind: poll.kind, text: poll.text, options: poll.options, correctAnswers: poll.correctAnswers, results: TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil), isClosed: false, deadlineTimeout: nil))
|
||||
messageMedia = updatedMedia
|
||||
}
|
||||
if let _ = media as? TelegramMediaDice {
|
||||
hasDice = true
|
||||
}
|
||||
}
|
||||
|
||||
var forwardInfo: MessageForwardInfo?
|
||||
if let existingForwardInfo = message.forwardInfo {
|
||||
forwardInfo = MessageForwardInfo(author: existingForwardInfo.author, source: existingForwardInfo.source, sourceMessageId: nil, date: 0, authorSignature: nil, psaType: nil, flags: [])
|
||||
}
|
||||
else {
|
||||
forwardInfo = MessageForwardInfo(author: message.author, source: nil, sourceMessageId: nil, date: 0, authorSignature: nil, psaType: nil, flags: [])
|
||||
}
|
||||
if hideNames && !hasDice {
|
||||
forwardInfo = nil
|
||||
}
|
||||
|
||||
return message.withUpdatedFlags(flags).withUpdatedText(messageText).withUpdatedMedia(messageMedia).withUpdatedTimestamp(Int32(context.account.network.context.globalTime())).withUpdatedAttributes(attributes).withUpdatedAuthor(accountPeer).withUpdatedForwardInfo(forwardInfo)
|
||||
} else {
|
||||
return message
|
||||
}
|
||||
|
||||
var forwardInfo: MessageForwardInfo?
|
||||
if let existingForwardInfo = message.forwardInfo {
|
||||
forwardInfo = MessageForwardInfo(author: existingForwardInfo.author, source: existingForwardInfo.source, sourceMessageId: nil, date: 0, authorSignature: nil, psaType: nil, flags: [])
|
||||
}
|
||||
else {
|
||||
forwardInfo = MessageForwardInfo(author: message.author, source: nil, sourceMessageId: nil, date: 0, authorSignature: nil, psaType: nil, flags: [])
|
||||
}
|
||||
if hideNames && !hasDice {
|
||||
forwardInfo = nil
|
||||
}
|
||||
|
||||
return message.withUpdatedFlags(flags).withUpdatedText(messageText).withUpdatedMedia(messageMedia).withUpdatedTimestamp(Int32(context.account.network.context.globalTime())).withUpdatedAttributes(attributes).withUpdatedAuthor(accountPeer).withUpdatedForwardInfo(forwardInfo)
|
||||
}
|
||||
|
||||
return (messages, Int32(messages.count), false)
|
||||
@ -891,6 +896,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
|
||||
var height = self.historyNode.scroller.contentSize.height
|
||||
height += 3.0
|
||||
height = min(height, layout.size.height)
|
||||
return CGSize(width: layout.size.width, height: height)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, CGFloat, ContainedViewLayoutTransition) -> Void) {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if let _ = self.scheduledAnimateInAsOverlayFromNode {
|
||||
|
@ -814,7 +814,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
initialSearchLocation = .index(MessageIndex.absoluteUpperBound())
|
||||
}
|
||||
}
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: historyMessageCount, highlight: highlight), id: 0)
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: historyMessageCount, highlight: highlight != nil), id: 0)
|
||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: historyMessageCount, highlight: true), id: 0)
|
||||
} else {
|
||||
@ -1372,7 +1372,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
initialSearchLocation = .index(.absoluteUpperBound())
|
||||
}
|
||||
}
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: historyMessageCount, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: historyMessageCount, highlight: highlight != nil), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: historyMessageCount, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
} else if var chatHistoryLocation = strongSelf.chatHistoryLocationValue {
|
||||
|
@ -73,10 +73,12 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS
|
||||
replyPanelNode.interfaceInteraction = interfaceInteraction
|
||||
replyPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||
return replyPanelNode
|
||||
} else {
|
||||
let panelNode = ReplyAccessoryPanelNode(context: context, messageId: replyMessageSubject.messageId, quote: replyMessageSubject.quote, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, dateTimeFormat: chatPresentationInterfaceState.dateTimeFormat, animationCache: chatControllerInteraction?.presentationContext.animationCache, animationRenderer: chatControllerInteraction?.presentationContext.animationRenderer)
|
||||
} else if let peerId = chatPresentationInterfaceState.chatLocation.peerId {
|
||||
let panelNode = ReplyAccessoryPanelNode(context: context, chatPeerId: peerId, messageId: replyMessageSubject.messageId, quote: replyMessageSubject.quote, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, dateTimeFormat: chatPresentationInterfaceState.dateTimeFormat, animationCache: chatControllerInteraction?.presentationContext.animationCache, animationRenderer: chatControllerInteraction?.presentationContext.animationRenderer)
|
||||
panelNode.interfaceInteraction = interfaceInteraction
|
||||
return panelNode
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
|
@ -1536,7 +1536,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
guard let peer = messages[0].peers[messages[0].id.peerId] else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(EnginePeer(peer)), subject: .message(id: .id(messages[0].id), highlight: true, timecode: nil), useExisting: true))
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(EnginePeer(peer)), subject: .message(id: .id(messages[0].id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), useExisting: true))
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
@ -1237,6 +1237,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
var replyMessage: Message?
|
||||
var replyForward: QuotedReplyMessageAttribute?
|
||||
var replyQuote: EngineMessageReplyQuote?
|
||||
var replyStory: StoryId?
|
||||
for attribute in item.message.attributes {
|
||||
@ -1265,6 +1266,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
|
||||
}
|
||||
replyQuote = replyAttribute.quote
|
||||
} else if let quoteReplyAttribute = attribute as? QuotedReplyMessageAttribute {
|
||||
replyForward = quoteReplyAttribute
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyStory = attribute.storyId
|
||||
} else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty {
|
||||
@ -1272,7 +1275,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
var hasReply = replyMessage != nil || replyStory != nil
|
||||
var hasReply = replyMessage != nil || replyForward != nil || replyStory != nil
|
||||
if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil {
|
||||
if let threadId = item.message.threadId, let replyMessage = replyMessage, Int64(replyMessage.id.id) == threadId {
|
||||
hasReply = false
|
||||
@ -1292,13 +1295,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
))
|
||||
}
|
||||
|
||||
if hasReply, (replyMessage != nil || replyStory != nil) {
|
||||
if hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
replyForward: replyForward,
|
||||
quote: replyQuote,
|
||||
story: replyStory,
|
||||
parentMessage: item.message,
|
||||
|
@ -1525,6 +1525,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
var inlineBotNameString: String?
|
||||
var replyMessage: Message?
|
||||
var replyForward: QuotedReplyMessageAttribute?
|
||||
var replyQuote: EngineMessageReplyQuote?
|
||||
var replyStory: StoryId?
|
||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||
@ -1543,6 +1544,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
replyMessage = firstMessage.associatedMessages[attribute.messageId]
|
||||
}
|
||||
replyQuote = attribute.quote
|
||||
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
|
||||
replyForward = attribute
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyStory = attribute.storyId
|
||||
} else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty && !isPreview {
|
||||
@ -1778,7 +1781,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
hasForwardLikeContent = true
|
||||
}
|
||||
|
||||
if inlineBotNameString == nil && (ignoreForward || !hasForwardLikeContent) && replyMessage == nil && replyStory == nil {
|
||||
if inlineBotNameString == nil && (ignoreForward || !hasForwardLikeContent) && replyMessage == nil && replyForward == nil && replyStory == nil {
|
||||
if let first = contentPropertiesAndLayouts.first, first.1.hidesSimpleAuthorHeader && !ignoreNameHiding {
|
||||
if let author = firstMessage.author as? TelegramChannel, case .group = author.info, author.id == firstMessage.id.peerId, !incoming {
|
||||
} else {
|
||||
@ -1848,7 +1851,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if firstMessage.media.contains(where: { $0 is TelegramMediaStory }) {
|
||||
displayHeader = true
|
||||
}
|
||||
if replyMessage != nil || replyStory != nil {
|
||||
if replyMessage != nil || replyForward != nil || replyStory != nil {
|
||||
displayHeader = true
|
||||
}
|
||||
if !displayHeader, case .peer = item.chatLocation, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil {
|
||||
@ -2115,7 +2118,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
|
||||
var hasReply = replyMessage != nil || replyStory != nil
|
||||
var hasReply = replyMessage != nil || replyForward != nil || replyStory != nil
|
||||
if !isInstantVideo, case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil {
|
||||
if let threadId = item.message.threadId, let replyMessage = replyMessage, Int64(replyMessage.id.id) == threadId {
|
||||
hasReply = false
|
||||
@ -2147,7 +2150,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
|
||||
if !isInstantVideo, hasReply, (replyMessage != nil || replyStory != nil) {
|
||||
if !isInstantVideo, hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) {
|
||||
if headerSize.height.isZero {
|
||||
headerSize.height += 10.0
|
||||
} else {
|
||||
@ -2159,6 +2162,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
context: item.context,
|
||||
type: .bubble(incoming: incoming),
|
||||
message: replyMessage,
|
||||
replyForward: replyForward,
|
||||
quote: replyQuote,
|
||||
story: replyStory,
|
||||
parentMessage: item.message,
|
||||
|
@ -444,6 +444,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
|
||||
var replyMessage: Message?
|
||||
var replyForward: QuotedReplyMessageAttribute?
|
||||
var replyQuote: EngineMessageReplyQuote?
|
||||
var replyStory: StoryId?
|
||||
for attribute in item.message.attributes {
|
||||
@ -488,6 +489,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
|
||||
}
|
||||
replyQuote = replyAttribute.quote
|
||||
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
|
||||
replyForward = attribute
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyStory = attribute.storyId
|
||||
} else if let _ = attribute as? InlineBotMessageAttribute {
|
||||
@ -496,13 +499,14 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
}
|
||||
|
||||
if replyMessage != nil || replyStory != nil {
|
||||
if replyMessage != nil || replyForward != nil || replyStory != nil {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
replyForward: replyForward,
|
||||
quote: replyQuote,
|
||||
story: replyStory,
|
||||
parentMessage: item.message,
|
||||
|
@ -317,6 +317,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
|
||||
if !ignoreHeaders {
|
||||
var replyMessage: Message?
|
||||
var replyForward: QuotedReplyMessageAttribute?
|
||||
var replyQuote: EngineMessageReplyQuote?
|
||||
var replyStory: StoryId?
|
||||
|
||||
@ -348,12 +349,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
|
||||
}
|
||||
replyQuote = replyAttribute.quote
|
||||
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
|
||||
replyForward = attribute
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyStory = attribute.storyId
|
||||
}
|
||||
}
|
||||
|
||||
if replyMessage != nil || replyStory != nil {
|
||||
if replyMessage != nil || replyForward != nil || replyStory != nil {
|
||||
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyMessage?.id {
|
||||
} else {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
@ -362,6 +365,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
context: item.context,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
replyForward: replyForward,
|
||||
quote: replyQuote,
|
||||
story: replyStory,
|
||||
parentMessage: item.message,
|
||||
|
@ -509,7 +509,13 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
|
||||
let nodeLayout = node.asyncLayout()
|
||||
let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem)
|
||||
let (layout, apply) = nodeLayout(self, params, top, bottom, dateAtBottom && !self.disableDate)
|
||||
|
||||
var disableDate = self.disableDate
|
||||
if let subject = self.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind {
|
||||
disableDate = true
|
||||
}
|
||||
|
||||
let (layout, apply) = nodeLayout(self, params, top, bottom, dateAtBottom && !disableDate)
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
@ -639,6 +639,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
var replyMessage: Message?
|
||||
var replyForward: QuotedReplyMessageAttribute?
|
||||
var replyQuote: EngineMessageReplyQuote?
|
||||
var replyStory: StoryId?
|
||||
for attribute in item.message.attributes {
|
||||
@ -668,6 +669,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
|
||||
}
|
||||
replyQuote = replyAttribute.quote
|
||||
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
|
||||
replyForward = attribute
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
replyStory = attribute.storyId
|
||||
} else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty {
|
||||
@ -675,7 +678,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
var hasReply = replyMessage != nil || replyStory != nil
|
||||
var hasReply = replyMessage != nil || replyForward != nil || replyStory != nil
|
||||
if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil {
|
||||
if let threadId = item.message.threadId, let replyMessage = replyMessage, Int64(replyMessage.id.id) == threadId {
|
||||
hasReply = false
|
||||
@ -695,13 +698,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
))
|
||||
}
|
||||
|
||||
if hasReply, (replyMessage != nil || replyStory != nil) {
|
||||
if hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
replyForward: replyForward,
|
||||
quote: replyQuote,
|
||||
story: replyStory,
|
||||
parentMessage: item.message,
|
||||
|
@ -983,11 +983,11 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
break
|
||||
case let .channelMessage(peer, messageId, timecode):
|
||||
if let navigationController = strongSelf.getNavigationController() {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), subject: .message(id: .id(messageId), highlight: true, timecode: timecode)))
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: timecode)))
|
||||
}
|
||||
case let .replyThreadMessage(replyThreadMessage, messageId):
|
||||
if let navigationController = strongSelf.getNavigationController() {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadMessage), subject: .message(id: .id(messageId), highlight: true, timecode: nil)))
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadMessage), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil)))
|
||||
}
|
||||
case let .replyThread(messageId):
|
||||
if let navigationController = strongSelf.getNavigationController() {
|
||||
|
@ -251,7 +251,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
switch item.content {
|
||||
case let .peer(peerData):
|
||||
if let message = peerData.messages.first {
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerData.peer.peerId), subject: .message(id: .id(message.id), highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true))
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerData.peer.peerId), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list([]))), gesture: gesture)
|
||||
presentInGlobalOverlay(contextController)
|
||||
|
@ -3732,7 +3732,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
] as [UIAction])
|
||||
|
||||
let formatMenu = UIMenu(title: self.strings?.TextFormat_Format ?? "Format", image: nil, children: children)
|
||||
actions.insert(formatMenu, at: 3)
|
||||
actions.insert(formatMenu, at: 2)
|
||||
}
|
||||
return UIMenu(children: actions)
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePee
|
||||
context: context,
|
||||
chatLocation: .replyThread(result.message),
|
||||
chatLocationContextHolder: result.contextHolder,
|
||||
subject: messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) },
|
||||
subject: messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) },
|
||||
activateInput: actualActivateInput,
|
||||
keepStack: keepStack
|
||||
)
|
||||
|
@ -187,7 +187,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
dismissInput()
|
||||
navigationController?.pushViewController(controller)
|
||||
case let .channelMessage(peer, messageId, timecode):
|
||||
openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil))
|
||||
openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: timecode), peekData: nil))
|
||||
case let .replyThreadMessage(replyThreadMessage, messageId):
|
||||
if let navigationController = navigationController {
|
||||
let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in
|
||||
@ -979,7 +979,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
openPeer(peer, .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: nil), peekData: nil))
|
||||
openPeer(peer, .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), peekData: nil))
|
||||
})
|
||||
},
|
||||
shareLink: { link in
|
||||
|
@ -210,7 +210,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
self.isGlobalSearch = false
|
||||
}
|
||||
|
||||
self.historyNode = ChatHistoryListNode(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, source: source, subject: .message(id: .id(initialMessageId), highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
|
||||
self.historyNode = ChatHistoryListNode(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, source: source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
|
||||
self.historyNode.clipsToBounds = true
|
||||
|
||||
super.init()
|
||||
@ -552,7 +552,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}
|
||||
|
||||
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
||||
let historyNode = ChatHistoryListNode(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(id: .id(messageId), highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
|
||||
let historyNode = ChatHistoryListNode(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
|
||||
historyNode.clipsToBounds = true
|
||||
historyNode.preloadPages = true
|
||||
historyNode.stackFromBottom = true
|
||||
|
@ -2442,7 +2442,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
|
||||
let currentPeerId = strongSelf.peerId
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: targetLocation, subject: .message(id: .id(message.id), highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: targetLocation, subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
var indexesToRemove = Set<Int>()
|
||||
var keptCurrentChatController = false
|
||||
@ -2604,7 +2604,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
|
||||
let currentPeerId = strongSelf.peerId
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: targetLocation, subject: .message(id: .id(message.id), highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: targetLocation, subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
var indexesToRemove = Set<Int>()
|
||||
var keptCurrentChatController = false
|
||||
@ -9333,7 +9333,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
chatController: nil,
|
||||
context: strongSelf.context,
|
||||
chatLocation: .peer(EnginePeer(peer)),
|
||||
subject: .message(id: .id(index.id), highlight: false, timecode: nil),
|
||||
subject: .message(id: .id(index.id), highlight: nil, timecode: nil),
|
||||
botStart: nil,
|
||||
updateTextInputState: nil,
|
||||
keepStack: .never,
|
||||
@ -9354,7 +9354,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
))
|
||||
})))
|
||||
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: strongSelf.peerId), subject: .message(id: .id(index.id), highlight: false, timecode: nil), botStart: nil, mode: .standard(previewing: true))
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: strongSelf.peerId), subject: .message(id: .id(index.id), highlight: nil, timecode: nil), botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||
|
@ -69,7 +69,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n
|
||||
openResolvedPeerImpl(EnginePeer(peer), .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)))
|
||||
case let .channelMessage(peer, messageId, timecode):
|
||||
if let navigationController = controller.navigationController as? NavigationController {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(EnginePeer(peer)), subject: .message(id: .id(messageId), highlight: true, timecode: timecode)))
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(EnginePeer(peer)), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: timecode)))
|
||||
}
|
||||
case let .replyThreadMessage(replyThreadMessage, messageId):
|
||||
if let navigationController = controller.navigationController as? NavigationController {
|
||||
|
@ -235,13 +235,18 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
public private(set) var recognizer: TextSelectionGestureRecognizer?
|
||||
private var displayLinkAnimator: DisplayLinkAnimator?
|
||||
|
||||
public var enableCopy: Bool = true
|
||||
public var enableLookup: Bool = true
|
||||
public var enableQuote: Bool = false
|
||||
public var enableTranslate: Bool = true
|
||||
public var enableShare: Bool = true
|
||||
|
||||
public var didRecognizeTap: Bool {
|
||||
return self.recognizer?.didRecognizeTap ?? false
|
||||
}
|
||||
|
||||
private weak var contextMenu: ContextMenuController?
|
||||
|
||||
public init(theme: TextSelectionTheme, strings: PresentationStrings, textNode: TextNode, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: @escaping () -> ASDisplayNode?, externalKnobSurface: UIView? = nil, performAction: @escaping (NSAttributedString, TextSelectionAction) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@ -438,6 +443,24 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
self.displayLinkAnimator = displayLinkAnimator
|
||||
}
|
||||
|
||||
public func setSelection(range: NSRange, displayMenu: Bool) {
|
||||
self.currentRange = (range.lowerBound, range.upperBound)
|
||||
self.updateSelection(range: range, animateIn: true)
|
||||
self.updateIsActive(true)
|
||||
|
||||
if displayMenu {
|
||||
self.displayMenu()
|
||||
}
|
||||
}
|
||||
|
||||
public func getSelection() -> NSRange? {
|
||||
guard let currentRange = self.currentRange else {
|
||||
return nil
|
||||
}
|
||||
let range = NSRange(location: min(currentRange.0, currentRange.1), length: max(currentRange.0, currentRange.1) - min(currentRange.0, currentRange.1))
|
||||
return range
|
||||
}
|
||||
|
||||
private func updateSelection(range: NSRange?, animateIn: Bool) {
|
||||
self.updateRange?(range)
|
||||
|
||||
@ -570,10 +593,12 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var actions: [ContextMenuAction] = []
|
||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in
|
||||
self?.performAction(string, .copy)
|
||||
self?.cancelSelection()
|
||||
}))
|
||||
if self.enableCopy {
|
||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in
|
||||
self?.performAction(string, .copy)
|
||||
self?.cancelSelection()
|
||||
}))
|
||||
}
|
||||
if self.enableQuote {
|
||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuQuote, accessibilityLabel: self.strings.Conversation_ContextMenuQuote), action: { [weak self] in
|
||||
self?.performAction(string, .quote(range: range.lowerBound ..< range.upperBound))
|
||||
@ -586,10 +611,12 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
}))
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
||||
self?.performAction(string, .translate)
|
||||
self?.cancelSelection()
|
||||
}))
|
||||
if self.enableTranslate {
|
||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
||||
self?.performAction(string, .translate)
|
||||
self?.cancelSelection()
|
||||
}))
|
||||
}
|
||||
}
|
||||
// if isSpeakSelectionEnabled() {
|
||||
// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
|
||||
@ -597,16 +624,41 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
// self?.dismissSelection()
|
||||
// }))
|
||||
// }
|
||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in
|
||||
self?.performAction(string, .share)
|
||||
self?.cancelSelection()
|
||||
}))
|
||||
|
||||
self.present(ContextMenuController(actions: actions, catchTapsOutside: false, hasHapticFeedback: false), ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
||||
let realFullRange = NSRange(location: 0, length: attributedString.length)
|
||||
if range != realFullRange {
|
||||
//TODO:localize
|
||||
actions.append(ContextMenuAction(content: .text(title: "Select All", accessibilityLabel: "Select All"), action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.contextMenu?.dismiss()
|
||||
self.setSelection(range: realFullRange, displayMenu: true)
|
||||
}))
|
||||
} else if self.enableShare {
|
||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in
|
||||
self?.performAction(string, .share)
|
||||
self?.cancelSelection()
|
||||
}))
|
||||
}
|
||||
|
||||
let contextMenu = ContextMenuController(actions: actions, catchTapsOutside: false, hasHapticFeedback: false)
|
||||
contextMenu.dismissOnTap = { [weak self] view, point in
|
||||
guard let self else {
|
||||
return true
|
||||
}
|
||||
if self.knobAtPoint(view.convert(point, to: self.view)) == nil {
|
||||
//self.cancelSelection()
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
self.contextMenu = contextMenu
|
||||
self.present(contextMenu, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
||||
guard let strongSelf = self, let rootNode = strongSelf.rootNode() else {
|
||||
return nil
|
||||
}
|
||||
return (strongSelf, completeRect, rootNode, rootNode.bounds)
|
||||
return (strongSelf, completeRect, rootNode, rootNode.bounds.insetBy(dx: 0.0, dy: -100.0))
|
||||
}, bounce: false))
|
||||
}
|
||||
|
||||
|
@ -831,7 +831,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
||||
return .replyThreadMessage(replyThreadMessage: result, messageId: messageId)
|
||||
}
|
||||
} else {
|
||||
return .single(.peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil)))
|
||||
return .single(.peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: timecode), peekData: nil)))
|
||||
}
|
||||
} else {
|
||||
return .single(.inaccessiblePeer)
|
||||
|
Loading…
x
Reference in New Issue
Block a user