Files
Mikhail Filimonov ca33a64a97 folding quotes
2024-05-28 17:57:16 +04:00

450 lines
16 KiB
Swift

//
// ChatMessageView.swift
// Telegram-Mac
//
// Created by keepcoder on 08/09/16.
// Copyright © 2016 Telegram. All rights reserved.
//
import Cocoa
import TGUIKit
import SwiftSignalKit
import TelegramCore
import Postbox
class ChatMessageView: ChatRowView, ModalPreviewRowViewProtocol {
class ActionButton: TextButton {
var urlView: ImageView?
required init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func set(isExternalUrl: Bool, color: NSColor) {
if isExternalUrl {
let current: ImageView
if let view = self.urlView {
current = view
} else {
current = ImageView()
addSubview(current)
self.urlView = current
}
current.image = NSImage.init(named: "Icon_InlineBotUrl")?.precomposed(color)
current.sizeToFit()
} else if let view = self.urlView {
view.removeFromSuperview()
self.urlView = nil
}
needsLayout = true
}
override func layout() {
super.layout()
if let view = urlView {
view.setFrameOrigin(NSMakePoint(frame.width - view.frame.width - 5, 5))
}
}
}
class AdSettingsView : View {
private let close: ImageButton = ImageButton()
private var more: ImageButton?
private let separator = View()
private weak var item: ChatMessageItem?
required init(frame frameRect: NSRect) {
super.init(frame: frameRect)
addSubview(close)
addSubview(separator)
close.scaleOnClick = true
close.autohighlight = false
close.set(handler: { [weak self] _ in
if let item = self?.item {
item.webpageLayout?.premiumBoarding()
}
}, for: .Click)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layout() {
super.layout()
close.setFrameOrigin(.zero)
separator.frame = NSMakeRect(6, close.frame.maxY + 3, frame.width - 12, .borderSize)
more?.setFrameOrigin(NSMakePoint(0, close.frame.maxY + 6))
}
func update(_ item: ChatMessageItem, animated: Bool) {
self.item = item
backgroundColor = theme.colors.background
separator.backgroundColor = theme.colors.grayIcon.withAlphaComponent(0.4)
close.set(image: NSImage(resource: .iconAdHide).precomposed(theme.colors.grayIcon), for: .Normal)
close.sizeToFit(.zero, NSMakeSize(30, 30), thatFit: true)
separator.isHidden = !item.isFragmentAd
if item.isFragmentAd {
let current: ImageButton
if let view = self.more {
current = view
} else {
current = ImageButton(frame: NSMakeRect(0, 30, 30, 30))
current.scaleOnClick = true
current.autohighlight = false
self.more = current
addSubview(current)
}
current.removeAllHandlers()
current.set(handler: { [weak item] control in
if let item = item, let event = NSApp.currentEvent {
_ = item.menuItems(in: .zero).startStandalone(next: { [weak control] items in
if let control = control {
let menu = ContextMenu()
menu.items = items
AppMenu.show(menu: menu, event: event, for: control)
}
})
}
}, for: .Down)
current.set(image: NSImage(resource: .iconAdMore).precomposed(theme.colors.grayIcon), for: .Normal)
current.sizeToFit(.zero, NSMakeSize(30, 30), thatFit: true)
} else if let view = self.more {
performSubviewRemoval(view, animated: animated)
self.more = nil
}
}
}
func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? {
if let webpageContent = webpageContent {
return webpageContent.fileAtPoint(convert(point, from: self))
}
return nil
}
override func forceClick(in location: NSPoint) {
if previewMediaIfPossible() {
} else {
super.forceClick(in: location)
}
}
override func previewMediaIfPossible() -> Bool {
return webpageContent?.previewMediaIfPossible() ?? false
}
private var text:FoldingTextView?
private(set) var webpageContent:WPContentView?
private var actionButton: ActionButton?
private var adSettingView: AdSettingsView?
override func draw(_ dirtyRect: NSRect) {
// Drawing code here.
}
required init(frame frameRect: NSRect) {
super.init(frame: frameRect)
// self.layerContentsRedrawPolicy = .never
}
override func layout() {
super.layout()
}
func webpageFrame(_ item: ChatMessageItem) -> NSRect {
guard let item = self.item as? ChatMessageItem else {
return .zero
}
let maxY: CGFloat
if item.webpageAboveContent {
maxY = 1
} else {
maxY = textFrame(item).maxY + item.defaultContentInnerInset
}
if let webpageLayout = item.webpageLayout {
let size = webpageLayout.size
return CGRect(origin: NSMakePoint(0, maxY), size: size)
}
return .zero
}
func textFrame(_ item: ChatMessageItem) -> NSRect {
guard let item = self.item as? ChatMessageItem else {
return .zero
}
let maxY: CGFloat
if item.webpageAboveContent, item.webpageLayout != nil {
maxY = webpageFrame(item).maxY + item.defaultContentInnerInset - 2
} else {
maxY = 0
}
return CGRect(origin: NSMakePoint(0, maxY), size: item.textLayout.size)
}
func adSettingFrame(_ item: ChatMessageItem) -> NSRect {
let webpage = webpageFrame(item)
var rect = NSMakeRect(webpage.maxX + contentFrame(item).minX + 10, contentFrame(item).minY + webpage.minY, 30, item.isFragmentAd ? 69 : 30)
if item.isBubbled {
rect.origin.x += 10
rect.origin.y = bubbleFrame(item).minY
}
return rect
}
override func updateLayout(size: NSSize, transition: ContainedViewLayoutTransition) {
super.updateLayout(size: size, transition: transition)
guard let item = self.item as? ChatMessageItem else {
return
}
if let webpageContent = webpageContent {
transition.updateFrame(view: webpageContent, frame: webpageFrame(item))
}
if let text = self.text {
transition.updateFrame(view: text, frame: textFrame(item))
}
if let actionButton = actionButton {
var add = item.additionalLineForDateInBubbleState ?? 0
if !item.isBubbled {
add = 0
}
let contentRect = self.contentFrame(item)
transition.updateFrame(view: actionButton, frame: CGRect(origin: NSMakePoint(contentRect.minX, contentRect.maxY - actionButton.frame.height + add), size: actionButton.frame.size))
}
if let adSettingView {
transition.updateFrame(view: adSettingView, frame: adSettingFrame(item))
}
}
override func canStartTextSelecting(_ event: NSEvent) -> Bool {
if let superTextView = text?.superview {
if let webpageContent = webpageContent {
return !NSPointInRect(superTextView.convert(event.locationInWindow, from: nil), webpageContent.frame)
}
return true
}
return false
}
override var selectableTextViews: [TextView] {
var views:[TextView] = super.selectableTextViews + (text?.textViews ?? [])
if let webpage = webpageContent {
views += webpage.selectableTextViews
}
return views
}
override func updateMouse(animated: Bool) {
super.updateMouse(animated: animated)
webpageContent?.updateMouse()
}
override func canMultiselectTextIn(_ location: NSPoint) -> Bool {
let point = self.contentView.convert(location, from: nil)
return true
}
override func focusAnimation(_ innerId: AnyHashable?, text: String?) {
super.focusAnimation(innerId, text: text)
guard let item = item as? ChatRowItem else {
return
}
if let text = text, !text.isEmpty {
self.text?.highlight(text: text, color: item.presentation.colors.focusAnimationColor)
}
}
override func set(item:TableRowItem, animated:Bool = false) {
let previous = self.item as? ChatMessageItem
super.set(item: item, animated: animated)
if let item = item as? ChatMessageItem {
let isEqual = previous?.textLayout.string == item.textLayout.string
if isEqual, let view = self.text {
view.update(layout: item.textLayout, animated: animated)
} else {
if let view = self.text {
performSubviewRemoval(view, animated: animated)
}
let current: FoldingTextView = FoldingTextView(frame: textFrame(item))
current.update(layout: item.textLayout, animated: false)
current.revealBlockAtIndex = { [weak self] index in
if let item = self?.item as? ChatMessageItem {
item.revealBlockAtIndex(index)
}
}
self.text = current
addSubview(current)
if animated {
current.layer?.animateAlpha(from: 0, to: 1, duration: 0.2)
}
}
if let webpageLayout = item.webpageLayout {
let updated = webpageContent == nil || !webpageContent!.isKind(of: webpageLayout.viewClass())
let current: WPContentView
if !updated, let view = self.webpageContent {
current = view
} else {
if let view = self.webpageContent {
performSubviewRemoval(view, animated: animated)
}
let vz = webpageLayout.viewClass() as! WPContentView.Type
current = vz.init()
current.frame = webpageFrame(item)
self.webpageContent = current
addSubview(current)
if animated {
current.layer?.animateAlpha(from: 0, to: 1, duration: 0.2)
}
}
current.update(with: webpageLayout, animated: animated)
} else if let view = webpageContent {
performSubviewRemoval(view, animated: animated)
webpageContent = nil
}
if let text = item.actionButtonText {
var isNew = false
if actionButton == nil {
actionButton = ActionButton(frame: .zero)
actionButton?.layer?.cornerRadius = .cornerRadius
actionButton?.disableActions()
actionButton?.set(font: .normal(.text), for: .Normal)
self.rowView.addSubview(actionButton!)
isNew = true
}
actionButton?.set(isExternalUrl: item.hasExternalLink, color: item.wpPresentation.activity.main)
actionButton?.scaleOnClick = true
actionButton?.removeAllHandlers()
actionButton?.set(handler: { [weak item] _ in
item?.invokeAction()
}, for: .Click)
actionButton?.set(text: text, for: .Normal)
actionButton?.set(color: item.wpPresentation.activity.main, for: .Normal)
actionButton?.set(background: item.wpPresentation.activity.main.withAlphaComponent(0.1), for: .Normal)
_ = actionButton?.sizeToFit(NSZeroSize, NSMakeSize(item.actionButtonWidth, 36), thatFit: true)
if animated, isNew {
actionButton?.layer?.animateScaleCenter(from: 0.1, to: 1, duration: 0.2, timingFunction: .easeOut)
actionButton?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2)
}
} else {
if let view = actionButton {
performSubviewRemoval(view, animated: animated)
actionButton = nil
}
}
if let adAttribute = item.message?.adAttribute {
let current: AdSettingsView
if let view = self.adSettingView {
current = view
} else {
current = AdSettingsView(frame: adSettingFrame(item))
self.adSettingView = current
self.rowView.addSubview(current)
}
current.update(item, animated: animated)
current.layer?.cornerRadius = current.frame.width / 2
} else if let view = self.adSettingView {
performSubviewRemoval(view, animated: animated)
self.adSettingView = nil
}
}
}
override func updateAnimatableContent() {
super.updateAnimatableContent()
}
override func clickInContent(point: NSPoint) -> Bool {
guard let item = item as? ChatMessageItem else {return true}
if let text = self.text {
return text.clickInContent(point: point)
} else {
return false
}
}
override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView {
if let webpageContent = webpageContent {
return webpageContent.interactionContentView(for: innerId, animateIn: animateIn)
}
return self
}
override func convertWindowPointToContent(_ point: NSPoint) -> NSPoint {
let main = super.convertWindowPointToContent(point)
if let webpageContent = webpageContent, NSPointInRect(main, webpageContent.frame) {
return webpageContent.convertWindowPointToContent(point)
} else {
return main
}
}
override func storyControl(_ storyId: StoryId) -> NSView? {
if let item = item as? ChatRowItem {
if item.message?.storyAttribute?.storyId == storyId {
return super.storyControl(storyId)
} else {
return self.webpageContent?.mediaContentView ?? super.storyControl(storyId)
}
}
return nil
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}