This commit is contained in:
Isaac 2025-07-22 16:08:51 +02:00
parent 79c2f40d0b
commit a5ad030123
19 changed files with 357 additions and 138 deletions

View File

@ -330,6 +330,7 @@ public enum ResolvedUrl {
case messageLink(link: TelegramResolvedMessageLink?)
case stars
case shareStory(Int64)
case storyFolder(peerId: PeerId, id: Int64)
}
public enum ResolveUrlResult {
@ -667,6 +668,7 @@ public enum PeerInfoControllerMode {
case myProfileGifts
case groupsInCommon
case monoforum(EnginePeer.Id)
case storyAlbum(id: Int64)
}
public enum ContactListActionItemInlineIconPosition {

View File

@ -650,7 +650,8 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
}
return self.controller?.navigationController as? NavigationController
},
listContext: storySearchContext
listContext: storySearchContext,
initialStoryFolderId: nil
)
self.storySearchPaneNode = storySearchPaneNode
if let storySearchView = self.storySearchComponentView?.view {

View File

@ -1462,7 +1462,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
#if DEBUG
//debugSaveState(basePath: basePath + "/db", name: "previous2")
//debugRestoreState(basePath: basePath + "/db", name: "previous2")
debugRestoreState(basePath: basePath + "/db", name: "previous2")
#endif
let startTime = CFAbsoluteTimeGetCurrent()

View File

@ -5115,7 +5115,8 @@ func replayFinalState(
isMy: item.isMy,
myReaction: updatedReaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
))
if let entry = CodableEntry(updatedItem) {
updatedPeerEntries[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: item.expirationTimestamp, isCloseFriends: item.isCloseFriends)
@ -5149,7 +5150,8 @@ func replayFinalState(
isMy: item.isMy,
myReaction: MessageReaction.Reaction(apiReaction: reaction),
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: peerId, id: id), value: entry)

View File

@ -350,7 +350,8 @@ private final class StoryStatsPublicForwardsContextImpl {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
resultForwards.append(.story(EnginePeer(peer), mappedItem))
}

View File

@ -559,7 +559,8 @@ public final class EngineStoryViewListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
),
storyStats: transaction.getPeerStoryStats(peerId: peer.id)
)))
@ -599,7 +600,8 @@ public final class EngineStoryViewListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry)
@ -639,7 +641,8 @@ public final class EngineStoryViewListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
@ -756,7 +759,8 @@ public final class EngineStoryViewListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
),
storyStats: transaction.getPeerStoryStats(peerId: peer.id)
)))

View File

