Fix monoforum user unread counters

This commit is contained in:
Isaac 2025-05-27 18:41:24 +08:00
parent 8f7006281c
commit 3cad999c75
21 changed files with 117 additions and 984 deletions

View File

@ -14382,3 +14382,17 @@ Sorry for the inconvenience.";
"ChannelMessages.SwitchTitle" = "Allow Channel Messages"; "ChannelMessages.SwitchTitle" = "Allow Channel Messages";
"ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE"; "ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE";
"ChannelMessages.PriceSectionFooter" = "Charge users for the ability to suggest one post for your channel. You're not required to publish any suggestions by charging this. You'll receive 85% of the selected fee for each incoming suggestion."; "ChannelMessages.PriceSectionFooter" = "Charge users for the ability to suggest one post for your channel. You're not required to publish any suggestions by charging this. You'll receive 85% of the selected fee for each incoming suggestion.";
"ChatList.MonoforumLabel" = "MESSAGES";
"ChatList.MonoforumEmptyText" = "No messages here yet...";
"Chat.InlineTopicMenu.Reorder" = "Reorder";
"Chat.InlineTopicMenu.Edit" = "Edit";
"Chat.InlineTopicMenu.AllTab" = "All";
"Chat.ChannelMessagesHint" = "Tap here to send a message";
"Chat.ChannelMessagesHintBadge" = "NEW";
"PeerInfo.AllowChannelMessages" = "Allow Channel Messages";
"PeerInfo.AllowChannelMessages.On" = "On";
"PeerInfo.AllowChannelMessages.Off" = "Off";
"PeerInfo.ChannelMessages" = "Channel Messages";

View File

