Various improvements

This commit is contained in:
Isaac 2024-02-06 00:06:31 +04:00
parent 1b410e9d0c
commit 51fcc024d6
31 changed files with 419 additions and 146 deletions

View File

@ -10939,6 +10939,7 @@ Sorry for the inconvenience.";
"Chat.ReactionContextMenu.RemoveTag" = "Remove Tag"; "Chat.ReactionContextMenu.RemoveTag" = "Remove Tag";
"Chat.EmptyStateMessagingRestrictedToPremium.Text" = "Subscribe to **Premium**\nto message **%@**."; "Chat.EmptyStateMessagingRestrictedToPremium.Text" = "Subscribe to **Premium**\nto message **%@**.";
"Chat.EmptyStateMessagingRestrictedToPremiumDisabled.Text" = "Subscribe to **Premium**\nto message **%@**.";
"Chat.EmptyStateMessagingRestrictedToPremium.Action" = "Get Premium"; "Chat.EmptyStateMessagingRestrictedToPremium.Action" = "Get Premium";
"Chat.ContextMenuReadDate.ReadAvailablePrefix" = "read"; "Chat.ContextMenuReadDate.ReadAvailablePrefix" = "read";
@ -11041,3 +11042,5 @@ Sorry for the inconvenience.";
"Chat.BottomSearchPanel.DisplayModeFormat" = "Show as %@"; "Chat.BottomSearchPanel.DisplayModeFormat" = "Show as %@";
"Chat.BottomSearchPanel.DisplayModeChat" = "Chat"; "Chat.BottomSearchPanel.DisplayModeChat" = "Chat";
"Chat.BottomSearchPanel.DisplayModeList" = "List"; "Chat.BottomSearchPanel.DisplayModeList" = "List";
"Conversation.SendMessageErrorNonPremiumForbidden" = "Only Premium users can message %@";

View File

@ -151,6 +151,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
previousItemNode.listNode.setPeerThreadPinned = nil previousItemNode.listNode.setPeerThreadPinned = nil
previousItemNode.listNode.setPeerThreadHidden = nil previousItemNode.listNode.setPeerThreadHidden = nil
previousItemNode.listNode.peerSelected = nil previousItemNode.listNode.peerSelected = nil
previousItemNode.listNode.disabledPeerSelected = nil
previousItemNode.listNode.groupSelected = nil previousItemNode.listNode.groupSelected = nil
previousItemNode.listNode.updatePeerGrouping = nil previousItemNode.listNode.updatePeerGrouping = nil
previousItemNode.listNode.contentOffsetChanged = nil previousItemNode.listNode.contentOffsetChanged = nil
@ -205,6 +206,9 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
itemNode.listNode.peerSelected = { [weak self] peerId, threadId, animated, activateInput, promoInfo in itemNode.listNode.peerSelected = { [weak self] peerId, threadId, animated, activateInput, promoInfo in
self?.peerSelected?(peerId, threadId, animated, activateInput, promoInfo) self?.peerSelected?(peerId, threadId, animated, activateInput, promoInfo)
} }
itemNode.listNode.disabledPeerSelected = { [weak self] peerId, threadId, reason in
self?.disabledPeerSelected?(peerId, threadId, reason)
}
itemNode.listNode.groupSelected = { [weak self] groupId in itemNode.listNode.groupSelected = { [weak self] groupId in
self?.groupSelected?(groupId) self?.groupSelected?(groupId)
} }
@ -390,6 +394,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)? var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)?
var setPeerThreadHidden: ((EnginePeer.Id, Int64, Bool) -> Void)? var setPeerThreadHidden: ((EnginePeer.Id, Int64, Bool) -> Void)?
public var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? public var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)?
public var disabledPeerSelected: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
var groupSelected: ((EngineChatList.Group) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)?
var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)?
var contentOffset: ListViewVisibleContentOffset? var contentOffset: ListViewVisibleContentOffset?

View File

@ -1754,7 +1754,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let foundThreads: Signal<[EngineChatList.Item], NoError> let foundThreads: Signal<[EngineChatList.Item], NoError>
if case let .forum(peerId) = location, (key == .topics || key == .chats) { if case let .forum(peerId) = location, (key == .topics || key == .chats) {
foundThreads = chatListViewForLocation(chatListLocation: location, location: .initial(count: 1000, filter: nil), account: context.account) foundThreads = chatListViewForLocation(chatListLocation: location, location: .initial(count: 1000, filter: nil), account: context.account, shouldLoadCanMessagePeer: true)
|> map { view -> [EngineChatList.Item] in |> map { view -> [EngineChatList.Item] in
var filteredItems: [EngineChatList.Item] = [] var filteredItems: [EngineChatList.Item] = []
let queryTokens = stringIndexTokens(finalQuery, transliteration: .combined) let queryTokens = stringIndexTokens(finalQuery, transliteration: .combined)

View File

@ -588,6 +588,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
peerMode: .generalSearch(isSavedMessages: false), peerMode: .generalSearch(isSavedMessages: false),
peer: peerContent, peer: peerContent,
status: status, status: status,
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
enabled: enabled, enabled: enabled,
selection: selectable ? .selectable(selected: selected) : .none, selection: selectable ? .selectable(selected: selected) : .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
@ -601,9 +602,9 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction.peerSelected(chatPeer, nil, threadId, nil) nodeInteraction.peerSelected(chatPeer, nil, threadId, nil)
} }
} }
}, disabledAction: isForum && editing ? nil : { _ in }, disabledAction: (isForum && editing) && !peerEntry.requiresPremiumForMessaging ? nil : { _ in
if let chatPeer = chatPeer { if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer, threadId, .generic) nodeInteraction.disabledPeerSelected(chatPeer, threadId, peerEntry.requiresPremiumForMessaging ? .premiumRequired : .generic)
} }
}, },
animationCache: nodeInteraction.animationCache, animationCache: nodeInteraction.animationCache,
@ -627,6 +628,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
peerMode: .generalSearch(isSavedMessages: false), peerMode: .generalSearch(isSavedMessages: false),
peer: peerContent, peer: peerContent,
status: status, status: status,
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
enabled: true, enabled: true,
selection: .none, selection: .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
@ -636,7 +638,11 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
if let chatPeer = chatPeer { if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer, nil, nil, nil) nodeInteraction.peerSelected(chatPeer, nil, nil, nil)
} }
}, disabledAction: nil, }, disabledAction: peerEntry.requiresPremiumForMessaging ? { _ in
if let chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer, nil, .premiumRequired)
}
} : nil,
animationCache: nodeInteraction.animationCache, animationCache: nodeInteraction.animationCache,
animationRenderer: nodeInteraction.animationRenderer animationRenderer: nodeInteraction.animationRenderer
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
@ -912,6 +918,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
peerMode: .generalSearch(isSavedMessages: false), peerMode: .generalSearch(isSavedMessages: false),
peer: peerContent, peer: peerContent,
status: status, status: status,
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
enabled: enabled, enabled: enabled,
selection: selectable ? .selectable(selected: selected) : .none, selection: selectable ? .selectable(selected: selected) : .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
@ -925,9 +932,9 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction.peerSelected(chatPeer, nil, threadId, nil) nodeInteraction.peerSelected(chatPeer, nil, threadId, nil)
} }
} }
}, disabledAction: isForum && editing ? nil : { _ in }, disabledAction: (isForum && editing) && !peerEntry.requiresPremiumForMessaging ? nil : { _ in
if let chatPeer = chatPeer { if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer, threadId, .generic) nodeInteraction.disabledPeerSelected(chatPeer, threadId, peerEntry.requiresPremiumForMessaging ? .premiumRequired : .generic)
} }
}, },
animationCache: nodeInteraction.animationCache, animationCache: nodeInteraction.animationCache,
@ -951,6 +958,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
peerMode: .generalSearch(isSavedMessages: false), peerMode: .generalSearch(isSavedMessages: false),
peer: peerContent, peer: peerContent,
status: status, status: status,
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
enabled: true, enabled: true,
selection: .none, selection: .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
@ -960,7 +968,11 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
if let chatPeer = chatPeer { if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer, nil, nil, nil) nodeInteraction.peerSelected(chatPeer, nil, nil, nil)
} }
}, disabledAction: nil, }, disabledAction: peerEntry.requiresPremiumForMessaging ? { _ in
if let chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer, nil, .premiumRequired)
}
} : nil,
animationCache: nodeInteraction.animationCache, animationCache: nodeInteraction.animationCache,
animationRenderer: nodeInteraction.animationRenderer animationRenderer: nodeInteraction.animationRenderer
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
@ -1758,10 +1770,17 @@ public final class ChatListNode: ListView {
let viewProcessingQueue = self.viewProcessingQueue let viewProcessingQueue = self.viewProcessingQueue
let shouldLoadCanMessagePeer: Bool
if case .peers = mode {
shouldLoadCanMessagePeer = true
} else {
shouldLoadCanMessagePeer = false
}
let chatListViewUpdate = self.chatListLocation.get() let chatListViewUpdate = self.chatListLocation.get()
|> distinctUntilChanged |> distinctUntilChanged
|> mapToSignal { listLocation -> Signal<(ChatListNodeViewUpdate, ChatListFilter?), NoError> in |> mapToSignal { listLocation -> Signal<(ChatListNodeViewUpdate, ChatListFilter?), NoError> in
return chatListViewForLocation(chatListLocation: location, location: listLocation, account: context.account) return chatListViewForLocation(chatListLocation: location, location: listLocation, account: context.account, shouldLoadCanMessagePeer: shouldLoadCanMessagePeer)
|> map { update in |> map { update in
return (update, listLocation.filter) return (update, listLocation.filter)
} }
@ -3719,7 +3738,15 @@ public final class ChatListNode: ListView {
guard case let .chatList(groupId) = self.location else { guard case let .chatList(groupId) = self.location else {
return return
} }
let _ = (chatListViewForLocation(chatListLocation: .chatList(groupId: groupId), location: .initial(count: 10, filter: filter), account: self.context.account)
let shouldLoadCanMessagePeer: Bool
if case .peers = self.mode {
shouldLoadCanMessagePeer = true
} else {
shouldLoadCanMessagePeer = false
}
let _ = (chatListViewForLocation(chatListLocation: .chatList(groupId: groupId), location: .initial(count: 10, filter: filter), account: self.context.account, shouldLoadCanMessagePeer: shouldLoadCanMessagePeer)
|> take(1) |> take(1)
|> deliverOnMainQueue).startStandalone(next: { update in |> deliverOnMainQueue).startStandalone(next: { update in
let items = update.list.items let items = update.list.items

View File

@ -714,7 +714,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
hasUnseenCloseFriends: stats.hasUnseenCloseFriends hasUnseenCloseFriends: stats.hasUnseenCloseFriends
) )
}, },
requiresPremiumForMessaging: false, requiresPremiumForMessaging: entry.isPremiumRequiredToMessage,
displayAsTopicList: entry.displayAsTopicList displayAsTopicList: entry.displayAsTopicList
)) ))

