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.EmptyStateMessagingRestrictedToPremium.Text" = "Subscribe to **Premium**\nto message **%@**.";
"Chat.EmptyStateMessagingRestrictedToPremiumDisabled.Text" = "Subscribe to **Premium**\nto message **%@**.";
"Chat.EmptyStateMessagingRestrictedToPremium.Action" = "Get Premium";
"Chat.ContextMenuReadDate.ReadAvailablePrefix" = "read";
@ -11041,3 +11042,5 @@ Sorry for the inconvenience.";
"Chat.BottomSearchPanel.DisplayModeFormat" = "Show as %@";
"Chat.BottomSearchPanel.DisplayModeChat" = "Chat";
"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.setPeerThreadHidden = nil
previousItemNode.listNode.peerSelected = nil
previousItemNode.listNode.disabledPeerSelected = nil
previousItemNode.listNode.groupSelected = nil
previousItemNode.listNode.updatePeerGrouping = 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
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
self?.groupSelected?(groupId)
}
@ -390,6 +394,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)?
var setPeerThreadHidden: ((EnginePeer.Id, Int64, Bool) -> Void)?
public var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)?
public var disabledPeerSelected: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
var groupSelected: ((EngineChatList.Group) -> Void)?
var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)?
var contentOffset: ListViewVisibleContentOffset?

View File

@ -1754,7 +1754,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let foundThreads: Signal<[EngineChatList.Item], NoError>
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
var filteredItems: [EngineChatList.Item] = []
let queryTokens = stringIndexTokens(finalQuery, transliteration: .combined)

View File

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

View File