@ -632,8 +632,7 @@ public func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, thr
}))) })))
if isPinned, let reorder { if isPinned, let reorder {
//TODO:localize items.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_InlineTopicMenu_Reorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
c?.dismiss(completion: { c?.dismiss(completion: {
}) })
reorder() reorder()
@ -664,8 +663,7 @@ public func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, thr
} }
if threadId != 1, canOpenClose, let customEdit { if threadId != 1, canOpenClose, let customEdit {
//TODO:localize items.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_InlineTopicMenu_Edit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { c, f in
items.append(.action(ContextMenuActionItem(text: "Edit", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { c, f in
if let c = c as? ContextController { if let c = c as? ContextController {
customEdit(c) customEdit(c)
} else { } else {

View File

@ -3014,8 +3014,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
textColor = theme.titleColor textColor = theme.titleColor
} }
if case let .channel(channel) = itemPeer.peer, channel.flags.contains(.isMonoforum) { if case let .channel(channel) = itemPeer.peer, channel.flags.contains(.isMonoforum) {
//TODO:localize titleBadgeText = item.presentationData.strings.ChatList_MonoforumLabel
titleBadgeText = "MESSAGES"
} }
titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: textColor) titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: textColor)
} }

View File

@ -185,7 +185,31 @@ final class ChatListIndexTable: Table {
var updatedPeerTags: [PeerId: (previous: PeerSummaryCounterTags, updated: PeerSummaryCounterTags)] = [:] var updatedPeerTags: [PeerId: (previous: PeerSummaryCounterTags, updated: PeerSummaryCounterTags)] = [:]
var updatedIsThreadBasedUnreadCountCalculation: [PeerId: Bool] = [:] var updatedIsThreadBasedUnreadCountCalculation: [PeerId: Bool] = [:]
var upatedPeerMap: [PeerId: (Peer?, Peer)] = [:]
for (previous, updated) in updatedPeers { for (previous, updated) in updatedPeers {
var needsAssociatedPeers = false
if let previous, previous.0.associatedPeerId != nil {
needsAssociatedPeers = true
} else if updated.0.associatedPeerId != nil {
needsAssociatedPeers = true
}
if needsAssociatedPeers && upatedPeerMap.isEmpty {
for (previous, updated) in updatedPeers {
upatedPeerMap[updated.0.id] = (previous?.0, updated.0)
}
}
var previousAssociatedPeer: Peer?
var updatedAssociatedPeer: Peer?
if let previous, let previousAssociatedPeerId = previous.0.associatedPeerId {
previousAssociatedPeer = upatedPeerMap[previousAssociatedPeerId]?.0 ?? postbox.peerTable.get(previousAssociatedPeerId)
}
if let updatedAssociatedPeerId = updated.0.associatedPeerId {
updatedAssociatedPeer = upatedPeerMap[updatedAssociatedPeerId]?.1 ?? postbox.peerTable.get(updatedAssociatedPeerId)
}
let previousTags: PeerSummaryCounterTags let previousTags: PeerSummaryCounterTags
if let (previous, previousIsContact) = previous { if let (previous, previousIsContact) = previous {
previousTags = postbox.seedConfiguration.peerSummaryCounterTags(previous, previousIsContact) previousTags = postbox.seedConfiguration.peerSummaryCounterTags(previous, previousIsContact)
@ -198,13 +222,13 @@ final class ChatListIndexTable: Table {
} }
if let previous = previous { if let previous = previous {
var isThreadBasedUnreadCalculation = postbox.seedConfiguration.peerSummaryIsThreadBased(updated.0).value var isThreadBasedUnreadCalculation = postbox.seedConfiguration.peerSummaryIsThreadBased(updated.0, updatedAssociatedPeer).value
if let cachedData = updatedCachedPeerData[updated.0.id]?.1, postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedData) { if let cachedData = updatedCachedPeerData[updated.0.id]?.1, postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedData) {
isThreadBasedUnreadCalculation = false isThreadBasedUnreadCalculation = false
} }
var wasThreadBasedUnreadCalculation = false var wasThreadBasedUnreadCalculation = false
if postbox.seedConfiguration.peerSummaryIsThreadBased(previous.0).value { if postbox.seedConfiguration.peerSummaryIsThreadBased(previous.0, previousAssociatedPeer).value {
if let cachedData = postbox.cachedPeerDataTable.get(previous.0.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedData) { if let cachedData = postbox.cachedPeerDataTable.get(previous.0.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedData) {
} else { } else {
wasThreadBasedUnreadCalculation = true wasThreadBasedUnreadCalculation = true
@ -223,14 +247,15 @@ final class ChatListIndexTable: Table {
guard let peer = postbox.peerTable.get(peerId) else { guard let peer = postbox.peerTable.get(peerId) else {
continue continue
} }
let associatedPeer = peer.associatedPeerId.flatMap(postbox.peerTable.get)
var isThreadBasedUnreadCalculation = postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value var isThreadBasedUnreadCalculation = postbox.seedConfiguration.peerSummaryIsThreadBased(peer, associatedPeer).value
if postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedDataUpdate.1) { if postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedDataUpdate.1) {
isThreadBasedUnreadCalculation = false isThreadBasedUnreadCalculation = false
} }
var wasThreadBasedUnreadCalculation = false var wasThreadBasedUnreadCalculation = false
if postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if postbox.seedConfiguration.peerSummaryIsThreadBased(peer, associatedPeer).value {
if let previousCachedData = cachedDataUpdate.0, postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(previousCachedData) { if let previousCachedData = cachedDataUpdate.0, postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(previousCachedData) {
} else { } else {
wasThreadBasedUnreadCalculation = true wasThreadBasedUnreadCalculation = true
@ -439,7 +464,7 @@ final class ChatListIndexTable: Table {
displayAsRegularChat = true displayAsRegularChat = true
} }
if let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value, !displayAsRegularChat { if let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value, !displayAsRegularChat {
let previousCount: Int32 let previousCount: Int32
if let previousSummary = alteredInitialPeerThreadsSummaries[peerId] { if let previousSummary = alteredInitialPeerThreadsSummaries[peerId] {
previousCount = previousSummary.effectiveUnreadCount previousCount = previousSummary.effectiveUnreadCount
@ -458,7 +483,7 @@ final class ChatListIndexTable: Table {
} }
let currentReadState: CombinedPeerReadState? let currentReadState: CombinedPeerReadState?
if let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value, !displayAsRegularChat { if let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value, !displayAsRegularChat {
let count = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0 let count = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0
currentReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 1, maxKnownId: 0, count: count, markedUnread: false))]) currentReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 1, maxKnownId: 0, count: count, markedUnread: false))])
} else { } else {
@ -691,7 +716,7 @@ final class ChatListIndexTable: Table {
} }
let combinedState: CombinedPeerReadState? let combinedState: CombinedPeerReadState?
if postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value {
let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0 let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0
combinedState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 1, maxKnownId: 0, count: count, markedUnread: false))]) combinedState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 1, maxKnownId: 0, count: count, markedUnread: false))])
} else { } else {
@ -788,7 +813,7 @@ final class ChatListIndexTable: Table {
} }
let combinedState: CombinedPeerReadState? let combinedState: CombinedPeerReadState?
if postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value {
let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0 let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0
combinedState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 1, maxKnownId: 0, count: count, markedUnread: false))]) combinedState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 1, maxKnownId: 0, count: count, markedUnread: false))])
} else { } else {

View File

@ -714,7 +714,7 @@ final class MutableChatListView {
let renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers)) let renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers))
let isUnread: Bool let isUnread: Bool
if postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value {
let hasUnmutedUnread = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.hasUnmutedUnread ?? false let hasUnmutedUnread = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.hasUnmutedUnread ?? false
isUnread = hasUnmutedUnread isUnread = hasUnmutedUnread
} else { } else {
@ -846,7 +846,7 @@ final class MutableChatListView {
displayAsRegularChat = postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedData) displayAsRegularChat = postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedData)
} }
if let peer = groupEntries[i].renderedPeers[j].peer.peer, postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value, !displayAsRegularChat { if let peer = groupEntries[i].renderedPeers[j].peer.peer, postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value, !displayAsRegularChat {
isUnread = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.hasUnmutedUnread ?? false isUnread = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.hasUnmutedUnread ?? false
} else { } else {
isUnread = postbox.readStateTable.getCombinedState(groupEntries[i].renderedPeers[j].peer.peerId)?.isUnread ?? false isUnread = postbox.readStateTable.getCombinedState(groupEntries[i].renderedPeers[j].peer.peerId)?.isUnread ?? false
@ -942,7 +942,7 @@ final class MutableChatListView {
var isThreadBased = false var isThreadBased = false
var threadsArePeers = false var threadsArePeers = false
if let peer = renderedPeer.peer { if let peer = renderedPeer.peer {
let value = postbox.seedConfiguration.peerSummaryIsThreadBased(peer) let value = postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get))
isThreadBased = value.value isThreadBased = value.value
threadsArePeers = value.threadsArePeers threadsArePeers = value.threadsArePeers
} }

View File

@ -65,7 +65,7 @@ private func mappedChatListFilterPredicate(postbox: PostboxImpl, currentTransact
if let cachedPeerData = postbox.cachedPeerDataTable.get(peer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) { if let cachedPeerData = postbox.cachedPeerDataTable.get(peer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true displayAsRegularChat = true
} }
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value
if !isThreadBased { if !isThreadBased {
displayAsRegularChat = true displayAsRegularChat = true
} }
@ -439,7 +439,7 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(peer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) { if let cachedPeerData = postbox.cachedPeerDataTable.get(peer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true displayAsRegularChat = true
} }
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value
if !isThreadBased { if !isThreadBased {
displayAsRegularChat = true displayAsRegularChat = true
} }
@ -577,7 +577,7 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(entryPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) { if let cachedPeerData = postbox.cachedPeerDataTable.get(entryPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true displayAsRegularChat = true
} }
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer).value let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer, entryPeer.associatedPeerId.flatMap(postbox.peerTable.get)).value
if !isThreadBased { if !isThreadBased {
displayAsRegularChat = true displayAsRegularChat = true
} }
@ -630,7 +630,7 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(mainPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) { if let cachedPeerData = postbox.cachedPeerDataTable.get(mainPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true displayAsRegularChat = true
} }
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer).value let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer, mainPeer.associatedPeerId.flatMap(postbox.peerTable.get)).value
if !isThreadBased { if !isThreadBased {
displayAsRegularChat = true displayAsRegularChat = true
} }
@ -816,7 +816,7 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(entryPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) { if let cachedPeerData = postbox.cachedPeerDataTable.get(entryPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true displayAsRegularChat = true
} }
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer).value let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer, entryPeer.associatedPeerId.flatMap(postbox.peerTable.get)).value
if !isThreadBased { if !isThreadBased {
displayAsRegularChat = true displayAsRegularChat = true
} }
@ -878,7 +878,7 @@ private final class ChatListViewSpaceState {
if let cachedPeerData = postbox.cachedPeerDataTable.get(mainPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) { if let cachedPeerData = postbox.cachedPeerDataTable.get(mainPeer.id), postbox.seedConfiguration.decodeDisplayPeerAsRegularChat(cachedPeerData) {
displayAsRegularChat = true displayAsRegularChat = true
} }
let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer).value let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer, mainPeer.associatedPeerId.flatMap(postbox.peerTable.get)).value
if !isThreadBased { if !isThreadBased {
displayAsRegularChat = true displayAsRegularChat = true
} }
@ -978,7 +978,7 @@ private final class ChatListViewSpaceState {
} }
let isThreadBased: Bool let isThreadBased: Bool
if let peer = postbox.peerTable.get(entryData.index.messageIndex.id.peerId) { if let peer = postbox.peerTable.get(entryData.index.messageIndex.id.peerId) {
isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value
} else { } else {
isThreadBased = false isThreadBased = false
} }
@ -1620,7 +1620,7 @@ struct ChatListViewState {
var isThreadBased = false var isThreadBased = false
var threadsArePeers = false var threadsArePeers = false
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) { if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
let value = postbox.seedConfiguration.peerSummaryIsThreadBased(peer) let value = postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get))
isThreadBased = value.value isThreadBased = value.value
threadsArePeers = value.threadsArePeers threadsArePeers = value.threadsArePeers
} }

View File