@ -263,6 +263,7 @@ public enum Stories {
case myReaction
case forwardInfo
case authorId
case folderIds
}
public let id: Int32
@ -287,6 +288,7 @@ public enum Stories {
public let myReaction: MessageReaction.Reaction?
public let forwardInfo: ForwardInfo?
public let authorId: PeerId?
public let folderIds: [Int64]?
public init(
id: Int32,
@ -310,7 +312,8 @@ public enum Stories {
isMy: Bool,
myReaction: MessageReaction.Reaction?,
forwardInfo: ForwardInfo?,
authorId: PeerId?
authorId: PeerId?,
folderIds: [Int64]?
) {
self.id = id
self.timestamp = timestamp
@ -334,6 +337,7 @@ public enum Stories {
self.myReaction = myReaction
self.forwardInfo = forwardInfo
self.authorId = authorId
self.folderIds = folderIds
}
public init(from decoder: Decoder) throws {
@ -381,6 +385,7 @@ public enum Stories {
self.myReaction = try container.decodeIfPresent(MessageReaction.Reaction.self, forKey: .myReaction)
self.forwardInfo = try container.decodeIfPresent(ForwardInfo.self, forKey: .forwardInfo)
self.authorId = try container.decodeIfPresent(Int64.self, forKey: .authorId).flatMap { PeerId($0) }
self.folderIds = try container.decodeIfPresent([Int64].self, forKey: .folderIds)
}
public func encode(to encoder: Encoder) throws {
@ -422,6 +427,7 @@ public enum Stories {
try container.encodeIfPresent(self.myReaction, forKey: .myReaction)
try container.encodeIfPresent(self.forwardInfo, forKey: .forwardInfo)
try container.encodeIfPresent(self.authorId?.toInt64(), forKey: .authorId)
try container.encodeIfPresent(self.folderIds, forKey: .folderIds)
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
@ -497,6 +503,9 @@ public enum Stories {
if lhs.authorId != rhs.authorId {
return false
}
if lhs.folderIds != rhs.folderIds {
return false
}
return true
}
}
@ -1294,7 +1303,8 @@ func _internal_uploadStoryImpl(
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo,
authorId: fromId?.peerId
authorId: fromId?.peerId,
folderIds: item.folderIds
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends))
@ -1702,7 +1712,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
transaction.setStory(id: storyId, value: entry)
@ -1734,7 +1745,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
@ -1930,7 +1942,8 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
@ -1961,7 +1974,8 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
)
updatedItems.append(updatedItem)
}
@ -2081,7 +2095,10 @@ extension Stories.StoredItem {
init?(apiStoryItem: Api.StoryItem, existingItem: Stories.Item? = nil, peerId: PeerId, transaction: Transaction) {
switch apiStoryItem {
case let .storyItem(flags, id, date, fromId, forwardFrom, expireDate, caption, entities, media, mediaAreas, privacy, views, sentReaction, albums):
let _ = albums
var folderIds: [Int64]?
if let albums {
folderIds = albums.map(Int64.init)
}
let parsedMedia = textMediaAndExpirationTimerFromApiMedia(media, peerId).media
if let parsedMedia = parsedMedia {
@ -2194,7 +2211,8 @@ extension Stories.StoredItem {
isMy: mergedIsMy,
myReaction: mergedMyReaction,
forwardInfo: mergedForwardInfo,
authorId: fromId?.peerId
authorId: fromId?.peerId,
folderIds: folderIds
)
self = .item(item)
} else {
@ -2271,7 +2289,8 @@ func _internal_getStoryById(accountPeerId: PeerId, postbox: Postbox, network: Ne
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
}
}
@ -2748,7 +2767,8 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
isMy: item.isMy,
myReaction: reaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
))
updatedItemValue = updatedItem
if let entry = CodableEntry(updatedItem) {
@ -2782,7 +2802,8 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
isMy: item.isMy,
myReaction: reaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
))
updatedItemValue = updatedItem
if let entry = CodableEntry(updatedItem) {

View File

@ -86,8 +86,9 @@ public final class EngineStoryItem: Equatable {
public let myReaction: MessageReaction.Reaction?
public let forwardInfo: ForwardInfo?
public let author: EnginePeer?
public let folderIds: [Int64]?
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, alternativeMediaList: [EngineMedia], mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?, forwardInfo: ForwardInfo?, author: EnginePeer?) {
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, alternativeMediaList: [EngineMedia], mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?, forwardInfo: ForwardInfo?, author: EnginePeer?, folderIds: [Int64]?) {
self.id = id
self.timestamp = timestamp
self.expirationTimestamp = expirationTimestamp
@ -111,6 +112,7 @@ public final class EngineStoryItem: Equatable {
self.myReaction = myReaction
self.forwardInfo = forwardInfo
self.author = author
self.folderIds = folderIds
}
public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool {
@ -183,6 +185,9 @@ public final class EngineStoryItem: Equatable {
if lhs.author != rhs.author {
return false
}
if lhs.folderIds != rhs.folderIds {
return false
}
return true
}
}
@ -237,7 +242,8 @@ public extension EngineStoryItem {
myReaction: self.myReaction,
forwardInfo: self.forwardInfo?.storedForwardInfo,
authorId: self.author?.id
authorId: self.author?.id,
folderIds: self.folderIds
)
}
}
@ -735,7 +741,8 @@ public final class PeerStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
items.append(State.Item(
id: StoryId(peerId: peerId, id: mappedItem.id),
@ -830,6 +837,7 @@ public final class PeerStoryListContext: StoryListContext {
let accountPeerId = account.peerId
let isArchived = self.isArchived
let folderId = self.folderId
let queue = self.queue
self.requestDisposable = (self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
@ -838,7 +846,7 @@ public final class PeerStoryListContext: StoryListContext {
return .single(([], 0, nil, [], false))
}
let signal: Signal<Api.stories.Stories?, MTRpcError>
var signal: Signal<Api.stories.Stories?, MTRpcError>
var additionalFolders: Signal<Api.stories.Albums?, MTRpcError> = .single(nil)
if let folderId {
signal = account.network.request(Api.functions.stories.getAlbumStories(peer: inputPeer, albumId: Int32(clamping: folderId), offset: Int32(loadMoreToken), limit: Int32(limit))) |> map(Optional.init)
@ -851,6 +859,13 @@ public final class PeerStoryListContext: StoryListContext {
|> map(Optional.init)
}
}
#if DEBUG
if folderId != nil {
signal = signal |> delay(2.0, queue: queue)
}
#endif
return combineLatest(
signal
|> `catch` { _ -> Signal<Api.stories.Stories?, NoError> in
@ -945,7 +960,8 @@ public final class PeerStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
storyItems.append(State.Item(
id: StoryId(peerId: peerId, id: mappedItem.id),
@ -988,7 +1004,7 @@ public final class PeerStoryListContext: StoryListContext {
}
}
}
|> deliverOn(self.queue)).start(next: { [weak self] storyItems, totalCount, peerReference, folderItems, hasMore in
|> deliverOn(queue)).start(next: { [weak self] storyItems, totalCount, peerReference, folderItems, hasMore in
guard let self else {
return
}
@ -1139,7 +1155,8 @@ public final class PeerStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
folderIds: item.folderIds
), peer: nil)
finalUpdatedState = updatedState
}
@ -1188,7 +1205,8 @@ public final class PeerStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
folderIds: item.folderIds
), peer: nil)
finalUpdatedState = updatedState
} else {
@ -1239,7 +1257,8 @@ public final class PeerStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
folderIds: item.folderIds
), peer: nil))
updatedState.items.sort(by: { lhs, rhs in
let lhsPinned = updatedState.pinnedIds.firstIndex(of: lhs.storyItem.id)
@ -1296,7 +1315,8 @@ public final class PeerStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
folderIds: item.folderIds
), peer: nil))
updatedState.items.sort(by: { lhs, rhs in
let lhsPinned = updatedState.pinnedIds.firstIndex(of: lhs.storyItem.id)
@ -1575,7 +1595,8 @@ public final class PeerStoryListContext: StoryListContext {
return .unknown(name: name, isModified: isModified)
}
},
authorId: item.author?.id
authorId: item.author?.id,
folderIds: item.folderIds
)
if !updatedItems.contains(where: { $0.id == mappedItem.id }) {
updatedItems.insert(mappedItem, at: 0)
@ -1910,7 +1931,8 @@ public final class PeerStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
}
}
@ -2092,7 +2114,8 @@ public final class SearchStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
storyItems.append(State.Item(
id: StoryId(peerId: peer.peerId, id: mappedItem.id),
@ -2109,7 +2132,7 @@ public final class SearchStoryListContext: StoryListContext {
}
}
|> deliverOn(self.queue)).start(next: { [weak self] storyItems, totalCount, nextOffset in
guard let `self` = self else {
guard let self else {
return
}
@ -2241,7 +2264,8 @@ public final class SearchStoryListContext: StoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
folderIds: item.folderIds
),
peer: currentItem.peer
)
@ -2302,7 +2326,8 @@ public final class SearchStoryListContext: StoryListContext {
isMy: item.storyItem.isMy,
myReaction: reaction,
forwardInfo: item.storyItem.forwardInfo,
author: item.storyItem.author
author: item.storyItem.author,
folderIds: item.storyItem.folderIds
),
peer: item.peer
)
@ -2431,7 +2456,8 @@ public final class PeerExpiringStoryListContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
items.append(.item(mappedItem))
}
@ -2877,7 +2903,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
))
@ -2926,7 +2953,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
))
@ -3038,7 +3066,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
))
@ -3115,7 +3144,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
))
@ -3178,7 +3208,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
)

