Various improvements

This commit is contained in:
Isaac 2024-04-09 12:47:57 +04:00
parent 3423fb8319
commit a742df315c
6 changed files with 332 additions and 176 deletions

View File

@ -95,7 +95,22 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
} }
} }
func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, peerSelected: @escaping (EnginePeer, Int64?) -> Void, disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void) -> ListViewItem { func item(
context: AccountContext,
presentationData: ChatListPresentationData,
filter: ChatListNodePeersFilter,
key: ChatListSearchPaneKey,
peerSelected: @escaping (EnginePeer, Int64?) -> Void,
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
clearRecentlySearchedPeers: @escaping () -> Void,
deletePeer: @escaping (EnginePeer.Id) -> Void,
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer,
openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void,
isChannelsTabExpanded: Bool,
toggleChannelsTabExpanded: @escaping () -> Void
) -> ListViewItem {
switch self { switch self {
case let .topPeers(peers, theme, strings): case let .topPeers(peers, theme, strings):
return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in
@ -224,9 +239,12 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
if case .channels = key { if case .channels = key {
if case .recommendedChannels = section { if case .recommendedChannels = section {
//TODO:localize //TODO:localize
header = ChatListSearchItemHeader(type: .text("RECOMMENDED CHANNELS", 0), theme: theme, strings: strings) header = ChatListSearchItemHeader(type: .text("RECOMMENDED CHANNELS", 1), theme: theme, strings: strings)
} else { } else {
header = nil //TODO:localize
header = ChatListSearchItemHeader(type: .text("CHANNELS YOU JOINED", 0), theme: theme, strings: strings, actionTitle: isChannelsTabExpanded ? "Show less" : "Show more", action: {
toggleChannelsTabExpanded()
})
} }
} else { } else {
header = ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear, action: { header = ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear, action: {
@ -944,12 +962,30 @@ public struct ChatListSearchContainerTransition {
} }
} }
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, peerSelected: @escaping (EnginePeer, Int64?) -> Void, disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void) -> ChatListSearchContainerRecentTransition { private func chatListSearchContainerPreparedRecentTransition(
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) from fromEntries: [ChatListRecentEntry],
to toEntries: [ChatListRecentEntry],
forceUpdateAll: Bool,
context: AccountContext,
presentationData: ChatListPresentationData,
filter: ChatListNodePeersFilter,
key: ChatListSearchPaneKey,
peerSelected: @escaping (EnginePeer, Int64?) -> Void,
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
clearRecentlySearchedPeers: @escaping () -> Void,
deletePeer: @escaping (EnginePeer.Id) -> Void,
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer,
openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void,
isChannelsTabExpanded: Bool,
toggleChannelsTabExpanded: @escaping () -> Void
) -> ChatListSearchContainerRecentTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdateAll)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, key: key, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer, openStories: openStories), directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, key: key, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer, openStories: openStories, isChannelsTabExpanded: isChannelsTabExpanded, toggleChannelsTabExpanded: toggleChannelsTabExpanded), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, key: key, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer, openStories: openStories), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, key: key, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer, openStories: openStories, isChannelsTabExpanded: isChannelsTabExpanded, toggleChannelsTabExpanded: toggleChannelsTabExpanded), directionHint: nil) }
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
} }
@ -2744,7 +2780,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
})) }))
let previousRecentItems = Atomic<[ChatListRecentEntry]?>(value: nil) let previousRecentItemsValue = Atomic<RecentItems?>(value: nil)
let hasRecentPeers: Signal<Bool, NoError> let hasRecentPeers: Signal<Bool, NoError>
if case .channels = key { if case .channels = key {
hasRecentPeers = .single(false) hasRecentPeers = .single(false)
@ -2761,7 +2797,20 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|> distinctUntilChanged |> distinctUntilChanged
} }
var recentItems = combineLatest( struct RecentItems {
var entries: [ChatListRecentEntry]
var isChannelsTabExpanded: Bool
var recommendedChannelOrder: [EnginePeer.Id]
}
let isChannelsTabExpandedValue = ValuePromise<Bool>(false, ignoreRepeated: true)
let toggleChannelsTabExpanded: () -> Void = {
let _ = (isChannelsTabExpandedValue.get() |> take(1)).startStandalone(next: { value in
isChannelsTabExpandedValue.set(!value)
})
}
var recentItems: Signal<RecentItems, NoError> = combineLatest(
hasRecentPeers, hasRecentPeers,
fixedRecentlySearchedPeers |> mapToSignal { peers -> Signal<([RecentlySearchedPeer], [EnginePeer.Id: PeerStoryStats], [EnginePeer.Id: Bool], Set<EnginePeer.Id>), NoError> in fixedRecentlySearchedPeers |> mapToSignal { peers -> Signal<([RecentlySearchedPeer], [EnginePeer.Id: PeerStoryStats], [EnginePeer.Id: Bool], Set<EnginePeer.Id>), NoError> in
return context.engine.data.subscribe( return context.engine.data.subscribe(
@ -2804,7 +2853,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
presentationDataPromise.get(), presentationDataPromise.get(),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()) context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global())
) )
|> mapToSignal { hasRecentPeers, peersAndStories, presentationData, globalNotificationSettings -> Signal<[ChatListRecentEntry], NoError> in |> mapToSignal { hasRecentPeers, peersAndStories, presentationData, globalNotificationSettings -> Signal<RecentItems, NoError> in
let (peers, peerStoryStats, requiresPremiumForMessaging, refreshIsPremiumRequiredForMessaging) = peersAndStories let (peers, peerStoryStats, requiresPremiumForMessaging, refreshIsPremiumRequiredForMessaging) = peersAndStories
if !refreshIsPremiumRequiredForMessaging.isEmpty { if !refreshIsPremiumRequiredForMessaging.isEmpty {
@ -2834,17 +2883,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
} }
return .single(entries) return .single(RecentItems(entries: entries, isChannelsTabExpanded: false, recommendedChannelOrder: []))
} }
if peersFilter.contains(.excludeRecent) { if peersFilter.contains(.excludeRecent) {
recentItems = .single([]) recentItems = .single(RecentItems(entries: [], isChannelsTabExpanded: false, recommendedChannelOrder: []))
} }
if case .savedMessagesChats = location { if case .savedMessagesChats = location {
recentItems = .single([]) recentItems = .single(RecentItems(entries: [], isChannelsTabExpanded: false, recommendedChannelOrder: []))
} }
if case .channels = key { if case .channels = key {
let localChannels = context.engine.messages.getAllLocalChannels() struct LocalChannels {
var peerIds: [EnginePeer.Id]
var isExpanded: Bool
}
let localChannels = isChannelsTabExpandedValue.get()
|> mapToSignal { isChannelsTabExpanded -> Signal<LocalChannels, NoError> in
return context.engine.messages.getAllLocalChannels(count: isChannelsTabExpanded ? 500 : 5)
|> map { peerIds -> LocalChannels in
return LocalChannels(peerIds: peerIds, isExpanded: isChannelsTabExpanded)
}
}
let remoteChannels: Signal<RecommendedChannels?, NoError> = context.engine.peers.recommendedChannels(peerId: nil) let remoteChannels: Signal<RecommendedChannels?, NoError> = context.engine.peers.recommendedChannels(peerId: nil)
let _ = self.context.engine.peers.requestGlobalRecommendedChannelsIfNeeded().startStandalone() let _ = self.context.engine.peers.requestGlobalRecommendedChannelsIfNeeded().startStandalone()
@ -2853,16 +2913,19 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
localChannels, localChannels,
remoteChannels remoteChannels
) )
|> mapToSignal { localChannelIds, remoteChannels -> Signal<[ChatListRecentEntry], NoError> in |> mapToSignal { localChannels, remoteChannels -> Signal<RecentItems, NoError> in
var allChannelIds = localChannelIds var allChannelIds = localChannels.peerIds
let isChannelsTabExpanded = localChannels.isExpanded
var cachedSubscribers: [EnginePeer.Id: Int32] = [:] var cachedSubscribers: [EnginePeer.Id: Int32] = [:]
var recommendedChannelOrder: [EnginePeer.Id] = []
if let remoteChannels { if let remoteChannels {
for channel in remoteChannels.channels { for channel in remoteChannels.channels {
if !allChannelIds.contains(channel.peer.id) { if !allChannelIds.contains(channel.peer.id) {
allChannelIds.append(channel.peer.id) allChannelIds.append(channel.peer.id)
} }
cachedSubscribers[channel.peer.id] = channel.subscribers cachedSubscribers[channel.peer.id] = channel.subscribers
recommendedChannelOrder.append(channel.peer.id)
} }
} }
@ -2882,6 +2945,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId) return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId)
} }
), ),
EngineDataMap(
allChannelIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.StoryStats in
return TelegramEngine.EngineData.Item.Peer.StoryStats(id: peerId)
}
),
EngineDataMap( EngineDataMap(
allChannelIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.ParticipantCount in allChannelIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.ParticipantCount in
return TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId) return TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId)
@ -2889,11 +2957,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
), ),
TelegramEngine.EngineData.Item.NotificationSettings.Global() TelegramEngine.EngineData.Item.NotificationSettings.Global()
) )
|> map { peers, notificationSettings, unreadCounts, participantCounts, globalNotificationSettings -> [ChatListRecentEntry] in |> map { peers, notificationSettings, unreadCounts, storyStats, participantCounts, globalNotificationSettings -> RecentItems in
var result: [ChatListRecentEntry] = [] var result: [ChatListRecentEntry] = []
var existingIds = Set<PeerId>() var existingIds = Set<PeerId>()
for id in localChannelIds { for id in localChannels.peerIds {
if existingIds.contains(id) { if existingIds.contains(id) {
continue continue
} }
@ -2908,6 +2976,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} else if let count = cachedSubscribers[id] { } else if let count = cachedSubscribers[id] {
subpeerSummary = RecentlySearchedPeerSubpeerSummary(count: Int(count)) subpeerSummary = RecentlySearchedPeerSubpeerSummary(count: Int(count))
} }
var peerStoryStats: PeerStoryStats?
if let value = storyStats[peer.id] {
peerStoryStats = value
}
result.append(.peer( result.append(.peer(
index: result.count, index: result.count,
peer: RecentlySearchedPeer( peer: RecentlySearchedPeer(
@ -2924,7 +2996,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
presentationData.nameSortOrder, presentationData.nameSortOrder,
presentationData.nameDisplayOrder, presentationData.nameDisplayOrder,
globalNotificationSettings, globalNotificationSettings,
nil, peerStoryStats,
false false
)) ))
} }
@ -2944,6 +3016,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} else if let count = cachedSubscribers[channel.peer.id] { } else if let count = cachedSubscribers[channel.peer.id] {
subpeerSummary = RecentlySearchedPeerSubpeerSummary(count: Int(count)) subpeerSummary = RecentlySearchedPeerSubpeerSummary(count: Int(count))
} }
var peerStoryStats: PeerStoryStats?
if let value = storyStats[peer.id] {
peerStoryStats = value
}
result.append(.peer( result.append(.peer(
index: result.count, index: result.count,
peer: RecentlySearchedPeer( peer: RecentlySearchedPeer(
@ -2960,13 +3036,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
presentationData.nameSortOrder, presentationData.nameSortOrder,
presentationData.nameDisplayOrder, presentationData.nameDisplayOrder,
globalNotificationSettings, globalNotificationSettings,
nil, peerStoryStats,
false false
)) ))
} }
} }
return result return RecentItems(entries: result, isChannelsTabExpanded: isChannelsTabExpanded, recommendedChannelOrder: recommendedChannelOrder)
} }
} }
} }
@ -2979,18 +3055,26 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
presentationDataPromise.get(), presentationDataPromise.get(),
recentItems recentItems
) )
|> deliverOnMainQueue).startStrict(next: { [weak self] presentationData, entries in |> deliverOnMainQueue).startStrict(next: { [weak self] presentationData, recentItems in
if let strongSelf = self { if let strongSelf = self {
let previousEntries = previousRecentItems.swap(entries) let previousRecentItems = previousRecentItemsValue.swap(recentItems)
var firstTime = previousEntries == nil var firstTime = previousRecentItems == nil
if let previousEntries { var forceUpdateAll = false
if previousEntries.count < entries.count { if let previousRecentItems {
if previousRecentItems.entries.count < recentItems.entries.count {
firstTime = true firstTime = true
} }
if previousRecentItems.recommendedChannelOrder != recentItems.recommendedChannelOrder {
firstTime = true
}
if previousRecentItems.isChannelsTabExpanded != recentItems.isChannelsTabExpanded {
firstTime = true
forceUpdateAll = true
}
} }
let transition = chatListSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, filter: peersFilter, key: key, peerSelected: { peer, threadId in let transition = chatListSearchContainerPreparedRecentTransition(from: previousRecentItems?.entries ?? [], to: recentItems.entries, forceUpdateAll: forceUpdateAll, context: context, presentationData: presentationData, filter: peersFilter, key: key, peerSelected: { peer, threadId in
guard let self else { guard let self else {
return return
} }
@ -2998,7 +3082,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if case .channels = key { if case .channels = key {
if let navigationController = self.navigationController { if let navigationController = self.navigationController {
var customChatNavigationStack: [EnginePeer.Id] = [] var customChatNavigationStack: [EnginePeer.Id] = []
if let entries = previousRecentItems.with({ $0 }) { if let entries = previousRecentItemsValue.with({ $0 })?.entries {
for entry in entries { for entry in entries {
if case let .peer(_, peer, _, _, _, _, _, _, _, _, _) = entry { if case let .peer(_, peer, _, _, _, _, _, _, _, _, _) = entry {
customChatNavigationStack.append(peer.peer.peerId) customChatNavigationStack.append(peer.peer.peerId)
@ -3040,6 +3124,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let _ = context.engine.peers.removeRecentlySearchedPeer(peerId: peerId).startStandalone() let _ = context.engine.peers.removeRecentlySearchedPeer(peerId: peerId).startStandalone()
}, animationCache: strongSelf.animationCache, animationRenderer: strongSelf.animationRenderer, openStories: { peerId, avatarNode in }, animationCache: strongSelf.animationCache, animationRenderer: strongSelf.animationRenderer, openStories: { peerId, avatarNode in
interaction.openStories?(peerId, avatarNode) interaction.openStories?(peerId, avatarNode)
},
isChannelsTabExpanded: recentItems.isChannelsTabExpanded,
toggleChannelsTabExpanded: {
toggleChannelsTabExpanded()
}) })
strongSelf.enqueueRecentTransition(transition, firstTime: firstTime) strongSelf.enqueueRecentTransition(transition, firstTime: firstTime)
} }

View File

@ -1438,7 +1438,9 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility)) entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay)) entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers)) entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
entries.append(.browserExperiment(experimentalSettings.browserExperiment)) if sharedContext.applicationBindings.appBuildType == .internal {
entries.append(.browserExperiment(experimentalSettings.browserExperiment))
}
entries.append(.localTranscription(experimentalSettings.localTranscription)) entries.append(.localTranscription(experimentalSettings.localTranscription))
if case .internal = sharedContext.applicationBindings.appBuildType { if case .internal = sharedContext.applicationBindings.appBuildType {
entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides)) entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides))

View File

@ -1411,7 +1411,7 @@ public extension TelegramEngine {
return _internal_reportAdMessage(account: self.account, peerId: peerId, opaqueId: opaqueId, option: option) return _internal_reportAdMessage(account: self.account, peerId: peerId, opaqueId: opaqueId, option: option)
} }
public func getAllLocalChannels() -> Signal<[EnginePeer.Id], NoError> { public func getAllLocalChannels(count: Int) -> Signal<[EnginePeer.Id], NoError> {
return self.account.postbox.transaction { transaction -> [EnginePeer.Id] in return self.account.postbox.transaction { transaction -> [EnginePeer.Id] in
var result: [EnginePeer.Id] = [] var result: [EnginePeer.Id] = []
@ -1444,7 +1444,7 @@ public extension TelegramEngine {
} }
filteredResult.append(id) filteredResult.append(id)
if filteredResult.count >= 5 { if filteredResult.count >= count {
break break
} }
} }

View File

@ -419,7 +419,18 @@ private final class PeerInfoPendingPane {
let paneNode: PeerInfoPaneNode let paneNode: PeerInfoPaneNode
switch key { switch key {
case .stories, .storyArchive: case .stories, .storyArchive:
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: key == .storyArchive, isProfileEmbedded: true, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext) var canManage = false
if let peer = data.peer {
if peer.id == context.account.peerId {
canManage = true
} else if let channel = peer as? TelegramChannel {
if channel.hasPermission(.editStories) {
canManage = true
}
}
}
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: key == .storyArchive, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext)
paneNode = visualPaneNode paneNode = visualPaneNode
visualPaneNode.openCurrentDate = { visualPaneNode.openCurrentDate = {
openMediaCalendar() openMediaCalendar()

View File

@ -462,6 +462,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
isSaved: true, isSaved: true,
isArchive: component.scope == .archive, isArchive: component.scope == .archive,
isProfileEmbedded: false, isProfileEmbedded: false,
canManageStories: true,
navigationController: { [weak self] in navigationController: { [weak self] in
guard let self else { guard let self else {
return nil return nil

View File

@ -1185,6 +1185,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
private let isSaved: Bool private let isSaved: Bool
private let isArchive: Bool private let isArchive: Bool
private let isProfileEmbedded: Bool private let isProfileEmbedded: Bool
private let canManageStories: Bool
public private(set) var contentType: ContentType public private(set) var contentType: ContentType
private var contentTypePromise: ValuePromise<ContentType> private var contentTypePromise: ValuePromise<ContentType>
@ -1298,7 +1299,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
private weak var contextControllerToDismissOnSelection: ContextControllerProtocol? private weak var contextControllerToDismissOnSelection: ContextControllerProtocol?
private weak var tempContextContentItemNode: TempExtractedItemNode? private weak var tempContextContentItemNode: TempExtractedItemNode?
public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, isProfileEmbedded: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) { public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, isProfileEmbedded: Bool, canManageStories: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.chatLocation = chatLocation self.chatLocation = chatLocation
@ -1308,6 +1309,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.isSaved = isSaved self.isSaved = isSaved
self.isArchive = isArchive self.isArchive = isArchive
self.isProfileEmbedded = isProfileEmbedded self.isProfileEmbedded = isProfileEmbedded
self.canManageStories = canManageStories
self.isSelectionModeActive = !isProfileEmbedded && isArchive self.isSelectionModeActive = !isProfileEmbedded && isArchive
@ -1657,6 +1659,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
guard let item = strongSelf.itemGrid.item(at: point) else { guard let item = strongSelf.itemGrid.item(at: point) else {
return false return false
} }
guard let layer = item.layer as? ItemLayer else {
return false
}
guard let storyItem = layer.item else {
return false
}
if let result = strongSelf.view.hitTest(point, with: nil) { if let result = strongSelf.view.hitTest(point, with: nil) {
if result.asyncdisplaykit_node is SparseItemGridScrollingArea { if result.asyncdisplaykit_node is SparseItemGridScrollingArea {
@ -1664,6 +1672,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
} }
} }
if !strongSelf.canManageStories {
if !storyItem.story.isForwardingDisabled, case .everyone = storyItem.story.privacy?.base {
} else {
return false
}
}
strongSelf.currentGestureItem = item strongSelf.currentGestureItem = item
return true return true
@ -1768,167 +1783,190 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
} }
private func openContextMenu(item: EngineStoryItem, itemLayer: ItemLayer, rect: CGRect, gesture: ContextGesture?) { private func openContextMenu(item: EngineStoryItem, itemLayer: ItemLayer, rect: CGRect, gesture: ContextGesture?) {
guard let parentController = self.parentController else { let _ = (self.context.engine.data.get(
return TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)
} )
|> deliverOnMainQueue).start(next: { [weak self] _ in
var items: [ContextMenuItem] = []
//TODO:localize
items.append(.action(ContextMenuActionItem(text: !self.isArchive ? "Archive" : "Unarchive", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: self.isArchive ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
guard let self else { guard let self else {
f(.default) return
}
guard let parentController = self.parentController else {
return return
} }
if self.isArchive { let canManage = self.canManageStories
f(.default)
} else {
f(.dismissWithoutContent)
}
let _ = self.context.engine.messages.updateStoriesArePinned(peerId: self.peerId, ids: [item.id: item], isPinned: self.isArchive ? true : false).startStandalone() var items: [ContextMenuItem] = []
})))
if !self.isArchive { //TODO:localize
let isPinned = self.pinnedIds.contains(item.id)
items.append(.action(ContextMenuActionItem(text: isPinned ? "Unpin" : "Pin", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak itemLayer] _, f in
itemLayer?.isHidden = false
guard let self else {
f(.default)
return
}
if !isPinned && self.pinnedIds.count >= 3 { if canManage {
f(.default) items.append(.action(ContextMenuActionItem(text: !self.isArchive ? "Archive" : "Unarchive", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: self.isArchive ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
let presentationData = self.presentationData
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "You can't pin more than 3 posts.", timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
return
}
f(.dismissWithoutContent)
var updatedPinnedIds = self.pinnedIds
if isPinned {
updatedPinnedIds.remove(item.id)
} else {
updatedPinnedIds.insert(item.id)
}
let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: self.peerId, ids: Array(updatedPinnedIds)).startStandalone()
//TODO:localize
let presentationData = self.presentationData
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: isPinned ? nil : "Story Pinned", text: isPinned ? "Story Unpinned." : "Now it will always be shown on the top.", cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})))
}
/*items.append(.action(ContextMenuActionItem(text: "Edit", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: {
guard let self else {
return
}
let _ = self
})
})))*/
if !item.isForwardingDisabled {
items.append(.action(ContextMenuActionItem(text: "Forward", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: {
guard let self else { guard let self else {
f(.default)
return return
} }
let _ = (self.context.engine.data.get( if self.isArchive {
TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId) f(.default)
) } else {
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in f(.dismissWithoutContent)
}
let _ = self.context.engine.messages.updateStoriesArePinned(peerId: self.peerId, ids: [item.id: item], isPinned: self.isArchive ? true : false).startStandalone()
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: self.isArchive ? "Story unarchived." : "Story archived.", cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})))
if !self.isArchive {
let isPinned = self.pinnedIds.contains(item.id)
items.append(.action(ContextMenuActionItem(text: isPinned ? "Unpin" : "Pin", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak itemLayer] _, f in
itemLayer?.isHidden = false
guard let self else {
f(.default)
return
}
if !isPinned && self.pinnedIds.count >= 3 {
f(.default)
let presentationData = self.presentationData
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "You can't pin more than 3 posts.", timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
return
}
f(.dismissWithoutContent)
var updatedPinnedIds = self.pinnedIds
if isPinned {
updatedPinnedIds.remove(item.id)
} else {
updatedPinnedIds.insert(item.id)
}
let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: self.peerId, ids: Array(updatedPinnedIds)).startStandalone()
//TODO:localize
let presentationData = self.presentationData
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: isPinned ? nil : "Story Pinned", text: isPinned ? "Story Unpinned." : "Now it will always be shown on the top.", cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})))
}
/*items.append(.action(ContextMenuActionItem(text: "Edit", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: {
guard let self else {
return
}
let _ = self
})
})))*/
}
if !item.isForwardingDisabled, case .everyone = item.privacy?.base {
items.append(.action(ContextMenuActionItem(text: "Forward", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: {
guard let self else { guard let self else {
return return
} }
guard let peer, let peerReference = PeerReference(peer._asPeer()) else {
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
guard let self else {
return
}
guard let peer, let peerReference = PeerReference(peer._asPeer()) else {
return
}
let shareController = ShareController(
context: self.context,
subject: .media(.story(peer: peerReference, id: item.id, media: TelegramMediaStory(storyId: StoryId(peerId: self.peerId, id: item.id), isMention: false))),
presetText: nil,
preferredAction: .default,
showInChat: nil,
fromForeignApp: false,
segmentedValues: nil,
externalShare: false,
immediateExternalShare: false,
switchableAccounts: [],
immediatePeerId: nil,
updatedPresentationData: nil,
forceTheme: nil,
forcedActionTitle: nil,
shareAsLink: false,
collectibleItemInfo: nil
)
self.parentController?.present(shareController, in: .window(.root))
})
})
})))
}
if canManage {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in
c.dismiss(completion: {
guard let self else {
return return
} }
let shareController = ShareController( self.presentDeleteConfirmation(ids: Set([item.id]))
context: self.context,
subject: .media(.story(peer: peerReference, id: item.id, media: TelegramMediaStory(storyId: StoryId(peerId: self.peerId, id: item.id), isMention: false))),
presetText: nil,
preferredAction: .default,
showInChat: nil,
fromForeignApp: false,
segmentedValues: nil,
externalShare: false,
immediateExternalShare: false,
switchableAccounts: [],
immediatePeerId: nil,
updatedPresentationData: nil,
forceTheme: nil,
forcedActionTitle: nil,
shareAsLink: false,
collectibleItemInfo: nil
)
self.parentController?.present(shareController, in: .window(.root))
}) })
}) })))
}))) }
}
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in if self.canManageStories {
c.dismiss(completion: { if !items.isEmpty {
guard let self else { items.append(.separator)
return
} }
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak self] c, f in
guard let self, let parentController = self.parentController as? PeerInfoScreen else {
f(.default)
return
}
self.presentDeleteConfirmation(ids: Set([item.id])) self.contextControllerToDismissOnSelection = c
}) parentController.toggleStorySelection(ids: [item.id], isSelected: true)
})))
items.append(.separator) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: { [weak self] in
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in guard let self, let contextControllerToDismissOnSelection = self.contextControllerToDismissOnSelection else {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor) return
}, action: { [weak self] c, f in }
guard let self, let parentController = self.parentController as? PeerInfoScreen else { if let contextControllerToDismissOnSelection = contextControllerToDismissOnSelection as? ContextController {
f(.default) contextControllerToDismissOnSelection.dismissWithCustomTransition(transition: .animated(duration: 0.4, curve: .spring), completion: nil)
}
})
})))
}
if items.isEmpty {
return return
} }
self.contextControllerToDismissOnSelection = c let tempSourceNode = TempExtractedItemNode(
parentController.toggleStorySelection(ids: [item.id], isSelected: true) item: item,
itemLayer: itemLayer
)
tempSourceNode.frame = rect
tempSourceNode.update(size: rect.size)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: { [weak self] in let scaleSide = itemLayer.bounds.width
guard let self, let contextControllerToDismissOnSelection = self.contextControllerToDismissOnSelection else { let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide)
return let currentScale = minScale
}
if let contextControllerToDismissOnSelection = contextControllerToDismissOnSelection as? ContextController {
contextControllerToDismissOnSelection.dismissWithCustomTransition(transition: .animated(duration: 0.4, curve: .spring), completion: nil)
}
})
})))
let tempSourceNode = TempExtractedItemNode( ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: tempSourceNode.contextSourceNode.contentNode, scale: currentScale)
item: item, ContainedViewLayoutTransition.immediate.updateTransformScale(layer: itemLayer, scale: 1.0)
itemLayer: itemLayer
)
tempSourceNode.frame = rect
tempSourceNode.update(size: rect.size)
let scaleSide = itemLayer.bounds.width self.tempContextContentItemNode = tempSourceNode
let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) self.addSubnode(tempSourceNode)
let currentScale = minScale
ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: tempSourceNode.contextSourceNode.contentNode, scale: currentScale) let contextController = ContextController(presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: parentController, sourceNode: tempSourceNode.contextSourceNode, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
ContainedViewLayoutTransition.immediate.updateTransformScale(layer: itemLayer, scale: 1.0) parentController.presentInGlobalOverlay(contextController)
})
self.tempContextContentItemNode = tempSourceNode
self.addSubnode(tempSourceNode)
let contextController = ContextController(presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: parentController, sourceNode: tempSourceNode.contextSourceNode, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
parentController.presentInGlobalOverlay(contextController)
} }
public func updateContentType(contentType: ContentType) { public func updateContentType(contentType: ContentType) {
@ -2400,7 +2438,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData)
var bottomInset = bottomInset var bottomInset = bottomInset
if let selectedIds = self.itemInteraction.selectedIds { if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories {
let selectionPanel: ComponentView<Empty> let selectionPanel: ComponentView<Empty>
var selectionPanelTransition = Transition(transition) var selectionPanelTransition = Transition(transition)
if let current = self.selectionPanel { if let current = self.selectionPanel {
@ -2484,6 +2522,22 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
} }
let _ = self.context.engine.messages.updateStoriesArePinned(peerId: self.peerId, ids: items, isPinned: self.isArchive ? true : false).startStandalone() let _ = self.context.engine.messages.updateStoriesArePinned(peerId: self.peerId, ids: items, isPinned: self.isArchive ? true : false).startStandalone()
let text: String
if self.isArchive {
if items.count == 1 {
text = "Story unarchived."
} else {
text = "Stories unarchived."
}
} else {
if items.count == 1 {
text = "Story archived."
} else {
text = "Stories archived."
}
}
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: text, cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
} }
)) ))
selectionItems.append(BottomActionsPanelComponent.Item( selectionItems.append(BottomActionsPanelComponent.Item(