@ -3187,7 +3187,6 @@ final class PostboxImpl {
let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal(userInteractive: true, { subscriber, transaction in let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal(userInteractive: true, { subscriber, transaction in
let peerIds = self.peerIdsForLocation(chatLocation, ignoreRelatedChats: false) let peerIds = self.peerIdsForLocation(chatLocation, ignoreRelatedChats: false)
//TODO:release jump to unread
var anchor: HistoryViewInputAnchor = .upperBound var anchor: HistoryViewInputAnchor = .upperBound
switch peerIds { switch peerIds {
case let .single(peerId, threadId): case let .single(peerId, threadId):

View File

@ -66,7 +66,7 @@ public final class SeedConfiguration {
public let existingGlobalMessageTags: GlobalMessageTags public let existingGlobalMessageTags: GlobalMessageTags
public let peerNamespacesRequiringMessageTextIndex: [PeerId.Namespace] public let peerNamespacesRequiringMessageTextIndex: [PeerId.Namespace]
public let peerSummaryCounterTags: (Peer, Bool) -> PeerSummaryCounterTags public let peerSummaryCounterTags: (Peer, Bool) -> PeerSummaryCounterTags
public let peerSummaryIsThreadBased: (Peer) -> (value: Bool, threadsArePeers: Bool) public let peerSummaryIsThreadBased: (Peer, Peer?) -> (value: Bool, threadsArePeers: Bool)
public let additionalChatListIndexNamespace: MessageId.Namespace? public let additionalChatListIndexNamespace: MessageId.Namespace?
public let messageNamespacesRequiringGroupStatsValidation: Set<MessageId.Namespace> public let messageNamespacesRequiringGroupStatsValidation: Set<MessageId.Namespace>
public let defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState] public let defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState]
@ -97,7 +97,7 @@ public final class SeedConfiguration {
existingGlobalMessageTags: GlobalMessageTags, existingGlobalMessageTags: GlobalMessageTags,
peerNamespacesRequiringMessageTextIndex: [PeerId.Namespace], peerNamespacesRequiringMessageTextIndex: [PeerId.Namespace],
peerSummaryCounterTags: @escaping (Peer, Bool) -> PeerSummaryCounterTags, peerSummaryCounterTags: @escaping (Peer, Bool) -> PeerSummaryCounterTags,
peerSummaryIsThreadBased: @escaping (Peer) -> (value: Bool, threadsArePeers: Bool), peerSummaryIsThreadBased: @escaping (Peer, Peer?) -> (value: Bool, threadsArePeers: Bool),
additionalChatListIndexNamespace: MessageId.Namespace?, additionalChatListIndexNamespace: MessageId.Namespace?,
messageNamespacesRequiringGroupStatsValidation: Set<MessageId.Namespace>, messageNamespacesRequiringGroupStatsValidation: Set<MessageId.Namespace>,
defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState], defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState],

View File

@ -64,7 +64,7 @@ final class MutableUnreadMessageCountsView: MutablePostboxView {
case let .totalInGroup(groupId): case let .totalInGroup(groupId):
return .totalInGroup(groupId, postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: groupId)) return .totalInGroup(groupId, postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: groupId))
case let .peer(peerId, handleThreads): case let .peer(peerId, handleThreads):
if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value {
var count: Int32 = 0 var count: Int32 = 0
if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) { if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
count = summary.totalUnreadCount count = summary.totalUnreadCount
@ -113,7 +113,7 @@ final class MutableUnreadMessageCountsView: MutablePostboxView {
} }
} }
case let .peer(peerId, handleThreads, _): case let .peer(peerId, handleThreads, _):
if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value {
if transaction.updatedPeerThreadsSummaries.contains(peerId) { if transaction.updatedPeerThreadsSummaries.contains(peerId) {
var count: Int32 = 0 var count: Int32 = 0
if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) { if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
@ -143,7 +143,7 @@ final class MutableUnreadMessageCountsView: MutablePostboxView {
case let .totalInGroup(groupId): case let .totalInGroup(groupId):
return .totalInGroup(groupId, postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: groupId)) return .totalInGroup(groupId, postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: groupId))
case let .peer(peerId, handleThreads): case let .peer(peerId, handleThreads):
if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value {
var count: Int32 = 0 var count: Int32 = 0
if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) { if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
count = summary.totalUnreadCount count = summary.totalUnreadCount
@ -240,7 +240,7 @@ final class MutableCombinedReadStateView: MutablePostboxView {
var updated = false var updated = false
if transaction.alteredInitialPeerCombinedReadStates[self.peerId] != nil || transaction.updatedPeerThreadCombinedStates.contains(self.peerId) { if transaction.alteredInitialPeerCombinedReadStates[self.peerId] != nil || transaction.updatedPeerThreadCombinedStates.contains(self.peerId) {
if self.handleThreads, let peer = postbox.peerTable.get(self.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if self.handleThreads, let peer = postbox.peerTable.get(self.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value {
var count: Int32 = 0 var count: Int32 = 0
if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) { if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) {
count = summary.totalUnreadCount count = summary.totalUnreadCount
@ -260,14 +260,14 @@ final class MutableCombinedReadStateView: MutablePostboxView {
func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool {
let state: CombinedPeerReadState? let state: CombinedPeerReadState?
if handleThreads, let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer).value { if self.handleThreads, let peer = postbox.peerTable.get(self.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer, peer.associatedPeerId.flatMap(postbox.peerTable.get)).value {
var count: Int32 = 0 var count: Int32 = 0
if let summary = postbox.peerThreadsSummaryTable.get(peerId: peerId) { if let summary = postbox.peerThreadsSummaryTable.get(peerId: self.peerId) {
count = summary.totalUnreadCount count = summary.totalUnreadCount
} }
state = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: count, markedUnread: false))]) state = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: count, markedUnread: false))])
} else { } else {
state = postbox.readStateTable.getCombinedState(peerId) state = postbox.readStateTable.getCombinedState(self.peerId)
} }
if state != self.state { if state != self.state {

View File

@ -73,7 +73,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
return .nonContact return .nonContact
} }
}, },
peerSummaryIsThreadBased: { peer in peerSummaryIsThreadBased: { peer, associatedPeer in
if let channel = peer as? TelegramChannel { if let channel = peer as? TelegramChannel {
if channel.flags.contains(.isForum) { if channel.flags.contains(.isForum) {
if channel.flags.contains(.displayForumAsTabs) { if channel.flags.contains(.displayForumAsTabs) {
@ -82,7 +82,11 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
return (true, false) return (true, false)
} }
} else if channel.flags.contains(.isMonoforum) { } else if channel.flags.contains(.isMonoforum) {
return (true, true) if let associatedPeer = associatedPeer as? TelegramChannel, associatedPeer.hasPermission(.sendSomething) {
return (true, true)
} else {
return (false, false)
}
} else { } else {
return (false, false) return (false, false)
} }

View File

@ -140,8 +140,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} else { } else {
if forChatList { if forChatList {
if isMonoforum { if isMonoforum {
//TODO:Localize attributedString = NSAttributedString(string: strings.ChatList_MonoforumEmptyText, font: titleFont, textColor: primaryTextColor)
attributedString = NSAttributedString(string: "No messages here yet...", font: titleFont, textColor: primaryTextColor)
} else { } else {
attributedString = NSAttributedString(string: strings.Notification_CreatedGroup, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_CreatedGroup, font: titleFont, textColor: primaryTextColor)
} }

View File

@ -526,14 +526,29 @@ public final class AsyncListComponent: Component {
var scrollToItem: ListViewScrollToItem? var scrollToItem: ListViewScrollToItem?
if let resetScrollingRequest = component.externalStateValue.resetScrollingRequest, previousComponent?.externalStateValue.resetScrollingRequest != component.externalStateValue.resetScrollingRequest { if let resetScrollingRequest = component.externalStateValue.resetScrollingRequest, previousComponent?.externalStateValue.resetScrollingRequest != component.externalStateValue.resetScrollingRequest {
//TODO:release calculate direction hint
if let index = entries.firstIndex(where: { $0.id == resetScrollingRequest.id }) { if let index = entries.firstIndex(where: { $0.id == resetScrollingRequest.id }) {
var directionHint: ListViewScrollToItemDirectionHint = .Down
var didSelectDirection = false
self.listNode.forEachItemNode { itemNode in
if didSelectDirection {
return
}
if let itemNode = itemNode as? ListItemNodeImpl, let itemIndex = itemNode.index {
if itemIndex <= index {
directionHint = .Up
} else {
directionHint = .Down
}
didSelectDirection = true
}
}
scrollToItem = ListViewScrollToItem( scrollToItem = ListViewScrollToItem(
index: index, index: index,
position: .visible, position: .visible,
animated: animateTransition, animated: animateTransition,
curve: updateSizeAndInsets.curve, curve: updateSizeAndInsets.curve,
directionHint: .Down directionHint: directionHint
) )
} }
} }
@ -548,13 +563,25 @@ public final class AsyncListComponent: Component {
transactionOptions.insert(.Synchronous) transactionOptions.insert(.Synchronous)
self.listNode.transaction(
deleteIndices: [],
insertIndicesAndItems: [],
updateIndicesAndItems: [],
options: transactionOptions,
scrollToItem: nil,
updateSizeAndInsets: updateSizeAndInsets,
stationaryItemRange: nil,
updateOpaqueState: nil,
completion: { _ in }
)
self.listNode.transaction( self.listNode.transaction(
deleteIndices: deletions, deleteIndices: deletions,
insertIndicesAndItems: insertions, insertIndicesAndItems: insertions,
updateIndicesAndItems: updates, updateIndicesAndItems: updates,
options: transactionOptions, options: transactionOptions,
scrollToItem: scrollToItem, scrollToItem: scrollToItem,
updateSizeAndInsets: updateSizeAndInsets, updateSizeAndInsets: nil,
stationaryItemRange: nil, stationaryItemRange: nil,
updateOpaqueState: nil, updateOpaqueState: nil,
completion: { _ in } completion: { _ in }

View File

@ -381,14 +381,13 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = presentationData let _ = presentationData
//TODO:localize let text: String = presentationData.strings.Chat_ChannelMessagesHint
let text: String = "Tap here to send a message"
let tooltipController = TooltipScreen( let tooltipController = TooltipScreen(
account: context.account, account: context.account,
sharedContext: context.sharedContext, sharedContext: context.sharedContext,
text: .plain(text: text), text: .plain(text: text),
textBadge: "NEW", textBadge: presentationData.strings.Chat_ChannelMessagesHintBadge,
balancedTextLayout: false, balancedTextLayout: false,
style: .wide, style: .wide,
arrowStyle: .small, arrowStyle: .small,

View File

@ -619,8 +619,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
private let shadowNode: ChatMessageShadowNode private let shadowNode: ChatMessageShadowNode
private var clippingNode: ChatMessageBubbleClippingNode private var clippingNode: ChatMessageBubbleClippingNode
private var suggestedPostInfoNode: ChatMessageSuggestedPostInfoNode?
override public var extractedBackgroundNode: ASDisplayNode? { override public var extractedBackgroundNode: ASDisplayNode? {
return self.shadowNode return self.shadowNode
} }
@ -1427,8 +1425,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let weakSelf = Weak(self) let weakSelf = Weak(self)
let makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout = ChatMessageSuggestedPostInfoNode.asyncLayout(self.suggestedPostInfoNode)
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData) let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
return ChatMessageBubbleItemNode.beginLayout( return ChatMessageBubbleItemNode.beginLayout(
@ -1451,7 +1447,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
unlockButtonLayout: unlockButtonLayout, unlockButtonLayout: unlockButtonLayout,
mediaInfoLayout: mediaInfoLayout, mediaInfoLayout: mediaInfoLayout,
mosaicStatusLayout: mosaicStatusLayout, mosaicStatusLayout: mosaicStatusLayout,
makeSuggestedPostInfoNodeLayout: makeSuggestedPostInfoNodeLayout,
layoutConstants: layoutConstants, layoutConstants: layoutConstants,
currentItem: currentItem, currentItem: currentItem,
currentForwardInfo: currentForwardInfo, currentForwardInfo: currentForwardInfo,
@ -1480,7 +1475,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode), unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode),
mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode), mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode),
mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)), mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)),
makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout,
layoutConstants: ChatMessageItemLayoutConstants, layoutConstants: ChatMessageItemLayoutConstants,
currentItem: ChatMessageItem?, currentItem: ChatMessageItem?,
currentForwardInfo: (Peer?, String?)?, currentForwardInfo: (Peer?, String?)?,
@ -3000,7 +2994,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
var totalContentNodesHeight: CGFloat = 0.0 var totalContentNodesHeight: CGFloat = 0.0
var currentContainerGroupOverlap: CGFloat = 0.0 var currentContainerGroupOverlap: CGFloat = 0.0
var detachedContentNodesHeight: CGFloat = 0.0 var detachedContentNodesHeight: CGFloat = 0.0
var additionalTopHeight: CGFloat = 0.0 let additionalTopHeight: CGFloat = 0.0
var mosaicStatusOrigin: CGPoint? var mosaicStatusOrigin: CGPoint?
var unlockButtonPosition: CGPoint? var unlockButtonPosition: CGPoint?
@ -3177,13 +3171,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
reactionButtonsSizeAndApply = reactionButtonsFinalize(maxContentWidth) reactionButtonsSizeAndApply = reactionButtonsFinalize(maxContentWidth)
} }
var suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)?
suggestedPostInfoNodeLayout = nil
if let suggestedPostInfoNodeLayout {
additionalTopHeight += 4.0 + suggestedPostInfoNodeLayout.0.height + 8.0
}
let minimalContentSize: CGSize let minimalContentSize: CGSize
if hideBackground { if hideBackground {
minimalContentSize = CGSize(width: 1.0, height: 1.0) minimalContentSize = CGSize(width: 1.0, height: 1.0)
@ -3320,7 +3307,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
avatarOffset: avatarOffset, avatarOffset: avatarOffset,
hidesHeaders: hidesHeaders, hidesHeaders: hidesHeaders,
disablesComments: disablesComments, disablesComments: disablesComments,
suggestedPostInfoNodeLayout: suggestedPostInfoNodeLayout,
alignment: alignment, alignment: alignment,
isSidePanelOpen: isSidePanelOpen isSidePanelOpen: isSidePanelOpen
) )
@ -3384,7 +3370,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
avatarOffset: CGFloat?, avatarOffset: CGFloat?,
hidesHeaders: Bool, hidesHeaders: Bool,
disablesComments: Bool, disablesComments: Bool,
suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)?,
alignment: ChatMessageBubbleContentAlignment, alignment: ChatMessageBubbleContentAlignment,
isSidePanelOpen: Bool isSidePanelOpen: Bool
) -> Void { ) -> Void {
@ -3468,22 +3453,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
strongSelf.backgroundNode.backgroundFrame = backgroundFrame strongSelf.backgroundNode.backgroundFrame = backgroundFrame
if let (suggestedPostInfoSize, suggestedPostInfoApply) = suggestedPostInfoNodeLayout {
let suggestedPostInfoNode = suggestedPostInfoApply()
if suggestedPostInfoNode !== strongSelf.suggestedPostInfoNode {
strongSelf.suggestedPostInfoNode?.removeFromSupernode()
strongSelf.suggestedPostInfoNode = suggestedPostInfoNode
strongSelf.mainContextSourceNode.contentNode.addSubnode(suggestedPostInfoNode)
let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: 4.0), size: suggestedPostInfoSize)
suggestedPostInfoNode.frame = suggestedPostInfoFrame
//animation.animator.updateFrame(layer: suggestedPostInfoNode.layer, frame: suggestedPostInfoFrame, completion: nil)
}
} else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode {
strongSelf.suggestedPostInfoNode = nil
suggestedPostInfoNode.removeFromSupernode()
}
if let avatarOffset { if let avatarOffset {
strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring)) strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring))
} }

