mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
239 lines
9.3 KiB
Swift
239 lines
9.3 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import Postbox
|
|
import TelegramCore
|
|
|
|
struct ChatMessageItemWidthFill {
|
|
let compactInset: CGFloat
|
|
let compactWidthBoundary: CGFloat
|
|
let freeMaximumFillFactor: CGFloat
|
|
|
|
func widthFor(_ width: CGFloat) -> CGFloat {
|
|
if width <= self.compactWidthBoundary {
|
|
return max(1.0, width - self.compactInset)
|
|
} else {
|
|
return max(1.0, floor(width * self.freeMaximumFillFactor))
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ChatMessageItemBubbleLayoutConstants {
|
|
let edgeInset: CGFloat
|
|
let defaultSpacing: CGFloat
|
|
let mergedSpacing: CGFloat
|
|
let maximumWidthFill: ChatMessageItemWidthFill
|
|
let minimumSize: CGSize
|
|
let contentInsets: UIEdgeInsets
|
|
}
|
|
|
|
struct ChatMessageItemTextLayoutConstants {
|
|
let bubbleInsets: UIEdgeInsets
|
|
}
|
|
|
|
struct ChatMessageItemImageLayoutConstants {
|
|
let bubbleInsets: UIEdgeInsets
|
|
let statusInsets: UIEdgeInsets
|
|
let defaultCornerRadius: CGFloat
|
|
let mergedCornerRadius: CGFloat
|
|
let contentMergedCornerRadius: CGFloat
|
|
let maxDimensions: CGSize
|
|
let minDimensions: CGSize
|
|
}
|
|
|
|
struct ChatMessageItemInstantVideoConstants {
|
|
let insets: UIEdgeInsets
|
|
let dimensions: CGSize
|
|
}
|
|
|
|
struct ChatMessageItemFileLayoutConstants {
|
|
let bubbleInsets: UIEdgeInsets
|
|
}
|
|
|
|
struct ChatMessageItemLayoutConstants {
|
|
let avatarDiameter: CGFloat
|
|
let timestampHeaderHeight: CGFloat
|
|
|
|
let bubble: ChatMessageItemBubbleLayoutConstants
|
|
let image: ChatMessageItemImageLayoutConstants
|
|
let text: ChatMessageItemTextLayoutConstants
|
|
let file: ChatMessageItemFileLayoutConstants
|
|
let instantVideo: ChatMessageItemInstantVideoConstants
|
|
|
|
init() {
|
|
self.avatarDiameter = 37.0
|
|
self.timestampHeaderHeight = 34.0
|
|
|
|
self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 40.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0))
|
|
self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0))
|
|
self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.0 + UIScreenPixel, left: 1.0 + UIScreenPixel, bottom: 1.0 + UIScreenPixel, right: 1.0 + UIScreenPixel), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 300.0, height: 300.0), minDimensions: CGSize(width: 74.0, height: 74.0))
|
|
self.file = ChatMessageItemFileLayoutConstants(bubbleInsets: UIEdgeInsets(top: 15.0, left: 9.0, bottom: 15.0, right: 12.0))
|
|
self.instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 212.0, height: 212.0))
|
|
}
|
|
}
|
|
|
|
enum ChatMessageItemBottomNeighbor {
|
|
case none
|
|
case merged(semi: Bool)
|
|
}
|
|
|
|
let defaultChatMessageItemLayoutConstants = ChatMessageItemLayoutConstants()
|
|
|
|
enum ChatMessagePeekPreviewContent {
|
|
case media(Media)
|
|
case url(ASDisplayNode, CGRect, String)
|
|
}
|
|
|
|
public class ChatMessageItemView: ListViewItemNode {
|
|
let layoutConstants = defaultChatMessageItemLayoutConstants
|
|
|
|
var item: ChatMessageItem?
|
|
|
|
public required convenience init() {
|
|
self.init(layerBacked: false)
|
|
}
|
|
|
|
public init(layerBacked: Bool) {
|
|
super.init(layerBacked: layerBacked, dynamicBounce: true, rotated: true)
|
|
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
|
}
|
|
|
|
required public init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override public func reuse() {
|
|
super.reuse()
|
|
|
|
self.item = nil
|
|
self.frame = CGRect()
|
|
}
|
|
|
|
func setupItem(_ item: ChatMessageItem) {
|
|
self.item = item
|
|
}
|
|
|
|
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
|
if let item = item as? ChatMessageItem {
|
|
let doLayout = self.asyncLayout()
|
|
let merged = item.mergedWithItems(top: previousItem, bottom: nextItem)
|
|
let (layout, apply) = doLayout(item, params, merged.top, merged.bottom, merged.dateAtBottom)
|
|
self.contentSize = layout.contentSize
|
|
self.insets = layout.insets
|
|
apply(.None, false)
|
|
}
|
|
}
|
|
|
|
override public func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode, leftInset: CGFloat, rightInset: CGFloat) {
|
|
if let avatarNode = accessoryItemNode as? ChatMessageAvatarAccessoryItemNode {
|
|
avatarNode.frame = CGRect(origin: CGPoint(x: leftInset + 3.0, y: self.apparentFrame.height - 38.0 - self.insets.top + 1.0), size: CGSize(width: 38.0, height: 38.0))
|
|
}
|
|
}
|
|
|
|
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
|
if short {
|
|
//self.layer.animateBoundsOriginYAdditive(from: -self.bounds.size.height, to: 0.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
|
} else {
|
|
self.transitionOffset = -self.bounds.size.height * 1.6
|
|
self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp)
|
|
}
|
|
}
|
|
|
|
func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, Bool) -> Void) {
|
|
return { _, _, _, _, _ in
|
|
return (ListViewItemNodeLayout(contentSize: CGSize(width: 32.0, height: 32.0), insets: UIEdgeInsets()), { _, _ in
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, () -> UIView?)? {
|
|
return nil
|
|
}
|
|
|
|
func peekPreviewContent(at point: CGPoint) -> (Message, ChatMessagePeekPreviewContent)? {
|
|
return nil
|
|
}
|
|
|
|
func updateHiddenMedia() {
|
|
}
|
|
|
|
func updateSelectionState(animated: Bool) {
|
|
}
|
|
|
|
func updateHighlightedState(animated: Bool) {
|
|
var isHighlightedInOverlay = false
|
|
if let item = self.item, let contextHighlightedState = item.controllerInteraction.contextHighlightedState {
|
|
switch item.content {
|
|
case let .message(message, _, _, _):
|
|
if contextHighlightedState.messageStableId == message.stableId {
|
|
isHighlightedInOverlay = true
|
|
}
|
|
case let .group(messages):
|
|
for (message, _, _, _) in messages {
|
|
if contextHighlightedState.messageStableId == message.stableId {
|
|
isHighlightedInOverlay = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
self.isHighlightedInOverlay = isHighlightedInOverlay
|
|
}
|
|
|
|
func updateAutomaticMediaDownloadSettings() {
|
|
}
|
|
|
|
override public func header() -> ListViewItemHeader? {
|
|
if let item = self.item {
|
|
return item.header
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func performMessageButtonAction(button: ReplyMarkupButton) {
|
|
if let item = self.item {
|
|
switch button.action {
|
|
case .text:
|
|
item.controllerInteraction.sendMessage(button.title)
|
|
case let .url(url):
|
|
item.controllerInteraction.openUrl(url, true, nil)
|
|
case .requestMap:
|
|
item.controllerInteraction.shareCurrentLocation()
|
|
case .requestPhone:
|
|
item.controllerInteraction.shareAccountContact()
|
|
case .openWebApp:
|
|
item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true)
|
|
case let .callback(data):
|
|
item.controllerInteraction.requestMessageActionCallback(item.message.id, data, false)
|
|
case let .switchInline(samePeer, query):
|
|
var botPeer: Peer?
|
|
|
|
var found = false
|
|
for attribute in item.message.attributes {
|
|
if let attribute = attribute as? InlineBotMessageAttribute {
|
|
if let peerId = attribute.peerId {
|
|
botPeer = item.message.peers[peerId]
|
|
found = true
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
botPeer = item.message.author
|
|
}
|
|
|
|
var peerId: PeerId?
|
|
if samePeer {
|
|
peerId = item.message.id.peerId
|
|
}
|
|
if let botPeer = botPeer, let addressName = botPeer.addressName {
|
|
item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)")
|
|
}
|
|
case .payment:
|
|
item.controllerInteraction.openCheckoutOrReceipt(item.message.id)
|
|
}
|
|
}
|
|
}
|
|
}
|