View File

@ -113,7 +113,7 @@ public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: E
}) })
} }
func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account) -> Signal<ChatListNodeViewUpdate, NoError> { func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account, shouldLoadCanMessagePeer: Bool) -> Signal<ChatListNodeViewUpdate, NoError> {
let accountPeerId = account.peerId let accountPeerId = account.peerId
switch chatListLocation { switch chatListLocation {
@ -128,7 +128,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
switch location { switch location {
case let .initial(count, _): case let .initial(count, _):
let signal: Signal<(ChatListView, ViewUpdateType), NoError> let signal: Signal<(ChatListView, ViewUpdateType), NoError>
signal = account.viewTracker.tailChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, count: count) signal = account.viewTracker.tailChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, count: count, shouldLoadCanMessagePeer: shouldLoadCanMessagePeer)
return signal return signal
|> map { view, updateType -> ChatListNodeViewUpdate in |> map { view, updateType -> ChatListNodeViewUpdate in
return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: updateType, scrollPosition: nil) return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: updateType, scrollPosition: nil)
@ -138,7 +138,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
return .never() return .never()
} }
var first = true var first = true
return account.viewTracker.aroundChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, index: index, count: 80) return account.viewTracker.aroundChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, index: index, count: 80, shouldLoadCanMessagePeer: shouldLoadCanMessagePeer)
|> map { view, updateType -> ChatListNodeViewUpdate in |> map { view, updateType -> ChatListNodeViewUpdate in
let genericType: ViewUpdateType let genericType: ViewUpdateType
if first { if first {
@ -157,7 +157,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > .chatList(index) ? .Down : .Up let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > .chatList(index) ? .Down : .Up
let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated)
var first = true var first = true
return account.viewTracker.aroundChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, index: index, count: 80) return account.viewTracker.aroundChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, index: index, count: 80, shouldLoadCanMessagePeer: shouldLoadCanMessagePeer)
|> map { view, updateType -> ChatListNodeViewUpdate in |> map { view, updateType -> ChatListNodeViewUpdate in
let genericType: ViewUpdateType let genericType: ViewUpdateType
let scrollPosition: ChatListNodeViewScrollPosition? = first ? chatScrollPosition : nil let scrollPosition: ChatListNodeViewScrollPosition? = first ? chatScrollPosition : nil
@ -295,7 +295,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
isContact: false, isContact: false,
autoremoveTimeout: nil, autoremoveTimeout: nil,
storyStats: nil, storyStats: nil,
displayAsTopicList: false displayAsTopicList: false,
isPremiumRequiredToMessage: false
)) ))
} }
@ -372,7 +373,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
isContact: false, isContact: false,
autoremoveTimeout: nil, autoremoveTimeout: nil,
storyStats: nil, storyStats: nil,
displayAsTopicList: false displayAsTopicList: false,
isPremiumRequiredToMessage: false
)) ))
} }

View File