View File

@ -1,169 +0,0 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import TextFormat
import AccountContext
import WallpaperBackgroundNode
import ChatMessageItem
import TelegramStringFormatting
public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode {
private var titleNode: TextNode?
private var priceLabelNode: TextNode?
private var priceValueNode: TextNode?
private var timeLabelNode: TextNode?
private var timeValueNode: TextNode?
private var backgroundNode: WallpaperBubbleBackgroundNode?
override public init() {
super.init()
}
public typealias AsyncLayout = (ChatMessageItem, CGFloat) -> (CGSize, () -> ChatMessageSuggestedPostInfoNode)
public static func asyncLayout(_ node: ChatMessageSuggestedPostInfoNode?) -> (ChatMessageItem, CGFloat) -> (CGSize, () -> ChatMessageSuggestedPostInfoNode) {
let makeTitleLayout = TextNode.asyncLayout(node?.titleNode)
let makePriceLabelLayout = TextNode.asyncLayout(node?.priceLabelNode)
let makePriceValueLayout = TextNode.asyncLayout(node?.priceValueNode)
let makeTimeLabelLayout = TextNode.asyncLayout(node?.timeLabelNode)
let makeTimeValueLayout = TextNode.asyncLayout(node?.timeValueNode)
return { item, maxWidth in
let insets = UIEdgeInsets(
top: 12.0,
left: 12.0,
bottom: 12.0,
right: 12.0
)
let titleSpacing: CGFloat = 8.0
let labelSpacing: CGFloat = 8.0
let valuesVerticalSpacing: CGFloat = 2.0
var amount: Int64 = 0
var timestamp: Int32?
if "".isEmpty {
amount = 0
timestamp = nil
}
/*for attribute in item.message.attributes {
if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
amount = attribute.price.value
timestamp = attribute.timestamp
}
}*/
//TODO:localize
let amountString: String
if amount == 0 {
amountString = "Free"
} else if amount == 1 {
amountString = "1 Star"
} else {
amountString = "\(amount) Stars"
}
var timestampString: String
if let timestamp {
timestampString = humanReadableStringForTimestamp(strings: item.presentationData.strings, dateTimeFormat: PresentationDateTimeFormat(), timestamp: timestamp, alwaysShowTime: true).string
if timestampString.count > 1 {
timestampString = String(timestampString[timestampString.startIndex]).capitalized + timestampString[timestampString.index(after: timestampString.startIndex)...]
}
} else {
timestampString = "Anytime"
}
let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
//TODO:localize
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "You suggest to post\nthis message.", font: Font.regular(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let priceLabelLayout = makePriceLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Price", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let timeLabelLayout = makeTimeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Time", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let priceValueLayout = makePriceValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: amountString, font: Font.semibold(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let timeValueLayout = makeTimeValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: timestampString, font: Font.semibold(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var maxContentWidth: CGFloat = 0.0
var contentHeight: CGFloat = 0.0
maxContentWidth = max(maxContentWidth, titleLayout.0.size.width)
contentHeight += titleLayout.0.size.height
contentHeight += titleSpacing
maxContentWidth = max(maxContentWidth, priceLabelLayout.0.size.width + labelSpacing + priceValueLayout.0.size.width)
contentHeight += priceLabelLayout.0.size.height + valuesVerticalSpacing
maxContentWidth = max(maxContentWidth, timeLabelLayout.0.size.width + labelSpacing + timeValueLayout.0.size.width)
contentHeight += timeLabelLayout.0.size.height
let size = CGSize(width: insets.left + insets.right + maxContentWidth, height: insets.top + insets.bottom + contentHeight)
return (size, {
let node = node ?? ChatMessageSuggestedPostInfoNode()
if node.backgroundNode == nil {
if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
node.backgroundNode = backgroundNode
backgroundNode.layer.masksToBounds = true
backgroundNode.layer.cornerRadius = 15.0
node.insertSubnode(backgroundNode, at: 0)
}
}
if let backgroundNode = node.backgroundNode {
backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
}
let titleNode = titleLayout.1()
if node.titleNode !== titleNode {
node.titleNode = titleNode
node.addSubnode(titleNode)
}
let priceLabelNode = priceLabelLayout.1()
if node.priceLabelNode !== priceLabelNode {
node.priceLabelNode = priceLabelNode
node.addSubnode(priceLabelNode)
}
let priceValueNode = priceValueLayout.1()
if node.priceValueNode !== priceValueNode {
node.priceValueNode = priceValueNode
node.addSubnode(priceValueNode)
}
let timeLabelNode = timeLabelLayout.1()
if node.timeLabelNode !== timeLabelNode {
node.timeLabelNode = timeLabelNode
node.addSubnode(timeLabelNode)
}
let timeValueNode = timeValueLayout.1()
if node.timeValueNode !== timeValueNode {
node.timeValueNode = timeValueNode
node.addSubnode(timeValueNode)
}
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.0.size.width) * 0.5), y: insets.top), size: titleLayout.0.size)
titleNode.frame = titleFrame
let priceLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: titleFrame.maxY + titleSpacing), size: priceLabelLayout.0.size)
priceLabelNode.frame = priceLabelFrame
priceValueNode.frame = CGRect(origin: CGPoint(x: priceLabelFrame.maxX + labelSpacing, y: priceLabelFrame.minY), size: priceValueLayout.0.size)
let timeLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: priceLabelFrame.maxY + valuesVerticalSpacing), size: timeLabelLayout.0.size)
timeLabelNode.frame = timeLabelFrame
timeValueNode.frame = CGRect(origin: CGPoint(x: timeLabelFrame.maxX + labelSpacing, y: timeLabelFrame.minY), size: timeValueLayout.0.size)
return node
})
}
}
}

