Merge branch 'story-folders'

# Conflicts:
#	submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift
This commit is contained in:
Isaac 2025-07-25 15:21:33 +02:00
commit b20652120e
35 changed files with 1731 additions and 445 deletions

View file

@ -1,6 +1,6 @@
http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
bazel_dep(name = "bazel_features", version = "1.29.0")
bazel_dep(name = "bazel_features", version = "1.30.0")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "platforms", version = "0.0.11")

24
MODULE.bazel.lock generated
View file

@ -20,9 +20,9 @@
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
"https://bcr.bazel.build/modules/bazel_features/1.29.0/MODULE.bazel": "fc2373c9825b71bcd06b60ef19b1715a42fa58edc4e39086375f47aa277e45ba",
"https://bcr.bazel.build/modules/bazel_features/1.29.0/source.json": "714dbd96075890f20f4f158ed9fa7fd1080bf6a6e0d5a89a05e2765e3ed74260",
"https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
@ -81,12 +81,12 @@
"https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
"https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a",
"https://bcr.bazel.build/modules/rules_cc/0.0.17/source.json": "4db99b3f55c90ab28d14552aa0632533e3e8e5e9aea0f5c24ac0014282c2a7c5",
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
@ -100,8 +100,8 @@
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
"https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
"https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad",
"https://bcr.bazel.build/modules/rules_java/8.11.0/source.json": "302b52a39259a85aa06ca3addb9787864ca3e03b432a5f964ea68244397e7544",
"https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761",
"https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba",
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
@ -151,15 +151,15 @@
"https://bcr.bazel.build/modules/xcodeproj/8.27.3/MODULE.bazel": "49276599207dae3df1e4336c2067505323dfb0606b53ef63e144087d1226e0eb",
"https://bcr.bazel.build/modules/xcodeproj/8.27.3/source.json": "bbbb718187dcbdfbb3a9a0ec7d49446cdf48c67657cafd79b5cf33aa8918f608",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
},
"selectedYankedVersions": {},
"moduleExtensions": {
"@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": {
"general": {
"bzlTransitiveDigest": "IK7QnlhcNBu2jc4wZoGZeDTu3keF2LldFiFUINRcKvo=",
"bzlTransitiveDigest": "RjubjYIojbv0PxTpnoknalV9QzT9asbV7elDuN7m2A4=",
"usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -190,7 +190,7 @@
},
"@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
"general": {
"bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=",
"bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=",
"usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -290,7 +290,7 @@
},
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%internal": {
"general": {
"bzlTransitiveDigest": "+kmqZtEKFY8zgqpV6mrwdQkTJqGUZhL8b3ZMsxrqSyc=",
"bzlTransitiveDigest": "6MYik+6MZUO7rOzaI0dUJYVD8dJrR1Q2rT+5vo1j73U=",
"usagesDigest": "fvsnMonVwKDYnBfww4bXuYie3WU0d9VSqT2gePSdQco=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -312,7 +312,7 @@
},
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%non_module_deps": {
"general": {
"bzlTransitiveDigest": "+kmqZtEKFY8zgqpV6mrwdQkTJqGUZhL8b3ZMsxrqSyc=",
"bzlTransitiveDigest": "6MYik+6MZUO7rOzaI0dUJYVD8dJrR1Q2rT+5vo1j73U=",
"usagesDigest": "jzxYhnOC9BE0dJ0biFLfxWXi/+R19uAAZkJ0p9CY0JI=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},

View file

@ -91,8 +91,8 @@ def locate_bazel(base_path, cache_host_or_path, cache_dir):
if test_sha256 == versions.bazel_version_sha256:
shutil.copyfile(temp_output_file.name, bazel_path)
elif resolved_cache_path is not None:
(cache_cas_id, cache_cas_name) = cache_cas_name(versions.bazel_version_sha256)
cached_path = '{}/cas/{}/{}'.format(resolved_cache_path, cache_cas_id, cache_cas_name)
(cache_cas_id, cache_cas_name_value) = cache_cas_name(versions.bazel_version_sha256)
cached_path = '{}/cas/{}/{}'.format(resolved_cache_path, cache_cas_id, cache_cas_name_value)
if os.path.isfile(cached_path):
shutil.copyfile(cached_path, bazel_path)
@ -137,8 +137,8 @@ def locate_bazel(base_path, cache_host_or_path, cache_dir):
)
], check_result=False)
elif resolved_cache_path is not None:
(cache_cas_id, cache_cas_name) = cache_cas_name(versions.bazel_version_sha256)
cached_path = '{}/cas/{}/{}'.format(resolved_cache_path, cache_cas_id, cache_cas_name)
(cache_cas_id, cache_cas_name_value) = cache_cas_name(versions.bazel_version_sha256)
cached_path = '{}/cas/{}/{}'.format(resolved_cache_path, cache_cas_id, cache_cas_name_value)
os.makedirs(os.path.dirname(cached_path), exist_ok=True)
shutil.copyfile(bazel_path, cached_path)

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

