Update API

This commit is contained in:
Ilya Laktyushin 2025-07-14 20:10:52 +01:00
parent 3945acbe01
commit 3547bb0f2a
3 changed files with 430 additions and 7 deletions

View File

@ -144,6 +144,7 @@ public struct Namespaces {
public static let channelsForPublicReaction: Int8 = 45
public static let cachedGroupsInCommon: Int8 = 46
public static let groupCallPersistentSettings: Int8 = 47
public static let cachedProfileGiftsCollections: Int8 = 48
}
public struct UnorderedItemList {

View File

@ -1162,9 +1162,16 @@ private final class CachedProfileGifts: Codable {
}
}
private func entryId(peerId: EnginePeer.Id) -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
cacheKey.setInt64(0, value: peerId.toInt64())
private func entryId(peerId: EnginePeer.Id, collectionId: Int32?) -> ItemCacheEntryId {
let cacheKey: ValueBoxKey
if let collectionId {
cacheKey = ValueBoxKey(length: 8 + 4)
cacheKey.setInt64(0, value: peerId.toInt64())
cacheKey.setInt32(8, value: collectionId)
} else {
cacheKey = ValueBoxKey(length: 8)
cacheKey.setInt64(0, value: peerId.toInt64())
}
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedProfileGifts, key: cacheKey)
}
@ -1172,6 +1179,7 @@ private final class ProfileGiftsContextImpl {
private let queue: Queue
private let account: Account
private let peerId: PeerId
private let collectionId: Int32?
private let disposable = MetaDisposable()
private let cacheDisposable = MetaDisposable()
@ -1200,12 +1208,14 @@ private final class ProfileGiftsContextImpl {
queue: Queue,
account: Account,
peerId: EnginePeer.Id,
collectionId: Int32?,
sorting: ProfileGiftsContext.Sorting,
filter: ProfileGiftsContext.Filters
) {
self.queue = queue
self.account = account
self.peerId = peerId
self.collectionId = collectionId
self.sorting = sorting
self.filter = filter
@ -1226,6 +1236,7 @@ private final class ProfileGiftsContextImpl {
func loadMore(reload: Bool = false) {
let peerId = self.peerId
let collectionId = self.collectionId
let accountPeerId = self.account.peerId
let network = self.account.network
let postbox = self.account.postbox
@ -1244,7 +1255,7 @@ private final class ProfileGiftsContextImpl {
if case let .ready(true, initialNextOffset) = dataState {
if !isFiltered || isUniqueOnlyFilter, self.gifts.isEmpty, initialNextOffset == nil, !reload {
self.cacheDisposable.set((self.account.postbox.transaction { transaction -> CachedProfileGifts? in
let cachedGifts = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedProfileGifts.self)
let cachedGifts = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId, collectionId: collectionId))?.get(CachedProfileGifts.self)
cachedGifts?.render(transaction: transaction)
return cachedGifts
} |> deliverOn(self.queue)).start(next: { [weak self] cachedGifts in
@ -1292,6 +1303,9 @@ private final class ProfileGiftsContextImpl {
return .single(([], 0, nil, nil))
}
var flags: Int32 = 0
if let _ = collectionId {
flags |= (1 << 6)
}
if case .value = sorting {
flags |= (1 << 5)
}
@ -1310,7 +1324,7 @@ private final class ProfileGiftsContextImpl {
if !filter.contains(.unique) {
flags |= (1 << 4)
}
return network.request(Api.functions.payments.getSavedStarGifts(flags: flags, peer: inputPeer, collectionId: nil, offset: initialNextOffset ?? "", limit: 36))
return network.request(Api.functions.payments.getSavedStarGifts(flags: flags, peer: inputPeer, collectionId: collectionId, offset: initialNextOffset ?? "", limit: 36))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.payments.SavedStarGifts?, NoError> in
return .single(nil)
@ -1363,7 +1377,7 @@ private final class ProfileGiftsContextImpl {
self.gifts = gifts
self.cacheDisposable.set(self.account.postbox.transaction { transaction in
if let entry = CodableEntry(CachedProfileGifts(gifts: gifts, count: count, notificationsEnabled: notificationsEnabled)) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
transaction.putItemCacheEntry(id: entryId(peerId: peerId, collectionId: collectionId), entry: entry)
}
}.start())
} else {
@ -2063,12 +2077,13 @@ public final class ProfileGiftsContext {
public init(
account: Account,
peerId: EnginePeer.Id,
collectionId: Int32? = nil,
sorting: ProfileGiftsContext.Sorting = .date,
filter: ProfileGiftsContext.Filters = .All
) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return ProfileGiftsContextImpl(queue: queue, account: account, peerId: peerId, sorting: sorting, filter: filter)
return ProfileGiftsContextImpl(queue: queue, account: account, peerId: peerId, collectionId: collectionId, sorting: sorting, filter: filter)
})
}

View File

@ -0,0 +1,407 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public struct StarGiftCollection: Codable, Equatable {
public let id: Int32
public let title: String
public let icon: TelegramMediaFile?
public let count: Int32
public let hash: Int64
public init(id: Int32, title: String, icon: TelegramMediaFile?, count: Int32, hash: Int64) {
self.id = id
self.title = title
self.icon = icon
self.count = count
self.hash = hash
}
public static func ==(lhs: StarGiftCollection, rhs: StarGiftCollection) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.icon != rhs.icon {
return false
}
if lhs.count != rhs.count {
return false
}
if lhs.hash != rhs.hash {
return false
}
return true
}
}
extension StarGiftCollection {
init?(apiStarGiftCollection: Api.StarGiftCollection) {
switch apiStarGiftCollection {
case let .starGiftCollection(_, collectionId, title, icon, giftsCount, hash):
self.id = collectionId
self.title = title
self.icon = icon.flatMap { telegramMediaFileFromApiDocument($0, altDocuments: nil) }
self.count = giftsCount
self.hash = hash
}
}
}
private final class CachedProfileGiftsCollections: Codable {
enum CodingKeys: String, CodingKey {
case collections
}
let collections: [StarGiftCollection]
init(collections: [StarGiftCollection]) {
self.collections = collections
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.collections = try container.decode([StarGiftCollection].self, forKey: .collections)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.collections, forKey: .collections)
}
}
private func entryId(peerId: EnginePeer.Id) -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
cacheKey.setInt64(0, value: peerId.toInt64())
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedProfileGiftsCollections, key: cacheKey)
}
private func intListSimpleHash(_ list: [Int64]) -> Int64 {
var acc: Int64 = 0
for value in list {
acc = ((acc * 20261) + Int64(0x80000000) + Int64(value)) % Int64(0x80000000)
}
return Int64(Int32(truncatingIfNeeded: acc))
}
private func _internal_getStarGiftCollections(postbox: Postbox, network: Network, peerId: EnginePeer.Id) -> Signal<[StarGiftCollection]?, NoError> {
return postbox.transaction { transaction -> (Api.InputPeer, [StarGiftCollection]?)? in
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
return nil
}
let collections = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedProfileGiftsCollections.self)
return (inputPeer, collections?.collections)
}
|> mapToSignal { inputPeerAndHash -> Signal<[StarGiftCollection]?, NoError> in
guard let (inputPeer, cachedCollections) = inputPeerAndHash else {
return .single(nil)
}
var hash: Int64 = 0
if let cachedCollections {
hash = intListSimpleHash(cachedCollections.map { $0.hash })
}
return network.request(Api.functions.payments.getStarGiftCollections(peer: inputPeer, hash: hash))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.payments.StarGiftCollections?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[StarGiftCollection]?, NoError> in
guard let result else {
return .single(nil)
}
return postbox.transaction { transaction -> [StarGiftCollection]? in
switch result {
case let .starGiftCollections(collections):
let collections = collections.compactMap { StarGiftCollection(apiStarGiftCollection: $0) }
if let entry = CodableEntry(CachedProfileGiftsCollections(collections: collections)) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
}
return collections
case .starGiftCollectionsNotModified:
return cachedCollections ?? []
}
}
}
}
}
private func _internal_createStarGiftCollection(account: Account, peerId: EnginePeer.Id, title: String, starGifts: [StarGiftReference]) -> Signal<StarGiftCollection?, NoError> {
return account.postbox.transaction { transaction -> (Api.InputPeer, [Api.InputSavedStarGift])? in
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
return nil
}
let inputStarGifts = starGifts.compactMap { $0.apiStarGiftReference(transaction: transaction) }
return (inputPeer, inputStarGifts)
}
|> mapToSignal { inputPeerAndGifts -> Signal<StarGiftCollection?, NoError> in
guard let (inputPeer, inputStarGifts) = inputPeerAndGifts else {
return .single(nil)
}
return account.network.request(Api.functions.payments.createStarGiftCollection(peer: inputPeer, title: title, stargift: inputStarGifts))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StarGiftCollection?, NoError> in
return .single(nil)
}
|> map { result -> StarGiftCollection? in
guard let result else {
return nil
}
return StarGiftCollection(apiStarGiftCollection: result)
}
}
}
private func _internal_reorderStarGiftCollections(account: Account, peerId: EnginePeer.Id, order: [Int32]) -> Signal<Bool, NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Bool, NoError> in
guard let inputPeer else {
return .single(false)
}
return account.network.request(Api.functions.payments.reorderStarGiftCollections(peer: inputPeer, order: order))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> map { result -> Bool in
if let result, case .boolTrue = result {
return true
}
return false
}
}
}
private func _internal_updateStarGiftCollection(account: Account, peerId: EnginePeer.Id, collectionId: Int32, actions: [StarGiftCollectionsContext.UpdateAction]) -> Signal<StarGiftCollection?, NoError> {
return account.postbox.transaction { transaction -> (Api.InputPeer, (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.StarGiftCollection>))? in
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
return nil
}
var flags: Int32 = 0
var title: String?
var deleteStarGift: [Api.InputSavedStarGift] = []
var addStarGift: [Api.InputSavedStarGift] = []
var order: [Api.InputSavedStarGift] = []
for action in actions {
switch action {
case let .updateTitle(newTitle):
flags |= (1 << 0)
title = newTitle
case let .addGifts(gifts):
flags |= (1 << 2)
addStarGift.append(contentsOf: gifts.compactMap { $0.apiStarGiftReference(transaction: transaction) })
case let .removeGifts(gifts):
flags |= (1 << 1)
deleteStarGift.append(contentsOf: gifts.compactMap { $0.apiStarGiftReference(transaction: transaction) })
case let .reorderGifts(gifts):
flags |= (1 << 3)
order = gifts.compactMap { $0.apiStarGiftReference(transaction: transaction) }
}
}
let request = Api.functions.payments.updateStarGiftCollection(flags: flags, peer: inputPeer, collectionId: collectionId, title: title, deleteStargift: deleteStarGift, addStargift: addStarGift, order: order)
return (inputPeer, request)
}
|> mapToSignal { peerAndRequest -> Signal<StarGiftCollection?, NoError> in
guard let (_, request) = peerAndRequest else {
return .single(nil)
}
return account.network.request(request)
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StarGiftCollection?, NoError> in
return .single(nil)
}
|> map { result -> StarGiftCollection? in
guard let result else {
return nil
}
return StarGiftCollection(apiStarGiftCollection: result)
}
}
}
private func _internal_deleteStarGiftCollection(account: Account, peerId: EnginePeer.Id, collectionId: Int32) -> Signal<Bool, NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Bool, NoError> in
guard let inputPeer else {
return .single(false)
}
return account.network.request(Api.functions.payments.deleteStarGiftCollection(peer: inputPeer, collectionId: collectionId))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> map { result -> Bool in
if let result, case .boolTrue = result {
return true
}
return false
}
}
}
public final class StarGiftCollectionsContext {
public struct State: Equatable {
public var collections: [StarGiftCollection]
public var isLoading: Bool
}
public enum UpdateAction {
case updateTitle(String)
case addGifts([StarGiftReference])
case removeGifts([StarGiftReference])
case reorderGifts([StarGiftReference])
}
private let queue: Queue = .mainQueue()
private let account: Account
private let peerId: EnginePeer.Id
private let disposable = MetaDisposable()
private var collections: [StarGiftCollection] = []
private var isLoading: Bool = false
private let stateValue = Promise<State>()
public var state: Signal<State, NoError> {
return self.stateValue.get()
}
public init(account: Account, peerId: EnginePeer.Id) {
self.account = account
self.peerId = peerId
self.reload()
}
deinit {
self.disposable.dispose()
}
public func reload() {
guard !self.isLoading else { return }
self.isLoading = true
self.pushState()
self.disposable.set((_internal_getStarGiftCollections(postbox: self.account.postbox, network: self.account.network, peerId: self.peerId)
|> deliverOn(self.queue)).start(next: { [weak self] collections in
guard let self else {
return
}
self.collections = collections ?? []
self.isLoading = false
self.pushState()
}))
}
public func createCollection(title: String, starGifts: [StarGiftReference]) -> Signal<StarGiftCollection?, NoError> {
return _internal_createStarGiftCollection(account: self.account, peerId: self.peerId, title: title, starGifts: starGifts)
|> deliverOn(self.queue)
|> afterNext { [weak self] collection in
guard let self else {
return
}
if let collection {
self.collections.append(collection)
self.pushState()
}
self.reload()
}
}
private func updateCollection(id: Int32, actions: [UpdateAction]) -> Signal<StarGiftCollection?, NoError> {
return _internal_updateStarGiftCollection(account: self.account, peerId: self.peerId, collectionId: id, actions: actions)
|> deliverOn(self.queue)
|> afterNext { [weak self] collection in
guard let self else {
return
}
if let collection {
if let index = self.collections.firstIndex(where: { $0.id == id }) {
self.collections[index] = collection
self.pushState()
}
}
self.reload()
}
}
public func addGifts(id: Int32, gifts: [StarGiftReference]) -> Signal<StarGiftCollection?, NoError> {
return self.updateCollection(id: id, actions: [.addGifts(gifts)])
}
public func removeGifts(id: Int32, gifts: [StarGiftReference]) -> Signal<StarGiftCollection?, NoError> {
return self.updateCollection(id: id, actions: [.addGifts(gifts)])
}
public func reorderGifts(id: Int32, gifts: [StarGiftReference]) -> Signal<StarGiftCollection?, NoError> {
return self.updateCollection(id: id, actions: [.reorderGifts(gifts)])
}
public func renameCollection(id: Int32, title: String) -> Signal<StarGiftCollection?, NoError> {
return self.updateCollection(id: id, actions: [.updateTitle(title)])
}
public func reorderCollections(order: [Int32]) -> Signal<Bool, NoError> {
return _internal_reorderStarGiftCollections(account: self.account, peerId: self.peerId, order: order)
|> deliverOn(self.queue)
|> afterNext { [weak self] collection in
guard let self else {
return
}
var collectionMap: [Int32: StarGiftCollection] = [:]
for collection in self.collections {
collectionMap[collection.id] = collection
}
var collections: [StarGiftCollection] = []
for id in order {
if let collection = collectionMap[id] {
collections.append(collection)
}
}
self.collections = collections
self.pushState()
self.reload()
}
}
public func deleteCollection(id: Int32) -> Signal<Bool, NoError> {
return _internal_deleteStarGiftCollection(account: self.account, peerId: self.peerId, collectionId: id)
|> deliverOn(self.queue)
|> afterNext { [weak self] _ in
guard let self else {
return
}
self.collections.removeAll(where: { $0.id == id })
self.pushState()
self.reload()
}
}
private func pushState() {
let state = State(
collections: self.collections,
isLoading: self.isLoading
)
self.stateValue.set(.single(state))
}
}