@ -126,6 +126,7 @@ public enum ChatListEntry: Comparable {
public var isContact: Bool public var isContact: Bool
public var autoremoveTimeout: Int32? public var autoremoveTimeout: Int32?
public var storyStats: PeerStoryStats? public var storyStats: PeerStoryStats?
public var extractedCachedData: AnyHashable?
public init( public init(
index: ChatListIndex, index: ChatListIndex,
@ -141,7 +142,8 @@ public enum ChatListEntry: Comparable {
hasFailed: Bool, hasFailed: Bool,
isContact: Bool, isContact: Bool,
autoremoveTimeout: Int32?, autoremoveTimeout: Int32?,
storyStats: PeerStoryStats? storyStats: PeerStoryStats?,
extractedCachedData: AnyHashable?
) { ) {
self.index = index self.index = index
self.messages = messages self.messages = messages
@ -157,6 +159,7 @@ public enum ChatListEntry: Comparable {
self.isContact = isContact self.isContact = isContact
self.autoremoveTimeout = autoremoveTimeout self.autoremoveTimeout = autoremoveTimeout
self.storyStats = storyStats self.storyStats = storyStats
self.extractedCachedData = extractedCachedData
} }
public static func ==(lhs: MessageEntryData, rhs: MessageEntryData) -> Bool { public static func ==(lhs: MessageEntryData, rhs: MessageEntryData) -> Bool {
@ -218,6 +221,9 @@ public enum ChatListEntry: Comparable {
if lhs.storyStats != rhs.storyStats { if lhs.storyStats != rhs.storyStats {
return false return false
} }
if lhs.extractedCachedData != rhs.extractedCachedData {
return false
}
return true return true
} }
@ -308,6 +314,7 @@ enum MutableChatListEntry: Equatable {
var isContact: Bool var isContact: Bool
var autoremoveTimeout: Int32? var autoremoveTimeout: Int32?
var storyStats: PeerStoryStats? var storyStats: PeerStoryStats?
var extractedCachedData: AnyHashable?
init( init(
index: ChatListIndex, index: ChatListIndex,
@ -325,7 +332,8 @@ enum MutableChatListEntry: Equatable {
hasFailedMessages: Bool, hasFailedMessages: Bool,
isContact: Bool, isContact: Bool,
autoremoveTimeout: Int32?, autoremoveTimeout: Int32?,
storyStats: PeerStoryStats? storyStats: PeerStoryStats?,
extractedCachedData: AnyHashable?
) { ) {
self.index = index self.index = index
self.messages = messages self.messages = messages
@ -343,6 +351,7 @@ enum MutableChatListEntry: Equatable {
self.isContact = isContact self.isContact = isContact
self.autoremoveTimeout = autoremoveTimeout self.autoremoveTimeout = autoremoveTimeout
self.storyStats = storyStats self.storyStats = storyStats
self.extractedCachedData = extractedCachedData
} }
} }
@ -352,10 +361,10 @@ enum MutableChatListEntry: Equatable {
init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) { init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) {
switch intermediateEntry { switch intermediateEntry {
case let .message(index, messageIndex): case let .message(index, messageIndex):
self = .IntermediateMessageEntry(index: index, messageIndex: messageIndex) self = .IntermediateMessageEntry(index: index, messageIndex: messageIndex)
case let .hole(hole): case let .hole(hole):
self = .HoleEntry(hole) self = .HoleEntry(hole)
} }
} }
@ -553,6 +562,7 @@ final class MutableChatListView {
let filterPredicate: ChatListFilterPredicate? let filterPredicate: ChatListFilterPredicate?
private let aroundIndex: ChatListIndex private let aroundIndex: ChatListIndex
private let summaryComponents: ChatListEntrySummaryComponents private let summaryComponents: ChatListEntrySummaryComponents
private let extractCachedData: ((CachedPeerData) -> AnyHashable?)?
fileprivate var groupEntries: [ChatListGroupReferenceEntry] fileprivate var groupEntries: [ChatListGroupReferenceEntry]
private var count: Int private var count: Int
@ -569,11 +579,16 @@ final class MutableChatListView {
private let displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey private let displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey
private(set) var displaySavedMessagesAsTopicList: PreferencesEntry? private(set) var displaySavedMessagesAsTopicList: PreferencesEntry?
init(postbox: PostboxImpl, currentTransaction: Transaction, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) { private let accountPeerId: PeerId?
private(set) var accountPeer: Peer?
init(postbox: PostboxImpl, currentTransaction: Transaction, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, extractCachedData: ((CachedPeerData) -> AnyHashable?)?, accountPeerId: PeerId?) {
self.groupId = groupId self.groupId = groupId
self.filterPredicate = filterPredicate self.filterPredicate = filterPredicate
self.aroundIndex = aroundIndex self.aroundIndex = aroundIndex
self.summaryComponents = summaryComponents self.summaryComponents = summaryComponents
self.extractCachedData = extractCachedData
self.accountPeerId = accountPeerId
self.currentHiddenPeerIds = postbox.hiddenChatIds self.currentHiddenPeerIds = postbox.hiddenChatIds
@ -595,7 +610,7 @@ final class MutableChatListView {
spaces.append(.group(groupId: self.groupId, pinned: .includePinned, predicate: filterPredicate)) spaces.append(.group(groupId: self.groupId, pinned: .includePinned, predicate: filterPredicate))
} }
self.spaces = spaces self.spaces = spaces
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: aroundIndex, summaryComponents: self.summaryComponents, halfLimit: count) self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: aroundIndex, summaryComponents: self.summaryComponents, extractCachedData: self.extractCachedData, halfLimit: count)
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction) self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
self.count = count self.count = count
@ -619,6 +634,8 @@ final class MutableChatListView {
} }
self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey) self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey)
self.accountPeer = self.accountPeerId.flatMap(postbox.peerTable.get)
} }
private func reloadGroups(postbox: PostboxImpl) { private func reloadGroups(postbox: PostboxImpl) {
@ -698,17 +715,20 @@ final class MutableChatListView {
} }
self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey) self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey)
self.accountPeer = self.accountPeerId.flatMap(postbox.peerTable.get)
} }
func refreshDueToExternalTransaction(postbox: PostboxImpl, currentTransaction: Transaction) -> Bool { func refreshDueToExternalTransaction(postbox: PostboxImpl, currentTransaction: Transaction) -> Bool {
var updated = false var updated = false
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count) self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, extractCachedData: self.extractCachedData, halfLimit: self.count)
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction) self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
updated = true updated = true
let currentGroupEntries = self.groupEntries let currentGroupEntries = self.groupEntries
let currentDisplaySavedMessagesAsTopicList = self.displaySavedMessagesAsTopicList let currentDisplaySavedMessagesAsTopicList = self.displaySavedMessagesAsTopicList
let currentAccountPeer = self.accountPeer
self.reloadGroups(postbox: postbox) self.reloadGroups(postbox: postbox)
@ -718,6 +738,9 @@ final class MutableChatListView {
if self.displaySavedMessagesAsTopicList != currentDisplaySavedMessagesAsTopicList { if self.displaySavedMessagesAsTopicList != currentDisplaySavedMessagesAsTopicList {
updated = true updated = true
} }
if !arePeersEqual(self.accountPeer, currentAccountPeer) {
updated = true
}
return updated return updated
} }
@ -733,11 +756,11 @@ final class MutableChatListView {
} }
if transaction.updatedGlobalNotificationSettings && self.filterPredicate != nil { if transaction.updatedGlobalNotificationSettings && self.filterPredicate != nil {
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count) self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, extractCachedData: self.extractCachedData, halfLimit: self.count)
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction) self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
hasChanges = true hasChanges = true
} else if hasFilterChanges { } else if hasFilterChanges {
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count) self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, extractCachedData: self.extractCachedData, halfLimit: self.count)
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction) self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
hasChanges = true hasChanges = true
} else { } else {
@ -761,6 +784,14 @@ final class MutableChatListView {
} }
} }
if let accountPeerId = self.accountPeerId, transaction.currentUpdatedPeers[accountPeerId] != nil {
let accountPeer = self.accountPeerId.flatMap(postbox.peerTable.get)
if !arePeersEqual(self.accountPeer, accountPeer) {
self.accountPeer = accountPeer
hasChanges = true
}
}
if case .root = self.groupId, self.filterPredicate == nil { if case .root = self.groupId, self.filterPredicate == nil {
var invalidatedGroups = false var invalidatedGroups = false
for (groupId, groupOperations) in operations { for (groupId, groupOperations) in operations {
@ -926,6 +957,11 @@ final class MutableChatListView {
let storyStats = fetchPeerStoryStats(postbox: postbox, peerId: index.messageIndex.id.peerId) let storyStats = fetchPeerStoryStats(postbox: postbox, peerId: index.messageIndex.id.peerId)
var extractedCachedData: AnyHashable?
if let extractCachedData = self.extractCachedData {
extractedCachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId).flatMap(extractCachedData)
}
return .MessageEntry(MutableChatListEntry.MessageEntryData( return .MessageEntry(MutableChatListEntry.MessageEntryData(
index: index, index: index,
messages: renderedMessages, messages: renderedMessages,
@ -942,7 +978,8 @@ final class MutableChatListView {
hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId),
isContact: isContact, isContact: isContact,
autoremoveTimeout: autoremoveTimeout, autoremoveTimeout: autoremoveTimeout,
storyStats: storyStats storyStats: storyStats,
extractedCachedData: extractedCachedData
)) ))
default: default:
return nil return nil
@ -966,6 +1003,7 @@ public final class ChatListView {
public let earlierIndex: ChatListIndex? public let earlierIndex: ChatListIndex?
public let laterIndex: ChatListIndex? public let laterIndex: ChatListIndex?
public let displaySavedMessagesAsTopicList: PreferencesEntry? public let displaySavedMessagesAsTopicList: PreferencesEntry?
public let accountPeer: Peer?
init(_ mutableView: MutableChatListView) { init(_ mutableView: MutableChatListView) {
self.groupId = mutableView.groupId self.groupId = mutableView.groupId
@ -988,7 +1026,8 @@ public final class ChatListView {
hasFailed: entryData.hasFailedMessages, hasFailed: entryData.hasFailedMessages,
isContact: entryData.isContact, isContact: entryData.isContact,
autoremoveTimeout: entryData.autoremoveTimeout, autoremoveTimeout: entryData.autoremoveTimeout,
storyStats: entryData.storyStats storyStats: entryData.storyStats,
extractedCachedData: entryData.extractedCachedData
))) )))
case let .HoleEntry(hole): case let .HoleEntry(hole):
entries.append(.HoleEntry(hole)) entries.append(.HoleEntry(hole))
@ -1022,7 +1061,8 @@ public final class ChatListView {
hasFailed: entryData.hasFailedMessages, hasFailed: entryData.hasFailedMessages,
isContact: entryData.isContact, isContact: entryData.isContact,
autoremoveTimeout: entryData.autoremoveTimeout, autoremoveTimeout: entryData.autoremoveTimeout,
storyStats: entryData.storyStats storyStats: entryData.storyStats,
extractedCachedData: entryData.extractedCachedData
)), )),
info: entry.info info: entry.info
)) ))
@ -1035,5 +1075,6 @@ public final class ChatListView {
self.additionalItemEntries = additionalItemEntries self.additionalItemEntries = additionalItemEntries
self.displaySavedMessagesAsTopicList = mutableView.displaySavedMessagesAsTopicList self.displaySavedMessagesAsTopicList = mutableView.displaySavedMessagesAsTopicList
self.accountPeer = mutableView.accountPeer
} }
} }

View File