@ -714,7 +714,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
hasUnseenCloseFriends: stats.hasUnseenCloseFriends
)
},
requiresPremiumForMessaging: false,
requiresPremiumForMessaging: entry.isPremiumRequiredToMessage,
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
switch chatListLocation {
@ -128,7 +128,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
switch location {
case let .initial(count, _):
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
|> map { view, updateType -> ChatListNodeViewUpdate in
return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: updateType, scrollPosition: nil)
@ -138,7 +138,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
return .never()
}
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
let genericType: ViewUpdateType
if first {
@ -157,7 +157,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > .chatList(index) ? .Down : .Up
let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated)
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
let genericType: ViewUpdateType
let scrollPosition: ChatListNodeViewScrollPosition? = first ? chatScrollPosition : nil
@ -295,7 +295,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
isContact: false,
autoremoveTimeout: nil,
storyStats: nil,
displayAsTopicList: false
displayAsTopicList: false,
isPremiumRequiredToMessage: false
))
}
@ -372,7 +373,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
isContact: false,
autoremoveTimeout: 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 autoremoveTimeout: Int32?
public var storyStats: PeerStoryStats?
public var extractedCachedData: AnyHashable?
public init(
index: ChatListIndex,
@ -141,7 +142,8 @@ public enum ChatListEntry: Comparable {
hasFailed: Bool,
isContact: Bool,
autoremoveTimeout: Int32?,
storyStats: PeerStoryStats?
storyStats: PeerStoryStats?,
extractedCachedData: AnyHashable?
) {
self.index = index
self.messages = messages
@ -157,6 +159,7 @@ public enum ChatListEntry: Comparable {
self.isContact = isContact
self.autoremoveTimeout = autoremoveTimeout
self.storyStats = storyStats
self.extractedCachedData = extractedCachedData
}
public static func ==(lhs: MessageEntryData, rhs: MessageEntryData) -> Bool {
@ -218,6 +221,9 @@ public enum ChatListEntry: Comparable {
if lhs.storyStats != rhs.storyStats {
return false
}
if lhs.extractedCachedData != rhs.extractedCachedData {
return false
}
return true
}
@ -308,6 +314,7 @@ enum MutableChatListEntry: Equatable {
var isContact: Bool
var autoremoveTimeout: Int32?
var storyStats: PeerStoryStats?
var extractedCachedData: AnyHashable?
init(
index: ChatListIndex,
@ -325,7 +332,8 @@ enum MutableChatListEntry: Equatable {
hasFailedMessages: Bool,
isContact: Bool,
autoremoveTimeout: Int32?,
storyStats: PeerStoryStats?
storyStats: PeerStoryStats?,
extractedCachedData: AnyHashable?
) {
self.index = index
self.messages = messages
@ -343,6 +351,7 @@ enum MutableChatListEntry: Equatable {
self.isContact = isContact
self.autoremoveTimeout = autoremoveTimeout
self.storyStats = storyStats
self.extractedCachedData = extractedCachedData
}
}
@ -352,10 +361,10 @@ enum MutableChatListEntry: Equatable {
init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) {
switch intermediateEntry {
case let .message(index, messageIndex):
self = .IntermediateMessageEntry(index: index, messageIndex: messageIndex)
case let .hole(hole):
self = .HoleEntry(hole)
case let .message(index, messageIndex):
self = .IntermediateMessageEntry(index: index, messageIndex: messageIndex)
case let .hole(hole):
self = .HoleEntry(hole)
}
}
@ -553,6 +562,7 @@ final class MutableChatListView {
let filterPredicate: ChatListFilterPredicate?
private let aroundIndex: ChatListIndex
private let summaryComponents: ChatListEntrySummaryComponents
private let extractCachedData: ((CachedPeerData) -> AnyHashable?)?
fileprivate var groupEntries: [ChatListGroupReferenceEntry]
private var count: Int
@ -569,11 +579,16 @@ final class MutableChatListView {
private let displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey
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.filterPredicate = filterPredicate
self.aroundIndex = aroundIndex
self.summaryComponents = summaryComponents
self.extractCachedData = extractCachedData
self.accountPeerId = accountPeerId
self.currentHiddenPeerIds = postbox.hiddenChatIds
@ -595,7 +610,7 @@ final class MutableChatListView {
spaces.append(.group(groupId: self.groupId, pinned: .includePinned, predicate: filterPredicate))
}
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.count = count
@ -619,6 +634,8 @@ final class MutableChatListView {
}
self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey)
self.accountPeer = self.accountPeerId.flatMap(postbox.peerTable.get)
}
private func reloadGroups(postbox: PostboxImpl) {
@ -698,17 +715,20 @@ final class MutableChatListView {
}
self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey)
self.accountPeer = self.accountPeerId.flatMap(postbox.peerTable.get)
}
func refreshDueToExternalTransaction(postbox: PostboxImpl, currentTransaction: Transaction) -> Bool {
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)
updated = true
let currentGroupEntries = self.groupEntries
let currentDisplaySavedMessagesAsTopicList = self.displaySavedMessagesAsTopicList
let currentAccountPeer = self.accountPeer
self.reloadGroups(postbox: postbox)
@ -718,6 +738,9 @@ final class MutableChatListView {
if self.displaySavedMessagesAsTopicList != currentDisplaySavedMessagesAsTopicList {
updated = true
}
if !arePeersEqual(self.accountPeer, currentAccountPeer) {
updated = true
}
return updated
}
@ -733,11 +756,11 @@ final class MutableChatListView {
}
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)
hasChanges = true
} 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)
hasChanges = true
} 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 {
var invalidatedGroups = false
for (groupId, groupOperations) in operations {
@ -926,6 +957,11 @@ final class MutableChatListView {
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(
index: index,
messages: renderedMessages,
@ -942,7 +978,8 @@ final class MutableChatListView {
hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId),
isContact: isContact,
autoremoveTimeout: autoremoveTimeout,
storyStats: storyStats
storyStats: storyStats,
extractedCachedData: extractedCachedData
))
default:
return nil
@ -966,6 +1003,7 @@ public final class ChatListView {
public let earlierIndex: ChatListIndex?
public let laterIndex: ChatListIndex?
public let displaySavedMessagesAsTopicList: PreferencesEntry?
public let accountPeer: Peer?
init(_ mutableView: MutableChatListView) {
self.groupId = mutableView.groupId
@ -988,7 +1026,8 @@ public final class ChatListView {
hasFailed: entryData.hasFailedMessages,
isContact: entryData.isContact,
autoremoveTimeout: entryData.autoremoveTimeout,
storyStats: entryData.storyStats
storyStats: entryData.storyStats,
extractedCachedData: entryData.extractedCachedData
)))
case let .HoleEntry(hole):
entries.append(.HoleEntry(hole))
@ -1022,7 +1061,8 @@ public final class ChatListView {
hasFailed: entryData.hasFailedMessages,
isContact: entryData.isContact,
autoremoveTimeout: entryData.autoremoveTimeout,
storyStats: entryData.storyStats
storyStats: entryData.storyStats,
extractedCachedData: entryData.extractedCachedData
)),
info: entry.info
))
@ -1035,5 +1075,6 @@ public final class ChatListView {
self.additionalItemEntries = additionalItemEntries
self.displaySavedMessagesAsTopicList = mutableView.displaySavedMessagesAsTopicList
self.accountPeer = mutableView.accountPeer
}
}

View File

