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 {
case let .topPeers(peers, theme, strings):
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 .recommendedChannels = section {
//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 {
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 {
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 {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
private func chatListSearchContainerPreparedRecentTransition(
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 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 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 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, isChannelsTabExpanded: isChannelsTabExpanded, toggleChannelsTabExpanded: toggleChannelsTabExpanded), directionHint: nil) }
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>
if case .channels = key {
hasRecentPeers = .single(false)
@ -2761,7 +2797,20 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|> 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,
fixedRecentlySearchedPeers |> mapToSignal { peers -> Signal<([RecentlySearchedPeer], [EnginePeer.Id: PeerStoryStats], [EnginePeer.Id: Bool], Set<EnginePeer.Id>), NoError> in
return context.engine.data.subscribe(
@ -2804,7 +2853,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
presentationDataPromise.get(),
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
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) {
recentItems = .single([])
recentItems = .single(RecentItems(entries: [], isChannelsTabExpanded: false, recommendedChannelOrder: []))
}
if case .savedMessagesChats = location {
recentItems = .single([])
recentItems = .single(RecentItems(entries: [], isChannelsTabExpanded: false, recommendedChannelOrder: []))
}
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 _ = self.context.engine.peers.requestGlobalRecommendedChannelsIfNeeded().startStandalone()
@ -2853,16 +2913,19 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
localChannels,
remoteChannels
)
|> mapToSignal { localChannelIds, remoteChannels -> Signal<[ChatListRecentEntry], NoError> in
var allChannelIds = localChannelIds
|> mapToSignal { localChannels, remoteChannels -> Signal<RecentItems, NoError> in
var allChannelIds = localChannels.peerIds
let isChannelsTabExpanded = localChannels.isExpanded
var cachedSubscribers: [EnginePeer.Id: Int32] = [:]
var recommendedChannelOrder: [EnginePeer.Id] = []
if let remoteChannels {
for channel in remoteChannels.channels {
if !allChannelIds.contains(channel.peer.id) {
allChannelIds.append(channel.peer.id)
}
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)
}
),
EngineDataMap(
allChannelIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.StoryStats in
return TelegramEngine.EngineData.Item.Peer.StoryStats(id: peerId)
}
),
EngineDataMap(
allChannelIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.ParticipantCount in
return TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId)
@ -2889,11 +2957,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
),
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 existingIds = Set<PeerId>()
for id in localChannelIds {
for id in localChannels.peerIds {
if existingIds.contains(id) {
continue
}
@ -2908,6 +2976,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} else if let count = cachedSubscribers[id] {
subpeerSummary = RecentlySearchedPeerSubpeerSummary(count: Int(count))
}
var peerStoryStats: PeerStoryStats?
if let value = storyStats[peer.id] {
peerStoryStats = value
}
result.append(.peer(
index: result.count,
peer: RecentlySearchedPeer(
@ -2924,7 +2996,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
presentationData.nameSortOrder,
presentationData.nameDisplayOrder,
globalNotificationSettings,
nil,
peerStoryStats,
false
))
}
@ -2944,6 +3016,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} else if let count = cachedSubscribers[channel.peer.id] {
subpeerSummary = RecentlySearchedPeerSubpeerSummary(count: Int(count))
}
var peerStoryStats: PeerStoryStats?
if let value = storyStats[peer.id] {
peerStoryStats = value
}
result.append(.peer(
index: result.count,
peer: RecentlySearchedPeer(
@ -2960,13 +3036,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
presentationData.nameSortOrder,
presentationData.nameDisplayOrder,
globalNotificationSettings,
nil,
peerStoryStats,
false
))
}
}
return result
return RecentItems(entries: result, isChannelsTabExpanded: isChannelsTabExpanded, recommendedChannelOrder: recommendedChannelOrder)
}
}
}
@ -2979,18 +3055,26 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
presentationDataPromise.get(),
recentItems
)
|> deliverOnMainQueue).startStrict(next: { [weak self] presentationData, entries in
|> deliverOnMainQueue).startStrict(next: { [weak self] presentationData, recentItems in
if let strongSelf = self {
let previousEntries = previousRecentItems.swap(entries)
let previousRecentItems = previousRecentItemsValue.swap(recentItems)
var firstTime = previousEntries == nil
if let previousEntries {
if previousEntries.count < entries.count {
var firstTime = previousRecentItems == nil
var forceUpdateAll = false
if let previousRecentItems {
if previousRecentItems.entries.count < recentItems.entries.count {
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 {
return
}
@ -2998,7 +3082,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if case .channels = key {
if let navigationController = self.navigationController {
var customChatNavigationStack: [EnginePeer.Id] = []
if let entries = previousRecentItems.with({ $0 }) {
if let entries = previousRecentItemsValue.with({ $0 })?.entries {
for entry in entries {
if case let .peer(_, peer, _, _, _, _, _, _, _, _, _) = entry {
customChatNavigationStack.append(peer.peer.peerId)
@ -3040,6 +3124,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let _ = context.engine.peers.removeRecentlySearchedPeer(peerId: peerId).startStandalone()
}, animationCache: strongSelf.animationCache, animationRenderer: strongSelf.animationRenderer, openStories: { peerId, avatarNode in
interaction.openStories?(peerId, avatarNode)
},
isChannelsTabExpanded: recentItems.isChannelsTabExpanded,
toggleChannelsTabExpanded: {
toggleChannelsTabExpanded()
})
strongSelf.enqueueRecentTransition(transition, firstTime: firstTime)
}

View File

@ -1438,7 +1438,9 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
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))
if case .internal = sharedContext.applicationBindings.appBuildType {
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)
}
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
var result: [EnginePeer.Id] = []
@ -1444,7 +1444,7 @@ public extension TelegramEngine {
}
filteredResult.append(id)
if filteredResult.count >= 5 {
if filteredResult.count >= count {
break
}
}

View File

@ -419,7 +419,18 @@ private final class PeerInfoPendingPane {
let paneNode: PeerInfoPaneNode
switch key {
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
visualPaneNode.openCurrentDate = {
openMediaCalendar()

View File

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

View File

@ -1185,6 +1185,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
private let isSaved: Bool
private let isArchive: Bool
private let isProfileEmbedded: Bool
private let canManageStories: Bool
public private(set) var contentType: 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 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.peerId = peerId
self.chatLocation = chatLocation
@ -1308,6 +1309,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.isSaved = isSaved
self.isArchive = isArchive
self.isProfileEmbedded = isProfileEmbedded
self.canManageStories = canManageStories
self.isSelectionModeActive = !isProfileEmbedded && isArchive
@ -1657,12 +1659,25 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
guard let item = strongSelf.itemGrid.item(at: point) else {
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 result.asyncdisplaykit_node is SparseItemGridScrollingArea {
return false
}
}
if !strongSelf.canManageStories {
if !storyItem.story.isForwardingDisabled, case .everyone = storyItem.story.privacy?.base {
} else {
return false
}
}
strongSelf.currentGestureItem = item
@ -1768,167 +1783,190 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
private func openContextMenu(item: EngineStoryItem, itemLayer: ItemLayer, rect: CGRect, gesture: ContextGesture?) {
guard let parentController = self.parentController else {
return
}
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
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)
)
|> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else {
f(.default)
return
}
guard let parentController = self.parentController else {
return
}
if self.isArchive {
f(.default)
} else {
f(.dismissWithoutContent)
}
let canManage = self.canManageStories
let _ = self.context.engine.messages.updateStoriesArePinned(peerId: self.peerId, ids: [item.id: item], isPinned: self.isArchive ? true : false).startStandalone()
})))
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 {
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: {
var items: [ContextMenuItem] = []
//TODO:localize
if canManage {
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 {
f(.default)
return
}
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
if self.isArchive {
f(.default)
} else {
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 {
return
}
guard let peer, let peerReference = PeerReference(peer._asPeer()) else {
f(.default)
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))
})
})
})))
}
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
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)
})))
}
self.presentDeleteConfirmation(ids: Set([item.id]))
})
})))
items.append(.separator)
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)
/*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 {
return
}
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
}
self.presentDeleteConfirmation(ids: Set([item.id]))
})
})))
}
if self.canManageStories {
if !items.isEmpty {
items.append(.separator)
}
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.contextControllerToDismissOnSelection = c
parentController.toggleStorySelection(ids: [item.id], isSelected: true)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: { [weak self] in
guard let self, let contextControllerToDismissOnSelection = self.contextControllerToDismissOnSelection else {
return
}
if let contextControllerToDismissOnSelection = contextControllerToDismissOnSelection as? ContextController {
contextControllerToDismissOnSelection.dismissWithCustomTransition(transition: .animated(duration: 0.4, curve: .spring), completion: nil)
}
})
})))
}
if items.isEmpty {
return
}
self.contextControllerToDismissOnSelection = c
parentController.toggleStorySelection(ids: [item.id], isSelected: true)
let tempSourceNode = TempExtractedItemNode(
item: item,
itemLayer: itemLayer
)
tempSourceNode.frame = rect
tempSourceNode.update(size: rect.size)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: { [weak self] in
guard let self, let contextControllerToDismissOnSelection = self.contextControllerToDismissOnSelection else {
return
}
if let contextControllerToDismissOnSelection = contextControllerToDismissOnSelection as? ContextController {
contextControllerToDismissOnSelection.dismissWithCustomTransition(transition: .animated(duration: 0.4, curve: .spring), completion: nil)
}
})
})))
let tempSourceNode = TempExtractedItemNode(
item: item,
itemLayer: itemLayer
)
tempSourceNode.frame = rect
tempSourceNode.update(size: rect.size)
let scaleSide = itemLayer.bounds.width
let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide)
let currentScale = minScale
ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: tempSourceNode.contextSourceNode.contentNode, scale: currentScale)
ContainedViewLayoutTransition.immediate.updateTransformScale(layer: itemLayer, scale: 1.0)
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)
let scaleSide = itemLayer.bounds.width
let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide)
let currentScale = minScale
ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: tempSourceNode.contextSourceNode.contentNode, scale: currentScale)
ContainedViewLayoutTransition.immediate.updateTransformScale(layer: itemLayer, scale: 1.0)
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) {
@ -2400,7 +2438,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData)
var bottomInset = bottomInset
if let selectedIds = self.itemInteraction.selectedIds {
if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories {
let selectionPanel: ComponentView<Empty>
var selectionPanelTransition = Transition(transition)
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 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(