@ -141,14 +141,16 @@ private final class ChatListViewSpaceState {
private let space: ChatListViewSpace private let space: ChatListViewSpace
private let anchorIndex: MutableChatListEntryIndex private let anchorIndex: MutableChatListEntryIndex
private let summaryComponents: ChatListEntrySummaryComponents private let summaryComponents: ChatListEntrySummaryComponents
private let extractCachedData: ((CachedPeerData) -> AnyHashable?)?
private let halfLimit: Int private let halfLimit: Int
var orderedEntries: OrderedChatListViewEntries var orderedEntries: OrderedChatListViewEntries
init(postbox: PostboxImpl, currentTransaction: Transaction, space: ChatListViewSpace, anchorIndex: MutableChatListEntryIndex, summaryComponents: ChatListEntrySummaryComponents, halfLimit: Int) { init(postbox: PostboxImpl, currentTransaction: Transaction, space: ChatListViewSpace, anchorIndex: MutableChatListEntryIndex, summaryComponents: ChatListEntrySummaryComponents, extractCachedData: ((CachedPeerData) -> AnyHashable?)?, halfLimit: Int) {
self.space = space self.space = space
self.anchorIndex = anchorIndex self.anchorIndex = anchorIndex
self.summaryComponents = summaryComponents self.summaryComponents = summaryComponents
self.extractCachedData = extractCachedData
self.halfLimit = halfLimit self.halfLimit = halfLimit
self.orderedEntries = OrderedChatListViewEntries(anchorIndex: anchorIndex.index, lowerOrAtAnchor: [], higherThanAnchor: []) self.orderedEntries = OrderedChatListViewEntries(anchorIndex: anchorIndex.index, lowerOrAtAnchor: [], higherThanAnchor: [])
self.fillSpace(postbox: postbox, currentTransaction: currentTransaction) self.fillSpace(postbox: postbox, currentTransaction: currentTransaction)
@ -913,7 +915,7 @@ private final class ChatListViewSpaceState {
} }
} }
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty || cachedPeerDataUpdated || !transaction.currentStoryTopItemEvents.isEmpty || !transaction.storyPeerStatesEvents.isEmpty { if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty || cachedPeerDataUpdated || !transaction.currentStoryTopItemEvents.isEmpty || !transaction.storyPeerStatesEvents.isEmpty || !transaction.currentUpdatedCachedPeerData.isEmpty {
if self.orderedEntries.mutableScan({ entry in if self.orderedEntries.mutableScan({ entry in
switch entry { switch entry {
case let .MessageEntry(entryData): case let .MessageEntry(entryData):
@ -988,6 +990,14 @@ private final class ChatListViewSpaceState {
didUpdateSummaryInfo = true didUpdateSummaryInfo = true
} }
var extractedCachedData: AnyHashable?
if let extractCachedData = self.extractCachedData {
extractedCachedData = postbox.cachedPeerDataTable.get(entryData.index.messageIndex.id.peerId).flatMap(extractCachedData)
}
if entryData.extractedCachedData != extractedCachedData {
didUpdateSummaryInfo = true
}
if didUpdateSummaryInfo { if didUpdateSummaryInfo {
var entryData = entryData var entryData = entryData
entryData.readState = updatedReadState entryData.readState = updatedReadState
@ -995,6 +1005,7 @@ private final class ChatListViewSpaceState {
entryData.autoremoveTimeout = updatedAutoremoveTimeout entryData.autoremoveTimeout = updatedAutoremoveTimeout
entryData.storyStats = storyStats entryData.storyStats = storyStats
entryData.displayAsRegularChat = displayAsRegularChat entryData.displayAsRegularChat = displayAsRegularChat
entryData.extractedCachedData = extractedCachedData
return .MessageEntry(entryData) return .MessageEntry(entryData)
} else { } else {
return nil return nil
@ -1382,16 +1393,18 @@ final class ChatListViewSample {
struct ChatListViewState { struct ChatListViewState {
private let anchorIndex: MutableChatListEntryIndex private let anchorIndex: MutableChatListEntryIndex
private let summaryComponents: ChatListEntrySummaryComponents private let summaryComponents: ChatListEntrySummaryComponents
private let extractCachedData: ((CachedPeerData) -> AnyHashable?)?
private let halfLimit: Int private let halfLimit: Int
private var stateBySpace: [ChatListViewSpace: ChatListViewSpaceState] = [:] private var stateBySpace: [ChatListViewSpace: ChatListViewSpaceState] = [:]
init(postbox: PostboxImpl, currentTransaction: Transaction, spaces: [ChatListViewSpace], anchorIndex: ChatListIndex, summaryComponents: ChatListEntrySummaryComponents, halfLimit: Int) { init(postbox: PostboxImpl, currentTransaction: Transaction, spaces: [ChatListViewSpace], anchorIndex: ChatListIndex, summaryComponents: ChatListEntrySummaryComponents, extractCachedData: ((CachedPeerData) -> AnyHashable?)?, halfLimit: Int) {
self.anchorIndex = MutableChatListEntryIndex(index: anchorIndex, isMessage: true) self.anchorIndex = MutableChatListEntryIndex(index: anchorIndex, isMessage: true)
self.summaryComponents = summaryComponents self.summaryComponents = summaryComponents
self.extractCachedData = extractCachedData
self.halfLimit = halfLimit self.halfLimit = halfLimit
for space in spaces { for space in spaces {
self.stateBySpace[space] = ChatListViewSpaceState(postbox: postbox, currentTransaction: currentTransaction, space: space, anchorIndex: self.anchorIndex, summaryComponents: summaryComponents, halfLimit: halfLimit) self.stateBySpace[space] = ChatListViewSpaceState(postbox: postbox, currentTransaction: currentTransaction, space: space, anchorIndex: self.anchorIndex, summaryComponents: summaryComponents, extractCachedData: extractCachedData, halfLimit: halfLimit)
} }
} }
@ -1641,6 +1654,11 @@ struct ChatListViewState {
let storyStats = fetchPeerStoryStats(postbox: postbox, peerId: index.messageIndex.id.peerId) let storyStats = fetchPeerStoryStats(postbox: postbox, peerId: index.messageIndex.id.peerId)
var extractedCachedData: AnyHashable?
if let extractCachedData = self.extractCachedData {
extractedCachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId).flatMap(extractCachedData)
}
let updatedEntry: MutableChatListEntry = .MessageEntry(MutableChatListEntry.MessageEntryData( let updatedEntry: MutableChatListEntry = .MessageEntry(MutableChatListEntry.MessageEntryData(
index: index, index: index,
messages: renderedMessages, messages: renderedMessages,
@ -1657,7 +1675,8 @@ struct ChatListViewState {
hasFailedMessages: false, hasFailedMessages: false,
isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId), isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId),
autoremoveTimeout: autoremoveTimeout, autoremoveTimeout: autoremoveTimeout,
storyStats: storyStats storyStats: storyStats,
extractedCachedData: extractedCachedData
)) ))
if directionIndex == 0 { if directionIndex == 0 {
self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry) self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry)

View File

@ -3458,13 +3458,13 @@ final class PostboxImpl {
|> switchToLatest |> switchToLatest
} }
public func tailChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, count: Int, summaryComponents: ChatListEntrySummaryComponents) -> Signal<(ChatListView, ViewUpdateType), NoError> { public func tailChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, count: Int, summaryComponents: ChatListEntrySummaryComponents, extractCachedData: ((CachedPeerData) -> AnyHashable?)?, accountPeerId: PeerId?) -> Signal<(ChatListView, ViewUpdateType), NoError> {
return self.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: ChatListIndex.absoluteUpperBound, count: count, summaryComponents: summaryComponents, userInteractive: true) return self.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: ChatListIndex.absoluteUpperBound, count: count, summaryComponents: summaryComponents, userInteractive: true, extractCachedData: extractCachedData, accountPeerId: accountPeerId)
} }
public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> { public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false, extractCachedData: ((CachedPeerData) -> AnyHashable?)?, accountPeerId: PeerId?) -> Signal<(ChatListView, ViewUpdateType), NoError> {
return self.transactionSignal(userInteractive: userInteractive, { subscriber, transaction in return self.transactionSignal(userInteractive: userInteractive, { subscriber, transaction in
let mutableView = MutableChatListView(postbox: self, currentTransaction: transaction, groupId: groupId, filterPredicate: filterPredicate, aroundIndex: index, count: count, summaryComponents: summaryComponents) let mutableView = MutableChatListView(postbox: self, currentTransaction: transaction, groupId: groupId, filterPredicate: filterPredicate, aroundIndex: index, count: count, summaryComponents: summaryComponents, extractCachedData: extractCachedData, accountPeerId: accountPeerId)
mutableView.render(postbox: self) mutableView.render(postbox: self)
let (index, signal) = self.viewTracker.addChatListView(mutableView) let (index, signal) = self.viewTracker.addChatListView(mutableView)
@ -4568,7 +4568,9 @@ public class Postbox {
groupId: PeerGroupId, groupId: PeerGroupId,
filterPredicate: ChatListFilterPredicate? = nil, filterPredicate: ChatListFilterPredicate? = nil,
count: Int, count: Int,
summaryComponents: ChatListEntrySummaryComponents summaryComponents: ChatListEntrySummaryComponents,
extractCachedData: ((CachedPeerData) -> AnyHashable?)? = nil,
accountPeerId: PeerId? = nil
) -> Signal<(ChatListView, ViewUpdateType), NoError> { ) -> Signal<(ChatListView, ViewUpdateType), NoError> {
return Signal { subscriber in return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
@ -4578,7 +4580,9 @@ public class Postbox {
groupId: groupId, groupId: groupId,
filterPredicate: filterPredicate, filterPredicate: filterPredicate,
count: count, count: count,
summaryComponents: summaryComponents summaryComponents: summaryComponents,
extractCachedData: extractCachedData,
accountPeerId: accountPeerId
).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)) ).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
} }
@ -4592,7 +4596,9 @@ public class Postbox {
index: ChatListIndex, index: ChatListIndex,
count: Int, count: Int,
summaryComponents: ChatListEntrySummaryComponents, summaryComponents: ChatListEntrySummaryComponents,
userInteractive: Bool = false userInteractive: Bool = false,
extractCachedData: ((CachedPeerData) -> AnyHashable?)? = nil,
accountPeerId: PeerId? = nil
) -> Signal<(ChatListView, ViewUpdateType), NoError> { ) -> Signal<(ChatListView, ViewUpdateType), NoError> {
return Signal { subscriber in return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
@ -4604,7 +4610,9 @@ public class Postbox {
index: index, index: index,
count: count, count: count,
summaryComponents: summaryComponents, summaryComponents: summaryComponents,
userInteractive: userInteractive userInteractive: userInteractive,
extractCachedData: extractCachedData,
accountPeerId: accountPeerId
).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)) ).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
} }

View File

@ -1225,7 +1225,16 @@ public final class ShareController: ViewController {
} }
return true return true
} }
self.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: (self.environment is ShareControllerAppEnvironment) ? presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action : nil, timeout: nil, linkAction: { _ in
var hasAction = false
if let context = self.currentContext as? ShareControllerAppAccountContext {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.context.currentAppConfiguration.with { $0 })
if !premiumConfiguration.isPremiumDisabled {
hasAction = true
}
}
self.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: hasAction ? presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action : nil, timeout: nil, linkAction: { _ in
}), elevatedLayout: false, animateInAsReplacement: false, action: { [weak self] action in }), elevatedLayout: false, animateInAsReplacement: false, action: { [weak self] action in
guard let self, let parentNavigationController = self.parentNavigationController, let context = self.currentContext as? ShareControllerAppAccountContext else { guard let self, let parentNavigationController = self.parentNavigationController, let context = self.currentContext as? ShareControllerAppAccountContext else {
return false return false

View File

@ -1459,7 +1459,8 @@ private func threadList(accountPeerId: EnginePeer.Id, postbox: Postbox, peerId:
isContact: false, isContact: false,
autoremoveTimeout: nil, autoremoveTimeout: nil,
storyStats: nil, storyStats: nil,
displayAsTopicList: false displayAsTopicList: false,
isPremiumRequiredToMessage: false
)) ))
} }