View File

@ -139,8 +139,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
} }
if case let .channel(channel) = peer, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = firstMessage.peers[linkedMonoforumId] { if case let .channel(channel) = peer, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = firstMessage.peers[linkedMonoforumId] {
//TODO:localize title = authorString + "@" + EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
title = authorString + "@" + EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + " Messages"
} else { } else {
if let threadData = item.threadData { if let threadData = item.threadData {
title = "\(authorString)\(threadData.info.title)" title = "\(authorString)\(threadData.info.title)"

View File

@ -417,8 +417,7 @@ public final class ChatSideTopicsPanel: Component {
if let threadData = component.item.item.threadData { if let threadData = component.item.item.threadData {
titleText = threadData.info.title titleText = threadData.info.title
} else { } else {
//TODO:localize titleText = " "
titleText = "General"
} }
} else { } else {
titleText = component.item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " " titleText = component.item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " "
@ -847,8 +846,7 @@ public final class ChatSideTopicsPanel: Component {
if let threadData = component.item.item.threadData { if let threadData = component.item.item.threadData {
titleText = threadData.info.title titleText = threadData.info.title
} else { } else {
//TODO:localize titleText = " "
titleText = "General"
} }
} else { } else {
titleText = component.item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " " titleText = component.item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " "
@ -1220,8 +1218,7 @@ public final class ChatSideTopicsPanel: Component {
containerSize: CGSize(width: 100.0, height: 100.0) containerSize: CGSize(width: 100.0, height: 100.0)
) )
//TODO:localize let titleText: String = component.strings.Chat_InlineTopicMenu_AllTab
let titleText: String = "All"
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
@ -1348,8 +1345,7 @@ public final class ChatSideTopicsPanel: Component {
let leftInset: CGFloat = 6.0 let leftInset: CGFloat = 6.0
let rightInset: CGFloat = 12.0 let rightInset: CGFloat = 12.0
//TODO:localize let titleText: String = component.strings.Chat_InlineTopicMenu_AllTab
let titleText: String = "All"
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(

View File

@ -2219,8 +2219,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
interaction.editingOpenDiscussionGroupSetup() interaction.editingOpenDiscussionGroupSetup()
})) }))
//TODO:localize items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text(channel.linkedMonoforumId == nil ? presentationData.strings.PeerInfo_AllowChannelMessages_Off : presentationData.strings.PeerInfo_AllowChannelMessages_On), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_AllowChannelMessages, icon: UIImage(bundleImageName: "Chat/Info/PostSuggestionsIcon"), action: {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text(channel.linkedMonoforumId == nil ? "Off" : "On"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Allow Channel Messages", icon: UIImage(bundleImageName: "Chat/Info/PostSuggestionsIcon"), action: {
interaction.editingOpenPostSuggestionsSetup() interaction.editingOpenPostSuggestionsSetup()
})) }))
} }
@ -2357,8 +2356,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
})) }))
if channel.linkedMonoforumId != nil { if channel.linkedMonoforumId != nil {
//TODO:localize items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemChannelMessages, label: .none, text: presentationData.strings.PeerInfo_ChannelMessages, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: {
items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemChannelMessages, label: .none, text: "Channel Messages", icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: {
interaction.openChannelMessages() interaction.openChannelMessages()
})) }))
} }

