mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
[WIP] Animated emoji
This commit is contained in:
@@ -24,19 +24,25 @@ import TelegramUniversalVideoContent
|
||||
import UniversalMediaPlayer
|
||||
import GalleryUI
|
||||
import HierarchyTrackingLayer
|
||||
import TextNodeWithEntities
|
||||
|
||||
public enum ChatListItemContent {
|
||||
public final class DraftState: Equatable {
|
||||
let text: String
|
||||
let entities: [MessageTextEntity]
|
||||
|
||||
public init(text: String) {
|
||||
self.text = text
|
||||
public init(draft: EngineChatList.Draft) {
|
||||
self.text = draft.text
|
||||
self.entities = draft.entities
|
||||
}
|
||||
|
||||
public static func ==(lhs: DraftState, rhs: DraftState) -> Bool {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.entities != rhs.entities {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -440,7 +446,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let authorNode: TextNode
|
||||
let measureNode: TextNode
|
||||
private var currentItemHeight: CGFloat?
|
||||
let textNode: TextNode
|
||||
let textNode: TextNodeWithEntities
|
||||
var dustNode: InvisibleInkDustNode?
|
||||
let inputActivitiesNode: ChatListInputActivitiesNode
|
||||
let dateNode: TextNode
|
||||
@@ -624,6 +630,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
self.videoLoopCount = 0
|
||||
}
|
||||
self.updateVideoVisibility()
|
||||
|
||||
self.textNode.visibilityRect = self.visibilityStatus ? CGRect.infinite : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,9 +671,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
self.authorNode.isUserInteractionEnabled = false
|
||||
self.authorNode.displaysAsynchronously = true
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = true
|
||||
self.textNode = TextNodeWithEntities()
|
||||
self.textNode.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.textNode.displaysAsynchronously = true
|
||||
|
||||
self.inputActivitiesNode = ChatListInputActivitiesNode()
|
||||
self.inputActivitiesNode.isUserInteractionEnabled = false
|
||||
@@ -707,7 +715,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
self.contextContainer.addSubnode(self.titleNode)
|
||||
self.contextContainer.addSubnode(self.authorNode)
|
||||
self.contextContainer.addSubnode(self.textNode)
|
||||
self.contextContainer.addSubnode(self.textNode.textNode)
|
||||
self.contextContainer.addSubnode(self.dateNode)
|
||||
self.contextContainer.addSubnode(self.statusNode)
|
||||
self.contextContainer.addSubnode(self.pinnedIconNode)
|
||||
@@ -918,7 +926,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
func asyncLayout() -> (_ item: ChatListItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ nextIsPinned: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
|
||||
let dateLayout = TextNode.asyncLayout(self.dateNode)
|
||||
let textLayout = TextNode.asyncLayout(self.textNode)
|
||||
let textLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||
let titleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let authorLayout = TextNode.asyncLayout(self.authorNode)
|
||||
let makeMeasureLayout = TextNode.asyncLayout(self.measureNode)
|
||||
@@ -1186,10 +1194,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if inlineAuthorPrefix == nil, let draftState = draftState {
|
||||
hasDraft = true
|
||||
authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor)
|
||||
|
||||
let draftText: String = draftState.text
|
||||
|
||||
attributedText = NSAttributedString(string: foldLineBreaks(draftText.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor)
|
||||
let draftText = stringWithAppliedEntities(draftState.text, entities: draftState.entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, message: nil)
|
||||
|
||||
attributedText = foldLineBreaks(draftText)
|
||||
} else if let message = messages.last {
|
||||
var composedString: NSMutableAttributedString
|
||||
|
||||
@@ -1198,15 +1206,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
let entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in
|
||||
if case .Spoiler = entity.type {
|
||||
switch entity.type {
|
||||
case .Spoiler, .CustomEmoji:
|
||||
return true
|
||||
} else {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
let messageString: NSAttributedString
|
||||
if !message.text.isEmpty && entities.count > 0 {
|
||||
messageString = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: authorAttributedString == nil ? 2 : 1), entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false)
|
||||
messageString = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: authorAttributedString == nil ? 2 : 1), entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
|
||||
} else if let spoilers = spoilers {
|
||||
let mutableString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
|
||||
for range in spoilers {
|
||||
@@ -1787,7 +1796,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let _ = measureApply()
|
||||
let _ = dateApply()
|
||||
let _ = textApply()
|
||||
|
||||
let _ = textApply(TextNodeWithEntities.Arguments(
|
||||
context: item.context,
|
||||
cache: item.interaction.animationCache,
|
||||
renderer: item.interaction.animationRenderer,
|
||||
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor
|
||||
))
|
||||
|
||||
let _ = authorApply()
|
||||
let _ = titleApply()
|
||||
let _ = badgeApply(animateBadges, !isMuted)
|
||||
@@ -1862,7 +1878,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let authorNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height), size: authorLayout.size)
|
||||
strongSelf.authorNode.frame = authorNodeFrame
|
||||
let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size)
|
||||
strongSelf.textNode.frame = textNodeFrame
|
||||
strongSelf.textNode.textNode.frame = textNodeFrame
|
||||
|
||||
if !textLayout.spoilers.isEmpty {
|
||||
let dustNode: InvisibleInkDustNode
|
||||
@@ -1872,7 +1888,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
dustNode.isUserInteractionEnabled = false
|
||||
strongSelf.dustNode = dustNode
|
||||
strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode)
|
||||
strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode)
|
||||
}
|
||||
dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, textColor: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
|
||||
dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
||||
@@ -1901,13 +1917,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if strongSelf.inputActivitiesNode.alpha.isZero {
|
||||
strongSelf.inputActivitiesNode.alpha = 1.0
|
||||
strongSelf.textNode.alpha = 0.0
|
||||
strongSelf.textNode.textNode.alpha = 0.0
|
||||
strongSelf.authorNode.alpha = 0.0
|
||||
strongSelf.dustNode?.alpha = 0.0
|
||||
|
||||
if animated || animateContent {
|
||||
strongSelf.inputActivitiesNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
strongSelf.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||
strongSelf.textNode.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||
strongSelf.authorNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||
strongSelf.dustNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||
}
|
||||
@@ -1915,7 +1931,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
} else {
|
||||
if !strongSelf.inputActivitiesNode.alpha.isZero {
|
||||
strongSelf.inputActivitiesNode.alpha = 0.0
|
||||
strongSelf.textNode.alpha = 1.0
|
||||
strongSelf.textNode.textNode.alpha = 1.0
|
||||
strongSelf.authorNode.alpha = 1.0
|
||||
strongSelf.dustNode?.alpha = 1.0
|
||||
if animated || animateContent {
|
||||
@@ -1924,7 +1940,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.inputActivitiesNode.removeFromSupernode()
|
||||
}
|
||||
})
|
||||
strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
strongSelf.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
strongSelf.authorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
strongSelf.dustNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
} else {
|
||||
@@ -1983,7 +1999,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let titlePosition = strongSelf.titleNode.position
|
||||
transition.animatePosition(node: strongSelf.titleNode, from: CGPoint(x: titlePosition.x - contentDelta.x, y: titlePosition.y - contentDelta.y))
|
||||
|
||||
transition.animatePositionAdditive(node: strongSelf.textNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y))
|
||||
transition.animatePositionAdditive(node: strongSelf.textNode.textNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y))
|
||||
if let dustNode = strongSelf.dustNode {
|
||||
transition.animatePositionAdditive(node: dustNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y))
|
||||
}
|
||||
@@ -1995,7 +2011,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if crossfadeContent {
|
||||
strongSelf.authorNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
strongSelf.titleNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
strongSelf.textNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
strongSelf.textNode.textNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
}
|
||||
|
||||
var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.size.width + 3.0 + titleOffset
|
||||
@@ -2245,9 +2261,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
transition.updateFrame(node: self.inputActivitiesNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: self.inputActivitiesNode.frame.minY), size: self.inputActivitiesNode.bounds.size))
|
||||
|
||||
var textFrame = self.textNode.frame
|
||||
var textFrame = self.textNode.textNode.frame
|
||||
textFrame.origin.x = contentRect.origin.x
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
transition.updateFrameAdditive(node: self.textNode.textNode, frame: textFrame)
|
||||
|
||||
if let dustNode = self.dustNode {
|
||||
transition.updateFrameAdditive(node: dustNode, frame: textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0))
|
||||
|
||||
@@ -14,6 +14,9 @@ import ItemListUI
|
||||
import SearchUI
|
||||
import ChatListSearchItemHeader
|
||||
import PremiumUI
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import Postbox
|
||||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList
|
||||
@@ -75,7 +78,10 @@ public final class ChatListNodeInteraction {
|
||||
public var searchTextHighightState: String?
|
||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||
|
||||
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (EnginePeer, EnginePeer?, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, togglePeerSelected: @escaping (EnginePeer) -> Void, togglePeersSelection: @escaping ([PeerEntry], Bool) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (EnginePeer, EngineMessage, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (EngineChatList.Group) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setItemPinned: @escaping (EngineChatList.PinnedItem.Id, Bool) -> Void, setPeerMuted: @escaping (EnginePeer.Id, Bool) -> Void, deletePeer: @escaping (EnginePeer.Id, Bool) -> Void, updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void, togglePeerMarkedUnread: @escaping (EnginePeer.Id, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, hidePsa: @escaping (EnginePeer.Id) -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) {
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
public init(context: AccountContext, activateSearch: @escaping () -> Void, peerSelected: @escaping (EnginePeer, EnginePeer?, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, togglePeerSelected: @escaping (EnginePeer) -> Void, togglePeersSelection: @escaping ([PeerEntry], Bool) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (EnginePeer, EngineMessage, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (EngineChatList.Group) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setItemPinned: @escaping (EngineChatList.PinnedItem.Id, Bool) -> Void, setPeerMuted: @escaping (EnginePeer.Id, Bool) -> Void, deletePeer: @escaping (EnginePeer.Id, Bool) -> Void, updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void, togglePeerMarkedUnread: @escaping (EnginePeer.Id, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, hidePsa: @escaping (EnginePeer.Id) -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) {
|
||||
self.activateSearch = activateSearch
|
||||
self.peerSelected = peerSelected
|
||||
self.disabledPeerSelected = disabledPeerSelected
|
||||
@@ -95,6 +101,11 @@ public final class ChatListNodeInteraction {
|
||||
self.hidePsa = hidePsa
|
||||
self.activateChatPreview = activateChatPreview
|
||||
self.present = present
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +744,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
self.keepMinimalScrollHeightWithTopInset = navigationBarSearchContentHeight
|
||||
|
||||
let nodeInteraction = ChatListNodeInteraction(activateSearch: { [weak self] in
|
||||
let nodeInteraction = ChatListNodeInteraction(context: context, activateSearch: { [weak self] in
|
||||
if let strongSelf = self, let activateSearch = strongSelf.activateSearch {
|
||||
activateSearch()
|
||||
}
|
||||
|
||||
@@ -325,8 +325,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
}
|
||||
|
||||
var draftState: ChatListItemContent.DraftState?
|
||||
if let draftText = entry.draftText {
|
||||
draftState = ChatListItemContent.DraftState(text: draftText)
|
||||
if let draft = entry.draft {
|
||||
draftState = ChatListItemContent.DraftState(draft: draft)
|
||||
}
|
||||
|
||||
result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: entry.index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(entry.index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[entry.index.messageIndex.id.peerId], promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact))
|
||||
@@ -380,7 +380,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
case let .psa(type, message):
|
||||
promoInfo = .psa(type: type, message: message)
|
||||
}
|
||||
let draftState = item.item.draftText.flatMap(ChatListItemContent.DraftState.init(text:))
|
||||
let draftState = item.item.draft.flatMap(ChatListItemContent.DraftState.init)
|
||||
result.append(.PeerEntry(
|
||||
index: EngineChatList.Item.Index(pinningIndex: pinningIndex, messageIndex: item.item.index.messageIndex),
|
||||
presentationData: state.presentationData,
|
||||
|
||||
Reference in New Issue
Block a user