View File

@ -167,7 +167,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
isRemoteAudioMuted: false, isRemoteAudioMuted: false,
localVideo: nil, localVideo: nil,
remoteVideo: nil, remoteVideo: nil,
isRemoteBatteryLow: false isRemoteBatteryLow: false,
isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency
) )
if let peer = call.peer { if let peer = call.peer {
self.updatePeer(peer: peer) self.updatePeer(peer: peer)

View File

@ -1137,7 +1137,8 @@ public func _internal_searchForumTopics(account: Account, peerId: EnginePeer.Id,
isContact: false, isContact: false,
autoremoveTimeout: nil, autoremoveTimeout: nil,
storyStats: nil, storyStats: nil,
displayAsTopicList: false displayAsTopicList: false,
isPremiumRequiredToMessage: false
)) ))
} }

View File

@ -1966,14 +1966,26 @@ public final class AccountViewTracker {
if let account = self.account { if let account = self.account {
let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>
if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId, tag == nil { if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId, tag == nil {
signal = account.postbox.transaction { transaction -> MessageHistoryThreadData? in signal = account.postbox.transaction { transaction -> (MessageHistoryThreadData?, MessageIndex?) in
return transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) let interfaceState = transaction.getPeerChatThreadInterfaceState(peerId, threadId: threadId)
return (
transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self),
interfaceState?.historyScrollMessageIndex
)
} }
|> mapToSignal { threadInfo -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in |> mapToSignal { threadInfo, scrollRestorationIndex -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in
if peerId == account.peerId { if peerId == account.peerId {
let anchor: HistoryViewInputAnchor
if let scrollRestorationIndex {
anchor = .index(scrollRestorationIndex)
} else {
anchor = .upperBound
}
return account.postbox.aroundMessageHistoryViewForLocation( return account.postbox.aroundMessageHistoryViewForLocation(
chatLocation, chatLocation,
anchor: .upperBound, anchor: anchor,
ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange,
count: count, count: count,
fixedCombinedReadStates: .peer([peerId: CombinedPeerReadState(states: [ fixedCombinedReadStates: .peer([peerId: CombinedPeerReadState(states: [
@ -2374,7 +2386,7 @@ public final class AccountViewTracker {
}) })
} }
public func tailChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { public func tailChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, count: Int, shouldLoadCanMessagePeer: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> {
if let account = self.account { if let account = self.account {
return self.wrappedChatListView(signal: account.postbox.tailChatListView( return self.wrappedChatListView(signal: account.postbox.tailChatListView(
groupId: groupId, groupId: groupId,
@ -2397,14 +2409,16 @@ public final class AccountViewTracker {
actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud) actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud)
) )
] ]
) ),
extractCachedData: shouldLoadCanMessagePeer ? extractCachedDataIsPremiumRequiredToMessage : nil,
accountPeerId: shouldLoadCanMessagePeer ? account.peerId : nil
)) ))
} else { } else {
return .never() return .never()
} }
} }
public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int, shouldLoadCanMessagePeer: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> {
if let account = self.account { if let account = self.account {
return self.wrappedChatListView(signal: account.postbox.aroundChatListView( return self.wrappedChatListView(signal: account.postbox.aroundChatListView(
groupId: groupId, groupId: groupId,
@ -2428,7 +2442,9 @@ public final class AccountViewTracker {
actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud) actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud)
) )
] ]
) ),
extractCachedData: shouldLoadCanMessagePeer ? extractCachedDataIsPremiumRequiredToMessage : nil,
accountPeerId: shouldLoadCanMessagePeer ? account.peerId : nil
)) ))
} else { } else {
return .never() return .never()
@ -2483,3 +2499,26 @@ public final class AccountViewTracker {
} }
} }
} }
public final class ExtractedChatListItemCachedData: Hashable {
public let isPremiumRequiredToMessage: Bool
public init(isPremiumRequiredToMessage: Bool) {
self.isPremiumRequiredToMessage = isPremiumRequiredToMessage
}
public static func ==(lhs: ExtractedChatListItemCachedData, rhs: ExtractedChatListItemCachedData) -> Bool {
return true
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.isPremiumRequiredToMessage)
}
}
private func extractCachedDataIsPremiumRequiredToMessage(_ cachedData: CachedPeerData) -> AnyHashable? {
if let cachedData = cachedData as? CachedUserData {
return ExtractedChatListItemCachedData(isPremiumRequiredToMessage: cachedData.flags.contains(.premiumRequired))
}
return nil
}

View File

@ -58,6 +58,7 @@ public enum PendingMessageFailureReason {
case tooMuchScheduled case tooMuchScheduled
case voiceMessagesForbidden case voiceMessagesForbidden
case sendingTooFast case sendingTooFast
case nonPremiumMessagesForbidden
} }
func sendMessageReasonForError(_ error: String) -> PendingMessageFailureReason? { func sendMessageReasonForError(_ error: String) -> PendingMessageFailureReason? {
@ -75,6 +76,8 @@ func sendMessageReasonForError(_ error: String) -> PendingMessageFailureReason?
return .tooMuchScheduled return .tooMuchScheduled
} else if error.hasPrefix("VOICE_MESSAGES_FORBIDDEN") { } else if error.hasPrefix("VOICE_MESSAGES_FORBIDDEN") {
return .voiceMessagesForbidden return .voiceMessagesForbidden
} else if error.hasPrefix("PRIVACY_PREMIUM_REQUIRED") {
return .nonPremiumMessagesForbidden
} else { } else {
return nil return nil
} }

View File

