diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index a1d0129fa8..87ae1128f8 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -1237,7 +1237,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie } else { return nil } - case let .groupReference(groupId, _, _, _): + case let .groupReference(groupId, _, _, _, _): let chatListController = ChatListController(context: self.context, groupId: groupId, controlsHistoryPreload: false) chatListController.containerLayoutUpdated(ContainerViewLayout(size: contentSize, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) return (chatListController, sourceRect) diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index 25879a0bdb..d211fae9c4 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -8,7 +8,7 @@ import TelegramCore enum ChatListItemContent { case peer(message: Message?, peer: RenderedPeer, combinedReadState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, embeddedState: PeerChatListEmbeddedInterfaceState?, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool, ignoreUnreadBadge: Bool) - case groupReference(groupId: PeerGroupId, peers: [RenderedPeer], unreadState: PeerGroupUnreadCountersCombinedSummary, hiddenByDefault: Bool) + case groupReference(groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, unreadState: PeerGroupUnreadCountersCombinedSummary, hiddenByDefault: Bool) var chatLocation: ChatLocation? { switch self { @@ -22,7 +22,7 @@ enum ChatListItemContent { class ChatListItem: ListViewItem { let presentationData: ChatListPresentationData - let account: Account + let context: AccountContext let peerGroupId: PeerGroupId let index: ChatListIndex let content: ChatListItemContent @@ -41,10 +41,10 @@ class ChatListItem: ListViewItem { let header: ListViewItemHeader? - init(presentationData: ChatListPresentationData, account: Account, peerGroupId: PeerGroupId, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { + init(presentationData: ChatListPresentationData, context: AccountContext, peerGroupId: PeerGroupId, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { self.presentationData = presentationData self.peerGroupId = peerGroupId - self.account = account + self.context = context self.index = index self.content = content self.editing = editing @@ -113,7 +113,7 @@ class ChatListItem: ListViewItem { } else if let peer = peer.peers[peer.peerId] { self.interaction.peerSelected(peer) } - case let .groupReference(groupId, _, _, _): + case let .groupReference(groupId, _, _, _, _): self.interaction.groupSelected(groupId) } } @@ -275,6 +275,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let titleNode: TextNode let authorNode: TextNode let textNode: TextNode + let contentImageNode: TransformImageNode let inputActivitiesNode: ChatListInputActivitiesNode let dateNode: TextNode let separatorNode: ASDisplayNode @@ -293,6 +294,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { private var peerPresenceManager: PeerPresenceStatusManager? 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 @@ -358,7 +360,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { result += "Outgoing message" } - let (_, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: peer.message, chatPeer: peer.peer, accountPeerId: item.account.peerId) + let (_, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: peer.message, chatPeer: peer.peer, accountPeerId: item.context.account.peerId) 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))" } @@ -397,6 +399,9 @@ 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 @@ -435,6 +440,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.addSubnode(self.titleNode) self.addSubnode(self.authorNode) self.addSubnode(self.textNode) + self.addSubnode(self.contentImageNode) self.addSubnode(self.dateNode) self.addSubnode(self.statusNode) self.addSubnode(self.pinnedIconNode) @@ -463,15 +469,15 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { UIView.transition(with: self.avatarNode.view, duration: 0.3, options: [.transitionCrossDissolve], animations: { }, completion: nil) } - self.avatarNode.setPeer(account: item.account, theme: item.presentationData.theme, peer: peer, overrideImage: .archivedChatsIcon(hiddenByDefault: groupReference.hiddenByDefault), emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) + self.avatarNode.setPeer(account: item.context.account, theme: item.presentationData.theme, peer: peer, overrideImage: .archivedChatsIcon(hiddenByDefault: groupReference.hiddenByDefault), emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) } if let peer = peer { var overrideImage: AvatarNodeImageOverride? - if peer.id == item.account.peerId { + if peer.id == item.context.account.peerId { overrideImage = .savedMessagesIcon } - self.avatarNode.setPeer(account: item.account, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) + self.avatarNode.setPeer(account: item.context.account, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) } } @@ -562,13 +568,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode) let currentItem = self.layoutParams?.0 + let currentContentImageMedia = self.contentImageMedia return { item, params, first, last, firstWithHeader, nextIsPinned in - let account = item.account + let account = item.context.account var message: Message? enum ContentPeer { case chat(RenderedPeer) - case group([RenderedPeer]) + case group([ChatListGroupReferencePeer]) } let contentPeer: ContentPeer let combinedReadState: CombinedPeerReadState? @@ -604,9 +611,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { inputActivities = inputActivitiesValue isPeerGroup = false isAd = isAdValue - case let .groupReference(_, peers, unreadState, hiddenByDefault): - contentPeer = .group(peers) - message = nil + case let .groupReference(_, peers, messageValue, unreadState, hiddenByDefault): + if let messageValue = messageValue, !peers.isEmpty { + contentPeer = .chat(peers[0].peer) + } else { + contentPeer = .group(peers) + } + message = messageValue combinedReadState = nil notificationSettings = nil embeddedState = nil @@ -638,6 +649,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var authorAttributedString: NSAttributedString? var textAttributedString: NSAttributedString? + var textLeftCutout: CGFloat = 0.0 var dateAttributedString: NSAttributedString? var titleAttributedString: NSAttributedString? var badgeContent = ChatListBadgeContent.none @@ -672,11 +684,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { editingOffset = 0.0 } + let enableChatListPhotos = item.context.sharedContext.immediateExperimentalUISettings.chatListPhotos + let leftInset: CGFloat = params.leftInset + 78.0 enum ContentData { case chat(itemPeer: RenderedPeer, peer: Peer?, hideAuthor: Bool, messageText: String) - case group(peers: [RenderedPeer]) + case group(peers: [ChatListGroupReferencePeer]) } let contentData: ContentData @@ -684,7 +698,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var hideAuthor = false switch contentPeer { case let .chat(itemPeer): - let (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: message, chatPeer: itemPeer, accountPeerId: item.account.peerId) + let (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: message, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos) contentData = .chat(itemPeer: itemPeer, peer: peer, hideAuthor: hideAuthor, messageText: messageText) hideAuthor = initialHideAuthor case let .group(groupPeers): @@ -698,14 +712,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var inlineAuthorPrefix: String? if case .groupReference = item.content { if let author = message?.author as? TelegramUser { - if author.id == item.account.peerId { + if author.id == item.context.account.peerId { inlineAuthorPrefix = item.presentationData.strings.DialogList_You - } else { + } else if message?.id.peerId.namespace != Namespaces.Peer.CloudUser && message?.id.peerId.namespace != Namespaces.Peer.SecretChat { inlineAuthorPrefix = author.compactDisplayTitle } } } + var contentImageMedia: Media? + switch contentData { case let .chat(itemPeer, peer, _, messageText): if inlineAuthorPrefix == nil, let embeddedState = embeddedState as? ChatEmbeddedInterfaceState { @@ -738,6 +754,22 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let peerText = peerText { 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 { attributedText = NSAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor) @@ -753,24 +785,27 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } case let .group(peers): - var textString = "" + let textString = NSMutableAttributedString(string: "") + var isFirst = true for peer in peers { - if let peer = peer.chatMainPeer { - let peerTitle = peer.compactDisplayTitle + if let chatMainPeer = peer.peer.chatMainPeer { + let peerTitle = chatMainPeer.compactDisplayTitle if !peerTitle.isEmpty { - if !textString.isEmpty { - textString.append(", ") + if isFirst { + isFirst = false + } else { + textString.append(NSAttributedString(string: ", ", font: textFont, textColor: theme.messageTextColor)) } - textString.append(peerTitle) + textString.append(NSAttributedString(string: peerTitle, font: textFont, textColor: peer.isUnread ? theme.authorNameColor : theme.messageTextColor)) } } } - attributedText = NSAttributedString(string: textString, font: textFont, textColor: theme.messageTextColor) + attributedText = textString } switch contentData { case let .chat(_, peer, _, _): - if peer?.id == item.account.peerId { + if peer?.id == item.context.account.peerId { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleFont, textColor: theme.titleColor) } else if let displayTitle = peer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) { titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: item.index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat ? theme.secretTitleColor : theme.titleColor) @@ -942,7 +977,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let (authorLayout, authorApply) = authorLayout(TextNodeLayoutArguments(attributedString: hideAuthor ? nil : authorAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentRect.width - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, 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: rawContentRect.width - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))) + var textCutout: TextNodeCutout? + if !textLeftCutout.isZero { + textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 4.0), topRight: nil, bottomRight: nil) + } + let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentRect.width - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))) let titleRect = CGRect(origin: rawContentRect.origin, size: CGSize(width: rawContentRect.width - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth, height: rawContentRect.height)) let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRect.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -961,7 +1000,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let peerLeftRevealOptions: [ItemListRevealOption] switch item.content { case let .peer(_, renderedPeer, _, _, presence, _ ,_ ,_, _, _): - if let peer = renderedPeer.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.account.peerId { + if let peer = renderedPeer.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId { let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: timestamp) if case .online = relativeStatus { online = true @@ -971,9 +1010,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let isPinned = item.index.pinningIndex != nil if item.enableContextActions && !isAd { - peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: item.account.peerId != item.index.messageIndex.id.peerId ? (currentMutedIconImage != nil) : nil, groupId: item.peerGroupId, peerId: renderedPeer.peerId, accountPeerId: item.account.peerId, canDelete: true, isEditing: item.editing) + peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: item.context.account.peerId != item.index.messageIndex.id.peerId ? (currentMutedIconImage != nil) : nil, groupId: item.peerGroupId, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing) if case let .chat(itemPeer) = contentPeer { - peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.account.peerId, groupId: item.peerGroupId) + peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, groupId: item.peerGroupId) } else { peerLeftRevealOptions = [] } @@ -986,6 +1025,20 @@ 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 { @@ -999,9 +1052,43 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: max(0.0, itemHeight + heightOffset)), insets: insets) + let contentImageSize = CGSize(width: 22.0, height: 22.0) + return (layout, { [weak self] synchronousLoads, animated in if let strongSelf = self { strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize) + strongSelf.contentImageMedia = contentImageMedia + + var dimensions: CGSize? + if let contentImageMedia = contentImageMedia as? TelegramMediaImage { + dimensions = largestRepresentationForPhoto(contentImageMedia)?.dimensions + } else if let contentImageMedia = contentImageMedia as? TelegramMediaFile { + dimensions = contentImageMedia.dimensions + } + + 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) } @@ -1220,6 +1307,12 @@ 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: 0.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 { @@ -1411,8 +1504,13 @@ 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)) - let textFrame = self.textNode.frame - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: textFrame.origin.y), size: textFrame.size)) + var textFrame = self.textNode.frame + textFrame.origin.x = contentRect.origin.x + transition.updateFrame(node: self.textNode, frame: CGRect(origin: textFrame.origin, size: textFrame.size)) + + var contentImageFrame = self.contentImageNode.frame + contentImageFrame.origin = textFrame.origin.offsetBy(dx: 1.0, dy: 0.0) + transition.updateFrame(node: self.contentImageNode, frame: contentImageFrame) 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)) diff --git a/TelegramUI/ChatListItemStrings.swift b/TelegramUI/ChatListItemStrings.swift index 64bb5a1c2e..722ec952ac 100644 --- a/TelegramUI/ChatListItemStrings.swift +++ b/TelegramUI/ChatListItemStrings.swift @@ -2,7 +2,7 @@ import Foundation import Postbox import TelegramCore -public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message?, chatPeer: RenderedPeer, accountPeerId: PeerId) -> (peer: Peer?, hideAuthor: Bool, messageText: String) { +public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message?, chatPeer: RenderedPeer, accountPeerId: PeerId, enableMediaEmoji: Bool = true) -> (peer: Peer?, hideAuthor: Bool, messageText: String) { let peer: Peer? var hideAuthor = false @@ -22,7 +22,9 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: if message.text.isEmpty { messageText = strings.Message_Photo } else if #available(iOSApplicationExtension 9.0, *) { - messageText = "🖼 \(messageText)" + if enableMediaEmoji { + messageText = "🖼 \(messageText)" + } } case let fileMedia as TelegramMediaFile: if message.text.isEmpty { @@ -75,7 +77,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: if message.text.isEmpty { isVideo = true } else if #available(iOSApplicationExtension 9.0, *) { - if !fileMedia.isAnimated { + if !fileMedia.isAnimated && enableMediaEmoji { messageText = "📹 \(messageText)" } break inner diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index 8be973ec39..6f66b9a365 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -137,13 +137,13 @@ struct ChatListNodeState: Equatable { } } -private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { +private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { return entries.map { entry -> ListViewInsertItem in switch entry.entry { case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd): switch mode { case .chatList: - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), 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, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) case let .peers(filter): let itemPeer = peer.chatMainPeer var chatPeer: Peer? @@ -202,7 +202,7 @@ private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNode } } - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(theme: presentationData.theme, strings: presentationData.strings, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, account: account, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(theme: presentationData.theme, strings: presentationData.strings, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, account: context.account, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in if let chatPeer = chatPeer { nodeInteraction.peerSelected(chatPeer) } @@ -210,21 +210,21 @@ private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNode } case let .HoleEntry(_, theme): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint) - case let .GroupReferenceEntry(index, presentationData, groupId, peers, editing, unreadState, revealed, hiddenByDefault): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, peers: peers, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint) + case let .GroupReferenceEntry(index, presentationData, groupId, peers, message, editing, unreadState, revealed, hiddenByDefault): + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint) case let .ArchiveIntro(presentationData): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) } } } -private func mappedUpdateEntries(account: Account, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { +private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { return entries.map { entry -> ListViewUpdateItem in switch entry.entry { case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd): switch mode { case .chatList: - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), 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, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) case let .peers(filter): let itemPeer = peer.chatMainPeer var chatPeer: Peer? @@ -241,7 +241,7 @@ private func mappedUpdateEntries(account: Account, nodeInteraction: ChatListNode enabled = false } } - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(theme: presentationData.theme, strings: presentationData.strings, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, account: account, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(theme: presentationData.theme, strings: presentationData.strings, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, account: context.account, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in if let chatPeer = chatPeer { nodeInteraction.peerSelected(chatPeer) } @@ -249,16 +249,16 @@ private func mappedUpdateEntries(account: Account, nodeInteraction: ChatListNode } case let .HoleEntry(_, theme): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint) - case let .GroupReferenceEntry(index, presentationData, groupId, peers, editing, unreadState, revealed, hiddenByDefault): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, peers: peers, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint) + case let .GroupReferenceEntry(index, presentationData, groupId, peers, message, editing, unreadState, revealed, hiddenByDefault): + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint) case let .ArchiveIntro(presentationData): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) } } } -private func mappedChatListNodeViewListTransition(account: Account, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { - return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(account: account, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange) +private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { + return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange) } private final class ChatListOpaqueTransactionState { @@ -688,7 +688,7 @@ final class ChatListNode: ListView { } return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode) - |> map({ mappedChatListNodeViewListTransition(account: context.account, nodeInteraction: nodeInteraction, peerGroupId: groupId, mode: mode, transition: $0) }) + |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, peerGroupId: groupId, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue) } diff --git a/TelegramUI/ChatListNodeEntries.swift b/TelegramUI/ChatListNodeEntries.swift index fa8fba18bf..4f8b0c9a7f 100644 --- a/TelegramUI/ChatListNodeEntries.swift +++ b/TelegramUI/ChatListNodeEntries.swift @@ -12,7 +12,7 @@ enum ChatListNodeEntryId: Hashable { enum ChatListNodeEntry: Comparable, Identifiable { case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, message: Message?, readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool) case HoleEntry(ChatListHole, theme: PresentationTheme) - case GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, peers: [RenderedPeer], editing: Bool, unreadState: PeerGroupUnreadCountersCombinedSummary, revealed: Bool, hiddenByDefault: Bool) + case GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, editing: Bool, unreadState: PeerGroupUnreadCountersCombinedSummary, revealed: Bool, hiddenByDefault: Bool) case ArchiveIntro(presentationData: ChatListPresentationData) var sortIndex: ChatListIndex { @@ -21,7 +21,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { return index case let .HoleEntry(hole, _): return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) - case let .GroupReferenceEntry(index, _, _, _, _, _, _, _): + case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _): return index case .ArchiveIntro: return ChatListIndex.absoluteUpperBound @@ -34,7 +34,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { return .PeerId(index.messageIndex.id.peerId.toInt64()) case let .HoleEntry(hole, _): return .Hole(Int64(hole.index.id.id)) - case let .GroupReferenceEntry(_, _, groupId, _, _, _, _, _): + case let .GroupReferenceEntry(_, _, groupId, _, _, _, _, _, _): return .GroupId(groupId) case .ArchiveIntro: return .ArchiveIntro @@ -128,8 +128,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { default: return false } - case let .GroupReferenceEntry(lhsIndex, lhsPresentationData, lhsGroupId, lhsPeers, lhsEditing, lhsUnreadState, lhsRevealed, lhsHiddenByDefault): - if case let .GroupReferenceEntry(rhsIndex, rhsPresentationData, rhsGroupId, rhsPeers, rhsEditing, rhsUnreadState, rhsRevealed, rhsHiddenByDefault) = rhs { + case let .GroupReferenceEntry(lhsIndex, lhsPresentationData, lhsGroupId, lhsPeers, lhsMessage, lhsEditing, lhsUnreadState, lhsRevealed, lhsHiddenByDefault): + if case let .GroupReferenceEntry(rhsIndex, rhsPresentationData, rhsGroupId, rhsPeers, rhsMessage, rhsEditing, rhsUnreadState, rhsRevealed, rhsHiddenByDefault) = rhs { if lhsIndex != rhsIndex { return false } @@ -142,6 +142,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhsPeers != rhsPeers { return false } + if lhsMessage?.stableId != rhsMessage?.stableId { + return false + } if lhsEditing != rhsEditing { return false } @@ -242,7 +245,7 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, if view.laterIndex == nil, case .chatList = mode { for groupReference in view.groupEntries { let messageIndex = MessageIndex(id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: 0, id: 0), timestamp: 1) - result.append(.GroupReferenceEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: messageIndex), presentationData: state.presentationData, groupId: groupReference.groupId, peers: groupReference.renderedPeers, editing: state.editing, unreadState: groupReference.unreadState, revealed: state.archiveShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault)) + result.append(.GroupReferenceEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: messageIndex), presentationData: state.presentationData, groupId: groupReference.groupId, peers: groupReference.renderedPeers, message: groupReference.message, editing: state.editing, unreadState: groupReference.unreadState, revealed: state.archiveShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault)) if pinningIndex != 0 { pinningIndex -= 1 } diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index ecb980145e..7366b2afc6 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -446,7 +446,7 @@ enum ChatListSearchEntry: Comparable, Identifiable { interaction.peerSelected(peer.peer) }) case let .message(message, readState, presentationData): - return ChatListItem(presentationData: presentationData, account: context.account, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { interaction.addContact(phoneNumber) @@ -1174,7 +1174,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { switch item.content { case let .peer(message, peer, _, _, _, _, _, _, _, _): return (selectedItemNode.view, bounds, message?.id ?? peer.peerId) - case let .groupReference(groupId, _, _, _): + case let .groupReference(groupId, _, _, _, _): return (selectedItemNode.view, bounds, groupId) } } diff --git a/TelegramUI/DebugController.swift b/TelegramUI/DebugController.swift index 083d8f9ed4..837d46034b 100644 --- a/TelegramUI/DebugController.swift +++ b/TelegramUI/DebugController.swift @@ -47,6 +47,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case resetData(PresentationTheme) case resetBiometricsData(PresentationTheme) case animatedStickers(PresentationTheme) + case photoPreview(PresentationTheme, Bool) case versionInfo(PresentationTheme) var section: ItemListSectionId { @@ -59,7 +60,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .reimport, .resetData, .resetBiometricsData, .animatedStickers: + case .clearTips, .reimport, .resetData, .resetBiometricsData, .animatedStickers, .photoPreview: return DebugControllerSection.experiments.rawValue case .versionInfo: return DebugControllerSection.info.rawValue @@ -102,8 +103,10 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 15 case .animatedStickers: return 16 - case .versionInfo: + case .photoPreview: return 17 + case .versionInfo: + return 18 } } @@ -391,6 +394,16 @@ private enum DebugControllerEntry: ItemListNodeEntry { return ItemListSwitchItem(theme: theme, title: "AJSON", value: GlobalExperimentalSettings.animatedStickers, sectionId: self.section, style: .blocks, updated: { value in GlobalExperimentalSettings.animatedStickers = value }) + case let .photoPreview(theme, value): + return ItemListSwitchItem(theme: theme, title: "Photo Preview", value: value, sectionId: self.section, style: .blocks, updated: { value in + let _ = arguments.sharedContext.accountManager.transaction ({ transaction in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in + var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings + settings.chatListPhotos = value + return settings + }) + }).start() + }) case let .versionInfo(theme): let bundle = Bundle.main let bundleId = bundle.bundleIdentifier ?? "" @@ -425,6 +438,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS entries.append(.reimport(presentationData.theme)) } entries.append(.resetData(presentationData.theme)) + entries.append(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos)) entries.append(.versionInfo(presentationData.theme)) return entries diff --git a/TelegramUI/ExperimentalUISettings.swift b/TelegramUI/ExperimentalUISettings.swift index be6d184ce2..51b477d694 100644 --- a/TelegramUI/ExperimentalUISettings.swift +++ b/TelegramUI/ExperimentalUISettings.swift @@ -6,27 +6,31 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { public var keepChatNavigationStack: Bool public var skipReadHistory: Bool public var crashOnLongQueries: Bool + public var chatListPhotos: Bool public static var defaultSettings: ExperimentalUISettings { - return ExperimentalUISettings(keepChatNavigationStack: false, skipReadHistory: false, crashOnLongQueries: false) + return ExperimentalUISettings(keepChatNavigationStack: false, skipReadHistory: false, crashOnLongQueries: false, chatListPhotos: false) } - public init(keepChatNavigationStack: Bool, skipReadHistory: Bool, crashOnLongQueries: Bool) { + public init(keepChatNavigationStack: Bool, skipReadHistory: Bool, crashOnLongQueries: Bool, chatListPhotos: Bool) { self.keepChatNavigationStack = keepChatNavigationStack self.skipReadHistory = skipReadHistory self.crashOnLongQueries = crashOnLongQueries + self.chatListPhotos = chatListPhotos } public init(decoder: PostboxDecoder) { self.keepChatNavigationStack = decoder.decodeInt32ForKey("keepChatNavigationStack", orElse: 0) != 0 self.skipReadHistory = decoder.decodeInt32ForKey("skipReadHistory", orElse: 0) != 0 self.crashOnLongQueries = decoder.decodeInt32ForKey("crashOnLongQueries", orElse: 0) != 0 + self.chatListPhotos = decoder.decodeInt32ForKey("chatListPhotos", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.keepChatNavigationStack ? 1 : 0, forKey: "keepChatNavigationStack") encoder.encodeInt32(self.skipReadHistory ? 1 : 0, forKey: "skipReadHistory") encoder.encodeInt32(self.crashOnLongQueries ? 1 : 0, forKey: "crashOnLongQueries") + encoder.encodeInt32(self.chatListPhotos ? 1 : 0, forKey: "chatListPhotos") } public func isEqual(to: PreferencesEntry) -> Bool {