@ -1550,7 +1550,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
context: context,
initialPrivacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: state.peers),
stateContext: stateContext,
completion: { _, privacy ,_, _, _, _ in
completion: { _, privacy ,_, _, _, _, _ in
updateState { state in
var updatedState = state
updatedState.peers = privacy.additionallyIncludePeers
@ -1589,7 +1589,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
context: context,
initialPrivacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: state.peers),
stateContext: stateContext,
completion: { _, privacy ,_, _, _, _ in
completion: { _, privacy ,_, _, _, _, _ in
updateState { state in
var updatedState = state
updatedState.channels = privacy.additionallyIncludePeers

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

@ -98,6 +98,7 @@ public extension Stories {
case randomId
case forwardInfo
case uploadInfo
case folders
}
public let target: PendingTarget
@ -114,6 +115,7 @@ public extension Stories {
public let period: Int32
public let randomId: Int64
public let forwardInfo: PendingForwardInfo?
public let folders: [Int64]
public let uploadInfo: StoryUploadInfo?
public init(
@ -131,6 +133,7 @@ public extension Stories {
period: Int32,
randomId: Int64,
forwardInfo: PendingForwardInfo?,
folders: [Int64],
uploadInfo: StoryUploadInfo?
) {
self.target = target
@ -147,6 +150,7 @@ public extension Stories {
self.period = period
self.randomId = randomId
self.forwardInfo = forwardInfo
self.folders = folders
self.uploadInfo = uploadInfo
}
@ -176,6 +180,8 @@ public extension Stories {
self.forwardInfo = try container.decodeIfPresent(PendingForwardInfo.self, forKey: .forwardInfo)
self.folders = try container.decodeIfPresent([Int64].self, forKey: .folders) ?? []
self.uploadInfo = try container.decodeIfPresent(StoryUploadInfo.self, forKey: .uploadInfo)
}
@ -205,6 +211,7 @@ public extension Stories {
try container.encode(self.period, forKey: .period)
try container.encode(self.randomId, forKey: .randomId)
try container.encodeIfPresent(self.forwardInfo, forKey: .forwardInfo)
try container.encode(self.folders, forKey: .folders)
try container.encodeIfPresent(self.uploadInfo, forKey: .uploadInfo)
}
@ -245,6 +252,9 @@ public extension Stories {
if lhs.forwardInfo != rhs.forwardInfo {
return false
}
if lhs.folders != rhs.folders {
return false
}
if lhs.uploadInfo != rhs.uploadInfo {
return false
}
@ -473,7 +483,7 @@ final class PendingStoryManager {
let partTotalProgress = 1.0 / Float(uploadInfo.total)
pendingItemContext.progress = Float(uploadInfo.index) * partTotalProgress
}
pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, toPeerId: toPeerId, stableId: stableId, media: firstItem.media, mediaAreas: firstItem.mediaAreas, text: firstItem.text, entities: firstItem.entities, embeddedStickers: firstItem.embeddedStickers, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), randomId: firstItem.randomId, forwardInfo: firstItem.forwardInfo)
pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, toPeerId: toPeerId, stableId: stableId, media: firstItem.media, mediaAreas: firstItem.mediaAreas, text: firstItem.text, entities: firstItem.entities, embeddedStickers: firstItem.embeddedStickers, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), folders: firstItem.folders, randomId: firstItem.randomId, forwardInfo: firstItem.forwardInfo)
|> deliverOn(self.queue)).start(next: { [weak self] event in
guard let `self` = self else {
return

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
}
}
@ -988,7 +997,7 @@ public struct StoryUploadInfo: Codable, Equatable {
}
}
func _internal_uploadStory(account: Account, target: Stories.PendingTarget, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64, forwardInfo: Stories.PendingForwardInfo?, uploadInfo: StoryUploadInfo? = nil) -> Signal<Int32, NoError> {
func _internal_uploadStory(account: Account, target: Stories.PendingTarget, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64, forwardInfo: Stories.PendingForwardInfo?, folders: [Int64], uploadInfo: StoryUploadInfo? = nil) -> Signal<Int32, NoError> {
let inputMedia = prepareUploadStoryContent(account: account, media: media)
return (account.postbox.transaction { transaction in
@ -1017,6 +1026,7 @@ func _internal_uploadStory(account: Account, target: Stories.PendingTarget, medi
period: Int32(period),
randomId: randomId,
forwardInfo: forwardInfo,
folders: folders,
uploadInfo: uploadInfo
))
transaction.setLocalStoryState(state: CodableEntry(currentState))
@ -1064,6 +1074,7 @@ func _internal_cancelStoryUpload(account: Account, stableId: Int32) {
period: currentState.items[i].period,
randomId: currentState.items[i].randomId,
forwardInfo: currentState.items[i].forwardInfo,
folders: currentState.items[i].folders,
uploadInfo: StoryUploadInfo(
groupingId: groupingId,
index: newIndex,
@ -1141,6 +1152,7 @@ func _internal_uploadStoryImpl(
privacy: EngineStoryPrivacy,
isForwardingDisabled: Bool,
period: Int,
folders: [Int64],
randomId: Int64,
forwardInfo: Stories.PendingForwardInfo?
) -> Signal<StoryUploadResult, NoError> {
@ -1224,6 +1236,10 @@ func _internal_uploadStoryImpl(
fwdFromStory = forwardInfo.storyId
}
if !folders.isEmpty {
flags |= 1 << 8
}
return network.request(Api.functions.stories.sendStory(
flags: flags,
peer: inputPeer,
@ -1236,7 +1252,7 @@ func _internal_uploadStoryImpl(
period: Int32(period),
fwdFromId: fwdFromId,
fwdFromStory: fwdFromStory,
albums: nil
albums: folders.isEmpty ? nil : folders.map(Int32.init(clamping:))
))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
@ -1287,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))
@ -1695,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)
@ -1727,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)
@ -1923,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)
@ -1954,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)
}
@ -2073,7 +2094,12 @@ extension Stories.Item.ForwardInfo {
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, _):
case let .storyItem(flags, id, date, fromId, forwardFrom, expireDate, caption, entities, media, mediaAreas, privacy, views, sentReaction, albums):
var folderIds: [Int64]?
if let albums {
folderIds = albums.map(Int64.init)
}
let parsedMedia = textMediaAndExpirationTimerFromApiMedia(media, peerId).media
if let parsedMedia = parsedMedia {
var parsedPrivacy: Stories.Item.Privacy?
@ -2185,7 +2211,8 @@ extension Stories.StoredItem {
isMy: mergedIsMy,
myReaction: mergedMyReaction,
forwardInfo: mergedForwardInfo,
authorId: fromId?.peerId
authorId: fromId?.peerId,
folderIds: folderIds
)
self = .item(item)
} else {
@ -2262,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
)
}
}
@ -2739,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) {
@ -2773,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
)
}
}
@ -679,6 +685,8 @@ public final class PeerStoryListContext: StoryListContext {
return (nil, [], [], 0, [:], [], false)
}
let mainFolders: [State.Folder] = cachedMain.folders
let cached: CachedPeerStoryListHead
if let folderId {
let key = ValueBoxKey(length: 8 + 1 + 8)
@ -733,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),
@ -768,7 +777,7 @@ public final class PeerStoryListContext: StoryListContext {
let peerReference = transaction.getPeer(peerId).flatMap(PeerReference.init)
return (peerReference, items, cached.pinnedIds, Int(cached.totalCount), allEntityFiles, cached.folders, true)
return (peerReference, items, cached.pinnedIds, Int(cached.totalCount), allEntityFiles, mainFolders, true)
}
|> deliverOn(self.queue)).start(next: { [weak self] peerReference, items, pinnedIds, totalCount, allEntityFiles, folders, hasCache in
guard let self else {
@ -828,103 +837,81 @@ public final class PeerStoryListContext: StoryListContext {
let accountPeerId = account.peerId
let isArchived = self.isArchived
let folderId = self.folderId
let folders = self.stateValue.availableFolders
let queue = self.queue
self.requestDisposable = (self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<([State.Item], Int, PeerReference?, Bool), NoError> in
guard let inputPeer = inputPeer else {
return .single(([], 0, nil, false))
|> mapToSignal { inputPeer -> Signal<([State.Item], Int, PeerReference?, [State.Folder], Bool), NoError> in
guard let inputPeer else {
return .single(([], 0, nil, [], false))
}
let signal: Signal<Api.stories.Stories, MTRpcError>
if isArchived {
signal = account.network.request(Api.functions.stories.getStoriesArchive(peer: inputPeer, offsetId: Int32(loadMoreToken), limit: Int32(limit)))
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)
} else {
signal = account.network.request(Api.functions.stories.getPinnedStories(peer: inputPeer, offsetId: Int32(loadMoreToken), limit: Int32(limit)))
if isArchived {
signal = account.network.request(Api.functions.stories.getStoriesArchive(peer: inputPeer, offsetId: Int32(loadMoreToken), limit: Int32(limit))) |> map(Optional.init)
} else {
signal = account.network.request(Api.functions.stories.getPinnedStories(peer: inputPeer, offsetId: Int32(loadMoreToken), limit: Int32(limit))) |> map(Optional.init)
additionalFolders = account.network.request(Api.functions.stories.getAlbums(peer: inputPeer, hash: 0))
|> map(Optional.init)
}
}
return signal
|> map { result -> Api.stories.Stories? in
return result
#if DEBUG
if folderId != nil {
signal = signal |> delay(2.0, queue: queue)
}
|> `catch` { _ -> Signal<Api.stories.Stories?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<([State.Item], Int, PeerReference?, Bool), NoError> in
guard let result = result else {
return .single(([], 0, nil, false))
#endif
return combineLatest(
signal
|> `catch` { _ -> Signal<Api.stories.Stories?, NoError> in
return .single(nil)
},
additionalFolders
|> `catch` { _ -> Signal<Api.stories.Albums?, NoError> in
return .single(nil)
}
)
|> mapToSignal { result, updatedFolders -> Signal<([State.Item], Int, PeerReference?, [State.Folder], Bool), NoError> in
guard let result else {
return .single(([], 0, nil, [], false))
}
return account.postbox.transaction { transaction -> ([State.Item], Int, PeerReference?, Bool) in
return account.postbox.transaction { transaction -> ([State.Item], Int, PeerReference?, [State.Folder], Bool) in
var storyItems: [State.Item] = []
var totalCount: Int = 0
var hasMore: Bool = false
var folderItems: [State.Folder] = []
if let folderId {
let key = ValueBoxKey(length: 8 + 1 + 8)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
key.setInt64(8 + 1, value: folderId)
var updatedItems: [Stories.StoredItem] = []
if let currentEntry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
updatedItems = currentEntry.items
}
if let entry = CodableEntry(CachedPeerStoryListHead(items: updatedItems, pinnedIds: [], totalCount: Int32(updatedItems.count), folders: [])) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
for item in updatedItems {
if case let .item(item) = item, let media = item.media {
let mappedItem = EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
alternativeMediaList: item.alternativeMediaList.map(EngineMedia.init),
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
reactedCount: views.reactedCount,
forwardCount: views.forwardCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return transaction.getPeer(id).flatMap(EnginePeer.init)
},
reactions: views.reactions,
hasList: views.hasList
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
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) }
)
storyItems.append(State.Item(
id: StoryId(peerId: peerId, id: mappedItem.id),
storyItem: mappedItem,
peer: nil
))
if let updatedFolders {
switch updatedFolders {
case let .albums(_, albums):
for album in albums {
switch album {
case let .storyAlbum(_, albumId, title, iconPhoto, iconVideo):
let _ = iconPhoto
let _ = iconVideo
folderItems.append(State.Folder(
id: Int64(albumId),
title: title
))
}
}
case .albumsNotModified:
break
}
} else {
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: 0)
totalCount = storyItems.count
hasMore = false
return (storyItems, totalCount, transaction.getPeer(peerId).flatMap(PeerReference.init), hasMore)
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
folderItems = cached.folders
}
}
switch result {
@ -973,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),
@ -985,20 +973,38 @@ public final class PeerStoryListContext: StoryListContext {
}
if loadMoreToken == 0 {
let key: ValueBoxKey
if let folderId {
key = ValueBoxKey(length: 8 + 1 + 8)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
key.setInt64(8 + 1, value: folderId)
} else {
key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
}
if let entry = CodableEntry(CachedPeerStoryListHead(items: storyItems.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: count, folders: folderId == nil ? folderItems : [])) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
} else if folderId == nil && updatedFolders != nil {
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
if let entry = CodableEntry(CachedPeerStoryListHead(items: storyItems.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: count, folders: folders)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
key.setInt8(8, value: 0)
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
if let entry = CodableEntry(CachedPeerStoryListHead(items: cached.items, pinnedIds: cached.pinnedIds, totalCount: cached.totalCount, folders: folderItems)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
}
}
}
return (storyItems, totalCount, transaction.getPeer(peerId).flatMap(PeerReference.init), hasMore)
return (storyItems, totalCount, transaction.getPeer(peerId).flatMap(PeerReference.init), folderItems, hasMore)
}
}
}
|> deliverOn(self.queue)).start(next: { [weak self] storyItems, totalCount, peerReference, hasMore in
|> deliverOn(queue)).start(next: { [weak self] storyItems, totalCount, peerReference, folderItems, hasMore in
guard let self else {
return
}
@ -1022,6 +1028,8 @@ public final class PeerStoryListContext: StoryListContext {
updatedState.items.append(item)
}
updatedState.availableFolders = folderItems
if updatedState.peerReference == nil {
updatedState.peerReference = peerReference
}
@ -1147,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
}
@ -1196,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 {
@ -1247,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)
@ -1304,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)
@ -1376,55 +1388,143 @@ public final class PeerStoryListContext: StoryListContext {
})
}
func addFolder(title: String) -> Int64 {
let id = Int64.random(in: Int64.min ... Int64.max)
var state = self.stateValue
state.availableFolders.append(StoryListContextState.Folder(id: id, title: title))
self.stateValue = state
func addFolder(title: String, items: [EngineStoryItem], completion: @escaping (Int64?) -> Void) -> Disposable {
let peerId = self.peerId
let isArchived = self.isArchived
let items = state.items
let pinnedIds = state.pinnedIds
let totalCount = state.totalCount
let folders = state.availableFolders
let _ = (self.account.postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount), folders: folders)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
return (self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Api.StoryAlbum?, NoError> in
guard let inputPeer else {
return .single(nil)
}
}).start()
return id
return self.account.network.request(Api.functions.stories.createAlbum(
peer: inputPeer, title: title, stories: items.map(\.id)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StoryAlbum?, NoError> in
return .single(nil)
}
}
|> deliverOn(self.queue)).start(next: { [weak self] result in
guard let self else {
return
}
if let result {
switch result {
case let .storyAlbum(_, albumId, _, _, _):
var state = self.stateValue
state.availableFolders.insert(StoryListContextState.Folder(id: Int64(albumId), title: title), at: 0)
self.stateValue = state
let peerId = self.peerId
let isArchived = self.isArchived
let items = state.items
let pinnedIds = state.pinnedIds
let totalCount = state.totalCount
let folders = state.availableFolders
let _ = (self.account.postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount), folders: folders)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
let mappedItems = items.map { item in
return item.storyItem.asStoryItem()
}
let folderKey = ValueBoxKey(length: 8 + 1 + 8)
folderKey.setInt64(0, value: peerId.toInt64())
folderKey.setInt8(8, value: 0)
folderKey.setInt64(8 + 1, value: Int64(albumId))
if let entry = CodableEntry(CachedPeerStoryListHead(items: mappedItems.prefix(100).map { .item($0) }, pinnedIds: [], totalCount: Int32(mappedItems.count), folders: [])) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: folderKey), entry: entry)
}
} |> deliverOn(self.queue)).start(completed: {
completion(Int64(albumId))
})
}
} else {
completion(nil)
}
})
}
func removeFolder(id: Int64) {
func removeFolder(id: Int64) -> Disposable {
var state = self.stateValue
if let index = state.availableFolders.firstIndex(where: { $0.id == id }) {
state.availableFolders.remove(at: index)
}
self.stateValue = state
let account = self.account
let peerId = self.peerId
let isArchived = self.isArchived
let items = state.items
let pinnedIds = state.pinnedIds
let totalCount = state.totalCount
let folders = state.availableFolders
let _ = (self.account.postbox.transaction { transaction -> Void in
return (account.postbox.transaction { transaction -> Api.InputPeer? in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount), folders: folders)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
guard let inputPeer else {
return .complete()
}
return account.network.request(Api.functions.stories.deleteAlbum(peer: inputPeer, albumId: Int32(clamping: id)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
}).start()
}
func reorderFolders(ids: [Int64]) {
func renameFolder(id: Int64, title: String) -> Disposable {
var state = self.stateValue
if let index = state.availableFolders.firstIndex(where: { $0.id == id }) {
state.availableFolders[index] = State.Folder(id: state.availableFolders[index].id, title: title)
}
self.stateValue = state
let updatedFolders = state.availableFolders
let account = self.account
let peerId = self.peerId
return (account.postbox.transaction { transaction -> Api.InputPeer? in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: 0)
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self) {
if let entry = CodableEntry(CachedPeerStoryListHead(items: cached.items, pinnedIds: cached.pinnedIds, totalCount: cached.totalCount, folders: updatedFolders)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
}
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
guard let inputPeer else {
return .complete()
}
var flags: Int32 = 0
flags |= 1 << 0
return account.network.request(Api.functions.stories.updateAlbum(flags: flags, peer: inputPeer, albumId: Int32(clamping: id), title: title, deleteStories: nil, addStories: nil, order: nil))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StoryAlbum?, NoError> in
return .single(nil)
}
|> ignoreValues
}).start()
}
func reorderFolders(ids: [Int64]) -> Disposable {
var state = self.stateValue
var folders: [State.Folder] = []
for id in ids {
@ -1440,24 +1540,51 @@ public final class PeerStoryListContext: StoryListContext {
state.availableFolders = folders
self.stateValue = state
let account = self.account
let peerId = self.peerId
let isArchived = self.isArchived
let items = state.items
let pinnedIds = state.pinnedIds
let totalCount = state.totalCount
let _ = (self.account.postbox.transaction { transaction -> Void in
return (account.postbox.transaction { transaction -> Api.InputPeer? in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount), folders: folders)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
guard let inputPeer else {
return .complete()
}
return account.network.request(Api.functions.stories.reorderAlbums(peer: inputPeer, order: ids.map(Int32.init(clamping:))))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
}).start()
}
func addToFolder(id: Int64, items: [EngineStoryItem]) {
func addToFolder(id: Int64, items: [EngineStoryItem]) -> Disposable {
if self.folderId == id {
var state = self.stateValue
for item in items {
state.items.removeAll(where: { $0.id.id == item.id })
state.items.insert(StoryListContextState.Item(
id: StoryId(peerId: self.peerId, id: item.id),
storyItem: item,
peer: nil
), at: 0)
}
state.totalCount = state.items.count
self.stateValue = state
}
let account = self.account
let peerId = self.peerId
let _ = (self.account.postbox.transaction { transaction -> Void in
return (account.postbox.transaction { transaction -> Api.InputPeer? in
let key = ValueBoxKey(length: 8 + 1 + 8)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: 0)
@ -1518,7 +1645,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)
@ -1528,26 +1656,35 @@ public final class PeerStoryListContext: StoryListContext {
if let entry = CodableEntry(CachedPeerStoryListHead(items: updatedItems.map { .item($0) }, pinnedIds: [], totalCount: Int32(updatedItems.count), folders: [])) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
guard let inputPeer else {
return .complete()
}
var flags: Int32 = 0
flags |= 1 << 2
return account.network.request(Api.functions.stories.updateAlbum(flags: flags, peer: inputPeer, albumId: Int32(clamping: id), title: nil, deleteStories: nil, addStories: items.map(\.id), order: nil))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StoryAlbum?, NoError> in
return .single(nil)
}
|> ignoreValues
}).start()
}
func removeFromFolder(id: Int64, itemIds: [Int32]) -> Disposable {
if self.folderId == id {
var state = self.stateValue
for item in items {
state.items.removeAll(where: { $0.id.id == item.id })
state.items.insert(StoryListContextState.Item(
id: StoryId(peerId: self.peerId, id: item.id),
storyItem: item,
peer: nil
), at: 0)
}
state.items.removeAll(where: { itemIds.contains($0.id.id) })
state.totalCount = state.items.count
self.stateValue = state
}
}
func removeFromFolder(id: Int64, itemIds: [Int32]) {
let account = self.account
let peerId = self.peerId
let _ = (self.account.postbox.transaction { transaction -> Void in
return (account.postbox.transaction { transaction -> Api.InputPeer? in
let key = ValueBoxKey(length: 8 + 1 + 8)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: 0)
@ -1573,23 +1710,52 @@ public final class PeerStoryListContext: StoryListContext {
if let entry = CodableEntry(CachedPeerStoryListHead(items: updatedItems.map { .item($0) }, pinnedIds: [], totalCount: Int32(updatedItems.count), folders: [])) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
guard let inputPeer else {
return .complete()
}
var flags: Int32 = 0
flags |= 1 << 1
return account.network.request(Api.functions.stories.updateAlbum(flags: flags, peer: inputPeer, albumId: Int32(clamping: id), title: nil, deleteStories: itemIds, addStories: nil, order: nil))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StoryAlbum?, NoError> in
return .single(nil)
}
|> ignoreValues
}).start()
}
func reorderItemsInFolder(itemIds: [Int32]) -> Disposable {
guard let id = self.folderId else {
return EmptyDisposable
}
if self.folderId == id {
var state = self.stateValue
state.items.removeAll(where: { itemIds.contains($0.id.id) })
state.totalCount = state.items.count
let previousItems = state.items
var updatedItems: [State.Item] = []
for itemId in itemIds {
if let index = previousItems.firstIndex(where: { $0.id.id == itemId }) {
updatedItems.append(previousItems[index])
}
}
for item in previousItems {
if !updatedItems.contains(where: { $0.id == item.id }) {
updatedItems.append(item)
}
}
state.items = updatedItems
self.stateValue = state
}
}
func reorderItemsInFolder(itemIds: [Int32]) {
guard let id = self.folderId else {
return
}
let account = self.account
let peerId = self.peerId
let _ = (self.account.postbox.transaction { transaction -> Void in
return (account.postbox.transaction { transaction -> Api.InputPeer? in
let key = ValueBoxKey(length: 8 + 1 + 8)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: 0)
@ -1622,27 +1788,22 @@ public final class PeerStoryListContext: StoryListContext {
if let entry = CodableEntry(CachedPeerStoryListHead(items: updatedItems.map { .item($0) }, pinnedIds: [], totalCount: Int32(updatedItems.count), folders: [])) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
}).start()
if self.folderId == id {
var state = self.stateValue
let previousItems = state.items
var updatedItems: [State.Item] = []
for itemId in itemIds {
if let index = previousItems.firstIndex(where: { $0.id.id == itemId }) {
updatedItems.append(previousItems[index])
}
}
for item in previousItems {
if !updatedItems.contains(where: { $0.id == item.id }) {
updatedItems.append(item)
}
}
state.items = updatedItems
self.stateValue = state
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
guard let inputPeer else {
return .complete()
}
var flags: Int32 = 0
flags |= 1 << 3
return account.network.request(Api.functions.stories.updateAlbum(flags: flags, peer: inputPeer, albumId: Int32(clamping: id), title: nil, deleteStories: nil, addStories: nil, order: itemIds))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StoryAlbum?, NoError> in
return .single(nil)
}
|> ignoreValues
}).start()
}
}
@ -1676,61 +1837,100 @@ public final class PeerStoryListContext: StoryListContext {
}
}
public func addFolder(title: String, completion: @escaping (Int64) -> Void) {
public func addFolder(title: String, items: [EngineStoryItem], completion: @escaping (Int64?) -> Void) -> Disposable {
let disposable = MetaDisposable()
self.impl.with { impl in
completion(impl.addFolder(title: title))
disposable.set(impl.addFolder(title: title, items: items, completion: completion))
}
return disposable
}
public func removeFolder(id: Int64) {
public func removeFolder(id: Int64) -> Disposable {
let disposable = MetaDisposable()
self.impl.with { impl in
impl.removeFolder(id: id)
disposable.set(impl.removeFolder(id: id))
}
return disposable
}
public func reorderFolders(ids: [Int64]) {
public func renameFolder(id: Int64, title: String) -> Disposable {
let disposable = MetaDisposable()
self.impl.with { impl in
impl.reorderFolders(ids: ids)
disposable.set(impl.renameFolder(id: id, title: title))
}
return disposable
}
public func addToFolder(id: Int64, items: [EngineStoryItem]) {
public func reorderFolders(ids: [Int64]) -> Disposable {
let disposable = MetaDisposable()
self.impl.with { impl in
impl.addToFolder(id: id, items: items)
disposable.set(impl.reorderFolders(ids: ids))
}
return disposable
}
public func removeFromFolder(id: Int64, itemIds: [Int32]) {
public func addToFolder(id: Int64, items: [EngineStoryItem]) -> Disposable {
let disposable = MetaDisposable()
self.impl.with { impl in
impl.removeFromFolder(id: id, itemIds: itemIds)
disposable.set(impl.addToFolder(id: id, items: items))
}
return disposable
}
public func reorderItemsInFolder(itemIds: [Int32]) {
public func removeFromFolder(id: Int64, itemIds: [Int32]) -> Disposable {
let disposable = MetaDisposable()
self.impl.with { impl in
impl.reorderItemsInFolder(itemIds: itemIds)
disposable.set(impl.removeFromFolder(id: id, itemIds: itemIds))
}
return disposable
}
public static func addFolderExternal(account: Account, peerId: PeerId, title: String) -> Int64 {
let id = Int64.random(in: Int64.min ... Int64.max)
let _ = (account.postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: 0)
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
var folders = cached?.folders ?? []
folders.append(StoryListContextState.Folder(id: id, title: title))
if let entry = CodableEntry(CachedPeerStoryListHead(items: cached?.items ?? [], pinnedIds: cached?.pinnedIds ?? [], totalCount: cached?.totalCount ?? 0, folders: folders)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
public func reorderItemsInFolder(itemIds: [Int32]) -> Disposable {
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.reorderItemsInFolder(itemIds: itemIds))
}
return disposable
}
public static func addFolderExternal(account: Account, peerId: PeerId, title: String, completion: @escaping (Int64?) -> Void) -> Disposable {
return (account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Api.StoryAlbum?, NoError> in
guard let inputPeer else {
return .single(nil)
}
}).start()
return id
return account.network.request(Api.functions.stories.createAlbum(
peer: inputPeer, title: title, stories: []))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StoryAlbum?, NoError> in
return .single(nil)
}
}).start(next: { result in
if let result {
switch result {
case let .storyAlbum(_, albumId, _, _, _):
let _ = (account.postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: 0)
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
var folders = cached?.folders ?? []
folders.append(StoryListContextState.Folder(id: Int64(albumId), title: title))
if let entry = CodableEntry(CachedPeerStoryListHead(items: cached?.items ?? [], pinnedIds: cached?.pinnedIds ?? [], totalCount: cached?.totalCount ?? 0, folders: folders)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
}).start(completed: {
completion(Int64(albumId))
})
}
} else {
completion(nil)
}
})
}
public static func folderPreviews(peerId: EnginePeer.Id, account: Account) -> Signal<(peer: PeerReference, [(folder: StoryListContext.State.Folder, item: EngineStoryItem?)]), NoError> {
@ -1789,7 +1989,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
)
}
}
@ -1971,7 +2172,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),
@ -1988,7 +2190,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
}
@ -2120,7 +2322,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
)
@ -2181,7 +2384,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
)
@ -2310,7 +2514,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))
}
@ -2756,7 +2961,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
))
@ -2805,7 +3011,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
))
@ -2917,7 +3124,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
))
@ -2994,7 +3202,8 @@ public final class BotPreviewStoryListContext: StoryListContext {
isMy: false,
myReaction: nil,
forwardInfo: nil,
author: nil
author: nil,
folderIds: nil
),
peer: nil
))
@ -3057,7 +3266,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)
@ -1367,8 +1368,8 @@ public extension TelegramEngine {
}
}
public func uploadStory(target: Stories.PendingTarget, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64, forwardInfo: Stories.PendingForwardInfo?, uploadInfo: StoryUploadInfo? = nil) -> Signal<Int32, NoError> {
return _internal_uploadStory(account: self.account, target: target, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId, forwardInfo: forwardInfo, uploadInfo: uploadInfo)
public func uploadStory(target: Stories.PendingTarget, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64, forwardInfo: Stories.PendingForwardInfo?, folders: [Int64], uploadInfo: StoryUploadInfo? = nil) -> Signal<Int32, NoError> {
return _internal_uploadStory(account: self.account, target: target, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId, forwardInfo: forwardInfo, folders: folders, uploadInfo: uploadInfo)
}
public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> {

View file

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

View file

@ -15,6 +15,7 @@ public struct MediaEditorResultPrivacy: Codable, Equatable {
case timeout
case disableForwarding
case archive
case folderIds
}
public let sendAsPeerId: EnginePeer.Id?
@ -22,19 +23,22 @@ public struct MediaEditorResultPrivacy: Codable, Equatable {
public let timeout: Int
public let isForwardingDisabled: Bool
public let pin: Bool
public let folderIds: [Int64]
public init(
sendAsPeerId: EnginePeer.Id?,
privacy: EngineStoryPrivacy,
timeout: Int,
isForwardingDisabled: Bool,
pin: Bool
pin: Bool,
folderIds: [Int64]
) {
self.sendAsPeerId = sendAsPeerId
self.privacy = privacy
self.timeout = timeout
self.isForwardingDisabled = isForwardingDisabled
self.pin = pin
self.folderIds = folderIds
}
public init(from decoder: Decoder) throws {
@ -45,6 +49,7 @@ public struct MediaEditorResultPrivacy: Codable, Equatable {
self.timeout = Int(try container.decode(Int32.self, forKey: .timeout))
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .disableForwarding) ?? false
self.pin = try container.decode(Bool.self, forKey: .archive)
self.folderIds = try container.decodeIfPresent([Int64].self, forKey: .folderIds) ?? []
}
public func encode(to encoder: Encoder) throws {
@ -55,6 +60,7 @@ public struct MediaEditorResultPrivacy: Codable, Equatable {
try container.encode(Int32(self.timeout), forKey: .timeout)
try container.encode(self.isForwardingDisabled, forKey: .disableForwarding)
try container.encode(self.pin, forKey: .archive)
try container.encode(self.folderIds, forKey: .folderIds)
}
}

View file

@ -2915,7 +2915,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []),
timeout: 86400,
isForwardingDisabled: false,
pin: true
pin: true,
folderIds: []
)
}
@ -6641,7 +6642,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
self.mediaAreas = []
self.caption = NSAttributedString()
self.coverTimestamp = nil
self.options = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false)
self.options = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false, folderIds: [])
self.stickers = []
self.randomId = 0
}
@ -6651,7 +6652,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
mediaAreas: [MediaArea] = [],
caption: NSAttributedString = NSAttributedString(),
coverTimestamp: Double? = nil,
options: MediaEditorResultPrivacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
options: MediaEditorResultPrivacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false, folderIds: []),
stickers: [TelegramMediaFile] = [],
randomId: Int64 = 0
) {
@ -6760,7 +6761,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
privacy: initialPrivacy,
timeout: 86400,
isForwardingDisabled: false,
pin: false
pin: false,
folderIds: []
)
}
} else {
@ -6771,9 +6773,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
).start(next: { [weak self] state, peer in
if let self, var privacy = state?.privacy {
if case let .user(user) = peer, !user.isPremium && privacy.timeout != 86400 {
privacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: privacy.privacy, timeout: 86400, isForwardingDisabled: privacy.isForwardingDisabled, pin: privacy.pin)
privacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: privacy.privacy, timeout: 86400, isForwardingDisabled: privacy.isForwardingDisabled, pin: privacy.pin, folderIds: privacy.folderIds)
} else {
privacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: privacy.privacy, timeout: privacy.timeout, isForwardingDisabled: privacy.isForwardingDisabled, pin: privacy.pin)
privacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: privacy.privacy, timeout: privacy.timeout, isForwardingDisabled: privacy.isForwardingDisabled, pin: privacy.pin, folderIds: privacy.folderIds)
}
self.state.privacy = privacy
}
@ -6980,7 +6982,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
mentions: mentions,
coverImage: coverImage,
stateContext: stateContext,
completion: { [weak self] sendAsPeerId, privacy, allowScreenshots, pin, _, completed in
completion: { [weak self] sendAsPeerId, privacy, allowScreenshots, pin, _, folders, completed in
guard let self else {
return
}
@ -6989,13 +6991,14 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
privacy: privacy,
timeout: timeout,
isForwardingDisabled: !allowScreenshots,
pin: pin
pin: pin,
folderIds: folders
)
if completed {
completion()
}
},
editCategory: { [weak self] privacy, allowScreenshots, pin in
editCategory: { [weak self] privacy, allowScreenshots, pin, folders in
guard let self else {
return
}
@ -7008,11 +7011,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
privacy: privacy,
timeout: timeout,
isForwardingDisabled: !allowScreenshots,
pin: pin
pin: pin,
folderIds: folders
), completion: completion)
})
},
editBlockedPeers: { [weak self] privacy, allowScreenshots, pin in
editBlockedPeers: { [weak self] privacy, allowScreenshots, pin, folders in
guard let self else {
return
}
@ -7025,7 +7029,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
privacy: privacy,
timeout: timeout,
isForwardingDisabled: !allowScreenshots,
pin: pin
pin: pin,
folderIds: folders
), completion: completion)
})
},
@ -7078,7 +7083,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
allowScreenshots: !isForwardingDisabled,
pin: pin,
stateContext: stateContext,
completion: { [weak self] _, result, isForwardingDisabled, pin, peers, completed in
completion: { [weak self] _, result, isForwardingDisabled, pin, peers, folders, completed in
guard let self, completed else {
return
}
@ -7093,8 +7098,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
completion(result)
}
},
editCategory: { _, _, _ in },
editBlockedPeers: { _, _, _ in }
editCategory: { _, _, _, _ in },
editBlockedPeers: { _, _, _, _ in }
)
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
if let self, let controller {
@ -7175,7 +7180,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
privacy: self.state.privacy.privacy,
timeout: timeout ?? 86400,
isForwardingDisabled: self.state.privacy.isForwardingDisabled,
pin: self.state.privacy.pin
pin: self.state.privacy.pin,
folderIds: self.state.privacy.folderIds
)
}
@ -7672,7 +7678,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
mediaAreas: [],
caption: NSAttributedString(),
coverTimestamp: nil,
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false, folderIds: []),
stickers: [],
randomId: 0
)], { [weak self] finished in

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

@ -2907,6 +2907,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
@ -3031,7 +3032,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
@ -3047,6 +3048,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()
@ -3058,7 +3060,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()
@ -5393,7 +5395,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)
@ -5404,7 +5406,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))
@ -11712,7 +11714,23 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
})))
/*if pane.canReorder() {
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)
}, action: { [weak pane] _, a in
@ -11744,7 +11762,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
pane.presentDeleteCurrentStoryFolder()
}
})))
}*/
}
if let language = pane.currentBotPreviewLanguage {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in
@ -13084,6 +13102,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)
@ -13159,7 +13178,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
@ -13177,6 +13197,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 {
@ -13542,8 +13563,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

@ -44,9 +44,9 @@ final class ShareWithPeersScreenComponent: Component {
let categoryItems: [CategoryItem]
let optionItems: [OptionItem]
let coverItem: CoverItem?
let completion: (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void
let editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void
let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool) -> Void
let completion: (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], [Int64], Bool) -> Void
let editCategory: (EngineStoryPrivacy, Bool, Bool, [Int64]) -> Void
let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool, [Int64]) -> Void
let editCover: () -> Void
let peerCompletion: (EnginePeer.Id) -> Void
@ -62,9 +62,9 @@ final class ShareWithPeersScreenComponent: Component {
categoryItems: [CategoryItem],
optionItems: [OptionItem],
coverItem: CoverItem?,
completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], [Int64], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool, [Int64]) -> Void,
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool, [Int64]) -> Void,
editCover: @escaping () -> Void,
peerCompletion: @escaping (EnginePeer.Id) -> Void
) {
@ -833,9 +833,9 @@ final class ShareWithPeersScreenComponent: Component {
context: component.context,
initialPrivacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
stateContext: stateContext,
completion: { _, _, _, _, _, _ in },
editCategory: { _, _, _ in },
editBlockedPeers: { _, _, _ in },
completion: { _, _, _, _, _, _, _ in },
editCategory: { _, _, _, _ in },
editBlockedPeers: { _, _, _, _ in },
peerCompletion: { [weak self] peerId in
guard let self else {
return
@ -896,7 +896,7 @@ final class ShareWithPeersScreenComponent: Component {
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: "New Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] c, f in
items.append(.action(ContextMenuActionItem(text: "New Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddFolder"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] c, f in
guard let self else {
f(.default)
return
@ -1005,12 +1005,17 @@ final class ShareWithPeersScreenComponent: Component {
return
}
if let value, !value.isEmpty {
let id = PeerStoryListContext.addFolderExternal(account: component.context.account, peerId: component.context.account.peerId, title: value)
self.shareToFolders.append(StoryListContext.State.Folder(
id: id,
title: value
))
self.state?.updated(transition: .immediate)
let _ = PeerStoryListContext.addFolderExternal(account: component.context.account, peerId: component.context.account.peerId, title: value, completion: { [weak self] id in
guard let self, let id else {
return
}
self.shareToFolders.append(StoryListContext.State.Folder(
id: id,
title: value
))
self.state?.updated(transition: .immediate)
})
}
}
)
@ -1416,7 +1421,8 @@ final class ShareWithPeersScreenComponent: Component {
component.editCategory(
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
self.selectedOptions.contains(.pin),
self.shareToFolders.map(\.id)
)
controller.dismissAllTooltips()
controller.dismiss()
@ -1424,7 +1430,8 @@ final class ShareWithPeersScreenComponent: Component {
component.editCategory(
EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
self.selectedOptions.contains(.pin),
self.shareToFolders.map(\.id)
)
controller.dismissAllTooltips()
controller.dismiss()
@ -1452,7 +1459,8 @@ final class ShareWithPeersScreenComponent: Component {
component.editCategory(
EngineStoryPrivacy(base: base, additionallyIncludePeers: selectedPeers),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
self.selectedOptions.contains(.pin),
self.shareToFolders.map(\.id)
)
controller.dismissAllTooltips()
controller.dismiss()
@ -1540,7 +1548,8 @@ final class ShareWithPeersScreenComponent: Component {
component.editBlockedPeers(
EngineStoryPrivacy(base: base, additionallyIncludePeers: self.selectedPeers),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
self.selectedOptions.contains(.pin),
self.shareToFolders.map(\.id)
)
controller.dismissAllTooltips()
controller.dismiss()
@ -2262,6 +2271,7 @@ final class ShareWithPeersScreenComponent: Component {
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin),
self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? [],
self.shareToFolders.map(\.id),
false
)
controller.requestDismiss()
@ -3014,6 +3024,7 @@ final class ShareWithPeersScreenComponent: Component {
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin),
peers.values.compactMap { $0 },
self.shareToFolders.map(\.id),
true
)
@ -3265,9 +3276,9 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
mentions: [String] = [],
coverImage: UIImage? = nil,
stateContext: StateContext,
completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in },
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in },
completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], [Int64], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool, [Int64]) -> Void = { _, _, _, _ in },
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool, [Int64]) -> Void = { _, _, _, _ in },
editCover: @escaping () -> Void = { },
peerCompletion: @escaping (EnginePeer.Id) -> Void = { _ in }
) {

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

@ -1695,13 +1695,13 @@ private final class StoryContainerScreenComponent: Component {
if let content = component.content as? PeerStoryListContentContextImpl {
if let listSource = content.listContext as? PeerStoryListContext {
listSource.addFolder(title: title, completion: { [weak listSource] id in
let _ = listSource.addFolder(title: title, items: [], completion: { [weak listSource] id in
Queue.mainQueue().async {
guard let listSource else {
guard let listSource, let id else {
return
}
if !items.isEmpty {
listSource.addToFolder(id: id, items: items)
let _ = listSource.addToFolder(id: id, items: items)
}
}
})
@ -1717,7 +1717,7 @@ private final class StoryContainerScreenComponent: Component {
}
if let content = component.content as? PeerStoryListContentContextImpl {
if let listSource = content.listContext as? PeerStoryListContext {
listSource.addToFolder(id: folderId, items: [slice.item.storyItem])
let _ = listSource.addToFolder(id: folderId, items: [slice.item.storyItem])
}
}
},

View file

@ -5090,7 +5090,7 @@ public final class StoryItemSetContainerComponent: Component {
context: context,
initialPrivacy: privacy,
stateContext: stateContext,
completion: { [weak self] _, privacy, _, _, _, completed in
completion: { [weak self] _, privacy, _, _, _, _, completed in
guard let self, let component = self.component, completed else {
return
}
@ -5107,7 +5107,7 @@ public final class StoryItemSetContainerComponent: Component {
self.rewindCurrentItem()
self.updateIsProgressPaused()
},
editCategory: { [weak self] privacy, _, _ in
editCategory: { [weak self] privacy, _, _, _ in
guard let self else {
return
}
@ -5118,7 +5118,7 @@ public final class StoryItemSetContainerComponent: Component {
self.openItemPrivacySettings(updatedPrivacy: privacy)
})
},
editBlockedPeers: { [weak self] privacy, _, _ in
editBlockedPeers: { [weak self] privacy, _, _, _ in
guard let self else {
return
}
@ -5171,7 +5171,7 @@ public final class StoryItemSetContainerComponent: Component {
context: context,
initialPrivacy: privacy,
stateContext: stateContext,
completion: { [weak self] _, result, _, _, peers, completed in
completion: { [weak self] _, result, _, _, peers, _, completed in
guard completed else {
return
}
@ -5188,8 +5188,8 @@ public final class StoryItemSetContainerComponent: Component {
completion(result)
}
},
editCategory: { _, _, _ in },
editBlockedPeers: { _, _, _ in }
editCategory: { _, _, _, _ in },
editBlockedPeers: { _, _, _, _ in }
)
controller.dismissed = { [weak self] in
if let self {
@ -6110,7 +6110,7 @@ public final class StoryItemSetContainerComponent: Component {
var items: [ContextMenuItem] = []
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Add to Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
items.append(.action(ContextMenuActionItem(text: "Add to Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddToFolder"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
guard let self, let c else {
f(.default)
return
@ -6131,7 +6131,7 @@ public final class StoryItemSetContainerComponent: Component {
})))
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: "New Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] c, f in
items.append(.action(ContextMenuActionItem(text: "New Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddFolder"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] c, f in
guard let self else {
f(.default)
return

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "addfolder_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "folder1_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

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

@ -487,7 +487,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
externalState.isPeerArchived = channel.storiesHidden ?? false
}
self.proceedWithStoryUpload(target: target, results: results, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
self.proceedWithStoryUpload(target: target, results: results, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
dismissCameraImpl?()
})
@ -565,6 +565,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
case .botPreview:
targetPeerId = nil
}
let folders: [Int64] = results.first?.options.folderIds ?? []
if let rootTabController = self.rootTabController {
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
@ -736,6 +738,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
period: result.options.timeout,
randomId: result.randomId,
forwardInfo: forwardInfo,
folders: folders,
uploadInfo: results.count > 1 ? StoryUploadInfo(groupingId: groupingId, index: index, total: Int32(results.count)) : nil
)
|> deliverOnMainQueue).startStandalone(next: { stableId in

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))))

View file

@ -1,6 +1,6 @@
{
"app": "11.13.4",
"xcode": "16.2",
"bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3",
"bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217",
"macos": "15"
}