Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2025-07-26 11:43:20 +02:00
commit 380e6c4210
52 changed files with 2891 additions and 623 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")

4
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",

View file

@ -1736,7 +1736,6 @@ ios_application(
xcodeproj(
name = "Telegram_xcodeproj",
build_mode = "bazel",
bazel_path = telegram_bazel_path,
project_name = "Telegram",
tags = ["manual"],

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 {
@ -719,6 +721,7 @@ public enum ChatListSearchFilter: Equatable {
case topics
case channels
case apps
case globalPosts
case media
case downloads
case links
@ -740,22 +743,24 @@ public enum ChatListSearchFilter: Equatable {
return 2
case .apps:
return 3
case .media:
case .globalPosts:
return 4
case .downloads:
case .media:
return 5
case .links:
case .downloads:
return 6
case .files:
case .links:
return 7
case .music:
case .files:
return 8
case .voice:
case .music:
return 9
case .instantVideo:
case .voice:
return 10
case .publicPosts:
case .instantVideo:
return 11
case .publicPosts:
return 12
case let .peer(peerId, _, _, _):
return peerId.id._internalGetInt64Value()
case let .date(_, date, _):

View file

@ -115,6 +115,8 @@ swift_library(
"//submodules/TelegramUI/Components/AvatarUploadToastScreen",
"//submodules/TelegramUI/Components/Ads/AdsInfoScreen",
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
],
visibility = [
"//visibility:public",

View file

@ -345,6 +345,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
key = .channels
case .apps:
key = .apps
case .globalPosts:
key = .globalPosts
case .media:
key = .media
case .downloads:
@ -388,7 +390,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
switch filter {
case let .filter(filter):
switch filter {
case .downloads, .channels, .apps:
case .downloads, .channels, .apps, .globalPosts:
return false
default:
return true
@ -675,6 +677,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
filterKey = .channels
case .apps:
filterKey = .apps
case .globalPosts:
filterKey = .globalPosts
case .media:
filterKey = .media
case .downloads:

View file

@ -13,6 +13,7 @@ private final class ItemNode: ASDisplayNode {
private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode
private let titleActiveNode: ImmediateTextNode
private var titleBadgeView: UIImageView?
private let buttonNode: HighlightTrackingButtonNode
private var selectionFraction: CGFloat = 0.0
@ -74,6 +75,7 @@ private final class ItemNode: ASDisplayNode {
self.selectionFraction = selectionFraction
let title: String
var titleBadge: String?
let icon: UIImage?
let color = presentationData.theme.list.itemSecondaryTextColor
@ -90,6 +92,11 @@ private final class ItemNode: ASDisplayNode {
case .apps:
title = presentationData.strings.ChatList_Search_FilterApps
icon = nil
case .globalPosts:
//TODO:localize
title = "Posts"
titleBadge = "NEW"
icon = nil
case .media:
title = presentationData.strings.ChatList_Search_FilterMedia
icon = nil
@ -133,6 +140,38 @@ private final class ItemNode: ASDisplayNode {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: color)
self.titleActiveNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor)
if let titleBadge {
let titleBadgeView: UIImageView
if let current = self.titleBadgeView {
titleBadgeView = current
} else {
titleBadgeView = UIImageView()
self.titleBadgeView = titleBadgeView
self.view.addSubview(titleBadgeView)
let labelText = NSAttributedString(string: titleBadge, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor)
let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height))
let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0)
titleBadgeView.image = generateImage(badgeSize, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0))
context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath)
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
context.fillPath()
UIGraphicsPushContext(context)
labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel))
UIGraphicsPopContext()
})
}
} else if let titleBadgeView = self.titleBadgeView {
self.titleBadgeView = nil
titleBadgeView.removeFromSuperview()
}
let selectionAlpha: CGFloat = selectionFraction * selectionFraction
let deselectionAlpha: CGFloat = 1.0// - selectionFraction
transition.updateAlpha(node: self.titleNode, alpha: deselectionAlpha)
@ -163,8 +202,15 @@ private final class ItemNode: ASDisplayNode {
let titleFrame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left + iconInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
self.titleNode.frame = titleFrame
self.titleActiveNode.frame = titleFrame
return titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + iconInset
var width = titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + iconInset
if let titleBadgeView = self.titleBadgeView, let image = titleBadgeView.image {
width += 4.0 + image.size.width
titleBadgeView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - image.size.height) * 0.5) + 1.0), size: image.size)
}
return width
}
func updateArea(size: CGSize, sideInset: CGFloat, transition: ContainedViewLayoutTransition) {

File diff suppressed because it is too large Load diff

View file

@ -55,6 +55,7 @@ public enum ChatListSearchPaneKey {
case publicPosts
case channels
case apps
case globalPosts
case media
case downloads
case links
@ -77,6 +78,8 @@ extension ChatListSearchPaneKey {
return .channels
case .apps:
return .apps
case .globalPosts:
return .globalPosts
case .media:
return .media
case .downloads:
@ -107,6 +110,9 @@ func defaultAvailableSearchPanes(isForum: Bool, hasDownloads: Bool, hasPublicPos
}
result.append(.channels)
result.append(.apps)
if !isForum {
result.append(.globalPosts)
}
result.append(contentsOf: [.media, .downloads, .links, .files, .music, .voice])
if !hasDownloads {
@ -232,7 +238,10 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
}
return
}
#if DEBUG
#else
self.isAdjacentLoadingEnabled = true
#endif
if self.currentPanes[key] != nil {
self.currentPaneKey = key

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

@ -3511,15 +3511,18 @@ public extension Api.functions.channels {
}
}
public extension Api.functions.channels {
static func searchPosts(hashtag: String, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
static func searchPosts(flags: Int32, hashtag: String?, query: String?, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
buffer.appendInt32(-778069893)
serializeString(hashtag, buffer: buffer, boxed: false)
buffer.appendInt32(-221973939)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(hashtag!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(query!, buffer: buffer, boxed: false)}
serializeInt32(offsetRate, buffer: buffer, boxed: false)
offsetPeer.serialize(buffer, true)
serializeInt32(offsetId, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "channels.searchPosts", parameters: [("hashtag", String(describing: hashtag)), ("offsetRate", String(describing: offsetRate)), ("offsetPeer", String(describing: offsetPeer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
if Int(flags) & Int(1 << 2) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "channels.searchPosts", parameters: [("flags", String(describing: flags)), ("hashtag", String(describing: hashtag)), ("query", String(describing: query)), ("offsetRate", String(describing: offsetRate)), ("offsetPeer", String(describing: offsetPeer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit)), ("allowPaidStars", String(describing: allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
let reader = BufferReader(buffer)
var result: Api.messages.Messages?
if let signature = reader.readInt32() {

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

@ -86,6 +86,8 @@ public extension TelegramEngine {
return false
}
}.map(EngineRenderedPeer.init)
case .globalPosts:
return []
}
}
}

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

@ -473,38 +473,67 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
folderId = nil
}
if case let .general(scope, _, _, _) = location {
switch scope {
case .everywhere:
break
case .channels:
flags |= (1 << 1)
case .groups:
flags |= (1 << 2)
case .privateChats:
flags |= (1 << 3)
if case let .general(scope, _, _, _) = location, case let .globalPosts(allowPaidStars) = scope {
remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in
var lowerBound: MessageIndex?
if let state = state, let message = state.main.messages.last {
lowerBound = message.index
}
if let lowerBound = lowerBound, let peer = transaction.getPeer(lowerBound.id.peerId), let inputPeer = apiInputPeer(peer) {
return (state?.main.nextRate ?? 0, lowerBound, inputPeer)
} else {
return (0, lowerBound, .inputPeerEmpty)
}
}
}
let filter: Api.MessagesFilter = tags.flatMap { messageFilterForTagMask($0) } ?? .inputMessagesFilterEmpty
remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in
var lowerBound: MessageIndex?
if let state = state, let message = state.main.messages.last {
lowerBound = message.index
|> mapToSignal { (nextRate, lowerBound, inputPeer) in
var flags: Int32 = 0
flags |= 1 << 1
if allowPaidStars != nil {
flags |= 1 << 2
}
return account.network.request(Api.functions.channels.searchPosts(flags: flags, hashtag: nil, query: query, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit, allowPaidStars: allowPaidStars.flatMap(Int64.init)), automaticFloodWait: false)
|> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in
return (result, nil)
}
|> `catch` { _ -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in
return .single((nil, nil))
}
}
if let lowerBound = lowerBound, let peer = transaction.getPeer(lowerBound.id.peerId), let inputPeer = apiInputPeer(peer) {
return (state?.main.nextRate ?? 0, lowerBound, inputPeer)
} else {
return (0, lowerBound, .inputPeerEmpty)
} else {
if case let .general(scope, _, _, _) = location {
switch scope {
case .everywhere:
break
case .channels:
flags |= (1 << 1)
case .groups:
flags |= (1 << 2)
case .privateChats:
flags |= (1 << 3)
case .globalPosts:
break
}
}
}
|> mapToSignal { (nextRate, lowerBound, inputPeer) in
return account.network.request(Api.functions.messages.searchGlobal(flags: flags, folderId: folderId, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false)
|> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in
return (result, nil)
let filter: Api.MessagesFilter = tags.flatMap { messageFilterForTagMask($0) } ?? .inputMessagesFilterEmpty
remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in
var lowerBound: MessageIndex?
if let state = state, let message = state.main.messages.last {
lowerBound = message.index
}
if let lowerBound = lowerBound, let peer = transaction.getPeer(lowerBound.id.peerId), let inputPeer = apiInputPeer(peer) {
return (state?.main.nextRate ?? 0, lowerBound, inputPeer)
} else {
return (0, lowerBound, .inputPeerEmpty)
}
}
|> `catch` { _ -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in
return .single((nil, nil))
|> mapToSignal { (nextRate, lowerBound, inputPeer) in
return account.network.request(Api.functions.messages.searchGlobal(flags: flags, folderId: folderId, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false)
|> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in
return (result, nil)
}
|> `catch` { _ -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in
return .single((nil, nil))
}
}
}
case let .sentMedia(tags):
@ -594,7 +623,7 @@ func _internal_searchHashtagPosts(account: Account, hashtag: String, state: Sear
}
}
|> mapToSignal { (nextRate, lowerBound, inputPeer) in
return account.network.request(Api.functions.channels.searchPosts(hashtag: hashtag, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false)
return account.network.request(Api.functions.channels.searchPosts(flags: 1 << 0, hashtag: hashtag, query: nil, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit, allowPaidStars: nil), automaticFloodWait: false)
|> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in
return (result, nil)
}

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

@ -18,11 +18,12 @@ public struct FoundPeer: Equatable {
}
}
public enum TelegramSearchPeersScope {
public enum TelegramSearchPeersScope: Equatable {
case everywhere
case channels
case groups
case privateChats
case globalPosts(allowPaidStars: Int?)
}
public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, network: Network, query: String, scope: TelegramSearchPeersScope) -> Signal<([FoundPeer], [FoundPeer]), NoError> {
@ -138,6 +139,9 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo
return false
}
}
case .globalPosts:
renderedMyPeers = []
renderedPeers = []
}
return (renderedMyPeers, renderedPeers)

View file

@ -510,6 +510,8 @@ public final class ButtonComponent: Component {
animateIn = true
contentView.isUserInteractionEnabled = false
self.addSubview(contentView)
contentItem.view.parentState = state
}
let contentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - contentSize.height) * 0.5)), size: contentSize)

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

@ -152,8 +152,17 @@ public final class PeerInfoRatingComponent: Component {
let baseHeight: CGFloat = 20.0
let innerInset: CGFloat = 2.0
let compactLabelSize = self.compactLabel.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.compactLabel, font: Font.medium(11.0), textColor: .black))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let expandedSize = CGSize(width: 174.0, height: baseHeight)
let collapsedSize = CGSize(width: baseHeight, height: baseHeight)
let collapsedSize = CGSize(width: max(baseHeight, compactLabelSize.width + 6.0 * 2.0), height: baseHeight)
if self.backgroundView.image == nil {
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: baseHeight, color: .white)?.withRenderingMode(.alwaysTemplate)
@ -189,19 +198,11 @@ public final class PeerInfoRatingComponent: Component {
self.foregroundClippedView.backgroundColor = component.foregroundColor
transition.setFrame(view: self.foregroundClippedShapeView, frame: foregroundFrame)
let compactLabelSize = self.compactLabel.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.compactLabel, font: Font.medium(11.0), textColor: .black))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
if let compactLabelView = self.compactLabel.view {
if compactLabelView.superview == nil {
self.foregroundMaskView.addSubview(compactLabelView)
}
compactLabelView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((baseHeight - innerInset * 2.0 - compactLabelSize.width) * 0.5), y: floorToScreenPixels((baseHeight - innerInset * 2.0 - compactLabelSize.height) * 0.5) + UIScreenPixel), size: compactLabelSize)
compactLabelView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((collapsedSize.width - innerInset * 2.0 - compactLabelSize.width) * 0.5), y: floorToScreenPixels((baseHeight - innerInset * 2.0 - compactLabelSize.height) * 0.5) + UIScreenPixel), size: compactLabelSize)
alphaTransition.setAlpha(view: compactLabelView, alpha: component.isExpanded ? 0.0 : 1.0)
}
@ -284,50 +285,57 @@ public final class PeerInfoRatingComponent: Component {
})
}
let tooltipController: TooltipScreen
if let current = self.tooltipController {
tooltipController = current
if !component.tooltipLabel.isEmpty {
let tooltipController: TooltipScreen
if let current = self.tooltipController {
tooltipController = current
} else {
tooltipController = TooltipScreen(
context: component.context,
account: component.context.account,
sharedContext: component.context.sharedContext,
text: .attributedString(text: NSAttributedString(string: component.tooltipLabel, font: Font.semibold(11.0), textColor: .white)),
style: .customBlur(component.tooltipBackgroundColor, -4.0),
arrowStyle: .small,
location: .point(CGRect(origin: CGPoint(x: 100.0, y: 100.0), size: CGSize()), .bottom),
displayDuration: .infinite,
isShimmering: true,
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.tooltipController = tooltipController
tooltipController.containerLayoutUpdated(ContainerViewLayout(
size: CGSize(width: 200.0, height: 200.0),
metrics: LayoutMetrics(),
deviceMetrics: DeviceMetrics.iPhoneXSMax,
intrinsicInsets: UIEdgeInsets(),
safeInsets: UIEdgeInsets(),
additionalInsets: UIEdgeInsets(),
statusBarHeight: nil,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
), transition: .immediate)
self.layer.addSublayer(tooltipController.view.layer)
tooltipController.viewWillAppear(false)
tooltipController.viewDidAppear(false)
tooltipController.setIgnoreAppearanceMethodInvocations(true)
tooltipController.view.isUserInteractionEnabled = false
}
transition.setFrame(view: tooltipController.view, frame: CGRect(origin: CGPoint(), size: CGSize(width: 200.0, height: 200.0)).offsetBy(dx: -200.0 * 0.5 + foregroundFrame.width + 2.0, dy: -200.0 * 0.5))
alphaTransition.setAlpha(view: tooltipController.view, alpha: component.isExpanded ? 1.0 : 0.0)
} else {
tooltipController = TooltipScreen(
context: component.context,
account: component.context.account,
sharedContext: component.context.sharedContext,
text: .attributedString(text: NSAttributedString(string: component.tooltipLabel, font: Font.semibold(11.0), textColor: .white)),
style: .customBlur(component.tooltipBackgroundColor, -4.0),
arrowStyle: .small,
location: .point(CGRect(origin: CGPoint(x: 100.0, y: 100.0), size: CGSize()), .bottom),
displayDuration: .infinite,
isShimmering: true,
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.tooltipController = tooltipController
tooltipController.containerLayoutUpdated(ContainerViewLayout(
size: CGSize(width: 200.0, height: 200.0),
metrics: LayoutMetrics(),
deviceMetrics: DeviceMetrics.iPhoneXSMax,
intrinsicInsets: UIEdgeInsets(),
safeInsets: UIEdgeInsets(),
additionalInsets: UIEdgeInsets(),
statusBarHeight: nil,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
), transition: .immediate)
self.layer.addSublayer(tooltipController.view.layer)
tooltipController.viewWillAppear(false)
tooltipController.viewDidAppear(false)
tooltipController.setIgnoreAppearanceMethodInvocations(true)
tooltipController.view.isUserInteractionEnabled = false
if let tooltipController = self.tooltipController {
self.tooltipController = nil
tooltipController.view.layer.removeFromSuperlayer()
}
}
transition.setFrame(view: tooltipController.view, frame: CGRect(origin: CGPoint(), size: CGSize(width: 200.0, height: 200.0)).offsetBy(dx: -200.0 * 0.5 + foregroundFrame.width + 2.0, dy: -200.0 * 0.5))
alphaTransition.setAlpha(view: tooltipController.view, alpha: component.isExpanded ? 1.0 : 0.0)
return size
}
}

View file

@ -1917,7 +1917,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let apparentBackgroundHeight = (1.0 - transitionFraction) * backgroundHeight + transitionFraction * transitionSourceHeight
var subtitleRatingSize: CGSize?
if !"".isEmpty, let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
if let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
let subtitleRating: ComponentView<Empty>
var subtitleRatingTransition = ComponentTransition(transition)
if let current = self.subtitleRating {
@ -1978,7 +1978,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if self.subtitleRatingIsExpanded, let controller = self.controller, let presentationData = self.presentationData, !self.didDisplayRatingTooltip {
self.didDisplayRatingTooltip = true
controller.presentInGlobalOverlay(UndoOverlayController(
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .info(
title: nil,
@ -1987,10 +1987,21 @@ final class PeerInfoHeaderNode: ASDisplayNode {
customUndoText: "Learn More"
),
position: .top,
action: { _ in
action: { [weak self] action in
guard let self else {
return true
}
if case .undo = action {
var infoUrl = "https://telegram.org/blog/telegram-stars"
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["stars_rating_learnmore_url"] as? String {
infoUrl = value
}
self.context.sharedContext.applicationBindings.openUrl(infoUrl)
}
return true
}
))
), in: .current)
}
}
)),

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