View File

@ -1353,7 +1353,8 @@ public extension TelegramEngine {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo,
authorId: item.authorId
authorId: item.authorId,
folderIds: item.folderIds
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)

View File

@ -1456,6 +1456,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break
case .shareStory:
break
case .storyFolder:
break
}
}
}))

View File

@ -527,6 +527,7 @@ private final class PeerInfoPendingPane {
chatLocation: ChatLocation,
chatLocationContextHolder: Atomic<ChatLocationContextHolder?>,
sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?,
initialStoryFolderId: Int64?,
key: PeerInfoPaneKey,
hasBecomeReady: @escaping (PeerInfoPaneKey) -> Void,
parentController: ViewController?,
@ -609,7 +610,7 @@ private final class PeerInfoPendingPane {
listContext = data.storyListContext
}
let visualPaneNode = PeerInfoStoryPaneNode(context: context, scope: scope, captureProtected: captureProtected, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: listContext)
let visualPaneNode = PeerInfoStoryPaneNode(context: context, scope: scope, captureProtected: captureProtected, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: listContext, initialStoryFolderId: initialStoryFolderId)
paneNode = visualPaneNode
visualPaneNode.openCurrentDate = {
openMediaCalendar()
@ -728,6 +729,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
private var currentPanes: [PeerInfoPaneKey: PeerInfoPaneWrapper] = [:]
private var pendingPanes: [PeerInfoPaneKey: PeerInfoPendingPane] = [:]
private var shouldFadeIn = false
private var initialStoryFolderId: Int64?
private var transitionFraction: CGFloat = 0.0
@ -753,7 +755,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
private let initialPaneKey: PeerInfoPaneKey?
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, chatLocation: ChatLocation, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, isMediaOnly: Bool, initialPaneKey: PeerInfoPaneKey?) {
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, chatLocation: ChatLocation, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, isMediaOnly: Bool, initialPaneKey: PeerInfoPaneKey?, initialStoryFolderId: Int64?) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.peerId = peerId
@ -762,6 +764,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
self.sharedMediaFromForumTopic = sharedMediaFromForumTopic
self.isMediaOnly = isMediaOnly
self.initialPaneKey = initialPaneKey
self.initialStoryFolderId = initialStoryFolderId
self.additionalBackgroundNode = ASDisplayNode()
@ -1088,7 +1091,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
}
}
}
if let pendingSwitchToPaneKey = self.pendingSwitchToPaneKey {
if let pendingSwitchToPaneKey = self.pendingSwitchToPaneKey, availablePanes.contains(pendingSwitchToPaneKey) {
if self.currentPanes[pendingSwitchToPaneKey] == nil && self.pendingPanes[pendingSwitchToPaneKey] == nil {
if !requiredPendingKeys.contains(pendingSwitchToPaneKey) {
requiredPendingKeys.append(pendingSwitchToPaneKey)
@ -1099,6 +1102,13 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
for key in requiredPendingKeys {
if self.pendingPanes[key] == nil, let data {
var leftScope = false
var initialStoryFolderId: Int64?
if case .stories = key {
if let initialStoryFolderIdValue = self.initialStoryFolderId {
self.initialStoryFolderId = nil
initialStoryFolderId = initialStoryFolderIdValue
}
}
let pane = PeerInfoPendingPane(
context: self.context,
updatedPresentationData: self.updatedPresentationData,
@ -1117,6 +1127,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
chatLocation: self.chatLocation,
chatLocationContextHolder: self.chatLocationContextHolder,
sharedMediaFromForumTopic: self.sharedMediaFromForumTopic,
initialStoryFolderId: initialStoryFolderId,
key: key,
hasBecomeReady: { [weak self] key in
let apply: () -> Void = {
@ -1352,7 +1363,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
var removeKeys: [PeerInfoPaneKey] = []
for (key, paneNode) in self.pendingPanes {
if !availablePanes.contains(key) {
if !availablePanes.contains(key) && self.pendingSwitchToPaneKey != key {
removeKeys.append(key)
paneNode.pane.node.removeFromSupernode()
}
@ -1363,7 +1374,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
removeKeys.removeAll()
for (key, paneNode) in self.currentPanes {
if !availablePanes.contains(key) {
if !availablePanes.contains(key) && self.pendingSwitchToPaneKey != key {
removeKeys.append(key)
paneNode.node.removeFromSupernode()
}

View File

@ -2906,6 +2906,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
private let callMessages: [Message]
private let chatLocation: ChatLocation
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
private let switchToStoryFolder: Int64?
private let sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?
let isSettings: Bool
@ -3030,7 +3031,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
private var didSetReady = false
init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, profileGiftsContext: ProfileGiftsContext?, starsContext: StarsContext?, tonContext: StarsContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, initialPaneKey: PeerInfoPaneKey?, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?) {
init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, profileGiftsContext: ProfileGiftsContext?, starsContext: StarsContext?, tonContext: StarsContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, switchToStoryFolder: Int64?, initialPaneKey: PeerInfoPaneKey?, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?) {
self.controller = controller
self.context = context
self.peerId = peerId
@ -3046,6 +3047,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.chatLocationContextHolder = chatLocationContextHolder
self.isMediaOnly = context.account.peerId == peerId && !isSettings && !isMyProfile
self.initialExpandPanes = initialPaneKey != nil
self.switchToStoryFolder = switchToStoryFolder
self.sharedMediaFromForumTopic = sharedMediaFromForumTopic
self.scrollNode = ASScrollNode()
@ -3057,7 +3059,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
forumTopicThreadId = message.threadId
}
self.headerNode = PeerInfoHeaderNode(context: context, controller: controller, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, isMediaOnly: self.isMediaOnly, isSettings: isSettings, isMyProfile: isMyProfile, forumTopicThreadId: forumTopicThreadId, chatLocation: self.chatLocation)
self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, chatLocation: chatLocation, sharedMediaFromForumTopic: sharedMediaFromForumTopic, chatLocationContextHolder: chatLocationContextHolder, isMediaOnly: self.isMediaOnly, initialPaneKey: initialPaneKey)
self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, chatLocation: chatLocation, sharedMediaFromForumTopic: sharedMediaFromForumTopic, chatLocationContextHolder: chatLocationContextHolder, isMediaOnly: self.isMediaOnly, initialPaneKey: initialPaneKey, initialStoryFolderId: switchToStoryFolder)
super.init()
@ -5392,7 +5394,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
self.headerNode.updateIsAvatarExpanded(false, transition: transition)
self.updateNavigationExpansionPresentation(isExpanded: false, animated: true)
self.updateNavigationExpansionPresentation(isExpanded: false, animated: animated)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition, additive: true)
@ -5403,7 +5405,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let contentOffset = self.scrollNode.view.contentOffset
let paneAreaExpansionFinalPoint: CGFloat = self.paneContainerNode.frame.minY - navigationHeight
if contentOffset.y < paneAreaExpansionFinalPoint - CGFloat.ulpOfOne {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
self.ignoreScrolling = true
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: paneAreaExpansionFinalPoint), size: self.scrollNode.bounds.size))
@ -11710,6 +11712,22 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
})))
if let _ = pane.currentStoryFolder {
items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
}, action: { [weak pane] _, a in
if ignoreNextActions {
return
}
ignoreNextActions = true
a(.default)
if let pane {
pane.shareCurrentFolder()
}
})))
}
if pane.canReorder() {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
@ -13082,6 +13100,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
private let switchToRecommendedChannels: Bool
private let switchToGifts: Bool
private let switchToGroupsInCommon: Bool
private let switchToStoryFolder: Int64?
private let sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?
let chatLocation: ChatLocation
private let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
@ -13157,7 +13176,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
sharedMediaFromForumTopic: (EnginePeer.Id, Int64)? = nil,
switchToRecommendedChannels: Bool = false,
switchToGifts: Bool = false,
switchToGroupsInCommon: Bool = false
switchToGroupsInCommon: Bool = false,
switchToStoryFolder: Int64? = nil
) {
self.context = context
self.updatedPresentationData = updatedPresentationData
@ -13175,6 +13195,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
self.switchToRecommendedChannels = switchToRecommendedChannels
self.switchToGifts = switchToGifts
self.switchToGroupsInCommon = switchToGroupsInCommon
self.switchToStoryFolder = switchToStoryFolder
self.sharedMediaFromForumTopic = sharedMediaFromForumTopic
if let forumTopicThread = forumTopicThread {
@ -13540,8 +13561,10 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
initialPaneKey = .gifts
} else if self.switchToGroupsInCommon {
initialPaneKey = .groupsInCommon
} else if self.switchToStoryFolder != nil {
initialPaneKey = .stories
}
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, profileGiftsContext: self.profileGiftsContext, starsContext: self.starsContext, tonContext: self.tonContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: initialPaneKey, sharedMediaFromForumTopic: self.sharedMediaFromForumTopic)
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, profileGiftsContext: self.profileGiftsContext, starsContext: self.starsContext, tonContext: self.tonContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, switchToStoryFolder: self.switchToStoryFolder, initialPaneKey: initialPaneKey, sharedMediaFromForumTopic: self.sharedMediaFromForumTopic)
self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 })
self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get())
self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get())