@ -130,6 +130,7 @@ public final class EngineChatList: Equatable {
public let autoremoveTimeout: Int32? public let autoremoveTimeout: Int32?
public let storyStats: StoryStats? public let storyStats: StoryStats?
public let displayAsTopicList: Bool public let displayAsTopicList: Bool
public let isPremiumRequiredToMessage: Bool
public init( public init(
id: Id, id: Id,
@ -149,7 +150,8 @@ public final class EngineChatList: Equatable {
isContact: Bool, isContact: Bool,
autoremoveTimeout: Int32?, autoremoveTimeout: Int32?,
storyStats: StoryStats?, storyStats: StoryStats?,
displayAsTopicList: Bool displayAsTopicList: Bool,
isPremiumRequiredToMessage: Bool
) { ) {
self.id = id self.id = id
self.index = index self.index = index
@ -169,6 +171,7 @@ public final class EngineChatList: Equatable {
self.autoremoveTimeout = autoremoveTimeout self.autoremoveTimeout = autoremoveTimeout
self.storyStats = storyStats self.storyStats = storyStats
self.displayAsTopicList = displayAsTopicList self.displayAsTopicList = displayAsTopicList
self.isPremiumRequiredToMessage = isPremiumRequiredToMessage
} }
public static func ==(lhs: Item, rhs: Item) -> Bool { public static func ==(lhs: Item, rhs: Item) -> Bool {
@ -226,6 +229,9 @@ public final class EngineChatList: Equatable {
if lhs.displayAsTopicList != rhs.displayAsTopicList { if lhs.displayAsTopicList != rhs.displayAsTopicList {
return false return false
} }
if lhs.isPremiumRequiredToMessage != rhs.isPremiumRequiredToMessage {
return false
}
return true return true
} }
} }
@ -424,8 +430,21 @@ public extension EngineChatList.RelativePosition {
} }
} }
private func calculateIsPremiumRequiredToMessage(isPremium: Bool, targetPeer: Peer, cachedIsPremiumRequired: Bool) -> Bool {
if isPremium {
return false
}
guard let targetPeer = targetPeer as? TelegramUser else {
return false
}
if !targetPeer.flags.contains(.requirePremium) {
return false
}
return cachedIsPremiumRequired
}
extension EngineChatList.Item { extension EngineChatList.Item {
convenience init?(_ entry: ChatListEntry, displayAsTopicList: Bool) { convenience init?(_ entry: ChatListEntry, isPremium: Bool, displayAsTopicList: Bool) {
switch entry { switch entry {
case let .MessageEntry(entryData): case let .MessageEntry(entryData):
let index = entryData.index let index = entryData.index
@ -442,6 +461,11 @@ extension EngineChatList.Item {
let isContact = entryData.isContact let isContact = entryData.isContact
let autoremoveTimeout = entryData.autoremoveTimeout let autoremoveTimeout = entryData.autoremoveTimeout
var isPremiumRequiredToMessage = false
if let targetPeer = renderedPeer.chatMainPeer, let extractedData = entryData.extractedCachedData?.base as? ExtractedChatListItemCachedData {
isPremiumRequiredToMessage = calculateIsPremiumRequiredToMessage(isPremium: isPremium, targetPeer: targetPeer, cachedIsPremiumRequired: extractedData.isPremiumRequiredToMessage)
}
var draft: EngineChatList.Draft? var draft: EngineChatList.Draft?
if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp { if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp {
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
@ -511,7 +535,8 @@ extension EngineChatList.Item {
isContact: isContact, isContact: isContact,
autoremoveTimeout: autoremoveTimeout, autoremoveTimeout: autoremoveTimeout,
storyStats: entryData.storyStats, storyStats: entryData.storyStats,
displayAsTopicList: displayAsTopicList displayAsTopicList: displayAsTopicList,
isPremiumRequiredToMessage: isPremiumRequiredToMessage
) )
case .HoleEntry: case .HoleEntry:
return nil return nil
@ -551,7 +576,7 @@ extension EngineChatList.AdditionalItem.PromoInfo {
extension EngineChatList.AdditionalItem { extension EngineChatList.AdditionalItem {
convenience init?(_ entry: ChatListAdditionalItemEntry) { convenience init?(_ entry: ChatListAdditionalItemEntry) {
guard let item = EngineChatList.Item(entry.entry, displayAsTopicList: false) else { guard let item = EngineChatList.Item(entry.entry, isPremium: false, displayAsTopicList: false) else {
return nil return nil
} }
guard let promoInfo = (entry.info as? PromoChatListItem).flatMap(EngineChatList.AdditionalItem.PromoInfo.init) else { guard let promoInfo = (entry.info as? PromoChatListItem).flatMap(EngineChatList.AdditionalItem.PromoInfo.init) else {
@ -570,11 +595,13 @@ public extension EngineChatList {
displaySavedMessagesAsTopicList = value.value displaySavedMessagesAsTopicList = value.value
} }
let isPremium = view.accountPeer?.isPremium ?? false
var items: [EngineChatList.Item] = [] var items: [EngineChatList.Item] = []
loop: for entry in view.entries { loop: for entry in view.entries {
switch entry { switch entry {
case .MessageEntry: case .MessageEntry:
if let item = EngineChatList.Item(entry, displayAsTopicList: entry.index.messageIndex.id.peerId == accountPeerId ? displaySavedMessagesAsTopicList : false) { if let item = EngineChatList.Item(entry, isPremium: isPremium, displayAsTopicList: entry.index.messageIndex.id.peerId == accountPeerId ? displaySavedMessagesAsTopicList : false) {
items.append(item) items.append(item)
} }
case .HoleEntry: case .HoleEntry:

View File

@ -97,6 +97,7 @@ final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject {
private let colorSets: [ColorSet] private let colorSets: [ColorSet]
private let colorTransition: AnimatedProperty<ColorSet> private let colorTransition: AnimatedProperty<ColorSet>
private var stateIndex: Int = 0 private var stateIndex: Int = 0
private var isEnergySavingEnabled: Bool = false
private let phaseAcceleration = AnimatedProperty<CGFloat>(0.0) private let phaseAcceleration = AnimatedProperty<CGFloat>(0.0)
override init() { override init() {
@ -169,7 +170,9 @@ final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
func update(stateIndex: Int, transition: Transition) { func update(stateIndex: Int, isEnergySavingEnabled: Bool, transition: Transition) {
self.isEnergySavingEnabled = isEnergySavingEnabled
if self.stateIndex != stateIndex { if self.stateIndex != stateIndex {
self.stateIndex = stateIndex self.stateIndex = stateIndex
if !transition.animation.isImmediate { if !transition.animation.isImmediate {
@ -187,7 +190,7 @@ final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject {
return return
} }
let phase = self.phase let phase = self.isEnergySavingEnabled ? 0.0 : self.phase
for i in 0 ..< 2 { for i in 0 ..< 2 {
let isBlur = i == 1 let isBlur = i == 1

View File

@ -79,7 +79,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
public var localVideo: VideoSource? public var localVideo: VideoSource?
public var remoteVideo: VideoSource? public var remoteVideo: VideoSource?
public var isRemoteBatteryLow: Bool public var isRemoteBatteryLow: Bool
public var displaySnowEffect: Bool public var isEnergySavingEnabled: Bool
public init( public init(
strings: PresentationStrings, strings: PresentationStrings,
@ -93,7 +93,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
localVideo: VideoSource?, localVideo: VideoSource?,
remoteVideo: VideoSource?, remoteVideo: VideoSource?,
isRemoteBatteryLow: Bool, isRemoteBatteryLow: Bool,
displaySnowEffect: Bool = false isEnergySavingEnabled: Bool
) { ) {
self.strings = strings self.strings = strings
self.lifecycleState = lifecycleState self.lifecycleState = lifecycleState
@ -106,7 +106,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
self.localVideo = localVideo self.localVideo = localVideo
self.remoteVideo = remoteVideo self.remoteVideo = remoteVideo
self.isRemoteBatteryLow = isRemoteBatteryLow self.isRemoteBatteryLow = isRemoteBatteryLow
self.displaySnowEffect = displaySnowEffect self.isEnergySavingEnabled = isEnergySavingEnabled
} }
public static func ==(lhs: State, rhs: State) -> Bool { public static func ==(lhs: State, rhs: State) -> Bool {
@ -143,7 +143,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
if lhs.isRemoteBatteryLow != rhs.isRemoteBatteryLow { if lhs.isRemoteBatteryLow != rhs.isRemoteBatteryLow {
return false return false
} }
if lhs.displaySnowEffect != rhs.displaySnowEffect { if lhs.isEnergySavingEnabled != rhs.isEnergySavingEnabled {
return false return false
} }
return true return true
@ -696,7 +696,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
case .terminated: case .terminated:
backgroundStateIndex = 0 backgroundStateIndex = 0
} }
self.backgroundLayer.update(stateIndex: backgroundStateIndex, transition: transition) self.backgroundLayer.update(stateIndex: backgroundStateIndex, isEnergySavingEnabled: params.state.isEnergySavingEnabled, transition: transition)
transition.setFrame(view: self.buttonGroupView, frame: CGRect(origin: CGPoint(), size: params.size)) transition.setFrame(view: self.buttonGroupView, frame: CGRect(origin: CGPoint(), size: params.size))
@ -1220,7 +1220,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
titleString = params.state.strings.Call_StatusMissed titleString = params.state.strings.Call_StatusMissed
} }
default: default:
displayAudioLevelBlob = !params.state.isRemoteAudioMuted displayAudioLevelBlob = !params.state.isRemoteAudioMuted && !params.state.isEnergySavingEnabled
self.titleView.contentMode = .scaleToFill self.titleView.contentMode = .scaleToFill
titleString = params.state.name titleString = params.state.name
@ -1375,24 +1375,6 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
}) })
} }
} }
/*if params.state.displaySnowEffect {
let snowEffectView: SnowEffectView
if let current = self.snowEffectView {
snowEffectView = current
} else {
snowEffectView = SnowEffectView(frame: CGRect())
self.snowEffectView = snowEffectView
self.maskContents.addSubview(snowEffectView)
}
transition.setFrame(view: snowEffectView, frame: CGRect(origin: CGPoint(), size: params.size))
snowEffectView.update(size: params.size)
} else {
if let snowEffectView = self.snowEffectView {
self.snowEffectView = nil
snowEffectView.removeFromSuperview()
}
}*/
} }
} }

View File

@ -262,7 +262,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.edited = edited self.edited = edited
self.impressionCount = impressionCount self.impressionCount = impressionCount == 0 ? nil : impressionCount
self.dateText = dateText self.dateText = dateText
self.type = type self.type = type
self.layoutInput = layoutInput self.layoutInput = layoutInput

View File

@ -78,6 +78,7 @@ private let TitleNodeStateExpanded = 1
final class PeerInfoHeaderNode: ASDisplayNode { final class PeerInfoHeaderNode: ASDisplayNode {
private var context: AccountContext private var context: AccountContext
private let isPremiumDisabled: Bool
private weak var controller: PeerInfoScreenImpl? private weak var controller: PeerInfoScreenImpl?
private var presentationData: PresentationData? private var presentationData: PresentationData?
private var state: PeerInfoState? private var state: PeerInfoState?
@ -186,6 +187,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.forumTopicThreadId = forumTopicThreadId self.forumTopicThreadId = forumTopicThreadId
self.chatLocation = chatLocation self.chatLocation = chatLocation
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
self.avatarClippingNode = SparseNode() self.avatarClippingNode = SparseNode()
self.avatarClippingNode.alpha = 0.996 self.avatarClippingNode.alpha = 0.996
self.avatarClippingNode.clipsToBounds = true self.avatarClippingNode.clipsToBounds = true
@ -1249,7 +1253,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let _ = panelSubtitleNodeLayout[TitleNodeStateRegular]!.size let _ = panelSubtitleNodeLayout[TitleNodeStateRegular]!.size
let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size
if let statusData, statusData.isHiddenStatus { if let statusData, statusData.isHiddenStatus, !self.isPremiumDisabled {
let subtitleBadgeView: PeerInfoSubtitleBadgeView let subtitleBadgeView: PeerInfoSubtitleBadgeView
if let current = self.subtitleBadgeView { if let current = self.subtitleBadgeView {
subtitleBadgeView = current subtitleBadgeView = current

View File

@ -253,6 +253,9 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.chatListNode?.disabledPeerSelected = { [weak self] peer, threadId, reason in self.chatListNode?.disabledPeerSelected = { [weak self] peer, threadId, reason in
self?.requestOpenDisabledPeer?(peer, threadId, reason) self?.requestOpenDisabledPeer?(peer, threadId, reason)
} }
self.mainContainerNode?.disabledPeerSelected = { [weak self] peer, threadId, reason in
self?.requestOpenDisabledPeer?(peer, threadId, reason)
}
self.chatListNode?.contentOffsetChanged = { [weak self] offset in self.chatListNode?.contentOffsetChanged = { [weak self] offset in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -1748,6 +1748,14 @@ public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) -
} }
} }
if let representation = file.previewRepresentations.first {
signals.append(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(info.peer.id), userContentType: .story, reference: .media(media: .story(peer: info.peer, id: info.storyId, media: selectedMedia._asMedia()), resource: representation.resource), range: nil)
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
})
}
signals.append(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(info.peer.id), userContentType: .story, reference: .media(media: .story(peer: info.peer, id: info.storyId, media: selectedMedia._asMedia()), resource: file.resource), range: fetchRange) signals.append(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(info.peer.id), userContentType: .story, reference: .media(media: .story(peer: info.peer, id: info.storyId, media: selectedMedia._asMedia()), resource: file.resource), range: fetchRange)
|> ignoreValues |> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in |> `catch` { _ -> Signal<Never, NoError> in

View File

@ -164,8 +164,18 @@ final class StoryItemImageView: UIView {
} }
} }
self.disposable = (context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedVideoFirstFrameRepresentation(), complete: true, fetch: true, attemptSynchronously: false) let fullSize = context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedVideoFirstFrameRepresentation(), complete: true, fetch: true, attemptSynchronously: false)
|> map { result -> UIImage? in var previewSize: Signal<MediaResourceData?, NoError> = .single(nil)
if let representation = file.previewRepresentations.first {
previewSize = context.account.postbox.mediaBox.resourceData(representation.resource, option: .complete(waitUntilFetchStatus: false))
|> map(Optional.init)
}
self.disposable = (combineLatest(
fullSize,
previewSize
)
|> map { result, previewResult -> UIImage? in
if result.complete { if result.complete {
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
if let image = UIImage(contentsOfFile: result.path)?.preparingForDisplay() { if let image = UIImage(contentsOfFile: result.path)?.preparingForDisplay() {
@ -180,6 +190,20 @@ final class StoryItemImageView: UIView {
return nil return nil
} }
} }
} else if let previewResult, previewResult.complete {
if #available(iOS 15.0, *) {
if let image = UIImage(contentsOfFile: previewResult.path)?.preparingForDisplay() {
return image
} else {
return nil
}
} else {
if let image = UIImage(contentsOfFile: previewResult.path)?.precomposed() {
return image
} else {
return nil
}
}
} else { } else {
return nil return nil
} }

View File

@ -577,7 +577,14 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj
} }
return true return true
} }
controller.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action, timeout: nil, linkAction: { _ in
var hasAction = false
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: selfController.context.currentAppConfiguration.with { $0 })
if !premiumConfiguration.isPremiumDisabled {
hasAction = true
}
controller.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: hasAction ? presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action : nil, timeout: nil, linkAction: { _ in
}), elevatedLayout: false, animateInAsReplacement: true, action: { [weak selfController, weak controller] action in }), elevatedLayout: false, animateInAsReplacement: true, action: { [weak selfController, weak controller] action in
guard let selfController, let controller else { guard let selfController, let controller else {
return false return false

View File

@ -11230,6 +11230,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .voiceMessagesForbidden: case .voiceMessagesForbidden:
strongSelf.interfaceInteraction?.displayRestrictedInfo(.premiumVoiceMessages, .alert) strongSelf.interfaceInteraction?.displayRestrictedInfo(.premiumVoiceMessages, .alert)
return return
case .nonPremiumMessagesForbidden:
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer {
text = strongSelf.presentationData.strings.Conversation_SendMessageErrorNonPremiumForbidden(EnginePeer(peer).compactDisplayTitle).string
moreInfo = false
} else {
return
}
} }
let actions: [TextAlertAction] let actions: [TextAlertAction]
if moreInfo { if moreInfo {
@ -11990,7 +11997,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if replyThreadMessage.peerId == self.context.account.peerId && replyThreadMessage.threadId == self.context.account.peerId.toInt64() { if replyThreadMessage.peerId == self.context.account.peerId && replyThreadMessage.threadId == self.context.account.peerId.toInt64() {
peerId = replyThreadMessage.peerId peerId = replyThreadMessage.peerId
threadId = nil threadId = nil
includeScrollState = false includeScrollState = true
let scrollState = self.chatDisplayNode.historyNode.immediateScrollState()
let _ = ChatInterfaceState.update(engine: self.context.engine, peerId: peerId, threadId: replyThreadMessage.threadId, { current in
return current.withUpdatedHistoryScrollState(scrollState)
}).startStandalone()
} else { } else {
peerId = replyThreadMessage.peerId peerId = replyThreadMessage.peerId
threadId = replyThreadMessage.threadId threadId = replyThreadMessage.threadId
@ -12001,7 +12013,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let timestamp = Int32(Date().timeIntervalSince1970) let timestamp = Int32(Date().timeIntervalSince1970)
var interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp) var interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp)
if includeScrollState && threadId == nil { if includeScrollState {
let scrollState = self.chatDisplayNode.historyNode.immediateScrollState() let scrollState = self.chatDisplayNode.historyNode.immediateScrollState()
interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState) interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState)
} }

View File

@ -70,7 +70,14 @@ extension ChatControllerImpl {
} }
return true return true
} }
controller.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action, timeout: nil, linkAction: { _ in
var hasAction = false
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
if !premiumConfiguration.isPremiumDisabled {
hasAction = true
}
controller.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: hasAction ? presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action : nil, timeout: nil, linkAction: { _ in
}), elevatedLayout: false, animateInAsReplacement: true, action: { [weak controller] action in }), elevatedLayout: false, animateInAsReplacement: true, action: { [weak controller] action in
guard let self, let controller else { guard let self, let controller else {
return false return false

View File

@ -898,6 +898,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
} }
final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNodeContent { final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNodeContent {
private let isPremiumDisabled: Bool
private let interaction: ChatPanelInterfaceInteraction? private let interaction: ChatPanelInterfaceInteraction?
private let iconBackground: SimpleLayer private let iconBackground: SimpleLayer
@ -910,7 +911,10 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
private var currentTheme: PresentationTheme? private var currentTheme: PresentationTheme?
private var currentStrings: PresentationStrings? private var currentStrings: PresentationStrings?
init(interaction: ChatPanelInterfaceInteraction?) { init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
self.interaction = interaction self.interaction = interaction
self.iconBackground = SimpleLayer() self.iconBackground = SimpleLayer()
@ -927,23 +931,25 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
self.layer.addSublayer(self.iconBackground) self.layer.addSublayer(self.iconBackground)
self.view.addSubview(self.icon) self.view.addSubview(self.icon)
self.view.addSubview(self.button) if !self.isPremiumDisabled {
self.view.addSubview(self.button)
self.button.addSubnode(self.buttonStarsNode) self.button.addSubnode(self.buttonStarsNode)
self.button.highligthedChanged = { [weak self] highlighted in self.button.highligthedChanged = { [weak self] highlighted in
guard let self else { guard let self else {
return return
} }
if highlighted { if highlighted {
self.button.layer.removeAnimation(forKey: "opacity") self.button.layer.removeAnimation(forKey: "opacity")
self.button.alpha = 0.6 self.button.alpha = 0.6
} else { } else {
self.button.alpha = 1.0 self.button.alpha = 1.0
self.button.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) self.button.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
} }
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
} }
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
} }
@objc private func buttonPressed() { @objc private func buttonPressed() {
@ -954,13 +960,6 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
/*if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
self.currentTheme = interfaceState.theme
self.currentStrings = interfaceState.strings
self.titleNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_EmptyTopicPlaceholder_Title, font: titleFont, textColor: serviceColor.primaryText)
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_EmptyTopicPlaceholder_Text, font: messageFont, textColor: serviceColor.primaryText)
}*/
let maxWidth = min(200.0, size.width) let maxWidth = min(200.0, size.width)
@ -978,7 +977,12 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
peerTitle = " " peerTitle = " "
} }
let text = interfaceState.strings.Chat_EmptyStateMessagingRestrictedToPremium_Text(peerTitle).string let text: String
if self.isPremiumDisabled {
text = interfaceState.strings.Chat_EmptyStateMessagingRestrictedToPremiumDisabled_Text(peerTitle).string
} else {
text = interfaceState.strings.Chat_EmptyStateMessagingRestrictedToPremium_Text(peerTitle).string
}
let textSize = self.text.update( let textSize = self.text.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(BalancedTextComponent( component: AnyComponent(BalancedTextComponent(
@ -1010,7 +1014,10 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
var contentsWidth: CGFloat = 0.0 var contentsWidth: CGFloat = 0.0
contentsWidth = max(contentsWidth, iconBackgroundSize + sideInset * 2.0) contentsWidth = max(contentsWidth, iconBackgroundSize + sideInset * 2.0)
contentsWidth = max(contentsWidth, textSize.width + sideInset * 2.0) contentsWidth = max(contentsWidth, textSize.width + sideInset * 2.0)
contentsWidth = max(contentsWidth, buttonSize.width + sideInset * 2.0)
if !self.isPremiumDisabled {
contentsWidth = max(contentsWidth, buttonSize.width + sideInset * 2.0)
}
var contentsHeight: CGFloat = 0.0 var contentsHeight: CGFloat = 0.0
contentsHeight += topInset contentsHeight += topInset
@ -1036,22 +1043,28 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
textView.frame = textFrame textView.frame = textFrame
} }
contentsHeight += textSize.height contentsHeight += textSize.height
contentsHeight += textButtonSpacing
let buttonFrame = CGRect(origin: CGPoint(x: floor((contentsWidth - buttonSize.width) * 0.5), y: contentsHeight), size: buttonSize) if self.isPremiumDisabled {
transition.updateFrame(view: self.button, frame: buttonFrame) contentsHeight += bottomInset
transition.updateCornerRadius(layer: self.button.layer, cornerRadius: buttonFrame.height * 0.5) } else {
if let buttonTitleView = self.buttonTitle.view { contentsHeight += textButtonSpacing
if buttonTitleView.superview == nil {
buttonTitleView.isUserInteractionEnabled = false let buttonFrame = CGRect(origin: CGPoint(x: floor((contentsWidth - buttonSize.width) * 0.5), y: contentsHeight), size: buttonSize)
self.button.addSubview(buttonTitleView) transition.updateFrame(view: self.button, frame: buttonFrame)
transition.updateCornerRadius(layer: self.button.layer, cornerRadius: buttonFrame.height * 0.5)
if let buttonTitleView = self.buttonTitle.view {
if buttonTitleView.superview == nil {
buttonTitleView.isUserInteractionEnabled = false
self.button.addSubview(buttonTitleView)
}
transition.updateFrame(view: buttonTitleView, frame: CGRect(origin: CGPoint(x: floor((buttonSize.width - buttonTitleSize.width) * 0.5), y: floor((buttonSize.height - buttonTitleSize.height) * 0.5)), size: buttonTitleSize))
} }
transition.updateFrame(view: buttonTitleView, frame: CGRect(origin: CGPoint(x: floor((buttonSize.width - buttonTitleSize.width) * 0.5), y: floor((buttonSize.height - buttonTitleSize.height) * 0.5)), size: buttonTitleSize)) self.button.backgroundColor = interfaceState.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)
self.buttonStarsNode.frame = CGRect(origin: CGPoint(), size: buttonSize)
contentsHeight += buttonSize.height
contentsHeight += bottomInset
} }
self.button.backgroundColor = interfaceState.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)
self.buttonStarsNode.frame = CGRect(origin: CGPoint(), size: buttonSize)
contentsHeight += buttonSize.height
contentsHeight += bottomInset
return CGSize(width: contentsWidth, height: contentsHeight) return CGSize(width: contentsWidth, height: contentsHeight)
} }
@ -1218,7 +1231,7 @@ final class ChatEmptyNode: ASDisplayNode {
case .topic: case .topic:
node = ChatEmptyNodeTopicChatContent(context: self.context) node = ChatEmptyNodeTopicChatContent(context: self.context)
case .premiumRequired: case .premiumRequired:
node = ChatEmptyNodePremiumRequiredChatContent(interaction: self.interaction) node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction)
} }
self.content = (contentType, node) self.content = (contentType, node)
self.addSubnode(node) self.addSubnode(node)

View File

@ -89,7 +89,6 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
requestAroundId = true requestAroundId = true
} }
if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId { if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId {
requestAroundId = true
preFixedReadState = .peer([:]) preFixedReadState = .peer([:])
} }
@ -114,7 +113,11 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
let canScrollToRead: Bool let canScrollToRead: Bool
if case let .replyThread(message) = chatLocation, !message.isForumPost { if case let .replyThread(message) = chatLocation, !message.isForumPost {
canScrollToRead = true if message.peerId == context.account.peerId {
canScrollToRead = false
} else {
canScrollToRead = true
}
} else if view.isAddedToChatList { } else if view.isAddedToChatList {
canScrollToRead = true canScrollToRead = true
} else { } else {

View File

@ -175,7 +175,7 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineCo
return false return false
} }
private func canViewReadStats(message: Message, participantCount: Int?, isMessageRead: Bool, appConfig: AppConfiguration) -> Bool { private func canViewReadStats(message: Message, participantCount: Int?, isMessageRead: Bool, isPremium: Bool, appConfig: AppConfiguration) -> Bool {
guard let peer = message.peers[message.id.peerId] else { guard let peer = message.peers[message.id.peerId] else {
return false return false
} }
@ -251,6 +251,13 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag
if user.flags.contains(.isSupport) { if user.flags.contains(.isSupport) {
return false return false
} }
if !isPremium {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: appConfig)
if premiumConfiguration.isPremiumDisabled {
return false
}
}
default: default:
return false return false
} }
@ -1731,7 +1738,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
let canViewStats: Bool let canViewStats: Bool
if let messageReadStatsAreHidden = infoSummaryData.messageReadStatsAreHidden, !messageReadStatsAreHidden { if let messageReadStatsAreHidden = infoSummaryData.messageReadStatsAreHidden, !messageReadStatsAreHidden {
canViewStats = canViewReadStats(message: message, participantCount: infoSummaryData.participantCount, isMessageRead: isMessageRead, appConfig: appConfig) canViewStats = canViewReadStats(message: message, participantCount: infoSummaryData.participantCount, isMessageRead: isMessageRead, isPremium: isPremium, appConfig: appConfig)
} else { } else {
canViewStats = false canViewStats = false
} }