@ -115,7 +115,7 @@ extension ChatControllerImpl {
let textItems: [AnimatedTextComponent.Item] = [
AnimatedTextComponent.Item(id: 0, content: .text(text))
]
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems, hasUndo: true), elevatedLayout: false, position: .top, action: { [weak self] action in
guard let self else {
return false
}

View file

@ -535,9 +535,9 @@ extension ChatControllerImpl {
self.currentSendStarsUndoMessageId = messageId
if let current = self.currentSendStarsUndoController {
current.content = .starsSent(context: self.context, title: title, text: textItems)
current.content = .starsSent(context: self.context, title: title, text: textItems, hasUndo: true)
} else {
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems, hasUndo: true), elevatedLayout: false, position: .top, action: { [weak self] action in
guard let self else {
return false
}

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

@ -40,7 +40,7 @@ public enum UndoOverlayContent {
case copy(text: String)
case mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: String)
case starsSent(context: AccountContext, title: String, text: [AnimatedTextComponent.Item])
case starsSent(context: AccountContext, title: String, text: [AnimatedTextComponent.Item], hasUndo: Bool)
case inviteRequestSent(title: String, text: String)
case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?)
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)

View file

@ -452,7 +452,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = string
displayUndo = false
self.originalRemainingSeconds = 5
case let .starsSent(_, title, textItems):
case let .starsSent(_, title, textItems, hasUndo):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
@ -478,11 +478,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.animatedTextItems = textItems
displayUndo = true
self.originalRemainingSeconds = 4.9
displayUndo = hasUndo
self.originalRemainingSeconds = hasUndo ? 4.9 : 3.0
isUserInteractionEnabled = true
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
if hasUndo {
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
}
case let .messagesUnpinned(title, text, undo, isHidden):
self.avatarNode = nil
self.iconNode = nil
@ -1562,8 +1564,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content {
case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode)
case .starsSent:
self.panelWrapperNode.addSubnode(self.timerTextNode)
case let .starsSent(_, _, _, hasUndo):
if hasUndo {
self.panelWrapperNode.addSubnode(self.timerTextNode)
}
if self.textNode.tapAttributeAction != nil || displayUndo {
self.isUserInteractionEnabled = true
@ -1835,7 +1839,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
}
self.textNode.attributedText = attributedText
case let .starsSent(_, title, textItems):
case let .starsSent(_, title, textItems, _):
self.animatedTextItems = textItems
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)

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"
}