View File

@ -488,7 +488,8 @@ final class PeerInfoStoryGridScreenComponent: Component {
}
return self.environment?.controller()?.navigationController as? NavigationController
},
listContext: nil
listContext: nil,
initialStoryFolderId: nil
)
paneNode.isEmptyUpdated = { [weak self] _ in
guard let self else {

View File

@ -117,7 +117,8 @@ final class StorySearchGridScreenComponent: Component {
}
return self.environment?.controller()?.navigationController as? NavigationController
},
listContext: component.listContext
listContext: component.listContext,
initialStoryFolderId: nil
)
paneNode.parentController = environment.controller()
paneNode.isEmptyUpdated = { [weak self] _ in

View File

@ -1652,7 +1652,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
public var tabBarOffset: CGFloat {
if case .botPreview = self.scope {
return 0.0
} else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
} else if case let .peer(peerId, _, isArchived) = self.scope, ((peerId == self.context.account.peerId && !isArchived) || !self.currentStoryFolders.isEmpty || self.initialStoryFolderId != nil), self.isProfileEmbedded {
return 0.0
} else {
return self.itemGrid.coveringInsetOffset
@ -1686,6 +1686,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
private let defaultListSource: StoryListContext
private var cachedListSources: [AnyHashable: StoryListContext] = [:]
private var initialStoryFolderId: Int64?
public var currentBotPreviewLanguage: (id: String, name: String)? {
guard let listSource = self.listSource as? BotPreviewStoryListContext else {
return nil
@ -1741,12 +1743,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
return 0.0
}
public init(context: AccountContext, scope: Scope, captureProtected: Bool, isProfileEmbedded: Bool, canManageStories: Bool, navigationController: @escaping () -> NavigationController?, listContext: StoryListContext?) {
public init(context: AccountContext, scope: Scope, captureProtected: Bool, isProfileEmbedded: Bool, canManageStories: Bool, navigationController: @escaping () -> NavigationController?, listContext: StoryListContext?, initialStoryFolderId: Int64?) {
self.context = context
self.scope = scope
self.navigationController = navigationController
self.isProfileEmbedded = isProfileEmbedded
self.canManageStories = canManageStories
self.initialStoryFolderId = initialStoryFolderId
switch scope {
case let .peer(_, _, isArchived):
@ -2471,7 +2474,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
var items: [ContextMenuItem] = []
if canManage, case let .peer(peerId, _, isArchived) = self.scope {
if peerId == self.context.account.peerId && self.isProfileEmbedded {
if !isArchived && peerId == self.context.account.peerId && self.isProfileEmbedded {
if let folder = self.currentStoryFolder {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Remove from Album", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
@ -2851,7 +2854,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
public func ensureMessageIsVisible(id: MessageId) {
}
private func requestHistoryAroundVisiblePosition(synchronous: Bool, reloadAtTop: Bool) {
private func requestHistoryAroundVisiblePosition(synchronous: Bool, reloadAtTop: Bool, animated: Bool = true) {
if self.isRequestingView {
return
}
@ -2945,34 +2948,41 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
}
self.currentListState = state
var hasLocalItems = false
if let currentListState = self.currentListState {
for item in currentListState.items {
if item.storyItem.isPending {
hasLocalItems = true
if let initialStoryFolderId = self.initialStoryFolderId, let folder = storyFolders.first(where: { $0.id == initialStoryFolderId }) {
self.currentStoryFolders = storyFolders
self.isRequestingView = false
self.initialStoryFolderId = nil
self.setStoryFolder(id: folder.id, assumeEmpty: false, animated: false)
} else {
self.currentListState = state
var hasLocalItems = false
if let currentListState = self.currentListState {
for item in currentListState.items {
if item.storyItem.isPending {
hasLocalItems = true
}
}
}
}
var synchronous = synchronous
if hasLocalItems != hadLocalItems {
synchronous = true
}
self.updateItemsFromState(state: state, firstTime: firstTime, reloadAtTop: reloadAtTop, synchronous: synchronous, animated: false)
if self.currentBotPreviewLanguages != botPreviewLanguages || self.currentStoryFolders != storyFolders || reloadAtTop {
self.currentBotPreviewLanguages = botPreviewLanguages
self.currentStoryFolders = storyFolders
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: .immediate)
var synchronous = synchronous
if hasLocalItems != hadLocalItems {
synchronous = true
}
self.updateItemsFromState(state: state, firstTime: firstTime, reloadAtTop: reloadAtTop, synchronous: synchronous, animated: false)
if self.currentBotPreviewLanguages != botPreviewLanguages || self.currentStoryFolders != storyFolders || reloadAtTop {
self.currentBotPreviewLanguages = botPreviewLanguages
self.currentStoryFolders = storyFolders
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: .immediate)
}
}
firstTime = false
self.isRequestingView = false
}
firstTime = false
self.isRequestingView = false
}
})
}
@ -3021,8 +3031,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
switch self.scope {
case .botPreview:
isReorderable = !item.storyItem.isPending
case let .peer(id, _, _):
if id == self.context.account.peerId {
case .peer:
if self.canManageStories {
if self.currentStoryFolder != nil {
isReorderable = true
} else {
@ -3054,7 +3064,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
totalCount = state.totalCount
totalCount = max(mappedItems.count, totalCount)
if totalCount == 0 && state.loadMoreToken != nil && !state.isCached {
if totalCount == 0 && state.loadMoreToken != nil && (!state.isCached || !state.hasCache) {
totalCount = 100
}
@ -3130,7 +3140,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
} else {
if case .botPreview = self.scope {
} else if !self.currentStoryFolders.isEmpty {
} else if case let .peer(id, _, isArchived) = self.scope, id == self.context.account.peerId, !isArchived {
} else if case let .peer(id, _, isArchived) = self.scope, ((id == self.context.account.peerId && !isArchived) || !self.currentStoryFolders.isEmpty || self.initialStoryFolderId != nil) {
} else if reloadAtTop {
gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false)
}
@ -3606,7 +3616,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
if let _ = self.mapNode {
self.updateMapLayout(size: currentParams.size, topInset: currentParams.topInset, bottomInset: currentParams.bottomInset, deviceMetrics: currentParams.deviceMetrics, transition: transition)
}
if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
if case let .peer(peerId, _, isArchived) = self.scope, ((peerId == self.context.account.peerId && !isArchived) || !self.currentStoryFolders.isEmpty), self.isProfileEmbedded {
self.updateFolderTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition)
} else if case .botPreview = self.scope, self.canManageStories {
self.updateFolderTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition)
@ -3769,7 +3779,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
var displayFolderTab = false
if case .botPreview = self.scope, self.canManageStories {
displayFolderTab = true
} else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
} else if case let .peer(peerId, _, isArchived) = self.scope, ((peerId == self.context.account.peerId && !isArchived) || !self.currentStoryFolders.isEmpty), self.isProfileEmbedded {
displayFolderTab = true
}
@ -3777,10 +3787,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
return
}
var transition = transition
let folderTab: ComponentView<Empty>
if let current = self.folderTab {
folderTab = current
} else {
transition = .immediate
folderTab = ComponentView()
self.folderTab = folderTab
}
@ -3814,7 +3827,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
id: AnyHashable(folder.id),
title: folder.title,
isReorderable: self.canManageStories,
contextAction: self.canManageStories ? { [weak self] sourceNode, gesture in
contextAction: { [weak self] sourceNode, gesture in
guard let self else {
return
}
@ -3827,36 +3840,24 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
var items: [ContextMenuItem] = []
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Add Stories", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddStoryIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
guard let self else {
if self.canManageStories {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Add Stories", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddStoryIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
guard let self else {
a(.default)
return
}
a(.default)
return
}
a(.default)
self.presentAddStoriesToFolder()
})))
self.presentAddStoriesToFolder()
})))
}
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
guard let self else {
a(.default)
return
}
a(.default)
self.beginReordering()
})))
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Delete Album", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
guard let self else {
f(.default)
return
@ -3864,9 +3865,37 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
f(.dismissWithoutContent)
self.presentDeleteStoryFolder(id: folder.id)
self.shareFolder(id: folder.id)
})))
if self.canManageStories {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
guard let self else {
a(.default)
return
}
a(.default)
self.beginReordering()
})))
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Delete Album", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
guard let self else {
f(.default)
return
}
f(.dismissWithoutContent)
self.presentDeleteStoryFolder(id: folder.id)
})))
}
let presentationData = self.presentationData
let contextController = ContextController(
presentationData: presentationData,
@ -3880,14 +3909,16 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
gesture: gesture
)
controller.presentInGlobalOverlay(contextController)
} : nil
}
))
}
}
folderItems.append(TabSelectorComponent.Item(
id: AnyHashable("_add"),
title: addTitle
))
if self.canManageStories {
folderItems.append(TabSelectorComponent.Item(
id: AnyHashable("_add"),
title: addTitle
))
}
var selectedId = AnyHashable("_main")
if let listSource = self.listSource as? BotPreviewStoryListContext, let language = listSource.language {
selectedId = AnyHashable(language)
@ -4113,7 +4144,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
if self.isProfileEmbedded {
if case .botPreview = self.scope {
hasBarBackground = true
} else if case let .peer(id, _, isArchived) = self.scope, id == self.context.account.peerId, !isArchived {
} else if case let .peer(id, _, isArchived) = self.scope, ((id == self.context.account.peerId && !isArchived) || !self.currentStoryFolders.isEmpty) {
hasBarBackground = true
}
}
@ -4136,7 +4167,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
var displayFolderTab = false
if case .botPreview = self.scope, self.canManageStories {
displayFolderTab = true
} else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
} else if case let .peer(peerId, _, isArchived) = self.scope, ((peerId == self.context.account.peerId && !isArchived) || !self.currentStoryFolders.isEmpty || self.initialStoryFolderId != nil), self.isProfileEmbedded {
displayFolderTab = true
}
@ -4485,14 +4516,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
animationName: "StoryListEmpty",
title: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
text: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
actionTitle: isArchived ? nil : presentationData.strings.StoryList_SavedAddAction,
actionTitle: (isArchived || !self.canManageStories) ? nil : presentationData.strings.StoryList_SavedAddAction,
action: { [weak self] in
guard let self else {
return
}
self.emptyAction?()
},
additionalActionTitle: (isArchived || self.isProfileEmbedded) ? nil : presentationData.strings.StoryList_SavedEmptyAction,
additionalActionTitle: (isArchived || self.isProfileEmbedded || !self.canManageStories) ? nil : presentationData.strings.StoryList_SavedEmptyAction,
additionalAction: { [weak self] in
guard let self else {
return
@ -4716,7 +4747,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
if self.isProfileEmbedded, case .botPreview = self.scope {
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor)
} else if self.isProfileEmbedded, case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded {
} else if self.isProfileEmbedded, case let .peer(peerId, _, isArchived) = self.scope, ((peerId == self.context.account.peerId && !isArchived) || !self.currentStoryFolders.isEmpty), self.isProfileEmbedded {
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor)
} else if self.isProfileEmbedded {
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.plainBackgroundColor)
@ -5065,7 +5096,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: true)
}
private func setStoryFolder(id: Int64?, assumeEmpty: Bool) {
private func setStoryFolder(id: Int64?, assumeEmpty: Bool, animated: Bool = true) {
if let listSource = self.listSource as? PeerStoryListContext, listSource.folderId == id {
return
}
@ -5073,8 +5104,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
if let id {
if let cachedListSource = self.cachedListSources[AnyHashable(id)] {
self.listSource = cachedListSource
} else {
let listSource = PeerStoryListContext(account: self.context.account, peerId: self.context.account.peerId, isArchived: false, folderId: id)
} else if case let .peer(peerId, _, _) = self.scope {
let listSource = PeerStoryListContext(account: self.context.account, peerId: peerId, isArchived: false, folderId: id)
self.listSource = listSource
//self.cachedListSources[AnyHashable(id)] = listSource
}
@ -5082,7 +5113,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.listSource = self.defaultListSource
}
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: true)
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: true, animated: animated)
}
public func beginReordering() {
@ -5156,6 +5187,58 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.update(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
}
}
public func shareCurrentFolder() {
guard let folder = self.currentStoryFolder else {
return
}
self.shareFolder(id: folder.id)
}
private func shareFolder(id: Int64) {
guard case let .peer(peerId, _, _) = self.scope else {
return
}
Task { @MainActor [weak self] in
guard let self else {
return
}
guard let peer = await self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
).get() else {
return
}
let urlBase: String
if let addressName = peer.addressName {
urlBase = "\(addressName)"
} else {
urlBase = "\(peer.id.id._internalGetInt64Value())"
}
let shareController = ShareController(
context: self.context,
subject: .url("https://t.me/\(urlBase)?stories=\(id)"),
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))
}
}
}
private class MediaListSelectionRecognizer: UIPanGestureRecognizer {

View File

@ -318,7 +318,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: forwardInfo,
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
}
var totalCount = peerStoryItemsView.items.count
@ -355,7 +356,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isMy: true,
myReaction: nil,
forwardInfo: pendingForwardsInfo[item.randomId],
author: nil
author: nil,
folderIds: item.folders
))
totalCount += 1
}
@ -1354,7 +1356,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
isMy: itemValue.isMy,
myReaction: itemValue.myReaction,
forwardInfo: forwardInfo,
author: itemValue.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
author: itemValue.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
folderIds: itemValue.folderIds
)
let mainItem = StoryContentItem(
@ -2292,7 +2295,8 @@ private func getCachedStory(storyId: StoryId, transaction: Transaction) -> Engin
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
folderIds: item.folderIds
)
} else {
return nil

View File

@ -1498,5 +1498,26 @@ func openResolvedUrlImpl(
} else {
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.BusinessLink_ErrorExpired, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
case let .storyFolder(peerId, id):
Task { @MainActor [weak navigationController] in
guard let peer = await context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
).get() else {
return
}
guard let controller = context.sharedContext.makePeerInfoController(
context: context,
updatedPresentationData: updatedPresentationData,
peer: peer._asPeer(),
mode: .storyAlbum(id: id),
avatarInitiallyExpanded: false,
fromChat: false,
requestsContext: nil
) else {
return
}
navigationController?.pushViewController(controller)
}
}
}

