diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a9a7e58173..8dc703c92a 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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 %@"; diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index e52a226c3c..ea4b09cbcf 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -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? diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 613de3aa2c..045f395c13 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -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) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 3518584c23..1e99bd5f14 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -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 diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index d8fc0cac03..8c39c392b2 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -714,7 +714,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: false, + requiresPremiumForMessaging: entry.isPremiumRequiredToMessage, displayAsTopicList: entry.displayAsTopicList )) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 1fdbc34a8f..a559eef16b 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -113,7 +113,7 @@ public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: E }) } -func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account) -> Signal { +func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account, shouldLoadCanMessagePeer: Bool) -> Signal { 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 )) } diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index aa2b834f6e..fa0fde3d30 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -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 } } diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index cb22407a66..b4fde57f49 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -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) diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 7e12ea2a25..5666aa37d8 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -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)) } diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 07c730b300..64867fec54 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -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 diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 90c8435a2f..79c09bffc9 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -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 )) } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 68fd311c24..2eff0f1455 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 79351361b7..1917501ec3 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -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 )) } diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index be25264393..d6aff342b5 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -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 +} diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 262224da8b..4045a73bba 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -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 } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index a0544481ee..8836ceab30 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -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 { @@ -569,12 +594,14 @@ public extension EngineChatList { if let value = view.displaySavedMessagesAsTopicList?.get(EngineDisplaySavedChatsAsTopics.self) { 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: diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBackgroundLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBackgroundLayer.swift index 9c1aace84d..26ffbe4832 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBackgroundLayer.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBackgroundLayer.swift @@ -97,6 +97,7 @@ final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject { private let colorSets: [ColorSet] private let colorTransition: AnimatedProperty private var stateIndex: Int = 0 + private var isEnergySavingEnabled: Bool = false private let phaseAcceleration = AnimatedProperty(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 diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index 341a8134a6..1ae4c05e38 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -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() - } - }*/ } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift index 909a8df983..91e50ef149 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 8a7ee03cc9..bfe6ce944f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index da98736c5d..61e0ba5165 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 4fe2242128..93dd8ed0e1 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -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 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 in diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift index cf7c6a21a4..6200db9812 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift @@ -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 = .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 } diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 791993170b..2e09f56eae 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2388057a38..35d55c6f6a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift index 5ee94642bf..6669d37714 100644 --- a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift +++ b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 83409ccdee..fb6d8dc00b 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -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) - - 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) + if !self.isPremiumDisabled { + self.view.addSubview(self.button) + + 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.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) diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index c805a16494..47c47ed6c3 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index b9af97583d..ef1547d4d4 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift index 6610db7d28..de6fe788e4 100644 --- a/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift @@ -14,6 +14,7 @@ import ComponentFlow import MultilineTextComponent import PlainButtonComponent import ComponentDisplayAdapters +import AccountContext private let labelFont = Font.regular(15.0) @@ -100,19 +101,25 @@ 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] = [] + 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 diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index 6d1dc3626b..e7e3ee8dce 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -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