mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
UI improvements
This commit is contained in:
@@ -20,7 +20,7 @@ import ChatListSearchItemNode
|
||||
import ContextUI
|
||||
|
||||
public enum ChatListItemContent {
|
||||
case peer(message: Message?, peer: RenderedPeer, combinedReadState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, embeddedState: PeerChatListEmbeddedInterfaceState?, inputActivities: [(Peer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool)
|
||||
case peer(messages: [Message], peer: RenderedPeer, combinedReadState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, embeddedState: PeerChatListEmbeddedInterfaceState?, inputActivities: [(Peer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool)
|
||||
case groupReference(groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, unreadState: PeerGroupUnreadCountersCombinedSummary, hiddenByDefault: Bool)
|
||||
|
||||
public var chatLocation: ChatLocation? {
|
||||
@@ -124,8 +124,8 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
|
||||
|
||||
public func selected(listView: ListView) {
|
||||
switch self.content {
|
||||
case let .peer(message, peer, _, _, _, _, _, _, promoInfo, _, _, _):
|
||||
if let message = message, let peer = peer.peer {
|
||||
case let .peer(messages, peer, _, _, _, _, _, _, promoInfo, _, _, _):
|
||||
if let message = messages.last, let peer = peer.peer {
|
||||
self.interaction.messageSelected(peer, message, promoInfo)
|
||||
} else if let peer = peer.peer {
|
||||
self.interaction.peerSelected(peer, promoInfo)
|
||||
@@ -328,6 +328,61 @@ private final class CachedChatListSearchResult {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChatListMediaPreviewNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private let message: Message
|
||||
private let media: Media
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
|
||||
private var requestedImage: Bool = false
|
||||
private var disposable: Disposable?
|
||||
|
||||
init(context: AccountContext, message: Message, media: Media) {
|
||||
self.context = context
|
||||
self.message = message
|
||||
self.media = media
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, synchronousLoads: Bool) {
|
||||
var dimensions = CGSize(width: 100.0, height: 100.0)
|
||||
if let image = self.media as? TelegramMediaImage {
|
||||
if let largest = largestImageRepresentation(image.representations) {
|
||||
dimensions = largest.dimensions.cgSize
|
||||
if !self.requestedImage {
|
||||
self.requestedImage = true
|
||||
let signal = mediaGridMessagePhoto(account: self.context.account, photoReference: .message(message: MessageReference(self.message), media: image), synchronousLoad: synchronousLoads)
|
||||
self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoads)
|
||||
}
|
||||
}
|
||||
} else if let file = self.media as? TelegramMediaFile {
|
||||
if let mediaDimensions = file.dimensions {
|
||||
dimensions = mediaDimensions.cgSize
|
||||
if !self.requestedImage {
|
||||
self.requestedImage = true
|
||||
let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, videoReference: .message(message: MessageReference(self.message), media: file), synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true)
|
||||
self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoads)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let makeLayout = self.imageNode.asyncLayout()
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var item: ChatListItem?
|
||||
|
||||
@@ -342,7 +397,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let measureNode: TextNode
|
||||
private var currentItemHeight: CGFloat?
|
||||
let textNode: TextNode
|
||||
let contentImageNode: TransformImageNode
|
||||
let inputActivitiesNode: ChatListInputActivitiesNode
|
||||
let dateNode: TextNode
|
||||
let separatorNode: ASDisplayNode
|
||||
@@ -355,6 +409,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var credibilityIconNode: ASImageNode?
|
||||
let mutedIconNode: ASImageNode
|
||||
|
||||
private var currentTextLeftCutout: CGFloat = 0.0
|
||||
private var currentMediaPreviewSpecs: [(message: Message, media: Media, size: CGSize)] = []
|
||||
private var mediaPreviewNodes: [MediaId: ChatListMediaPreviewNode] = [:]
|
||||
|
||||
var selectableControlNode: ItemListSelectableControlNode?
|
||||
var reorderControlNode: ItemListEditableReorderControlNode?
|
||||
|
||||
@@ -364,7 +422,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
private var cachedChatListSearchResult: CachedChatListSearchResult?
|
||||
|
||||
var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams, countersSize: CGFloat)?
|
||||
private var contentImageMedia: Media?
|
||||
|
||||
private var isHighlighted: Bool = false
|
||||
private var skipFadeout: Bool = false
|
||||
@@ -423,14 +480,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
case .groupReference:
|
||||
return nil
|
||||
case let .peer(peer):
|
||||
if let message = peer.message {
|
||||
if let message = peer.messages.last {
|
||||
var result = ""
|
||||
if message.flags.contains(.Incoming) {
|
||||
result += "Message"
|
||||
} else {
|
||||
result += "Outgoing message"
|
||||
}
|
||||
let (_, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: peer.message, chatPeer: peer.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
|
||||
let (_, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, messages: peer.messages, chatPeer: peer.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
|
||||
if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, author is TelegramUser {
|
||||
result += "\nFrom: \(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))"
|
||||
}
|
||||
@@ -473,9 +530,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = true
|
||||
|
||||
self.contentImageNode = TransformImageNode()
|
||||
self.contentImageNode.isHidden = true
|
||||
|
||||
self.inputActivitiesNode = ChatListInputActivitiesNode()
|
||||
self.inputActivitiesNode.isUserInteractionEnabled = false
|
||||
self.inputActivitiesNode.alpha = 0.0
|
||||
@@ -517,7 +571,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
self.contextContainer.addSubnode(self.titleNode)
|
||||
self.contextContainer.addSubnode(self.authorNode)
|
||||
self.contextContainer.addSubnode(self.textNode)
|
||||
self.contextContainer.addSubnode(self.contentImageNode)
|
||||
self.contextContainer.addSubnode(self.dateNode)
|
||||
self.contextContainer.addSubnode(self.statusNode)
|
||||
self.contextContainer.addSubnode(self.pinnedIconNode)
|
||||
@@ -548,9 +601,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var displayAsMessage = false
|
||||
var enablePreview = true
|
||||
switch item.content {
|
||||
case let .peer(message, peerValue, _, _, _, _, _, _, _, _, displayAsMessageValue, _):
|
||||
case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, displayAsMessageValue, _):
|
||||
displayAsMessage = displayAsMessageValue
|
||||
if displayAsMessage, let author = message?.author as? TelegramUser {
|
||||
if displayAsMessage, let author = messages.last?.author as? TelegramUser {
|
||||
peer = author
|
||||
} else {
|
||||
peer = peerValue.chatMainPeer
|
||||
@@ -674,7 +727,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||
|
||||
let currentItem = self.layoutParams?.0
|
||||
let currentContentImageMedia = self.contentImageMedia
|
||||
let currentChatListText = self.cachedChatListText
|
||||
let currentChatListSearchResult = self.cachedChatListSearchResult
|
||||
|
||||
@@ -685,7 +737,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let badgeFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
|
||||
|
||||
let account = item.context.account
|
||||
var message: Message?
|
||||
var messages: [Message]
|
||||
enum ContentPeer {
|
||||
case chat(RenderedPeer)
|
||||
case group([ChatListGroupReferencePeer])
|
||||
@@ -706,8 +758,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var groupHiddenByDefault = false
|
||||
|
||||
switch item.content {
|
||||
case let .peer(messageValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, summaryInfoValue, embeddedStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, hasFailedMessagesValue):
|
||||
message = messageValue
|
||||
case let .peer(messagesValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, summaryInfoValue, embeddedStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, hasFailedMessagesValue):
|
||||
messages = messagesValue
|
||||
contentPeer = .chat(peerValue)
|
||||
combinedReadState = combinedReadStateValue
|
||||
if let combinedReadState = combinedReadState, promoInfoValue == nil && !ignoreUnreadBadge {
|
||||
@@ -729,14 +781,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
isPeerGroup = false
|
||||
promoInfo = promoInfoValue
|
||||
displayAsMessage = displayAsMessageValue
|
||||
hasFailedMessages = messageValue?.flags.contains(.Failed) ?? false // hasFailedMessagesValue
|
||||
hasFailedMessages = messagesValue.last?.flags.contains(.Failed) ?? false // hasFailedMessagesValue
|
||||
case let .groupReference(_, peers, messageValue, unreadState, hiddenByDefault):
|
||||
if let _ = messageValue, !peers.isEmpty {
|
||||
contentPeer = .chat(peers[0].peer)
|
||||
} else {
|
||||
contentPeer = .group(peers)
|
||||
}
|
||||
message = messageValue
|
||||
if let message = messageValue {
|
||||
messages = [message]
|
||||
} else {
|
||||
messages = []
|
||||
}
|
||||
combinedReadState = nil
|
||||
isRemovedFromTotalUnreadCount = false
|
||||
embeddedState = nil
|
||||
@@ -752,10 +808,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
hasFailedMessages = false
|
||||
}
|
||||
|
||||
if let messageValue = message {
|
||||
if let messageValue = messages.last {
|
||||
for media in messageValue.media {
|
||||
if let media = media as? TelegramMediaAction, case .historyCleared = media.action {
|
||||
message = nil
|
||||
messages = []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -824,7 +880,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var hideAuthor = false
|
||||
switch contentPeer {
|
||||
case let .chat(itemPeer):
|
||||
var (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: message, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup)
|
||||
var (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup)
|
||||
|
||||
if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText {
|
||||
initialHideAuthor = true
|
||||
@@ -843,19 +899,23 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
var inlineAuthorPrefix: String?
|
||||
if case .groupReference = item.content {
|
||||
if let author = message?.author as? TelegramUser {
|
||||
if let author = messages.last?.author as? TelegramUser {
|
||||
if author.id == item.context.account.peerId {
|
||||
inlineAuthorPrefix = item.presentationData.strings.DialogList_You
|
||||
} else if message?.id.peerId.namespace != Namespaces.Peer.CloudUser && message?.id.peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
} else if messages.last?.id.peerId.namespace != Namespaces.Peer.CloudUser && messages.last?.id.peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
inlineAuthorPrefix = author.compactDisplayTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var contentImageMedia: Media?
|
||||
var chatListText: (String, String)?
|
||||
var chatListSearchResult: CachedChatListSearchResult?
|
||||
|
||||
let contentImageSize = CGSize(width: 18.0, height: 18.0)
|
||||
let contentImageSpacing: CGFloat = 2.0
|
||||
let contentImageTrailingSpace: CGFloat = 5.0
|
||||
var contentImageSpecs: [(message: Message, media: Media, size: CGSize)] = []
|
||||
|
||||
switch contentData {
|
||||
case let .chat(itemPeer, _, _, text):
|
||||
var peerText: String?
|
||||
@@ -863,7 +923,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let messagePeer = itemPeer.chatMainPeer {
|
||||
peerText = messagePeer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
||||
}
|
||||
} else if let message = message, let author = message.author as? TelegramUser, let peer = itemPeer.chatMainPeer, !(peer is TelegramUser) {
|
||||
} else if let message = messages.last, let author = message.author as? TelegramUser, let peer = itemPeer.chatMainPeer, !(peer is TelegramUser) {
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
} else if !displayAsMessage {
|
||||
peerText = author.id == account.peerId ? item.presentationData.strings.DialogList_You : author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
||||
@@ -884,7 +944,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor)
|
||||
|
||||
attributedText = NSAttributedString(string: foldLineBreaks(embeddedState.text.string.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor)
|
||||
} else if let message = message {
|
||||
} else if let message = messages.last {
|
||||
let composedString: NSMutableAttributedString
|
||||
if let inlineAuthorPrefix = inlineAuthorPrefix {
|
||||
composedString = NSMutableAttributedString()
|
||||
@@ -920,28 +980,49 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor)
|
||||
}
|
||||
|
||||
if enableChatListPhotos && !message.containsSecretMedia {
|
||||
for media in message.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
textLeftCutout += 26.0
|
||||
contentImageMedia = image
|
||||
break
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if file.isVideo && !file.isInstantVideo {
|
||||
textLeftCutout += 26.0
|
||||
contentImageMedia = file
|
||||
break
|
||||
}
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
if let image = content.image {
|
||||
textLeftCutout += 26.0
|
||||
contentImageMedia = image
|
||||
break
|
||||
} else if let file = content.file {
|
||||
if file.isVideo && !file.isInstantVideo {
|
||||
textLeftCutout += 26.0
|
||||
contentImageMedia = file
|
||||
break
|
||||
var displayMediaPreviews = true
|
||||
if message.containsSecretMedia {
|
||||
displayMediaPreviews = false
|
||||
} else if let _ = message.peers[message.id.peerId] as? TelegramSecretChat {
|
||||
displayMediaPreviews = false
|
||||
}
|
||||
if displayMediaPreviews {
|
||||
let contentImageFillSize = CGSize(width: 8.0, height: contentImageSize.height)
|
||||
_ = contentImageFillSize
|
||||
for message in messages {
|
||||
inner: for media in message.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
if let _ = largestImageRepresentation(image.representations) {
|
||||
//let imageSize = largest.dimensions.cgSize
|
||||
//let fitSize = imageSize.aspectFilled(contentImageFillSize)
|
||||
let fitSize = contentImageSize
|
||||
contentImageSpecs.append((message, image, fitSize))
|
||||
}
|
||||
break inner
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if file.isVideo, !file.isInstantVideo, let _ = file.dimensions {
|
||||
//let imageSize = dimensions.cgSize
|
||||
//let fitSize = imageSize.aspectFilled(contentImageFillSize)
|
||||
let fitSize = contentImageSize
|
||||
contentImageSpecs.append((message, file, fitSize))
|
||||
}
|
||||
break inner
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
let imageTypes = ["photo", "video", "embed", "gif", "document", "telegram_album"]
|
||||
if let image = content.image, let type = content.type, imageTypes.contains(type) {
|
||||
if let _ = largestImageRepresentation(image.representations) {
|
||||
//let imageSize = largest.dimensions.cgSize
|
||||
let fitSize = contentImageSize
|
||||
contentImageSpecs.append((message, image, fitSize))
|
||||
}
|
||||
break inner
|
||||
} else if let file = content.file {
|
||||
if file.isVideo, !file.isInstantVideo, let _ = file.dimensions {
|
||||
//let imageSize = dimensions.cgSize
|
||||
let fitSize = contentImageSize
|
||||
contentImageSpecs.append((message, file, fitSize))
|
||||
}
|
||||
break inner
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -980,9 +1061,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
attributedText = textString
|
||||
}
|
||||
|
||||
for i in 0 ..< contentImageSpecs.count {
|
||||
if i != 0 {
|
||||
textLeftCutout += contentImageSpacing
|
||||
}
|
||||
textLeftCutout += contentImageSpecs[i].size.width
|
||||
if i == contentImageSpecs.count - 1 {
|
||||
textLeftCutout += contentImageTrailingSpace
|
||||
}
|
||||
}
|
||||
|
||||
switch contentData {
|
||||
case let .chat(itemPeer, _, _, _):
|
||||
if let message = message, let author = message.author as? TelegramUser, displayAsMessage {
|
||||
if let message = messages.last, let author = message.author as? TelegramUser, displayAsMessage {
|
||||
titleAttributedString = NSAttributedString(string: author.id == account.peerId ? item.presentationData.strings.DialogList_You : author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: titleFont, textColor: theme.titleColor)
|
||||
} else if isPeerGroup {
|
||||
titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor)
|
||||
@@ -1024,7 +1115,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: theme.dateTextColor)
|
||||
}
|
||||
|
||||
if !isPeerGroup, let message = message, message.author?.id == account.peerId && !hasDraft {
|
||||
if !isPeerGroup, let message = messages.last, message.author?.id == account.peerId && !hasDraft {
|
||||
if message.flags.isSending && !message.isSentOrAcknowledged {
|
||||
statusState = .clock(PresentationResourcesChatList.clockFrameImage(item.presentationData.theme), PresentationResourcesChatList.clockMinImage(item.presentationData.theme))
|
||||
} else if message.id.peerId != account.peerId {
|
||||
@@ -1041,7 +1132,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
if unreadCount.unread {
|
||||
if !isPeerGroup, let message = message, message.tags.contains(.unseenPersonalMessage), unreadCount.count == 1 {
|
||||
if !isPeerGroup, let message = messages.last, message.tags.contains(.unseenPersonalMessage), unreadCount.count == 1 {
|
||||
} else {
|
||||
let badgeTextColor: UIColor
|
||||
if unreadCount.muted {
|
||||
@@ -1111,8 +1202,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var credibilityIconOffset: CGFloat = 0.0
|
||||
if displayAsMessage {
|
||||
switch item.content {
|
||||
case let .peer(message, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if let peer = message?.author {
|
||||
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if let peer = messages.last?.author {
|
||||
if peer.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
||||
credibilityIconOffset = 2.0
|
||||
@@ -1184,7 +1275,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if !textLeftCutout.isZero {
|
||||
textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil)
|
||||
}
|
||||
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize - textLeftCutout, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
||||
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
||||
|
||||
let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth
|
||||
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
@@ -1241,20 +1332,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
peerLeftRevealOptions = []
|
||||
}
|
||||
|
||||
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
if let contentImageMedia = contentImageMedia {
|
||||
if let currentContentImageMedia = currentContentImageMedia, contentImageMedia.isSemanticallyEqual(to: currentContentImageMedia) {
|
||||
} else {
|
||||
if let message = message {
|
||||
if let image = contentImageMedia as? TelegramMediaImage {
|
||||
updateImageSignal = mediaGridMessagePhoto(account: item.context.account, photoReference: .message(message: MessageReference(message), media: image))
|
||||
} else if let file = contentImageMedia as? TelegramMediaFile {
|
||||
updateImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), autoFetchFullSizeThumbnail: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (onlineLayout, onlineApply) = onlineLayout(online)
|
||||
var animateContent = false
|
||||
if let currentItem = currentItem, currentItem.content.chatLocation == item.content.chatLocation {
|
||||
@@ -1287,8 +1364,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: max(0.0, itemHeight + heightOffset)), insets: insets)
|
||||
|
||||
let contentImageSize = CGSize(width: 18.0, height: 18.0)
|
||||
|
||||
var customActions: [ChatListItemAccessibilityCustomAction] = []
|
||||
for option in peerLeftRevealOptions {
|
||||
customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key))
|
||||
@@ -1301,42 +1376,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let strongSelf = self {
|
||||
strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize)
|
||||
strongSelf.currentItemHeight = itemHeight
|
||||
strongSelf.contentImageMedia = contentImageMedia
|
||||
strongSelf.cachedChatListText = chatListText
|
||||
strongSelf.cachedChatListSearchResult = chatListSearchResult
|
||||
|
||||
strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
|
||||
var dimensions: CGSize?
|
||||
if let contentImageMedia = contentImageMedia as? TelegramMediaImage {
|
||||
dimensions = largestRepresentationForPhoto(contentImageMedia)?.dimensions.cgSize
|
||||
} else if let contentImageMedia = contentImageMedia as? TelegramMediaFile {
|
||||
dimensions = contentImageMedia.dimensions?.cgSize
|
||||
}
|
||||
|
||||
var contentImageNodeAppeared = false
|
||||
if let dimensions = dimensions {
|
||||
let makeImageLayout = strongSelf.contentImageNode.asyncLayout()
|
||||
let imageSize = contentImageSize
|
||||
|
||||
let applyImageLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: dimensions.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
applyImageLayout()
|
||||
|
||||
if let updateImageSignal = updateImageSignal {
|
||||
strongSelf.contentImageNode.setSignal(updateImageSignal)
|
||||
if currentContentImageMedia == nil {
|
||||
strongSelf.contentImageNode.isHidden = false
|
||||
contentImageNodeAppeared = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if currentContentImageMedia != nil {
|
||||
strongSelf.contentImageNode.removeFromSupernode()
|
||||
strongSelf.contentImageNode.setSignal(.single({ _ in nil }))
|
||||
strongSelf.contentImageNode.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
if case .groupReference = item.content {
|
||||
strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0)
|
||||
}
|
||||
@@ -1541,12 +1585,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.authorNode.frame = authorNodeFrame
|
||||
let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, 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
|
||||
let contentImageFrame = CGRect(origin: textNodeFrame.origin.offsetBy(dx: 1.0, dy: 2.0), size: contentImageSize)
|
||||
if contentImageNodeAppeared {
|
||||
strongSelf.contentImageNode.frame = contentImageFrame
|
||||
} else {
|
||||
transition.updateFrame(node: strongSelf.contentImageNode, frame: contentImageFrame)
|
||||
}
|
||||
|
||||
var animateInputActivitiesFrame = false
|
||||
if let inputActivities = inputActivities, !inputActivities.isEmpty {
|
||||
@@ -1586,7 +1624,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
}
|
||||
if let inputActivitiesSize = inputActivitiesSize {
|
||||
let inputActivitiesFrame = CGRect(origin: CGPoint(x: authorNodeFrame.minX + 1.0, y: authorNodeFrame.minY + UIScreenPixel), size: inputActivitiesSize)
|
||||
let inputActivitiesFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: authorNodeFrame.minY + UIScreenPixel), size: inputActivitiesSize)
|
||||
if animateInputActivitiesFrame {
|
||||
transition.updateFrame(node: strongSelf.inputActivitiesNode, frame: inputActivitiesFrame)
|
||||
} else {
|
||||
@@ -1595,6 +1633,43 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
inputActivitiesApply?()
|
||||
|
||||
var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: 2.0)
|
||||
var validMediaIds: [MediaId] = []
|
||||
for (message, media, mediaSize) in contentImageSpecs {
|
||||
guard let mediaId = media.id else {
|
||||
continue
|
||||
}
|
||||
validMediaIds.append(mediaId)
|
||||
let previewNode: ChatListMediaPreviewNode
|
||||
var previewNodeTransition = transition
|
||||
var previewNodeAlphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
|
||||
if let current = strongSelf.mediaPreviewNodes[mediaId] {
|
||||
previewNode = current
|
||||
} else {
|
||||
previewNodeTransition = .immediate
|
||||
previewNodeAlphaTransition = .immediate
|
||||
previewNode = ChatListMediaPreviewNode(context: item.context, message: message, media: media)
|
||||
strongSelf.mediaPreviewNodes[mediaId] = previewNode
|
||||
strongSelf.contextContainer.addSubnode(previewNode)
|
||||
}
|
||||
previewNode.updateLayout(size: mediaSize, synchronousLoads: synchronousLoads)
|
||||
previewNodeAlphaTransition.updateAlpha(node: previewNode, alpha: strongSelf.inputActivitiesNode.alpha.isZero ? 1.0 : 0.0)
|
||||
previewNodeTransition.updateFrame(node: previewNode, frame: CGRect(origin: mediaPreviewOffset, size: mediaSize))
|
||||
mediaPreviewOffset.x += mediaSize.width + contentImageSpacing
|
||||
}
|
||||
var removeMediaIds: [MediaId] = []
|
||||
for (mediaId, itemNode) in strongSelf.mediaPreviewNodes {
|
||||
if !validMediaIds.contains(mediaId) {
|
||||
removeMediaIds.append(mediaId)
|
||||
itemNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
for mediaId in removeMediaIds {
|
||||
strongSelf.mediaPreviewNodes.removeValue(forKey: mediaId)
|
||||
}
|
||||
strongSelf.currentMediaPreviewSpecs = contentImageSpecs
|
||||
strongSelf.currentTextLeftCutout = textLeftCutout
|
||||
|
||||
if !contentDelta.x.isZero || !contentDelta.y.isZero {
|
||||
let titlePosition = strongSelf.titleNode.position
|
||||
transition.animatePosition(node: strongSelf.titleNode, from: CGPoint(x: titlePosition.x - contentDelta.x, y: titlePosition.y - contentDelta.y))
|
||||
@@ -1749,9 +1824,17 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
textFrame.origin.x = contentRect.origin.x
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
|
||||
var contentImageFrame = self.contentImageNode.frame
|
||||
contentImageFrame.origin = textFrame.origin.offsetBy(dx: 1.0, dy: 2.0)
|
||||
transition.updateFrame(node: self.contentImageNode, frame: contentImageFrame)
|
||||
var mediaPreviewOffset = textFrame.origin.offsetBy(dx: 1.0, dy: 2.0)
|
||||
let contentImageSpacing: CGFloat = 2.0
|
||||
for (_, media, mediaSize) in self.currentMediaPreviewSpecs {
|
||||
guard let mediaId = media.id else {
|
||||
continue
|
||||
}
|
||||
if let previewNode = self.mediaPreviewNodes[mediaId] {
|
||||
transition.updateFrame(node: previewNode, frame: CGRect(origin: mediaPreviewOffset, size: mediaSize))
|
||||
}
|
||||
mediaPreviewOffset.x += mediaSize.width + contentImageSpacing
|
||||
}
|
||||
|
||||
let dateFrame = self.dateNode.frame
|
||||
transition.updateFrame(node: self.dateNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width, y: dateFrame.minY), size: dateFrame.size))
|
||||
|
||||
@@ -7,9 +7,44 @@ import TelegramUIPreferences
|
||||
import TelegramStringFormatting
|
||||
import LocalizedPeerData
|
||||
|
||||
public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message?, chatPeer: RenderedPeer, accountPeerId: PeerId, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: Peer?, hideAuthor: Bool, messageText: String) {
|
||||
private enum MessageGroupType {
|
||||
case photos
|
||||
case videos
|
||||
case generic
|
||||
}
|
||||
|
||||
private func singleMessageType(message: Message) -> MessageGroupType {
|
||||
for media in message.media {
|
||||
if let _ = media as? TelegramMediaImage {
|
||||
return .photos
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if file.isVideo && !file.isInstantVideo {
|
||||
return .videos
|
||||
}
|
||||
}
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
|
||||
private func messageGroupType(messages: [Message]) -> MessageGroupType {
|
||||
if messages.isEmpty {
|
||||
return .generic
|
||||
}
|
||||
let currentType = singleMessageType(message: messages[0])
|
||||
for i in 1 ..< messages.count {
|
||||
let nextType = singleMessageType(message: messages[i])
|
||||
if nextType != currentType {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
return currentType
|
||||
}
|
||||
|
||||
public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, messages: [Message], chatPeer: RenderedPeer, accountPeerId: PeerId, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: Peer?, hideAuthor: Bool, messageText: String) {
|
||||
let peer: Peer?
|
||||
|
||||
let message = messages.last
|
||||
|
||||
var hideAuthor = false
|
||||
var messageText: String
|
||||
if let message = message {
|
||||
@@ -19,151 +54,178 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
||||
peer = chatPeer.chatMainPeer
|
||||
}
|
||||
|
||||
messageText = message.text
|
||||
messageText = messages[0].text
|
||||
|
||||
for media in message.media {
|
||||
switch media {
|
||||
case _ as TelegramMediaImage:
|
||||
if message.text.isEmpty {
|
||||
messageText = strings.Message_Photo
|
||||
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
if enableMediaEmoji {
|
||||
messageText = "🖼 \(messageText)"
|
||||
var textIsReady = false
|
||||
if messages.count > 1 {
|
||||
let groupType = messageGroupType(messages: messages)
|
||||
switch groupType {
|
||||
case .photos:
|
||||
if !messageText.isEmpty {
|
||||
textIsReady = true
|
||||
} else {
|
||||
messageText = strings.ChatList_MessagePhotos(Int32(messages.count))
|
||||
textIsReady = true
|
||||
}
|
||||
case .videos:
|
||||
if !messageText.isEmpty {
|
||||
textIsReady = true
|
||||
} else {
|
||||
messageText = strings.ChatList_MessageVideos(Int32(messages.count))
|
||||
textIsReady = true
|
||||
}
|
||||
case .generic:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !textIsReady {
|
||||
for media in message.media {
|
||||
switch media {
|
||||
case _ as TelegramMediaImage:
|
||||
if message.text.isEmpty {
|
||||
messageText = strings.Message_Photo
|
||||
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
if enableMediaEmoji {
|
||||
messageText = "🖼 \(messageText)"
|
||||
}
|
||||
}
|
||||
}
|
||||
case let fileMedia as TelegramMediaFile:
|
||||
var processed = false
|
||||
inner: for attribute in fileMedia.attributes {
|
||||
switch attribute {
|
||||
case .Animated:
|
||||
messageText = strings.Message_Animation
|
||||
processed = true
|
||||
break inner
|
||||
case let .Audio(isVoice, _, title, performer, _):
|
||||
if !message.text.isEmpty {
|
||||
messageText = "🎤 \(messageText)"
|
||||
} else if isVoice {
|
||||
if message.text.isEmpty {
|
||||
messageText = strings.Message_Audio
|
||||
} else {
|
||||
case let fileMedia as TelegramMediaFile:
|
||||
var processed = false
|
||||
inner: for attribute in fileMedia.attributes {
|
||||
switch attribute {
|
||||
case .Animated:
|
||||
messageText = strings.Message_Animation
|
||||
processed = true
|
||||
break inner
|
||||
case let .Audio(isVoice, _, title, performer, _):
|
||||
if !message.text.isEmpty {
|
||||
messageText = "🎤 \(messageText)"
|
||||
}
|
||||
processed = true
|
||||
break inner
|
||||
} else {
|
||||
let descriptionString: String
|
||||
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
|
||||
descriptionString = title + " — " + performer
|
||||
} else if let title = title, !title.isEmpty {
|
||||
descriptionString = title
|
||||
} else if let performer = performer, !performer.isEmpty {
|
||||
descriptionString = performer
|
||||
} else if let fileName = fileMedia.fileName {
|
||||
descriptionString = fileName
|
||||
} else {
|
||||
descriptionString = strings.Message_Audio
|
||||
}
|
||||
messageText = descriptionString
|
||||
processed = true
|
||||
break inner
|
||||
}
|
||||
case let .Sticker(displayText, _, _):
|
||||
if displayText.isEmpty {
|
||||
messageText = strings.Message_Sticker
|
||||
processed = true
|
||||
break inner
|
||||
} else {
|
||||
messageText = strings.Message_StickerText(displayText).0
|
||||
processed = true
|
||||
break inner
|
||||
}
|
||||
case let .Video(_, _, flags):
|
||||
if flags.contains(.instantRoundVideo) {
|
||||
messageText = strings.Message_VideoMessage
|
||||
processed = true
|
||||
break inner
|
||||
} else {
|
||||
if message.text.isEmpty {
|
||||
messageText = strings.Message_Video
|
||||
processed = true
|
||||
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
if !fileMedia.isAnimated && enableMediaEmoji {
|
||||
messageText = "📹 \(messageText)"
|
||||
processed = true
|
||||
} else if isVoice {
|
||||
if message.text.isEmpty {
|
||||
messageText = strings.Message_Audio
|
||||
} else {
|
||||
messageText = "🎤 \(messageText)"
|
||||
}
|
||||
processed = true
|
||||
break inner
|
||||
} else {
|
||||
let descriptionString: String
|
||||
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
|
||||
descriptionString = title + " — " + performer
|
||||
} else if let title = title, !title.isEmpty {
|
||||
descriptionString = title
|
||||
} else if let performer = performer, !performer.isEmpty {
|
||||
descriptionString = performer
|
||||
} else if let fileName = fileMedia.fileName {
|
||||
descriptionString = fileName
|
||||
} else {
|
||||
descriptionString = strings.Message_Audio
|
||||
}
|
||||
messageText = descriptionString
|
||||
processed = true
|
||||
break inner
|
||||
}
|
||||
case let .Sticker(displayText, _, _):
|
||||
if displayText.isEmpty {
|
||||
messageText = strings.Message_Sticker
|
||||
processed = true
|
||||
break inner
|
||||
} else {
|
||||
messageText = strings.Message_StickerText(displayText).0
|
||||
processed = true
|
||||
break inner
|
||||
}
|
||||
case let .Video(_, _, flags):
|
||||
if flags.contains(.instantRoundVideo) {
|
||||
messageText = strings.Message_VideoMessage
|
||||
processed = true
|
||||
break inner
|
||||
} else {
|
||||
if message.text.isEmpty {
|
||||
messageText = strings.Message_Video
|
||||
processed = true
|
||||
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
if enableMediaEmoji {
|
||||
if !fileMedia.isAnimated {
|
||||
messageText = "📹 \(messageText)"
|
||||
}
|
||||
}
|
||||
processed = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if !processed {
|
||||
if !message.text.isEmpty {
|
||||
messageText = "📎 \(messageText)"
|
||||
} else {
|
||||
if fileMedia.isAnimatedSticker {
|
||||
messageText = strings.Message_Sticker
|
||||
} else {
|
||||
if let fileName = fileMedia.fileName {
|
||||
messageText = fileName
|
||||
} else {
|
||||
messageText = strings.Message_File
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case let location as TelegramMediaMap:
|
||||
if location.liveBroadcastingTimeout != nil {
|
||||
messageText = strings.Message_LiveLocation
|
||||
} else {
|
||||
messageText = strings.Message_Location
|
||||
}
|
||||
case _ as TelegramMediaContact:
|
||||
messageText = strings.Message_Contact
|
||||
case let game as TelegramMediaGame:
|
||||
messageText = "🎮 \(game.title)"
|
||||
case let invoice as TelegramMediaInvoice:
|
||||
messageText = invoice.title
|
||||
case let action as TelegramMediaAction:
|
||||
switch action.action {
|
||||
case let .phoneCall(_, discardReason, _):
|
||||
hideAuthor = !isPeerGroup
|
||||
let incoming = message.flags.contains(.Incoming)
|
||||
if let discardReason = discardReason {
|
||||
switch discardReason {
|
||||
case .busy, .disconnect:
|
||||
messageText = strings.Notification_CallCanceled
|
||||
case .missed:
|
||||
messageText = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
|
||||
case .hangup:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if messageText.isEmpty {
|
||||
if incoming {
|
||||
messageText = strings.Notification_CallIncoming
|
||||
} else {
|
||||
messageText = strings.Notification_CallOutgoing
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
hideAuthor = true
|
||||
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) {
|
||||
messageText = text
|
||||
}
|
||||
}
|
||||
}
|
||||
if !processed {
|
||||
if !message.text.isEmpty {
|
||||
messageText = "📎 \(messageText)"
|
||||
} else {
|
||||
if fileMedia.isAnimatedSticker {
|
||||
messageText = strings.Message_Sticker
|
||||
} else {
|
||||
if let fileName = fileMedia.fileName {
|
||||
messageText = fileName
|
||||
} else {
|
||||
messageText = strings.Message_File
|
||||
}
|
||||
}
|
||||
case _ as TelegramMediaExpiredContent:
|
||||
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) {
|
||||
messageText = text
|
||||
}
|
||||
}
|
||||
case let location as TelegramMediaMap:
|
||||
if location.liveBroadcastingTimeout != nil {
|
||||
messageText = strings.Message_LiveLocation
|
||||
} else {
|
||||
messageText = strings.Message_Location
|
||||
}
|
||||
case _ as TelegramMediaContact:
|
||||
messageText = strings.Message_Contact
|
||||
case let game as TelegramMediaGame:
|
||||
messageText = "🎮 \(game.title)"
|
||||
case let invoice as TelegramMediaInvoice:
|
||||
messageText = invoice.title
|
||||
case let action as TelegramMediaAction:
|
||||
switch action.action {
|
||||
case let .phoneCall(_, discardReason, _):
|
||||
hideAuthor = !isPeerGroup
|
||||
let incoming = message.flags.contains(.Incoming)
|
||||
if let discardReason = discardReason {
|
||||
switch discardReason {
|
||||
case .busy, .disconnect:
|
||||
messageText = strings.Notification_CallCanceled
|
||||
case .missed:
|
||||
messageText = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
|
||||
case .hangup:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if messageText.isEmpty {
|
||||
if incoming {
|
||||
messageText = strings.Notification_CallIncoming
|
||||
} else {
|
||||
messageText = strings.Notification_CallOutgoing
|
||||
}
|
||||
}
|
||||
default:
|
||||
hideAuthor = true
|
||||
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) {
|
||||
messageText = text
|
||||
}
|
||||
}
|
||||
case _ as TelegramMediaExpiredContent:
|
||||
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) {
|
||||
messageText = text
|
||||
}
|
||||
case let poll as TelegramMediaPoll:
|
||||
messageText = "📊 \(poll.text)"
|
||||
case let dice as TelegramMediaDice:
|
||||
messageText = dice.emoji
|
||||
default:
|
||||
break
|
||||
case let poll as TelegramMediaPoll:
|
||||
messageText = "📊 \(poll.text)"
|
||||
case let dice as TelegramMediaDice:
|
||||
messageText = dice.emoji
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -176,10 +176,10 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
nodeInteraction.additionalCategorySelected(id)
|
||||
}
|
||||
), directionHint: entry.directionHint)
|
||||
case let .PeerEntry(index, presentationData, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact):
|
||||
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact):
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(messages: messages, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .peers(filter, isSelecting, _, filters):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
@@ -290,10 +290,10 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
return entries.map { entry -> ListViewUpdateItem in
|
||||
switch entry.entry {
|
||||
case let .PeerEntry(index, presentationData, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact):
|
||||
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact):
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(messages: messages, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .peers(filter, isSelecting, _, filters):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
|
||||
@@ -46,7 +46,7 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
|
||||
|
||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
case HeaderEntry
|
||||
case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, message: Message?, readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool)
|
||||
case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, messages: [Message], readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool)
|
||||
case HoleEntry(ChatListHole, theme: PresentationTheme)
|
||||
case GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, editing: Bool, unreadState: PeerGroupUnreadCountersCombinedSummary, revealed: Bool, hiddenByDefault: Bool)
|
||||
case ArchiveIntro(presentationData: ChatListPresentationData)
|
||||
@@ -98,27 +98,33 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessage, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact):
|
||||
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact):
|
||||
switch rhs {
|
||||
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessage, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact):
|
||||
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact):
|
||||
if lhsIndex != rhsIndex {
|
||||
return false
|
||||
}
|
||||
if lhsPresentationData !== rhsPresentationData {
|
||||
return false
|
||||
}
|
||||
if lhsMessage?.stableVersion != rhsMessage?.stableVersion {
|
||||
if lhsUnreadCount != rhsUnreadCount {
|
||||
return false
|
||||
}
|
||||
if lhsMessage?.id != rhsMessage?.id || lhsMessage?.flags != rhsMessage?.flags || lhsUnreadCount != rhsUnreadCount {
|
||||
if lhsMessages.count != rhsMessages.count {
|
||||
return false
|
||||
}
|
||||
if let lhsMessage = lhsMessage, let rhsMessage = rhsMessage {
|
||||
if lhsMessage.associatedMessages.count != rhsMessage.associatedMessages.count {
|
||||
for i in 0 ..< lhsMessages.count {
|
||||
if lhsMessages[i].stableVersion != rhsMessages[i].stableVersion {
|
||||
return false
|
||||
}
|
||||
for (id, message) in lhsMessage.associatedMessages {
|
||||
if let otherMessage = rhsMessage.associatedMessages[id] {
|
||||
if lhsMessages[i].id != rhsMessages[i].id {
|
||||
return false
|
||||
}
|
||||
if lhsMessages[i].associatedMessages.count != rhsMessages[i].associatedMessages.count {
|
||||
return false
|
||||
}
|
||||
for (id, message) in lhsMessages[i].associatedMessages {
|
||||
if let otherMessage = rhsMessages[i].associatedMessages[id] {
|
||||
if message.stableVersion != otherMessage.stableVersion {
|
||||
return false
|
||||
}
|
||||
@@ -295,20 +301,20 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
var filterAfterHole = false
|
||||
loop: for entry in view.entries {
|
||||
switch entry {
|
||||
case let .MessageEntry(index, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
|
||||
case let .MessageEntry(index, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
|
||||
if let savedMessagesPeer = savedMessagesPeer, savedMessagesPeer.id == index.messageIndex.id.peerId {
|
||||
continue loop
|
||||
}
|
||||
if state.pendingRemovalPeerIds.contains(index.messageIndex.id.peerId) {
|
||||
continue loop
|
||||
}
|
||||
var updatedMessage = message
|
||||
var updatedMessages = messages
|
||||
var updatedCombinedReadState = combinedReadState
|
||||
if state.pendingClearHistoryPeerIds.contains(index.messageIndex.id.peerId) {
|
||||
updatedMessage = nil
|
||||
updatedMessages = []
|
||||
updatedCombinedReadState = nil
|
||||
}
|
||||
result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: updatedMessage, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], promoInfo: nil, hasFailedMessages: hasFailed, isContact: isContact))
|
||||
result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], promoInfo: nil, hasFailedMessages: hasFailed, isContact: isContact))
|
||||
case let .HoleEntry(hole):
|
||||
if hole.index.timestamp == Int32.max - 1 {
|
||||
//return ([.HeaderEntry], true)
|
||||
@@ -321,7 +327,7 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
|
||||
|
||||
if let savedMessagesPeer = savedMessagesPeer {
|
||||
result.append(.PeerEntry(index: ChatListIndex.absoluteUpperBound.predecessor, presentationData: state.presentationData, message: nil, readState: nil, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: nil, peer: RenderedPeer(peerId: savedMessagesPeer.id, peers: SimpleDictionary([savedMessagesPeer.id: savedMessagesPeer])), presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), editing: state.editing, hasActiveRevealControls: false, selected: false, inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false))
|
||||
result.append(.PeerEntry(index: ChatListIndex.absoluteUpperBound.predecessor, presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: nil, peer: RenderedPeer(peerId: savedMessagesPeer.id, peers: SimpleDictionary([savedMessagesPeer.id: savedMessagesPeer])), presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), editing: state.editing, hasActiveRevealControls: false, selected: false, inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false))
|
||||
} else {
|
||||
if !filteredAdditionalItemEntries.isEmpty {
|
||||
for item in filteredAdditionalItemEntries.reversed() {
|
||||
@@ -336,8 +342,8 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
promoInfo = .psa(type: type, message: message)
|
||||
}
|
||||
switch item.entry {
|
||||
case let .MessageEntry(index, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
|
||||
result.append(.PeerEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex), presentationData: state.presentationData, message: message, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], promoInfo: promoInfo, hasFailedMessages: hasFailed, isContact: isContact))
|
||||
case let .MessageEntry(index, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
|
||||
result.append(.PeerEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex), presentationData: state.presentationData, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], promoInfo: promoInfo, hasFailedMessages: hasFailed, isContact: isContact))
|
||||
if pinningIndex != 0 {
|
||||
pinningIndex -= 1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user