View File

@ -3964,6 +3964,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
var isMyProfile = false
var switchToGifts = false
var switchToGroupsInCommon = false
var switchToStoryFolder: Int64?
switch mode {
case let .nearbyPeer(distance):
@ -3989,10 +3990,12 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
switchToGroupsInCommon = true
case let .monoforum(peerId):
sharedMediaFromForumTopic = (peerId, peer.id.toInt64())
case let .storyAlbum(id):
switchToStoryFolder = id
default:
break
}
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, sharedMediaFromForumTopic: sharedMediaFromForumTopic, switchToGifts: switchToGifts, switchToGroupsInCommon: switchToGroupsInCommon)
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, sharedMediaFromForumTopic: sharedMediaFromForumTopic, switchToGifts: switchToGifts, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder)
} else if peer is TelegramSecretChat {
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [])
}

View File

@ -81,6 +81,7 @@ public enum ParsedInternalPeerUrlParameter {
case text(String)
case profile
case referrer(String)
case storyFolder(Int64)
}
public enum ParsedInternalUrl {
@ -345,6 +346,8 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
}
} else if queryItem.name == "ref", let referrer = queryItem.value {
return .peer(.name(peerName), .referrer(referrer))
} else if queryItem.name == "stories", let value = queryItem.value, let folderId = Int64(value) {
return .peer(.name(peerName), .storyFolder(folderId))
}
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
return .peer(.name(peerName), .voiceChat(nil))
@ -401,6 +404,8 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
return .peer(.name(peerName), .appStart("", queryItem.value, mode))
} else if queryItem.name == "ref", let referrer = queryItem.value {
return .peer(.name(peerName), .referrer(referrer))
} else if queryItem.name == "stories", let value = queryItem.value, let folderId = Int64(value) {
return .peer(.name(peerName), .storyFolder(folderId))
}
}
}
@ -939,6 +944,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
)
case .referrer:
return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))))
case let .storyFolder(folderId):
return .single(.result(.storyFolder(peerId: peer.id, id: folderId)))
}
} else {
return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))))