@ -141,14 +141,16 @@ private final class ChatListViewSpaceState {
private let space: ChatListViewSpace
private let anchorIndex: MutableChatListEntryIndex
private let summaryComponents: ChatListEntrySummaryComponents
private let extractCachedData: ((CachedPeerData) -> AnyHashable?)?
private let halfLimit: Int
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.anchorIndex = anchorIndex
self.summaryComponents = summaryComponents
self.extractCachedData = extractCachedData
self.halfLimit = halfLimit
self.orderedEntries = OrderedChatListViewEntries(anchorIndex: anchorIndex.index, lowerOrAtAnchor: [], higherThanAnchor: [])
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
switch entry {
case let .MessageEntry(entryData):
@ -988,6 +990,14 @@ private final class ChatListViewSpaceState {
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 {
var entryData = entryData
entryData.readState = updatedReadState
@ -995,6 +1005,7 @@ private final class ChatListViewSpaceState {
entryData.autoremoveTimeout = updatedAutoremoveTimeout
entryData.storyStats = storyStats
entryData.displayAsRegularChat = displayAsRegularChat
entryData.extractedCachedData = extractedCachedData
return .MessageEntry(entryData)
} else {
return nil
@ -1382,16 +1393,18 @@ final class ChatListViewSample {
struct ChatListViewState {
private let anchorIndex: MutableChatListEntryIndex
private let summaryComponents: ChatListEntrySummaryComponents
private let extractCachedData: ((CachedPeerData) -> AnyHashable?)?
private let halfLimit: Int
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.summaryComponents = summaryComponents
self.extractCachedData = extractCachedData
self.halfLimit = halfLimit
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)
var extractedCachedData: AnyHashable?
if let extractCachedData = self.extractCachedData {
extractedCachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId).flatMap(extractCachedData)
}
let updatedEntry: MutableChatListEntry = .MessageEntry(MutableChatListEntry.MessageEntryData(
index: index,
messages: renderedMessages,
@ -1657,7 +1675,8 @@ struct ChatListViewState {
hasFailedMessages: false,
isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId),
autoremoveTimeout: autoremoveTimeout,
storyStats: storyStats
storyStats: storyStats,
extractedCachedData: extractedCachedData
))
if directionIndex == 0 {
self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry)

View File

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

View File

@ -1225,7 +1225,16 @@ public final class ShareController: ViewController {
}
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
guard let self, let parentNavigationController = self.parentNavigationController, let context = self.currentContext as? ShareControllerAppAccountContext else {
return false

View File

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

View File

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

View File

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

View File

@ -1966,14 +1966,26 @@ public final class AccountViewTracker {
if let account = self.account {
let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>
if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId, tag == nil {
signal = account.postbox.transaction { transaction -> MessageHistoryThreadData? in
return transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self)
signal = account.postbox.transaction { transaction -> (MessageHistoryThreadData?, MessageIndex?) in
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 {
let anchor: HistoryViewInputAnchor
if let scrollRestorationIndex {
anchor = .index(scrollRestorationIndex)
} else {
anchor = .upperBound
}
return account.postbox.aroundMessageHistoryViewForLocation(
chatLocation,
anchor: .upperBound,
anchor: anchor,
ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange,
count: count,
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 {
return self.wrappedChatListView(signal: account.postbox.tailChatListView(
groupId: groupId,
@ -2397,14 +2409,16 @@ public final class AccountViewTracker {
actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud)
)
]
)
),
extractCachedData: shouldLoadCanMessagePeer ? extractCachedDataIsPremiumRequiredToMessage : nil,
accountPeerId: shouldLoadCanMessagePeer ? account.peerId : nil
))
} else {
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 {
return self.wrappedChatListView(signal: account.postbox.aroundChatListView(
groupId: groupId,
@ -2428,7 +2442,9 @@ public final class AccountViewTracker {
actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud)
)
]
)
),
extractCachedData: shouldLoadCanMessagePeer ? extractCachedDataIsPremiumRequiredToMessage : nil,
accountPeerId: shouldLoadCanMessagePeer ? account.peerId : nil
))
} else {
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 voiceMessagesForbidden
case sendingTooFast
case nonPremiumMessagesForbidden
}
func sendMessageReasonForError(_ error: String) -> PendingMessageFailureReason? {
@ -75,6 +76,8 @@ func sendMessageReasonForError(_ error: String) -> PendingMessageFailureReason?
return .tooMuchScheduled
} else if error.hasPrefix("VOICE_MESSAGES_FORBIDDEN") {
return .voiceMessagesForbidden
} else if error.hasPrefix("PRIVACY_PREMIUM_REQUIRED") {
return .nonPremiumMessagesForbidden
} else {
return nil
}

View File

@ -130,6 +130,7 @@ public final class EngineChatList: Equatable {
public let autoremoveTimeout: Int32?
public let storyStats: StoryStats?
public let displayAsTopicList: Bool
public let isPremiumRequiredToMessage: Bool
public init(
id: Id,
@ -149,7 +150,8 @@ public final class EngineChatList: Equatable {
isContact: Bool,
autoremoveTimeout: Int32?,
storyStats: StoryStats?,
displayAsTopicList: Bool
displayAsTopicList: Bool,
isPremiumRequiredToMessage: Bool
) {
self.id = id
self.index = index
@ -169,6 +171,7 @@ public final class EngineChatList: Equatable {
self.autoremoveTimeout = autoremoveTimeout
self.storyStats = storyStats
self.displayAsTopicList = displayAsTopicList
self.isPremiumRequiredToMessage = isPremiumRequiredToMessage
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
@ -226,6 +229,9 @@ public final class EngineChatList: Equatable {
if lhs.displayAsTopicList != rhs.displayAsTopicList {
return false
}
if lhs.isPremiumRequiredToMessage != rhs.isPremiumRequiredToMessage {
return false
}
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 {
convenience init?(_ entry: ChatListEntry, displayAsTopicList: Bool) {
convenience init?(_ entry: ChatListEntry, isPremium: Bool, displayAsTopicList: Bool) {
switch entry {
case let .MessageEntry(entryData):
let index = entryData.index
@ -442,6 +461,11 @@ extension EngineChatList.Item {
let isContact = entryData.isContact
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?
if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp {
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
@ -511,7 +535,8 @@ extension EngineChatList.Item {
isContact: isContact,
autoremoveTimeout: autoremoveTimeout,
storyStats: entryData.storyStats,
displayAsTopicList: displayAsTopicList
displayAsTopicList: displayAsTopicList,
isPremiumRequiredToMessage: isPremiumRequiredToMessage
)
case .HoleEntry:
return nil
@ -551,7 +576,7 @@ extension EngineChatList.AdditionalItem.PromoInfo {
extension EngineChatList.AdditionalItem {
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
}
guard let promoInfo = (entry.info as? PromoChatListItem).flatMap(EngineChatList.AdditionalItem.PromoInfo.init) else {
@ -570,11 +595,13 @@ public extension EngineChatList {
displaySavedMessagesAsTopicList = value.value
}
let isPremium = view.accountPeer?.isPremium ?? false
var items: [EngineChatList.Item] = []
loop: for entry in view.entries {
switch entry {
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)
}
case .HoleEntry:

View File

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

View File

@ -79,7 +79,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
public var localVideo: VideoSource?
public var remoteVideo: VideoSource?
public var isRemoteBatteryLow: Bool
public var displaySnowEffect: Bool
public var isEnergySavingEnabled: Bool
public init(
strings: PresentationStrings,
@ -93,7 +93,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
localVideo: VideoSource?,
remoteVideo: VideoSource?,
isRemoteBatteryLow: Bool,
displaySnowEffect: Bool = false
isEnergySavingEnabled: Bool
) {
self.strings = strings
self.lifecycleState = lifecycleState
@ -106,7 +106,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
self.localVideo = localVideo
self.remoteVideo = remoteVideo
self.isRemoteBatteryLow = isRemoteBatteryLow
self.displaySnowEffect = displaySnowEffect
self.isEnergySavingEnabled = isEnergySavingEnabled
}
public static func ==(lhs: State, rhs: State) -> Bool {
@ -143,7 +143,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
if lhs.isRemoteBatteryLow != rhs.isRemoteBatteryLow {
return false
}
if lhs.displaySnowEffect != rhs.displaySnowEffect {
if lhs.isEnergySavingEnabled != rhs.isEnergySavingEnabled {
return false
}
return true
@ -696,7 +696,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
case .terminated:
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))
@ -1220,7 +1220,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
titleString = params.state.strings.Call_StatusMissed
}
default:
displayAudioLevelBlob = !params.state.isRemoteAudioMuted
displayAudioLevelBlob = !params.state.isRemoteAudioMuted && !params.state.isEnergySavingEnabled
self.titleView.contentMode = .scaleToFill
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.presentationData = presentationData
self.edited = edited
self.impressionCount = impressionCount
self.impressionCount = impressionCount == 0 ? nil : impressionCount
self.dateText = dateText
self.type = type
self.layoutInput = layoutInput

View File

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

View File

@ -253,6 +253,9 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.chatListNode?.disabledPeerSelected = { [weak self] peer, threadId, reason in
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
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)
|> ignoreValues
|> `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)
|> map { result -> UIImage? in
let fullSize = context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedVideoFirstFrameRepresentation(), complete: true, fetch: true, attemptSynchronously: false)
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 #available(iOS 15.0, *) {
if let image = UIImage(contentsOfFile: result.path)?.preparingForDisplay() {
@ -180,6 +190,20 @@ final class StoryItemImageView: UIView {
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 {
return nil
}

View File

@ -577,7 +577,14 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj
}
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
guard let selfController, let controller else {
return false

View File

@ -11230,6 +11230,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .voiceMessagesForbidden:
strongSelf.interfaceInteraction?.displayRestrictedInfo(.premiumVoiceMessages, .alert)
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]
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() {
peerId = replyThreadMessage.peerId
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 {
peerId = replyThreadMessage.peerId
threadId = replyThreadMessage.threadId
@ -12001,7 +12013,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let timestamp = Int32(Date().timeIntervalSince1970)
var interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp)
if includeScrollState && threadId == nil {
if includeScrollState {
let scrollState = self.chatDisplayNode.historyNode.immediateScrollState()
interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState)
}

View File

@ -70,7 +70,14 @@ extension ChatControllerImpl {
}
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
guard let self, let controller else {
return false

View File

@ -898,6 +898,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
}
final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNodeContent {
private let isPremiumDisabled: Bool
private let interaction: ChatPanelInterfaceInteraction?
private let iconBackground: SimpleLayer
@ -910,7 +911,10 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
private var currentTheme: PresentationTheme?
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.iconBackground = SimpleLayer()
@ -927,23 +931,25 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
self.layer.addSublayer(self.iconBackground)
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
guard let self else {
return
}
if highlighted {
self.button.layer.removeAnimation(forKey: "opacity")
self.button.alpha = 0.6
} else {
self.button.alpha = 1.0
self.button.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
self.button.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.button.layer.removeAnimation(forKey: "opacity")
self.button.alpha = 0.6
} else {
self.button.alpha = 1.0
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() {
@ -954,13 +960,6 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
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)
@ -978,7 +977,12 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
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(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
@ -1010,7 +1014,10 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
var contentsWidth: CGFloat = 0.0
contentsWidth = max(contentsWidth, iconBackgroundSize + 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
contentsHeight += topInset
@ -1036,22 +1043,28 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
textView.frame = textFrame
}
contentsHeight += textSize.height
contentsHeight += textButtonSpacing
let buttonFrame = CGRect(origin: CGPoint(x: floor((contentsWidth - buttonSize.width) * 0.5), y: contentsHeight), size: buttonSize)
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)
if self.isPremiumDisabled {
contentsHeight += bottomInset
} else {
contentsHeight += textButtonSpacing
let buttonFrame = CGRect(origin: CGPoint(x: floor((contentsWidth - buttonSize.width) * 0.5), y: contentsHeight), size: buttonSize)
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)
}
@ -1218,7 +1231,7 @@ final class ChatEmptyNode: ASDisplayNode {
case .topic:
node = ChatEmptyNodeTopicChatContent(context: self.context)
case .premiumRequired:
node = ChatEmptyNodePremiumRequiredChatContent(interaction: self.interaction)
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction)
}
self.content = (contentType, node)
self.addSubnode(node)

View File

@ -89,7 +89,6 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
requestAroundId = true
}
if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId {
requestAroundId = true
preFixedReadState = .peer([:])
}
@ -114,7 +113,11 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
let canScrollToRead: Bool
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 {
canScrollToRead = true
} else {

View File

@ -175,7 +175,7 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineCo
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 {
return false
}
@ -251,6 +251,13 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag
if user.flags.contains(.isSupport) {
return false
}
if !isPremium {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: appConfig)
if premiumConfiguration.isPremiumDisabled {
return false
}
}
default:
return false
}
@ -1731,7 +1738,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
let canViewStats: Bool
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 {
canViewStats = false
}

View File

@ -14,6 +14,7 @@ import ComponentFlow
import MultilineTextComponent
import PlainButtonComponent
import ComponentDisplayAdapters
import AccountContext
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 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 buttonSize = self.button.update(
transition: .immediate,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(VStack([
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)),
content: AnyComponent(VStack(buttonContents, spacing: 1.0)),
effectAlignment: .center,
minSize: size,
action: { [weak self] in

View File

@ -384,7 +384,14 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
}
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
guard let self else {
return false