Files
Swiftgram/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift
Kylmakalle fd86110711 Version 11.3.1
Fixes

fix localeWithStrings globally (#30)

Fix badge on zoomed devices. closes #9

Hide channel bottom panel closes #27

Another attempt to fix badge on some Zoomed devices

Force System Share sheet tg://sg/debug

fixes for device badge

New Crowdin updates (#34)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

Fix input panel hidden on selection (#31)

* added if check for selectionState != nil

* same order of subnodes

Revert "Fix input panel hidden on selection (#31)"

This reverts commit e8a8bb1496.

Fix input panel for channels Closes #37

Quickly share links with system's share menu

force tabbar when editing

increase height for correct animation

New translations sglocalizable.strings (Ukrainian) (#38)

Hide Post Story button

Fix 10.15.1

Fix archive option for long-tap

Enable in-app Safari

Disable some unsupported purchases

disableDeleteChatSwipeOption + refactor restart alert

Hide bot in suggestions list

Fix merge v11.0

Fix exceptions for safari webview controller

New Crowdin updates (#47)

* New translations sglocalizable.strings (Romanian)

* New translations sglocalizable.strings (French)

* New translations sglocalizable.strings (Spanish)

* New translations sglocalizable.strings (Afrikaans)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Catalan)

* New translations sglocalizable.strings (Czech)

* New translations sglocalizable.strings (Danish)

* New translations sglocalizable.strings (German)

* New translations sglocalizable.strings (Greek)

* New translations sglocalizable.strings (Finnish)

* New translations sglocalizable.strings (Hebrew)

* New translations sglocalizable.strings (Hungarian)

* New translations sglocalizable.strings (Italian)

* New translations sglocalizable.strings (Japanese)

* New translations sglocalizable.strings (Korean)

* New translations sglocalizable.strings (Dutch)

* New translations sglocalizable.strings (Norwegian)

* New translations sglocalizable.strings (Polish)

* New translations sglocalizable.strings (Portuguese)

* New translations sglocalizable.strings (Serbian (Cyrillic))

* New translations sglocalizable.strings (Swedish)

* New translations sglocalizable.strings (Turkish)

* New translations sglocalizable.strings (Vietnamese)

* New translations sglocalizable.strings (Indonesian)

* New translations sglocalizable.strings (Hindi)

* New translations sglocalizable.strings (Uzbek)

New Crowdin updates (#49)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Arabic)

New translations sglocalizable.strings (Russian) (#51)

Call confirmation

WIP Settings search

Settings Search

Localize placeholder

Update AccountUtils.swift

mark mutual contact

Align back context action to left

New Crowdin updates (#54)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Ukrainian)

Independent Playground app for simulator

New translations sglocalizable.strings (Ukrainian) (#55)

Playground UIKit base and controllers

Inject SwiftUI view with overflow to AsyncDisplayKit

Launch Playgound project on simulator

Create .swiftformat

Move Playground to example

Update .swiftformat

Init SwiftUIViewController

wip

New translations sglocalizable.strings (Chinese Traditional) (#57)

Xcode 16 fixes

Fix

New translations sglocalizable.strings (Italian) (#59)

New translations sglocalizable.strings (Chinese Simplified) (#63)

Force disable CallKit integration due to missing NSE Entitlement

Fix merge

Fix whole chat translator

Sweetpad config

Bump version

11.3.1 fixes

Mutual contact placement fix

Disable Video PIP swipe

Update versions.json

Fix PIP crash
2024-12-20 09:38:13 +02:00

962 lines
39 KiB
Swift

import Foundation
import UIKit
import Postbox
import TelegramCore
import TelegramPresentationData
import MergeLists
import AccountContext
enum ChatListNodeEntryId: Hashable {
case Header
case Hole(Int64)
case PeerId(Int64)
case ThreadId(Int64)
case GroupId(EngineChatList.Group)
case ContactId(EnginePeer.Id)
case ArchiveIntro
case EmptyIntro
case SectionHeader
case Notice
case additionalCategory(Int)
}
enum ChatListNodeEntrySortIndex: Comparable {
case index(EngineChatList.Item.Index)
case additionalCategory(Int)
case sectionHeader
case contact(id: EnginePeer.Id, presence: EnginePeer.Presence)
static func <(lhs: ChatListNodeEntrySortIndex, rhs: ChatListNodeEntrySortIndex) -> Bool {
switch lhs {
case let .index(lhsIndex):
switch rhs {
case let .index(rhsIndex):
return lhsIndex < rhsIndex
case .additionalCategory:
return false
case .sectionHeader:
return true
case .contact:
return true
}
case let .additionalCategory(lhsIndex):
switch rhs {
case let .additionalCategory(rhsIndex):
return lhsIndex < rhsIndex
case .index:
return true
case .sectionHeader:
return true
case .contact:
return true
}
case .sectionHeader:
switch rhs {
case .additionalCategory, .index, .sectionHeader:
return false
case .contact:
return true
}
case let .contact(lhsId, lhsPresense):
switch rhs {
case .sectionHeader:
return false
case let .contact(rhsId, rhsPresense):
if lhsPresense != rhsPresense {
return rhsPresense.status > rhsPresense.status
} else {
return lhsId < rhsId
}
default:
return false
}
}
}
}
public enum ChatListNodeEntryPromoInfo: Equatable {
case proxy
case psa(type: String, message: String?)
}
public enum ChatListNotice: Equatable {
case clearStorage(sizeFraction: Double)
case sgUrl(id: String, title: String, text: String?, url: String, needAuth: Bool, permanent: Bool)
case setupPassword
case premiumUpgrade(discount: Int32)
case premiumAnnualDiscount(discount: Int32)
case premiumRestore(discount: Int32)
case xmasPremiumGift
case setupBirthday
case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday])
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
case premiumGrace
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
}
enum ChatListNodeEntry: Comparable, Identifiable {
struct PeerEntryData: Equatable {
var index: EngineChatList.Item.Index
var presentationData: ChatListPresentationData
var messages: [EngineMessage]
var readState: EnginePeerReadCounters?
var isRemovedFromTotalUnreadCount: Bool
var draftState: ChatListItemContent.DraftState?
var mediaDraftContentType: EngineChatList.MediaDraftContentType?
var peer: EngineRenderedPeer
var threadInfo: ChatListItemContent.ThreadInfo?
var presence: EnginePeer.Presence?
var hasUnseenMentions: Bool
var hasUnseenReactions: Bool
var editing: Bool
var hasActiveRevealControls: Bool
var selected: Bool
var inputActivities: [(EnginePeer, PeerInputActivity)]?
var promoInfo: ChatListNodeEntryPromoInfo?
var hasFailedMessages: Bool
var isContact: Bool
var autoremoveTimeout: Int32?
var forumTopicData: EngineChatList.ForumTopicData?
var topForumTopicItems: [EngineChatList.ForumTopicData]
var revealed: Bool
var storyState: ChatListNodeState.StoryState?
var requiresPremiumForMessaging: Bool
var displayAsTopicList: Bool
init(
index: EngineChatList.Item.Index,
presentationData: ChatListPresentationData,
messages: [EngineMessage],
readState: EnginePeerReadCounters?,
isRemovedFromTotalUnreadCount: Bool,
draftState: ChatListItemContent.DraftState?,
mediaDraftContentType: EngineChatList.MediaDraftContentType?,
peer: EngineRenderedPeer,
threadInfo: ChatListItemContent.ThreadInfo?,
presence: EnginePeer.Presence?,
hasUnseenMentions: Bool,
hasUnseenReactions: Bool,
editing: Bool,
hasActiveRevealControls: Bool,
selected: Bool,
inputActivities: [(EnginePeer, PeerInputActivity)]?,
promoInfo: ChatListNodeEntryPromoInfo?,
hasFailedMessages: Bool,
isContact: Bool,
autoremoveTimeout: Int32?,
forumTopicData: EngineChatList.ForumTopicData?,
topForumTopicItems: [EngineChatList.ForumTopicData],
revealed: Bool,
storyState: ChatListNodeState.StoryState?,
requiresPremiumForMessaging: Bool,
displayAsTopicList: Bool
) {
self.index = index
self.presentationData = presentationData
self.messages = messages
self.readState = readState
self.isRemovedFromTotalUnreadCount = isRemovedFromTotalUnreadCount
self.draftState = draftState
self.mediaDraftContentType = mediaDraftContentType
self.peer = peer
self.threadInfo = threadInfo
self.presence = presence
self.hasUnseenMentions = hasUnseenMentions
self.hasUnseenReactions = hasUnseenReactions
self.editing = editing
self.hasActiveRevealControls = hasActiveRevealControls
self.selected = selected
self.inputActivities = inputActivities
self.promoInfo = promoInfo
self.hasFailedMessages = hasFailedMessages
self.isContact = isContact
self.autoremoveTimeout = autoremoveTimeout
self.forumTopicData = forumTopicData
self.topForumTopicItems = topForumTopicItems
self.revealed = revealed
self.storyState = storyState
self.requiresPremiumForMessaging = requiresPremiumForMessaging
self.displayAsTopicList = displayAsTopicList
}
static func ==(lhs: PeerEntryData, rhs: PeerEntryData) -> Bool {
if lhs.index != rhs.index {
return false
}
if lhs.presentationData !== rhs.presentationData {
return false
}
if lhs.readState != rhs.readState {
return false
}
if lhs.messages.count != rhs.messages.count {
return false
}
for i in 0 ..< lhs.messages.count {
if lhs.messages[i].stableVersion != rhs.messages[i].stableVersion {
return false
}
if lhs.messages[i].id != rhs.messages[i].id {
return false
}
if lhs.messages[i].associatedMessages.count != rhs.messages[i].associatedMessages.count {
return false
}
for (id, message) in lhs.messages[i].associatedMessages {
if let otherMessage = rhs.messages[i].associatedMessages[id] {
if message.stableVersion != otherMessage.stableVersion {
return false
}
} else {
return false
}
}
}
if lhs.isRemovedFromTotalUnreadCount != rhs.isRemovedFromTotalUnreadCount {
return false
}
if let lhsPeerPresence = lhs.presence, let rhsPeerPresence = rhs.presence {
if lhsPeerPresence != rhsPeerPresence {
return false
}
} else if (lhs.presence != nil) != (rhs.presence != nil) {
return false
}
if let lhsEmbeddedState = lhs.draftState, let rhsEmbeddedState = rhs.draftState {
if lhsEmbeddedState != rhsEmbeddedState {
return false
}
} else if (lhs.draftState != nil) != (rhs.draftState != nil) {
return false
}
if lhs.mediaDraftContentType != rhs.mediaDraftContentType {
return false
}
if lhs.editing != rhs.editing {
return false
}
if lhs.hasActiveRevealControls != rhs.hasActiveRevealControls {
return false
}
if lhs.selected != rhs.selected {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.threadInfo != rhs.threadInfo {
return false
}
if lhs.hasUnseenMentions != rhs.hasUnseenMentions {
return false
}
if lhs.hasUnseenReactions != rhs.hasUnseenReactions {
return false
}
if let lhsInputActivities = lhs.inputActivities, let rhsInputActivities = rhs.inputActivities {
if lhsInputActivities.count != rhsInputActivities.count {
return false
}
for i in 0 ..< lhsInputActivities.count {
if lhsInputActivities[i].0 != rhsInputActivities[i].0 {
return false
}
if lhsInputActivities[i].1 != rhsInputActivities[i].1 {
return false
}
}
} else if (lhs.inputActivities != nil) != (rhs.inputActivities != nil) {
return false
}
if lhs.promoInfo != rhs.promoInfo {
return false
}
if lhs.hasFailedMessages != rhs.hasFailedMessages {
return false
}
if lhs.isContact != rhs.isContact {
return false
}
if lhs.autoremoveTimeout != rhs.autoremoveTimeout {
return false
}
if lhs.forumTopicData != rhs.forumTopicData {
return false
}
if lhs.topForumTopicItems != rhs.topForumTopicItems {
return false
}
if lhs.revealed != rhs.revealed {
return false
}
if lhs.storyState != rhs.storyState {
return false
}
if lhs.requiresPremiumForMessaging != rhs.requiresPremiumForMessaging {
return false
}
if lhs.displayAsTopicList != rhs.displayAsTopicList {
return false
}
return true
}
}
struct ContactEntryData: Equatable {
var presentationData: ChatListPresentationData
var peer: EnginePeer
var presence: EnginePeer.Presence
init(presentationData: ChatListPresentationData, peer: EnginePeer, presence: EnginePeer.Presence) {
self.presentationData = presentationData
self.peer = peer
self.presence = presence
}
static func ==(lhs: ContactEntryData, rhs: ContactEntryData) -> Bool {
if lhs.presentationData !== rhs.presentationData {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.presence != rhs.presence {
return false
}
return true
}
}
struct GroupReferenceEntryData: Equatable {
var index: EngineChatList.Item.Index
var presentationData: ChatListPresentationData
var groupId: EngineChatList.Group
var peers: [EngineChatList.GroupItem.Item]
var message: EngineMessage?
var editing: Bool
var unreadCount: Int
var revealed: Bool
var hiddenByDefault: Bool
var storyState: ChatListNodeState.StoryState?
init(
index: EngineChatList.Item.Index,
presentationData: ChatListPresentationData,
groupId: EngineChatList.Group,
peers: [EngineChatList.GroupItem.Item],
message: EngineMessage?,
editing: Bool,
unreadCount: Int,
revealed: Bool,
hiddenByDefault: Bool,
storyState: ChatListNodeState.StoryState?
) {
self.index = index
self.presentationData = presentationData
self.groupId = groupId
self.peers = peers
self.message = message
self.editing = editing
self.unreadCount = unreadCount
self.revealed = revealed
self.hiddenByDefault = hiddenByDefault
self.storyState = storyState
}
static func ==(lhs: GroupReferenceEntryData, rhs: GroupReferenceEntryData) -> Bool {
if lhs.index != rhs.index {
return false
}
if lhs.presentationData !== rhs.presentationData {
return false
}
if lhs.groupId != rhs.groupId {
return false
}
if lhs.peers != rhs.peers {
return false
}
if lhs.message?.stableId != rhs.message?.stableId {
return false
}
if lhs.editing != rhs.editing {
return false
}
if lhs.unreadCount != rhs.unreadCount {
return false
}
if lhs.revealed != rhs.revealed {
return false
}
if lhs.hiddenByDefault != rhs.hiddenByDefault {
return false
}
if lhs.storyState != rhs.storyState {
return false
}
return true
}
}
case HeaderEntry
case PeerEntry(PeerEntryData)
case HoleEntry(EngineMessage.Index, theme: PresentationTheme)
case GroupReferenceEntry(GroupReferenceEntryData)
case ContactEntry(ContactEntryData)
case ArchiveIntro(presentationData: ChatListPresentationData)
case EmptyIntro(presentationData: ChatListPresentationData)
case SectionHeader(presentationData: ChatListPresentationData, displayHide: Bool)
case Notice(presentationData: ChatListPresentationData, notice: ChatListNotice)
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
var sortIndex: ChatListNodeEntrySortIndex {
switch self {
case .HeaderEntry:
return .index(.chatList(.absoluteUpperBound))
case let .PeerEntry(peerEntry):
return .index(peerEntry.index)
case let .HoleEntry(holeIndex, _):
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
case let .GroupReferenceEntry(groupReferenceEntry):
return .index(groupReferenceEntry.index)
case let .ContactEntry(contactEntry):
return .contact(id: contactEntry.peer.id, presence: contactEntry.presence)
case .ArchiveIntro:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
case .EmptyIntro:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
case .SectionHeader:
return .sectionHeader
case .Notice:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor))
case let .AdditionalCategory(index, _, _, _, _, _, _):
return .additionalCategory(index)
}
}
var stableId: ChatListNodeEntryId {
switch self {
case .HeaderEntry:
return .Header
case let .PeerEntry(peerEntry):
switch peerEntry.index {
case let .chatList(index):
return .PeerId(index.messageIndex.id.peerId.toInt64())
case let .forum(_, _, threadId, _, _):
return .ThreadId(threadId)
}
case let .HoleEntry(holeIndex, _):
return .Hole(Int64(holeIndex.id.id))
case let .GroupReferenceEntry(groupReferenceEntry):
return .GroupId(groupReferenceEntry.groupId)
case let .ContactEntry(contactEntry):
return .ContactId(contactEntry.peer.id)
case .ArchiveIntro:
return .ArchiveIntro
case .EmptyIntro:
return .EmptyIntro
case .SectionHeader:
return .SectionHeader
case .Notice:
return .Notice
case let .AdditionalCategory(_, id, _, _, _, _, _):
return .additionalCategory(id)
}
}
static func <(lhs: ChatListNodeEntry, rhs: ChatListNodeEntry) -> Bool {
return lhs.sortIndex < rhs.sortIndex
}
static func ==(lhs: ChatListNodeEntry, rhs: ChatListNodeEntry) -> Bool {
switch lhs {
case .HeaderEntry:
if case .HeaderEntry = rhs {
return true
} else {
return false
}
case let .PeerEntry(peerEntry):
if case .PeerEntry(peerEntry) = rhs {
return true
} else {
return false
}
case let .HoleEntry(lhsHole, lhsTheme):
switch rhs {
case let .HoleEntry(rhsHole, rhsTheme):
return lhsHole == rhsHole && lhsTheme === rhsTheme
default:
return false
}
case let .GroupReferenceEntry(groupReferenceEntry):
if case .GroupReferenceEntry(groupReferenceEntry) = rhs {
return true
} else {
return false
}
case let .ContactEntry(contactEntry):
if case .ContactEntry(contactEntry) = rhs {
return true
} else {
return false
}
case let .ArchiveIntro(lhsPresentationData):
if case let .ArchiveIntro(rhsPresentationData) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
return true
} else {
return false
}
case let .EmptyIntro(lhsPresentationData):
if case let .EmptyIntro(rhsPresentationData) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
return true
} else {
return false
}
case let .SectionHeader(lhsPresentationData, lhsDisplayHide):
if case let .SectionHeader(rhsPresentationData, rhsDisplayHide) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsDisplayHide != rhsDisplayHide {
return false
}
return true
} else {
return false
}
case let .Notice(lhsPresentationData, lhsInfo):
if case let .Notice(rhsPresentationData, rhsInfo) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsInfo != rhsInfo {
return false
}
return true
} else {
return false
}
case let .AdditionalCategory(lhsIndex, lhsId, lhsTitle, lhsImage, lhsAppearance, lhsSelected, lhsPresentationData):
if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsAppearance, rhsSelected, rhsPresentationData) = rhs {
if lhsIndex != rhsIndex {
return false
}
if lhsId != rhsId {
return false
}
if lhsTitle != rhsTitle {
return false
}
if lhsImage !== rhsImage {
return false
}
if lhsAppearance != rhsAppearance {
return false
}
if lhsSelected != rhsSelected {
return false
}
if lhsPresentationData !== rhsPresentationData {
return false
}
return true
} else {
return false
}
}
}
}
private func offsetPinnedIndex(_ index: EngineChatList.Item.Index, offset: UInt16) -> EngineChatList.Item.Index {
if case let .chatList(index) = index, let pinningIndex = index.pinningIndex {
return .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex + offset, messageIndex: index.messageIndex))
} else {
return index
}
}
struct ChatListContactPeer {
var peer: EnginePeer
var presence: EnginePeer.Presence
init(peer: EnginePeer, presence: EnginePeer.Presence) {
self.peer = peer
self.presence = presence
}
}
func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer], accountPeerId: EnginePeer.Id, isMainTab: Bool) -> (entries: [ChatListNodeEntry], loading: Bool) {
var groupItems = view.groupItems
if isMainTab && state.archiveStoryState != nil && groupItems.isEmpty {
groupItems.append(EngineChatList.GroupItem(
id: .archive,
topMessage: nil,
items: [],
unreadCount: 0
))
}
var result: [ChatListNodeEntry] = []
var hasContacts = false
if !view.hasEarlier {
var existingPeerIds = Set<EnginePeer.Id>()
for item in view.items {
existingPeerIds.insert(item.renderedPeer.peerId)
}
for contact in contacts {
if existingPeerIds.contains(contact.peer.id) {
continue
}
result.append(.ContactEntry(ChatListNodeEntry.ContactEntryData(
presentationData: state.presentationData,
peer: contact.peer,
presence: contact.presence
)))
hasContacts = true
}
if hasContacts {
result.append(.SectionHeader(presentationData: state.presentationData, displayHide: !view.items.isEmpty))
}
}
var pinnedIndexOffset: UInt16 = 0
if !view.hasLater, case .chatList = mode {
var groupEntryCount = 0
for _ in groupItems {
groupEntryCount += 1
}
pinnedIndexOffset += UInt16(groupEntryCount)
}
let filteredAdditionalItemEntries = view.additionalItems.filter { item -> Bool in
return item.item.renderedPeer.peerId != state.hiddenPsaPeerId
}
var foundPeerIds = Set<EnginePeer.Id>()
for peer in foundPeers {
foundPeerIds.insert(peer.0.id)
}
if !view.hasLater && savedMessagesPeer == nil {
pinnedIndexOffset += UInt16(filteredAdditionalItemEntries.count)
}
var hiddenGeneralThread: ChatListNodeEntry?
loop: for entry in view.items {
var peerId: EnginePeer.Id?
var threadId: Int64?
var activityItemId: ChatListNodePeerInputActivities.ItemId?
if case let .chatList(index) = entry.index {
peerId = index.messageIndex.id.peerId
activityItemId = ChatListNodePeerInputActivities.ItemId(peerId: index.messageIndex.id.peerId, threadId: nil)
} else if case let .forum(_, _, threadIdValue, _, _) = entry.index, case let .forum(peerIdValue) = chatListLocation {
peerId = peerIdValue
activityItemId = ChatListNodePeerInputActivities.ItemId(peerId: peerIdValue, threadId: threadIdValue)
threadId = threadIdValue
}
if let savedMessagesPeer = savedMessagesPeer, let peerId = peerId, savedMessagesPeer.id == peerId || foundPeerIds.contains(peerId) {
continue loop
}
if let peerId = peerId, state.pendingRemovalItemIds.contains(ChatListNodeState.ItemId(peerId: peerId, threadId: threadId)) {
continue loop
}
var updatedMessages = entry.messages
var updatedCombinedReadState = entry.readCounters
if let peerId = peerId, state.pendingClearHistoryPeerIds.contains(ChatListNodeState.ItemId(peerId: peerId, threadId: threadId)) {
updatedMessages = []
updatedCombinedReadState = nil
}
var draftState: ChatListItemContent.DraftState?
if let draft = entry.draft {
draftState = ChatListItemContent.DraftState(draft: draft)
}
var hasActiveRevealControls = false
if let peerId {
hasActiveRevealControls = ChatListNodeState.ItemId(peerId: peerId, threadId: threadId) == state.peerIdWithRevealedOptions
}
var inputActivities: [(EnginePeer, PeerInputActivity)]?
if let activityItemId {
inputActivities = state.peerInputActivities?.activities[activityItemId]
}
var isSelected = false
if let threadId, threadId != 0 {
isSelected = state.selectedThreadIds.contains(threadId)
} else if let peerId {
isSelected = state.selectedPeerIds.contains(peerId)
}
var threadInfo: ChatListItemContent.ThreadInfo?
if let threadData = entry.threadData, let threadId = threadId {
threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden)
}
let entry: ChatListNodeEntry = .PeerEntry(ChatListNodeEntry.PeerEntryData(
index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset),
presentationData: state.presentationData,
messages: updatedMessages,
readState: updatedCombinedReadState,
isRemovedFromTotalUnreadCount: entry.isMuted,
draftState: draftState,
mediaDraftContentType: entry.mediaDraftContentType,
peer: entry.renderedPeer,
threadInfo: threadInfo,
presence: entry.presence,
hasUnseenMentions: entry.hasUnseenMentions,
hasUnseenReactions: entry.hasUnseenReactions,
editing: state.editing,
hasActiveRevealControls: hasActiveRevealControls,
selected: isSelected,
inputActivities: inputActivities,
promoInfo: nil,
hasFailedMessages: entry.hasFailed,
isContact: entry.isContact,
autoremoveTimeout: entry.autoremoveTimeout,
forumTopicData: entry.forumTopicData,
topForumTopicItems: entry.topForumTopicItems,
revealed: threadId == 1 && (state.hiddenItemShouldBeTemporaryRevealed || state.editing),
storyState: entry.renderedPeer.peerId == accountPeerId ? nil : entry.storyStats.flatMap { stats -> ChatListNodeState.StoryState in
return ChatListNodeState.StoryState(
stats: stats,
hasUnseenCloseFriends: stats.hasUnseenCloseFriends
)
},
requiresPremiumForMessaging: entry.isPremiumRequiredToMessage,
displayAsTopicList: entry.displayAsTopicList
))
if let threadInfo, threadInfo.isHidden {
hiddenGeneralThread = entry
} else {
result.append(entry)
}
}
if let hiddenGeneralThread {
result.append(hiddenGeneralThread)
}
if !view.hasLater {
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
if let savedMessagesPeer = savedMessagesPeer {
if !foundPeers.isEmpty {
var foundPinningIndex: UInt16 = UInt16(foundPeers.count)
for peer in foundPeers.reversed() {
var peers: [EnginePeer.Id: EnginePeer] = [peer.0.id: peer.0]
if let chatPeer = peer.1 {
peers[chatPeer.id] = chatPeer
}
let messageIndex = EngineMessage.Index(id: EngineMessage.Id(peerId: peer.0.id, namespace: 0, id: 0), timestamp: 1)
result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData(
index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: foundPinningIndex, messageIndex: messageIndex)),
presentationData: state.presentationData,
messages: [],
readState: nil,
isRemovedFromTotalUnreadCount: false,
draftState: nil,
mediaDraftContentType: nil,
peer: EngineRenderedPeer(peerId: peer.0.id, peers: peers, associatedMedia: [:]),
threadInfo: nil,
presence: nil,
hasUnseenMentions: false,
hasUnseenReactions: false,
editing: state.editing,
hasActiveRevealControls: false,
selected: state.selectedPeerIds.contains(peer.0.id),
inputActivities: nil,
promoInfo: nil,
hasFailedMessages: false,
isContact: false,
autoremoveTimeout: nil,
forumTopicData: nil,
topForumTopicItems: [],
revealed: false,
storyState: nil,
requiresPremiumForMessaging: false,
displayAsTopicList: false
)))
if foundPinningIndex != 0 {
foundPinningIndex -= 1
}
}
}
result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData(
index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor),
presentationData: state.presentationData,
messages: [],
readState: nil,
isRemovedFromTotalUnreadCount: false,
draftState: nil,
mediaDraftContentType: nil,
peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]),
threadInfo: nil,
presence: nil,
hasUnseenMentions: false,
hasUnseenReactions: false,
editing: state.editing,
hasActiveRevealControls: false,
selected: state.selectedPeerIds.contains(savedMessagesPeer.id),
inputActivities: nil,
promoInfo: nil,
hasFailedMessages: false,
isContact: false,
autoremoveTimeout: nil,
forumTopicData: nil,
topForumTopicItems: [],
revealed: false,
storyState: nil,
requiresPremiumForMessaging: false,
displayAsTopicList: false
)))
} else {
if !filteredAdditionalItemEntries.isEmpty {
for item in filteredAdditionalItemEntries.reversed() {
guard case let .chatList(index) = item.item.index else {
continue
}
let promoInfo: ChatListNodeEntryPromoInfo
switch item.promoInfo.content {
case .proxy:
promoInfo = .proxy
case let .psa(type, message):
promoInfo = .psa(type: type, message: message)
}
let draftState = item.item.draft.flatMap(ChatListItemContent.DraftState.init)
let peerId = index.messageIndex.id.peerId
let isSelected = state.selectedPeerIds.contains(peerId)
var threadId: Int64 = 0
switch item.item.index {
case let .forum(_, _, threadIdValue, _, _):
threadId = threadIdValue
default:
break
}
result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData(
index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: index.messageIndex)),
presentationData: state.presentationData,
messages: item.item.messages,
readState: item.item.readCounters,
isRemovedFromTotalUnreadCount: item.item.isMuted,
draftState: draftState,
mediaDraftContentType: item.item.mediaDraftContentType,
peer: item.item.renderedPeer,
threadInfo: item.item.threadData.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0.info, isOwnedByMe: $0.isOwnedByMe, isClosed: $0.isClosed, isHidden: $0.isHidden) },
presence: item.item.presence,
hasUnseenMentions: item.item.hasUnseenMentions,
hasUnseenReactions: item.item.hasUnseenReactions,
editing: state.editing,
hasActiveRevealControls: ChatListNodeState.ItemId(peerId: peerId, threadId: threadId) == state.peerIdWithRevealedOptions,
selected: isSelected,
inputActivities: state.peerInputActivities?.activities[ChatListNodePeerInputActivities.ItemId(peerId: peerId, threadId: nil)],
promoInfo: promoInfo,
hasFailedMessages: item.item.hasFailed,
isContact: item.item.isContact,
autoremoveTimeout: item.item.autoremoveTimeout,
forumTopicData: item.item.forumTopicData,
topForumTopicItems: item.item.topForumTopicItems,
revealed: state.hiddenItemShouldBeTemporaryRevealed || state.editing,
storyState: nil,
requiresPremiumForMessaging: false,
displayAsTopicList: false
)))
if pinningIndex != 0 {
pinningIndex -= 1
}
}
}
}
if !view.hasLater, case .chatList = mode {
for groupReference in groupItems {
let messageIndex = EngineMessage.Index(id: EngineMessage.Id(peerId: EnginePeer.Id(0), namespace: 0, id: 0), timestamp: 1)
var mappedStoryState: ChatListNodeState.StoryState?
if let archiveStoryState = state.archiveStoryState {
mappedStoryState = archiveStoryState
}
result.append(.GroupReferenceEntry(ChatListNodeEntry.GroupReferenceEntryData(
index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: messageIndex)),
presentationData: state.presentationData,
groupId: groupReference.id,
peers: groupReference.items,
message: groupReference.topMessage,
editing: state.editing,
unreadCount: groupReference.unreadCount,
revealed: state.hiddenItemShouldBeTemporaryRevealed,
hiddenByDefault: hideArchivedFolderByDefault,
storyState: mappedStoryState
)))
if pinningIndex != 0 {
pinningIndex -= 1
}
}
if displayArchiveIntro {
//result.append(.ArchiveIntro(presentationData: state.presentationData))
} else if !contacts.isEmpty && !result.contains(where: { entry in
if case .PeerEntry = entry {
return true
} else {
return false
}
}) {
result.append(.EmptyIntro(presentationData: state.presentationData))
}
if let notice {
result.append(.Notice(presentationData: state.presentationData, notice: notice))
}
result.append(.HeaderEntry)
}
if !view.hasLater {
if case let .peers(_, _, additionalCategories, _, _, _) = mode {
var index = 0
for category in additionalCategories.reversed() {
result.append(.AdditionalCategory(index: index, id: category.id, title: category.title, image: category.icon, appearance: category.appearance, selected: state.selectedAdditionalCategoryIds.contains(category.id), presentationData: state.presentationData))
index += 1
}
} else if case let .peerType(types, hasCreate) = mode, !result.isEmpty && hasCreate {
for type in types {
switch type {
case .group:
result.append(.AdditionalCategory(index: 0, id: 0, title: state.presentationData.strings.RequestPeer_CreateNewGroup, image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData))
case .channel:
result.append(.AdditionalCategory(index: 0, id: 0, title: state.presentationData.strings.RequestPeer_CreateNewChannel, image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData))
default:
break
}
}
}
}
}
if result.count >= 1, case .HoleEntry = result[result.count - 1] {
return ([.HeaderEntry], true)
} else if result.count == 1, case .HoleEntry = result[0] {
return ([.HeaderEntry], true)
}
return (result, view.isLoading)
}