View File

@ -566,8 +566,6 @@ extension ChatControllerImpl {
let initTimestamp = self.initTimestamp let initTimestamp = self.initTimestamp
#endif #endif
//TODO:release ready must include chat node
self.ready.set(combineLatest(queue: .mainQueue(), [ self.ready.set(combineLatest(queue: .mainQueue(), [
self.contentDataReady.get(), self.contentDataReady.get(),
self.wallpaperReady.get(), self.wallpaperReady.get(),

View File

@ -547,7 +547,6 @@ extension ChatControllerImpl {
strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
} else if let channel = peer as? TelegramChannel, channel.isMonoForum { } else if let channel = peer as? TelegramChannel, channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] {
//TODO:localize
strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, nil, false) strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, nil, false)
} else { } else {
strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, false) strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, false)

View File

@ -49,486 +49,6 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
} }
} }
/*private final class ItemView: UIView {
private let context: AccountContext
private let action: () -> Void
private let extractedContainerNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let containerButton: HighlightTrackingButton
private var icon: ComponentView<Empty>?
private var avatarNode: AvatarNode?
private let title = ComponentView<Empty>()
private var badge: ComponentView<Empty>?
init(context: AccountContext, action: @escaping (() -> Void), contextGesture: @escaping (ContextGesture, ContextExtractedContentContainingNode) -> Void) {
self.context = context
self.action = action
self.extractedContainerNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.containerButton = HighlightTrackingButton()
super.init(frame: CGRect())
self.extractedContainerNode.contentNode.view.addSubview(self.containerButton)
self.containerNode.addSubnode(self.extractedContainerNode)
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
self.addSubview(self.containerNode.view)
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.containerButton.highligthedChanged = { [weak self] highlighted in
if let self, self.bounds.width > 0.0 {
let topScale: CGFloat = (self.bounds.width - 1.0) / self.bounds.width
let maxScale: CGFloat = (self.bounds.width + 1.0) / self.bounds.width
if highlighted {
self.layer.removeAnimation(forKey: "opacity")
self.layer.removeAnimation(forKey: "sublayerTransform")
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
transition.updateTransformScale(layer: self.layer, scale: topScale)
} else {
let transition: ContainedViewLayoutTransition = .immediate
transition.updateTransformScale(layer: self.layer, scale: 1.0)
self.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
guard let self else {
return
}
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
})
}
}
}
self.containerNode.isGestureEnabled = false
}
required init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func pressed() {
self.action()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var mappedPoint = point
if self.bounds.insetBy(dx: -8.0, dy: -4.0).contains(point) {
mappedPoint = self.bounds.center
}
return super.hitTest(mappedPoint, with: event)
}
func update(context: AccountContext, item: Item, isSelected: Bool, theme: PresentationTheme, height: CGFloat, transition: ComponentTransition) -> CGSize {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25)
let spacing: CGFloat = 3.0
let badgeSpacing: CGFloat = 4.0
let iconSize = CGSize(width: 18.0, height: 18.0)
var avatarIconContent: EmojiStatusComponent.Content?
if case let .forum(topicId) = item.item.id {
if topicId != 1, let threadData = item.item.threadData {
if let fileId = threadData.info.icon, fileId != 0 {
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0))
} else {
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
}
} else {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme), tintColor: theme.rootController.navigationBar.secondaryTextColor)
}
}
if let avatarIconContent {
let avatarIconComponent = EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: avatarIconContent,
isVisibleForAnimations: false,
action: nil
)
let icon: ComponentView<Empty>
if let current = self.icon {
icon = current
} else {
icon = ComponentView()
self.icon = icon
}
let _ = icon.update(
transition: .immediate,
component: AnyComponent(avatarIconComponent),
environment: {},
containerSize: CGSize(width: 18.0, height: 18.0)
)
} else if let icon = self.icon {
self.icon = nil
icon.view?.removeFromSuperview()
}
let titleText: String
if case let .forum(topicId) = item.item.id {
let _ = topicId
if let threadData = item.item.threadData {
titleText = threadData.info.title
} else {
//TODO:localize
titleText = "General"
}
} else {
titleText = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " "
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: titleText, font: Font.medium(14.0), textColor: isSelected ? theme.rootController.navigationBar.accentTextColor : theme.rootController.navigationBar.secondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
var badgeSize: CGSize?
if let readCounters = item.item.readCounters, readCounters.count > 0 {
let badge: ComponentView<Empty>
var badgeTransition = transition
if let current = self.badge {
badge = current
} else {
badgeTransition = .immediate
badge = ComponentView<Empty>()
self.badge = badge
}
badgeSize = badge.update(
transition: badgeTransition,
component: AnyComponent(TextBadgeComponent(
text: countString(Int64(readCounters.count)),
font: Font.regular(12.0),
background: item.item.isMuted ? theme.chatList.unreadBadgeInactiveBackgroundColor : theme.chatList.unreadBadgeActiveBackgroundColor,
foreground: item.item.isMuted ? theme.chatList.unreadBadgeInactiveTextColor : theme.chatList.unreadBadgeActiveTextColor,
insets: UIEdgeInsets(top: 1.0, left: 5.0, bottom: 2.0, right: 5.0)
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
}
var contentSize: CGFloat = iconSize.width + spacing + titleSize.width
if let badgeSize {
contentSize += badgeSpacing + badgeSize.width
}
let size = CGSize(width: contentSize, height: height)
let iconFrame = CGRect(origin: CGPoint(x: 0.0, y: 5.0 + floor((size.height - iconSize.height) * 0.5)), size: iconSize)
let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: 5.0 + floor((size.height - titleSize.height) * 0.5)), size: titleSize)
if let icon = self.icon {
if let iconView = icon.view {
if iconView.superview == nil {
iconView.isUserInteractionEnabled = false
self.containerButton.addSubview(iconView)
}
iconView.frame = iconFrame
}
if let avatarNode = self.avatarNode {
self.avatarNode = nil
avatarNode.view.removeFromSuperview()
}
} else {
let avatarNode: AvatarNode
if let current = self.avatarNode {
avatarNode = current
} else {
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 7.0))
self.avatarNode = avatarNode
self.containerButton.addSubview(avatarNode.view)
}
avatarNode.frame = iconFrame
avatarNode.updateSize(size: iconFrame.size)
if let peer = item.item.renderedPeer.chatMainPeer {
if peer.smallProfileImage != nil {
avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
} else {
avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size)
}
}
}
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.containerButton.addSubview(titleView)
}
titleView.frame = titleFrame
}
if let badgeSize, let badge = self.badge {
let badgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + badgeSpacing, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize)
if let badgeView = badge.view {
if badgeView.superview == nil {
badgeView.isUserInteractionEnabled = false
self.containerButton.addSubview(badgeView)
badgeView.frame = badgeFrame
badgeView.alpha = 0.0
}
transition.setPosition(view: badgeView, position: badgeFrame.center)
transition.setBounds(view: badgeView, bounds: CGRect(origin: CGPoint(), size: badgeFrame.size))
transition.setScale(view: badgeView, scale: 1.0)
alphaTransition.setAlpha(view: badgeView, alpha: 1.0)
}
} else if let badge = self.badge {
self.badge = nil
if let badgeView = badge.view {
let badgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + badgeSpacing, y: titleFrame.minX + floorToScreenPixels((titleFrame.height - badgeView.bounds.height) * 0.5)), size: badgeView.bounds.size)
transition.setPosition(view: badgeView, position: badgeFrame.center)
transition.setScale(view: badgeView, scale: 0.001)
alphaTransition.setAlpha(view: badgeView, alpha: 0.0, completion: { [weak badgeView] _ in
badgeView?.removeFromSuperview()
})
}
}
transition.setFrame(view: self.containerButton, frame: CGRect(origin: CGPoint(), size: size))
self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
return size
}
}
private final class TabItemView: UIView {
private let context: AccountContext
private let action: () -> Void
private let extractedContainerNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let containerButton: HighlightTrackingButton
private let icon = ComponentView<Empty>()
init(context: AccountContext, action: @escaping (() -> Void)) {
self.context = context
self.action = action
self.extractedContainerNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.containerButton = HighlightTrackingButton()
super.init(frame: CGRect())
self.extractedContainerNode.contentNode.view.addSubview(self.containerButton)
self.containerNode.addSubnode(self.extractedContainerNode)
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
self.addSubview(self.containerNode.view)
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.containerButton.highligthedChanged = { [weak self] highlighted in
if let self, self.bounds.width > 0.0 {
let topScale: CGFloat = (self.bounds.width - 1.0) / self.bounds.width
let maxScale: CGFloat = (self.bounds.width + 1.0) / self.bounds.width
if highlighted {
self.layer.removeAnimation(forKey: "opacity")
self.layer.removeAnimation(forKey: "sublayerTransform")
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
transition.updateTransformScale(layer: self.layer, scale: topScale)
} else {
let transition: ContainedViewLayoutTransition = .immediate
transition.updateTransformScale(layer: self.layer, scale: 1.0)
self.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
guard let self else {
return
}
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
})
}
}
}
self.containerNode.isGestureEnabled = false
}
required init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func pressed() {
self.action()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var mappedPoint = point
if self.bounds.insetBy(dx: -8.0, dy: -4.0).contains(point) {
mappedPoint = self.bounds.center
}
return super.hitTest(mappedPoint, with: event)
}
func update(context: AccountContext, theme: PresentationTheme, height: CGFloat, transition: ComponentTransition) -> CGSize {
let iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(
name: "Chat/Title Panels/SidebarIcon",
tintColor: theme.rootController.navigationBar.secondaryTextColor,
maxSize: nil,
scaleFactor: 1.0
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let contentSize: CGFloat = iconSize.width
let size = CGSize(width: contentSize, height: height)
let iconFrame = CGRect(origin: CGPoint(x: 0.0, y: 5.0 + floor((size.height - iconSize.height) * 0.5)), size: iconSize)
if let iconView = self.icon.view {
if iconView.superview == nil {
iconView.isUserInteractionEnabled = false
self.containerButton.addSubview(iconView)
}
iconView.frame = iconFrame
}
transition.setFrame(view: self.containerButton, frame: CGRect(origin: CGPoint(), size: size))
self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
return size
}
}
private final class AllItemView: UIView {
private let context: AccountContext
private let action: () -> Void
private let extractedContainerNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let containerButton: HighlightTrackingButton
private let title = ComponentView<Empty>()
init(context: AccountContext, action: @escaping (() -> Void)) {
self.context = context
self.action = action
self.extractedContainerNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.containerButton = HighlightTrackingButton()
super.init(frame: CGRect())
self.extractedContainerNode.contentNode.view.addSubview(self.containerButton)
self.containerNode.addSubnode(self.extractedContainerNode)
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
self.addSubview(self.containerNode.view)
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.containerButton.highligthedChanged = { [weak self] highlighted in
if let self, self.bounds.width > 0.0 {
let topScale: CGFloat = (self.bounds.width - 1.0) / self.bounds.width
let maxScale: CGFloat = (self.bounds.width + 1.0) / self.bounds.width
if highlighted {
self.layer.removeAnimation(forKey: "opacity")
self.layer.removeAnimation(forKey: "sublayerTransform")
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
transition.updateTransformScale(layer: self.layer, scale: topScale)
} else {
let transition: ContainedViewLayoutTransition = .immediate
transition.updateTransformScale(layer: self.layer, scale: 1.0)
self.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
guard let self else {
return
}
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
})
}
}
}
self.containerNode.isGestureEnabled = false
}
required init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func pressed() {
self.action()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var mappedPoint = point
if self.bounds.insetBy(dx: -8.0, dy: -4.0).contains(point) {
mappedPoint = self.bounds.center
}
return super.hitTest(mappedPoint, with: event)
}
func update(context: AccountContext, isSelected: Bool, theme: PresentationTheme, height: CGFloat, transition: ComponentTransition) -> CGSize {
//TODO:localize
let titleText: String = "All"
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: titleText, font: Font.medium(14.0), textColor: isSelected ? theme.rootController.navigationBar.accentTextColor : theme.rootController.navigationBar.secondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let contentSize: CGFloat = titleSize.width
let size = CGSize(width: contentSize, height: height)
let titleFrame = CGRect(origin: CGPoint(x: 0.0, y: 5.0 + floor((size.height - titleSize.height) * 0.5)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.containerButton.addSubview(titleView)
}
titleView.frame = titleFrame
}
transition.setFrame(view: self.containerButton, frame: CGRect(origin: CGPoint(), size: size))
self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
return size
}
}*/
private var params: Params? private var params: Params?
private let context: AccountContext private let context: AccountContext
@ -618,234 +138,11 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
} }
transition.updateFrame(view: panelView, frame: panelFrame) transition.updateFrame(view: panelView, frame: panelFrame)
} }
/*
let containerInsets = UIEdgeInsets(top: 0.0, left: params.leftInset + 16.0, bottom: 0.0, right: params.rightInset + 16.0)
let itemSpacing: CGFloat = 24.0
var leftContentInset: CGFloat = containerInsets.left + 8.0
do {
var itemTransition = transition
var animateIn = false
let itemView: TabItemView
if let current = self.tabItemView {
itemView = current
} else {
itemTransition = .immediate
animateIn = true
itemView = TabItemView(context: self.context, action: { [weak self] in
guard let self else {
return
}
self.interfaceInteraction?.toggleChatSidebarMode()
})
self.tabItemView = itemView
self.view.addSubview(itemView)
}
let itemSize = itemView.update(context: self.context, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate)
let itemFrame = CGRect(origin: CGPoint(x: leftContentInset, y: -5.0), size: itemSize)
itemTransition.updatePosition(layer: itemView.layer, position: itemFrame.center)
itemTransition.updateBounds(layer: itemView.layer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
if animateIn && transition.isAnimated {
itemView.layer.animateAlpha(from: 0.0, to: itemView.alpha, duration: 0.15)
transition.animateTransformScale(view: itemView, from: 0.001)
}
leftContentInset += itemSize.width + 8.0
}
var contentSize = CGSize(width: itemSpacing - 8.0, height: panelHeight)
var validIds: [Item.Id] = []
var isFirst = true
var selectedItemFrame: CGRect?
do {
if isFirst {
isFirst = false
} else {
contentSize.width += itemSpacing
}
var itemTransition = transition
var animateIn = false
let itemView: AllItemView
if let current = self.allItemView {
itemView = current
} else {
itemTransition = .immediate
animateIn = true
itemView = AllItemView(context: self.context, action: { [weak self] in
guard let self else {
return
}
self.interfaceInteraction?.updateChatLocationThread(nil, .left)
})
self.allItemView = itemView
self.scrollView.addSubview(itemView)
}
var isSelected = false
if params.interfaceState.chatLocation.threadId == nil {
isSelected = true
}
let itemSize = itemView.update(context: self.context, isSelected: isSelected, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate)
let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize)
if isSelected {
selectedItemFrame = itemFrame
}
itemTransition.updatePosition(layer: itemView.layer, position: itemFrame.center)
itemTransition.updateBounds(layer: itemView.layer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
if animateIn && transition.isAnimated {
itemView.layer.animateAlpha(from: 0.0, to: itemView.alpha, duration: 0.15)
transition.animateTransformScale(view: itemView, from: 0.001)
}
contentSize.width += itemSize.width
}
for item in self.items {
if isFirst {
isFirst = false
} else {
contentSize.width += itemSpacing
}
let itemId = item.id
validIds.append(itemId)
var itemTransition = transition
var animateIn = false
let itemView: ItemView
if let current = self.itemViews[itemId] {
itemView = current
} else {
itemTransition = .immediate
animateIn = true
let chatListItem = item.item
itemView = ItemView(context: self.context, action: { [weak self] in
guard let self else {
return
}
let topicId: Int64
if case let .forum(topicIdValue) = chatListItem.id {
topicId = topicIdValue
} else {
topicId = chatListItem.renderedPeer.peerId.toInt64()
}
var direction = true
if let params = self.params, let lhsIndex = self.topicIndex(threadId: params.interfaceState.chatLocation.threadId), let rhsIndex = self.topicIndex(threadId: topicId) {
direction = lhsIndex < rhsIndex
}
self.interfaceInteraction?.updateChatLocationThread(topicId, direction ? .right : .left)
}, contextGesture: { gesture, sourceNode in
})
self.itemViews[itemId] = itemView
self.scrollView.addSubview(itemView)
}
var isSelected = false
if case let .forum(topicId) = item.item.id {
isSelected = params.interfaceState.chatLocation.threadId == topicId
} else {
isSelected = params.interfaceState.chatLocation.threadId == item.item.renderedPeer.peerId.toInt64()
}
let itemSize = itemView.update(context: self.context, item: item, isSelected: isSelected, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate)
let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize)
if isSelected {
selectedItemFrame = itemFrame
}
itemTransition.updatePosition(layer: itemView.layer, position: itemFrame.center)
itemTransition.updateBounds(layer: itemView.layer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
if animateIn && transition.isAnimated {
itemView.layer.animateAlpha(from: 0.0, to: itemView.alpha, duration: 0.15)
transition.animateTransformScale(view: itemView, from: 0.001)
}
contentSize.width += itemSize.width
}
var removedIds: [Item.Id] = []
for (id, itemView) in self.itemViews {
if !validIds.contains(id) {
removedIds.append(id)
if transition.isAnimated {
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak itemView] _ in
itemView?.removeFromSuperview()
})
transition.updateTransformScale(layer: itemView.layer, scale: 0.001)
} else {
itemView.removeFromSuperview()
}
}
}
for id in removedIds {
self.itemViews.removeValue(forKey: id)
}
if let selectedItemFrame {
let lineFrame = CGRect(origin: CGPoint(x: selectedItemFrame.minX, y: panelHeight - 4.0), size: CGSize(width: selectedItemFrame.width + 4.0, height: 4.0))
if self.selectedLineView.isHidden {
self.selectedLineView.isHidden = false
self.selectedLineView.frame = lineFrame
} else {
transition.updateFrame(view: self.selectedLineView, frame: lineFrame)
}
} else {
self.selectedLineView.isHidden = true
}
contentSize.width += containerInsets.right
let scrollSize = CGSize(width: params.width - leftContentInset, height: contentSize.height)
self.scrollViewContainer.frame = CGRect(origin: CGPoint(x: leftContentInset, y: 0.0), size: scrollSize)
self.scrollViewMask.frame = CGRect(origin: CGPoint(), size: scrollSize)
if self.scrollView.bounds.size != scrollSize {
self.scrollView.frame = CGRect(origin: CGPoint(), size: scrollSize)
}
if self.scrollView.contentSize != contentSize {
self.scrollView.contentSize = contentSize
}
let scrollToId: ScrollId
if let threadId = params.interfaceState.chatLocation.threadId {
scrollToId = .topic(threadId)
} else {
scrollToId = .all
}
if self.appliedScrollToId != scrollToId {
if case let .topic(threadId) = scrollToId {
if let itemView = self.itemViews[.forum(threadId)] {
self.appliedScrollToId = scrollToId
self.scrollView.scrollRectToVisible(itemView.frame.insetBy(dx: -46.0, dy: 0.0), animated: hadItemViews)
}
} else if case .all = scrollToId {
self.appliedScrollToId = scrollToId
self.scrollView.scrollRectToVisible(CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), animated: hadItemViews)
} else {
self.appliedScrollToId = scrollToId
}
}*/
} }
public func updateGlobalOffset(globalOffset: CGFloat, transition: ComponentTransition) { public func updateGlobalOffset(globalOffset: CGFloat, transition: ComponentTransition) {
if let panelView = self.panel.view as? ChatSideTopicsPanel.View { if let panelView = self.panel.view as? ChatSideTopicsPanel.View {
panelView.updateGlobalOffset(globalOffset: globalOffset, transition: transition) panelView.updateGlobalOffset(globalOffset: globalOffset, transition: transition)
//transition.setTransform(view: tabItemView, transform: CATransform3DMakeTranslation(0.0, -globalOffset, 0.0))
} }
} }
@ -855,23 +152,5 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
} else { } else {
return nil return nil
} }
/*if let threadId {
if let value = self.items.firstIndex(where: { item in
if item.id == .chatList(PeerId(threadId)) {
return true
} else if item.id == .forum(threadId) {
return true
} else {
return false
}
}) {
return value + 1
} else {
return nil
}
} else {
return 0
}*/
} }
} }