View File

@ -14,6 +14,7 @@ import ComponentFlow
import MultilineTextComponent import MultilineTextComponent
import PlainButtonComponent import PlainButtonComponent
import ComponentDisplayAdapters import ComponentDisplayAdapters
import AccountContext
private let labelFont = Font.regular(15.0) private let labelFont = Font.regular(15.0)
@ -101,18 +102,24 @@ final class ChatPremiumRequiredInputPanelNode: ChatInputPanelNode {
let buttonTitle: String = params.interfaceState.strings.Chat_MessagingRestrictedPlaceholder(peerTitle).string let buttonTitle: String = params.interfaceState.strings.Chat_MessagingRestrictedPlaceholder(peerTitle).string
let buttonSubtitle: String = params.interfaceState.strings.Chat_MessagingRestrictedPlaceholderAction let buttonSubtitle: String = params.interfaceState.strings.Chat_MessagingRestrictedPlaceholderAction
var buttonContents: [AnyComponentWithIdentity<Empty>] = []
buttonContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: buttonTitle, font: Font.regular(13.0), textColor: params.interfaceState.theme.rootController.navigationBar.secondaryTextColor))
))))
if let context = self.context {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
if !premiumConfiguration.isPremiumDisabled {
buttonContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: buttonSubtitle, font: Font.regular(13.0), textColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor))
))))
}
}
let size = CGSize(width: params.width - params.additionalSideInsets.left * 2.0 - params.leftInset * 2.0, height: height) let size = CGSize(width: params.width - params.additionalSideInsets.left * 2.0 - params.leftInset * 2.0, height: height)
let buttonSize = self.button.update( let buttonSize = self.button.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(PlainButtonComponent( component: AnyComponent(PlainButtonComponent(
content: AnyComponent(VStack([ content: AnyComponent(VStack(buttonContents, spacing: 1.0)),
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: buttonTitle, font: Font.regular(13.0), textColor: params.interfaceState.theme.rootController.navigationBar.secondaryTextColor))
))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: buttonSubtitle, font: Font.regular(13.0), textColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor))
)))
], spacing: 1.0)),
effectAlignment: .center, effectAlignment: .center,
minSize: size, minSize: size,
action: { [weak self] in action: { [weak self] in

View File

@ -384,7 +384,14 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
} }
return true return true
} }
self.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action, timeout: nil, linkAction: { _ in
var hasAction = false
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
if !premiumConfiguration.isPremiumDisabled {
hasAction = true
}
self.present(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: nil, text: presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Text(peer.compactDisplayTitle).string, customUndoText: hasAction ? self.presentationData.strings.Chat_ToastMessagingRestrictedToPremium_Action : nil, timeout: nil, linkAction: { _ in
}), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in }), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
guard let self else { guard let self else {
return false return false