Update API

This commit is contained in:
Ilya Laktyushin 2023-10-19 21:21:01 +04:00
parent c4b35131a6
commit 934c6f18ae
7 changed files with 347 additions and 344 deletions

View File

@ -300,7 +300,7 @@ public enum ResolvedUrl {
case premiumOffer(reference: String?) case premiumOffer(reference: String?)
case chatFolder(slug: String) case chatFolder(slug: String)
case story(peerId: PeerId, id: Int32) case story(peerId: PeerId, id: Int32)
case boost(peerId: PeerId, status: ChannelBoostStatus?, canApplyStatus: CanApplyBoostStatus) case boost(peerId: PeerId, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
case premiumGiftCode(slug: String) case premiumGiftCode(slug: String)
} }

View File

@ -115,7 +115,7 @@ private enum StatsEntry: ItemListNodeEntry {
case boostersTitle(PresentationTheme, String) case boostersTitle(PresentationTheme, String)
case boostersPlaceholder(PresentationTheme, String) case boostersPlaceholder(PresentationTheme, String)
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32) case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer?, Int32)
case boostersExpand(PresentationTheme, String) case boostersExpand(PresentationTheme, String)
case boostersInfo(PresentationTheme, String) case boostersInfo(PresentationTheme, String)
@ -534,10 +534,16 @@ private enum StatsEntry: ItemListNodeEntry {
arguments.contextAction(message.id, node, gesture) arguments.contextAction(message.id, node, gesture)
}) })
case let .booster(_, _, dateTimeFormat, peer, expires): case let .booster(_, _, dateTimeFormat, peer, expires):
let expiresValue = stringForMediumDate(timestamp: expires, strings: presentationData.strings, dateTimeFormat: dateTimeFormat) let _ = dateTimeFormat
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: { let _ = peer
arguments.openPeer(peer) let _ = expires
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) return ItemListTextItem(presentationData: presentationData, text: .markdown("text"), sectionId: self.section)
// let expiresValue = stringForMediumDate(timestamp: expires, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
// return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: peer?.id != nil && peer?.id != arguments.context.account.peerId, sectionId: self.section, action: {
// if let peer {
// arguments.openPeer(peer)
// }
// }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .boostersExpand(theme, title): case let .boostersExpand(theme, title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandBoosters() arguments.expandBoosters()
@ -732,7 +738,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
if let boostersState { if let boostersState {
var boosterIndex: Int32 = 0 var boosterIndex: Int32 = 0
var boosters: [ChannelBoostersContext.State.Booster] = boostersState.boosters var boosters: [ChannelBoostersContext.State.Boost] = boostersState.boosts
var effectiveExpanded = state.boostersExpanded var effectiveExpanded = state.boostersExpanded
if boosters.count > maxUsersDisplayedLimit && !state.boostersExpanded { if boosters.count > maxUsersDisplayedLimit && !state.boostersExpanded {
boosters = Array(boosters.prefix(Int(maxUsersDisplayedLimit))) boosters = Array(boosters.prefix(Int(maxUsersDisplayedLimit)))

View File

@ -3,23 +3,39 @@ import TelegramApi
import Postbox import Postbox
import SwiftSignalKit import SwiftSignalKit
public final class ChannelBoostStatus: Equatable { public struct MyBoostStatus: Equatable {
public struct Boost: Equatable {
public let slot: Int32
public let peer: EnginePeer?
public let date: Int32
public let expires: Int32
public let cooldownUntil: Int32?
}
public let boosts: [Boost]
}
public struct ChannelBoostStatus: Equatable {
public let level: Int public let level: Int
public let boosts: Int public let boosts: Int
public let giftBoosts: Int?
public let currentLevelBoosts: Int public let currentLevelBoosts: Int
public let nextLevelBoosts: Int? public let nextLevelBoosts: Int?
public let premiumAudience: StatsPercentValue? public let premiumAudience: StatsPercentValue?
public let url: String public let url: String
public let prepaidGiveaways: [PrepaidGiveaway] public let prepaidGiveaways: [PrepaidGiveaway]
public let boostedByMe: Bool
public init(level: Int, boosts: Int, currentLevelBoosts: Int, nextLevelBoosts: Int?, premiumAudience: StatsPercentValue?, url: String, prepaidGiveaways: [PrepaidGiveaway]) { public init(level: Int, boosts: Int, giftBoosts: Int?, currentLevelBoosts: Int, nextLevelBoosts: Int?, premiumAudience: StatsPercentValue?, url: String, prepaidGiveaways: [PrepaidGiveaway], boostedByMe: Bool) {
self.level = level self.level = level
self.boosts = boosts self.boosts = boosts
self.giftBoosts = giftBoosts
self.currentLevelBoosts = currentLevelBoosts self.currentLevelBoosts = currentLevelBoosts
self.nextLevelBoosts = nextLevelBoosts self.nextLevelBoosts = nextLevelBoosts
self.premiumAudience = premiumAudience self.premiumAudience = premiumAudience
self.url = url self.url = url
self.prepaidGiveaways = prepaidGiveaways self.prepaidGiveaways = prepaidGiveaways
self.boostedByMe = boostedByMe
} }
public static func ==(lhs: ChannelBoostStatus, rhs: ChannelBoostStatus) -> Bool { public static func ==(lhs: ChannelBoostStatus, rhs: ChannelBoostStatus) -> Bool {
@ -29,6 +45,9 @@ public final class ChannelBoostStatus: Equatable {
if lhs.boosts != rhs.boosts { if lhs.boosts != rhs.boosts {
return false return false
} }
if lhs.giftBoosts != rhs.giftBoosts {
return false
}
if lhs.currentLevelBoosts != rhs.currentLevelBoosts { if lhs.currentLevelBoosts != rhs.currentLevelBoosts {
return false return false
} }
@ -44,133 +63,96 @@ public final class ChannelBoostStatus: Equatable {
if lhs.prepaidGiveaways != rhs.prepaidGiveaways { if lhs.prepaidGiveaways != rhs.prepaidGiveaways {
return false return false
} }
if lhs.boostedByMe != rhs.boostedByMe {
return false
}
return true return true
} }
} }
func _internal_getChannelBoostStatus(account: Account, peerId: PeerId) -> Signal<ChannelBoostStatus?, NoError> { func _internal_getChannelBoostStatus(account: Account, peerId: PeerId) -> Signal<ChannelBoostStatus?, NoError> {
return .single(ChannelBoostStatus(level: 0, boosts: 0, currentLevelBoosts: 0, nextLevelBoosts: nil, premiumAudience: nil, url: "", prepaidGiveaways: [])) return account.postbox.transaction { transaction -> Api.InputPeer? in
// return account.postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(peerId).flatMap(apiInputPeer)
// return transaction.getPeer(peerId).flatMap(apiInputPeer) }
// } |> mapToSignal { inputPeer -> Signal<ChannelBoostStatus?, NoError> in
// |> mapToSignal { inputPeer -> Signal<ChannelBoostStatus?, NoError> in guard let inputPeer = inputPeer else {
// guard let inputPeer = inputPeer else { return .single(nil)
// return .single(nil) }
// } return account.network.request(Api.functions.premium.getBoostsStatus(peer: inputPeer))
// return account.network.request(Api.functions.stories.getBoostsStatus(peer: inputPeer)) |> map(Optional.init)
// |> map(Optional.init) |> `catch` { _ -> Signal<Api.premium.BoostsStatus?, NoError> in
// |> `catch` { _ -> Signal<Api.stories.BoostsStatus?, NoError> in return .single(nil)
// return .single(nil) }
// } |> map { result -> ChannelBoostStatus? in
// |> map { result -> ChannelBoostStatus? in guard let result = result else {
// guard let result = result else { return nil
// return nil }
// } switch result {
// case let .boostsStatus(flags, level, currentLevelBoosts, boosts, giftBoosts, nextLevelBoosts, premiumAudience, boostUrl, prepaidGiveaways, myBoostSlots):
// switch result { let _ = myBoostSlots
// case let .boostsStatus(_, level, currentLevelBoosts, boosts, nextLevelBoosts, premiumAudience, url, prepaidGiveaways): return ChannelBoostStatus(level: Int(level), boosts: Int(boosts), giftBoosts: giftBoosts.flatMap(Int.init), currentLevelBoosts: Int(currentLevelBoosts), nextLevelBoosts: nextLevelBoosts.flatMap(Int.init), premiumAudience: premiumAudience.flatMap({ StatsPercentValue(apiPercentValue: $0) }), url: boostUrl, prepaidGiveaways: prepaidGiveaways?.map({ PrepaidGiveaway(apiPrepaidGiveaway: $0) }) ?? [], boostedByMe: (flags & (1 << 2)) != 0)
// return ChannelBoostStatus(level: Int(level), boosts: Int(boosts), currentLevelBoosts: Int(currentLevelBoosts), nextLevelBoosts: nextLevelBoosts.flatMap(Int.init), premiumAudience: premiumAudience.flatMap({ StatsPercentValue(apiPercentValue: $0) }), url: url, prepaidGiveaways: prepaidGiveaways?.map({ PrepaidGiveaway(apiPrepaidGiveaway: $0) }) ?? []) }
// } }
// }
// }
}
public enum CanApplyBoostStatus {
public enum ErrorReason {
case generic
case premiumRequired
case floodWait(Int32)
case peerBoostAlreadyActive
case giftedPremiumNotAllowed
} }
case ok
case replace(currentBoost: EnginePeer)
case error(ErrorReason)
} }
func _internal_canApplyChannelBoost(account: Account, peerId: PeerId) -> Signal<CanApplyBoostStatus, NoError> { func _internal_applyChannelBoost(account: Account, peerId: PeerId, slots: [Int32]) -> Signal<Bool, NoError> {
return .single(.error(.generic)) return account.postbox.transaction { transaction -> Api.InputPeer? in
// return account.postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(peerId).flatMap(apiInputPeer)
// return transaction.getPeer(peerId).flatMap(apiInputPeer) }
// } |> mapToSignal { inputPeer -> Signal<Bool, NoError> in
// |> mapToSignal { inputPeer -> Signal<CanApplyBoostStatus, NoError> in guard let inputPeer = inputPeer else {
// guard let inputPeer = inputPeer else { return .single(false)
// return .single(.error(.generic)) }
// } var flags: Int32 = 0
// return account.network.request(Api.functions.stories.canApplyBoost(peer: inputPeer), automaticFloodWait: false) if !slots.isEmpty {
// |> map { result -> (Api.stories.CanApplyBoostResult?, CanApplyBoostStatus.ErrorReason?) in flags |= (1 << 0)
// return (result, nil) }
// }
// |> `catch` { error -> Signal<(Api.stories.CanApplyBoostResult?, CanApplyBoostStatus.ErrorReason?), NoError> in return account.network.request(Api.functions.premium.applyBoost(flags: flags, slots: !slots.isEmpty ? slots : nil, peer: inputPeer))
// let reason: CanApplyBoostStatus.ErrorReason |> `catch` { error -> Signal<Api.Bool, NoError> in
// if error.errorDescription == "PREMIUM_ACCOUNT_REQUIRED" { return .single(.boolFalse)
// reason = .premiumRequired }
// } else if error.errorDescription.hasPrefix("FLOOD_WAIT_") { |> map { result -> Bool in
// let errorText = error.errorDescription ?? "" if case .boolTrue = result {
// if let underscoreIndex = errorText.lastIndex(of: "_") { return true
// let timeoutText = errorText[errorText.index(after: underscoreIndex)...] }
// if let timeoutValue = Int32(String(timeoutText)) { return false
// reason = .floodWait(timeoutValue) }
// } else { }
// reason = .generic
// }
// } else {
// reason = .generic
// }
// } else if error.errorDescription == "SAME_BOOST_ALREADY_ACTIVE" || error.errorDescription == "BOOST_NOT_MODIFIED" {
// reason = .peerBoostAlreadyActive
// } else if error.errorDescription == "PREMIUM_GIFTED_NOT_ALLOWED" {
// reason = .giftedPremiumNotAllowed
// } else {
// reason = .generic
// }
//
// return .single((nil, reason))
// }
// |> mapToSignal { result, errorReason -> Signal<CanApplyBoostStatus, NoError> in
// guard let result = result else {
// return .single(.error(errorReason ?? .generic))
// }
//
// return account.postbox.transaction { transaction -> CanApplyBoostStatus in
// switch result {
// case .canApplyBoostOk:
// return .ok
// case let .canApplyBoostReplace(currentBoost, chats):
// updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(transaction: transaction, chats: chats, users: []))
//
// if let peer = transaction.getPeer(currentBoost.peerId) {
// return .replace(currentBoost: EnginePeer(peer))
// } else {
// return .error(.generic)
// }
// }
// }
// }
// }
} }
func _internal_applyChannelBoost(account: Account, peerId: PeerId) -> Signal<Bool, NoError> { func _internal_getMyBoostStatus(account: Account) -> Signal<MyBoostStatus?, NoError> {
return .single(false) return account.network.request(Api.functions.premium.getMyBoosts())
// return account.postbox.transaction { transaction -> Api.InputPeer? in |> map(Optional.init)
// return transaction.getPeer(peerId).flatMap(apiInputPeer) |> `catch` { _ -> Signal<Api.premium.MyBoosts?, NoError> in
// } return .single(nil)
// |> mapToSignal { inputPeer -> Signal<Bool, NoError> in }
// guard let inputPeer = inputPeer else { |> mapToSignal { result -> Signal<MyBoostStatus?, NoError> in
// return .single(false) guard let result = result else {
// } return .single(nil)
// return account.network.request(Api.functions.stories.applyBoost(peer: inputPeer)) }
// |> `catch` { error -> Signal<Api.Bool, NoError> in return account.postbox.transaction { transaction -> MyBoostStatus? in
// return .single(.boolFalse) var boostsResult: [MyBoostStatus.Boost] = []
// } switch result {
// |> map { result -> Bool in case let .myBoosts(myBoosts, chats, users):
// if case .boolTrue = result { let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
// return true updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
// } for boost in myBoosts {
// return false let _ = boost
// } switch boost {
// } case let .myBoost(_, slot, peer, date, expires, cooldownUntilDate):
var boostPeer: EnginePeer?
if let peerId = peer?.peerId, let peer = transaction.getPeer(peerId) {
boostPeer = EnginePeer(peer)
}
boostsResult.append(MyBoostStatus.Boost(slot: slot, peer: boostPeer, date: date, expires: expires, cooldownUntil: cooldownUntilDate))
}
}
}
return MyBoostStatus(boosts: boostsResult)
}
}
} }
private final class ChannelBoostersContextImpl { private final class ChannelBoostersContextImpl {
@ -183,7 +165,7 @@ private final class ChannelBoostersContextImpl {
private var hasLoadedOnce: Bool = false private var hasLoadedOnce: Bool = false
private var canLoadMore: Bool = true private var canLoadMore: Bool = true
private var loadedFromCache = false private var loadedFromCache = false
private var results: [ChannelBoostersContext.State.Booster] = [] private var results: [ChannelBoostersContext.State.Boost] = []
private var count: Int32 private var count: Int32
private var lastOffset: String? private var lastOffset: String?
private var populateCache: Bool = true private var populateCache: Bool = true
@ -198,16 +180,13 @@ private final class ChannelBoostersContextImpl {
self.count = 0 self.count = 0
self.isLoadingMore = true self.isLoadingMore = true
self.disposable.set((account.postbox.transaction { transaction -> (peers: [ChannelBoostersContext.State.Booster], count: Int32, canLoadMore: Bool)? in self.disposable.set((account.postbox.transaction { transaction -> (peers: [ChannelBoostersContext.State.Boost], count: Int32, canLoadMore: Bool)? in
let cachedResult = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedChannelBoosters, key: CachedChannelBoosters.key(peerId: peerId)))?.get(CachedChannelBoosters.self) let cachedResult = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedChannelBoosts, key: CachedChannelBoosters.key(peerId: peerId)))?.get(CachedChannelBoosters.self)
if let cachedResult = cachedResult { if let cachedResult = cachedResult {
var result: [ChannelBoostersContext.State.Booster] = [] var result: [ChannelBoostersContext.State.Boost] = []
for peerId in cachedResult.peerIds { for boost in cachedResult.boosts {
if let peer = transaction.getPeer(peerId), let expires = cachedResult.dates[peerId] { let peer = boost.peerId.flatMap { transaction.getPeer($0) }
result.append(ChannelBoostersContext.State.Booster(peer: EnginePeer(peer), expires: expires)) result.append(ChannelBoostersContext.State.Boost(flags: ChannelBoostersContext.State.Boost.Flags(rawValue: boost.flags), id: boost.id, peer: peer.flatMap { EnginePeer($0) }, date: boost.date, expires: boost.expires))
} else {
return nil
}
} }
return (result, cachedResult.count, true) return (result, cachedResult.count, true)
} else { } else {
@ -243,95 +222,105 @@ private final class ChannelBoostersContextImpl {
} }
func loadMore() { func loadMore() {
// if self.isLoadingMore { if self.isLoadingMore {
// return return
// } }
// self.isLoadingMore = true self.isLoadingMore = true
// let account = self.account let account = self.account
// let accountPeerId = account.peerId let accountPeerId = account.peerId
// let peerId = self.peerId let peerId = self.peerId
// let populateCache = self.populateCache let populateCache = self.populateCache
//
// if self.loadedFromCache { if self.loadedFromCache {
// self.loadedFromCache = false self.loadedFromCache = false
// } }
// let lastOffset = self.lastOffset let lastOffset = self.lastOffset
//
// self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
// return transaction.getPeer(peerId).flatMap(apiInputPeer) return transaction.getPeer(peerId).flatMap(apiInputPeer)
// } }
// |> mapToSignal { inputPeer -> Signal<([ChannelBoostersContext.State.Booster], Int32, String?), NoError> in |> mapToSignal { inputPeer -> Signal<([ChannelBoostersContext.State.Boost], Int32, String?), NoError> in
// if let inputPeer = inputPeer { if let inputPeer = inputPeer {
// let offset = lastOffset ?? "" let offset = lastOffset ?? ""
// let limit: Int32 = lastOffset == nil ? 25 : 50 let limit: Int32 = lastOffset == nil ? 25 : 50
//
// let signal = account.network.request(Api.functions.stories.getBoostersList(peer: inputPeer, offset: offset, limit: limit)) let flags: Int32 = 0
// |> map(Optional.init) let signal = account.network.request(Api.functions.premium.getBoostsList(flags: flags, peer: inputPeer, offset: offset, limit: limit))
// |> `catch` { _ -> Signal<Api.stories.BoostersList?, NoError> in |> map(Optional.init)
// return .single(nil) |> `catch` { _ -> Signal<Api.premium.BoostsList?, NoError> in
// } return .single(nil)
// |> mapToSignal { result -> Signal<([ChannelBoostersContext.State.Booster], Int32, String?), NoError> in }
// return account.postbox.transaction { transaction -> ([ChannelBoostersContext.State.Booster], Int32, String?) in |> mapToSignal { result -> Signal<([ChannelBoostersContext.State.Boost], Int32, String?), NoError> in
// guard let result = result else { return account.postbox.transaction { transaction -> ([ChannelBoostersContext.State.Boost], Int32, String?) in
// return ([], 0, nil) guard let result = result else {
// } return ([], 0, nil)
// switch result { }
// case let .boostersList(_, count, boosters, nextOffset, users): switch result {
// updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users)) case let .boostsList(_, count, boosts, nextOffset, users):
// var resultBoosters: [ChannelBoostersContext.State.Booster] = [] updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users))
// for booster in boosters { var resultBoosts: [ChannelBoostersContext.State.Boost] = []
// let peerId: EnginePeer.Id for boost in boosts {
// let expires: Int32 switch boost {
// switch booster { case let .boost(flags, id, userId, giveawayMessageId, date, expires, usedGiftSlug):
// case let .booster(userId, expiresValue): let _ = giveawayMessageId
// peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) let _ = usedGiftSlug
// expires = expiresValue var boostFlags: ChannelBoostersContext.State.Boost.Flags = []
// } var boostPeer: EnginePeer?
// if let peer = transaction.getPeer(peerId) { if let userId = userId {
// resultBoosters.append(ChannelBoostersContext.State.Booster(peer: EnginePeer(peer), expires: expires)) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
// } if let peer = transaction.getPeer(peerId) {
// } boostPeer = EnginePeer(peer)
// if populateCache { }
// if let entry = CodableEntry(CachedChannelBoosters(boosters: resultBoosters, count: count)) { }
// transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedChannelBoosters, key: CachedChannelBoosters.key(peerId: peerId)), entry: entry) if (flags & (1 << 1)) != 0 {
// } boostFlags.insert(.isGift)
// } }
// return (resultBoosters, count, nextOffset) if (flags & (1 << 2)) != 0 {
// } boostFlags.insert(.isGiveaway)
// } }
// } if (flags & (1 << 3)) != 0 {
// return signal boostFlags.insert(.isUnclaimed)
// } else { }
// return .single(([], 0, nil)) resultBoosts.append(ChannelBoostersContext.State.Boost(flags: boostFlags, id: id, peer: boostPeer, date: date, expires: expires))
// } }
// } }
// |> deliverOn(self.queue)).start(next: { [weak self] boosters, updatedCount, nextOffset in if populateCache {
// guard let strongSelf = self else { if let entry = CodableEntry(CachedChannelBoosters(boosts: resultBoosts, count: count)) {
// return transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedChannelBoosts, key: CachedChannelBoosters.key(peerId: peerId)), entry: entry)
// } }
// strongSelf.lastOffset = nextOffset }
// if strongSelf.populateCache { return (resultBoosts, count, nextOffset)
// strongSelf.populateCache = false }
// strongSelf.results.removeAll() }
// } }
// var existingIds = Set(strongSelf.results.map { $0.peer.id }) return signal
// for booster in boosters { } else {
// if !existingIds.contains(booster.peer.id) { return .single(([], 0, nil))
// strongSelf.results.append(booster) }
// existingIds.insert(booster.peer.id) }
// } |> deliverOn(self.queue)).start(next: { [weak self] boosters, updatedCount, nextOffset in
// } guard let strongSelf = self else {
// strongSelf.isLoadingMore = false return
// strongSelf.hasLoadedOnce = true }
// strongSelf.canLoadMore = !boosters.isEmpty strongSelf.lastOffset = nextOffset
// if strongSelf.canLoadMore { if strongSelf.populateCache {
// strongSelf.count = max(updatedCount, Int32(strongSelf.results.count)) strongSelf.populateCache = false
// } else { strongSelf.results.removeAll()
// strongSelf.count = Int32(strongSelf.results.count) }
// } for booster in boosters {
// strongSelf.updateState() strongSelf.results.append(booster)
// })) }
// self.updateState() strongSelf.isLoadingMore = false
strongSelf.hasLoadedOnce = true
strongSelf.canLoadMore = !boosters.isEmpty
if strongSelf.canLoadMore {
strongSelf.count = max(updatedCount, Int32(strongSelf.results.count))
} else {
strongSelf.count = Int32(strongSelf.results.count)
}
strongSelf.updateState()
}))
self.updateState()
} }
private func updateCache() { private func updateCache() {
@ -340,34 +329,49 @@ private final class ChannelBoostersContextImpl {
} }
let peerId = self.peerId let peerId = self.peerId
let resultBoosters = Array(self.results.prefix(50)) let resultBoosts = Array(self.results.prefix(50))
let count = self.count let count = self.count
self.updateDisposables.add(self.account.postbox.transaction({ transaction in self.updateDisposables.add(self.account.postbox.transaction({ transaction in
if let entry = CodableEntry(CachedChannelBoosters(boosters: resultBoosters, count: count)) { if let entry = CodableEntry(CachedChannelBoosters(boosts: resultBoosts, count: count)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedChannelBoosters, key: CachedChannelBoosters.key(peerId: peerId)), entry: entry) transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedChannelBoosts, key: CachedChannelBoosters.key(peerId: peerId)), entry: entry)
} }
}).start()) }).start())
} }
private func updateState() { private func updateState() {
self.state.set(.single(ChannelBoostersContext.State(boosters: self.results, isLoadingMore: self.isLoadingMore, hasLoadedOnce: self.hasLoadedOnce, canLoadMore: self.canLoadMore, count: self.count))) self.state.set(.single(ChannelBoostersContext.State(boosts: self.results, isLoadingMore: self.isLoadingMore, hasLoadedOnce: self.hasLoadedOnce, canLoadMore: self.canLoadMore, count: self.count)))
} }
} }
public final class ChannelBoostersContext { public final class ChannelBoostersContext {
public struct State: Equatable { public struct State: Equatable {
public struct Booster: Equatable { public struct Boost: Equatable {
public var peer: EnginePeer public struct Flags: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let isGift = Flags(rawValue: 1 << 0)
public static let isGiveaway = Flags(rawValue: 1 << 1)
public static let isUnclaimed = Flags(rawValue: 1 << 2)
}
public var flags: Flags
public var id: String
public var peer: EnginePeer?
public var date: Int32
public var expires: Int32 public var expires: Int32
} }
public var boosters: [Booster] public var boosts: [Boost]
public var isLoadingMore: Bool public var isLoadingMore: Bool
public var hasLoadedOnce: Bool public var hasLoadedOnce: Bool
public var canLoadMore: Bool public var canLoadMore: Bool
public var count: Int32 public var count: Int32
public static var Empty = State(boosters: [], isLoadingMore: false, hasLoadedOnce: true, canLoadMore: false, count: 0) public static var Empty = State(boosts: [], isLoadingMore: false, hasLoadedOnce: true, canLoadMore: false, count: 0)
public static var Loading = State(boosters: [], isLoadingMore: false, hasLoadedOnce: false, canLoadMore: false, count: 0) public static var Loading = State(boosts: [], isLoadingMore: false, hasLoadedOnce: false, canLoadMore: false, count: 0)
} }
@ -408,38 +412,56 @@ public final class ChannelBoostersContext {
private final class CachedChannelBoosters: Codable { private final class CachedChannelBoosters: Codable {
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case peerIds case boosts
case expires
case count case count
} }
private struct DictionaryPair: Codable, Hashable { fileprivate struct CachedBoost: Codable, Hashable {
var key: Int64 private enum CodingKeys: String, CodingKey {
var value: String case flags
case id
case peerId
case date
case expires
}
init(_ key: Int64, value: String) { var flags: Int32
self.key = key var id: String
self.value = value var peerId: EnginePeer.Id?
var date: Int32
var expires: Int32
init(flags: Int32, id: String, peerId: EnginePeer.Id?, date: Int32, expires: Int32) {
self.flags = flags
self.id = id
self.peerId = peerId
self.date = date
self.expires = expires
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.key = try container.decode(Int64.self, forKey: "k") self.flags = try container.decode(Int32.self, forKey: .flags)
self.value = try container.decode(String.self, forKey: "v") self.id = try container.decode(String.self, forKey: .id)
self.peerId = try container.decodeIfPresent(Int64.self, forKey: .peerId).flatMap { EnginePeer.Id($0) }
self.date = try container.decode(Int32.self, forKey: .date)
self.expires = try container.decode(Int32.self, forKey: .expires)
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.key, forKey: "k") try container.encode(self.flags, forKey: .flags)
try container.encode(self.value, forKey: "v") try container.encode(self.id, forKey: .id)
try container.encodeIfPresent(self.peerId?.toInt64(), forKey: .peerId)
try container.encode(self.date, forKey: .date)
try container.encode(self.expires, forKey: .expires)
} }
} }
let peerIds: [EnginePeer.Id] fileprivate let boosts: [CachedBoost]
let dates: [EnginePeer.Id: Int32] fileprivate let count: Int32
let count: Int32
static func key(peerId: EnginePeer.Id) -> ValueBoxKey { static func key(peerId: EnginePeer.Id) -> ValueBoxKey {
let key = ValueBoxKey(length: 8) let key = ValueBoxKey(length: 8)
@ -447,50 +469,22 @@ private final class CachedChannelBoosters: Codable {
return key return key
} }
init(boosters: [ChannelBoostersContext.State.Booster], count: Int32) { init(boosts: [ChannelBoostersContext.State.Boost], count: Int32) {
self.peerIds = boosters.map { $0.peer.id } self.boosts = boosts.map { CachedBoost(flags: $0.flags.rawValue, id: $0.id, peerId: $0.peer?.id, date: $0.date, expires: $0.expires) }
self.dates = boosters.reduce(into: [EnginePeer.Id: Int32]()) {
$0[$1.peer.id] = $1.expires
}
self.count = count
}
init(peerIds: [PeerId], dates: [PeerId: Int32], count: Int32) {
self.peerIds = peerIds
self.dates = dates
self.count = count self.count = count
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.peerIds = (try container.decode([Int64].self, forKey: .peerIds)).map(EnginePeer.Id.init) self.boosts = (try container.decode([CachedBoost].self, forKey: .boosts))
var dates: [EnginePeer.Id: Int32] = [:]
let datesArray = try container.decode([Int64].self, forKey: .expires)
for index in stride(from: 0, to: datesArray.endIndex, by: 2) {
let userId = datesArray[index]
let date = datesArray[index + 1]
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
dates[peerId] = Int32(clamping: date)
}
self.dates = dates
self.count = try container.decode(Int32.self, forKey: .count) self.count = try container.decode(Int32.self, forKey: .count)
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.peerIds.map { $0.toInt64() }, forKey: .peerIds) try container.encode(self.boosts, forKey: .boosts)
var dates: [Int64] = []
for (peerId, date) in self.dates {
dates.append(peerId.id._internalGetInt64Value())
dates.append(Int64(date))
}
try container.encode(dates, forKey: .expires)
try container.encode(self.count, forKey: .count) try container.encode(self.count, forKey: .count)
} }
} }

View File

@ -111,7 +111,7 @@ public struct Namespaces {
public static let cachedPeerStoryListHeads: Int8 = 27 public static let cachedPeerStoryListHeads: Int8 = 27
public static let displayedStoryNotifications: Int8 = 28 public static let displayedStoryNotifications: Int8 = 28
public static let storySendAsPeerIds: Int8 = 29 public static let storySendAsPeerIds: Int8 = 29
public static let cachedChannelBoosters: Int8 = 30 public static let cachedChannelBoosts: Int8 = 31
} }
public struct UnorderedItemList { public struct UnorderedItemList {

View File

@ -1196,13 +1196,13 @@ public extension TelegramEngine {
public func getChannelBoostStatus(peerId: EnginePeer.Id) -> Signal<ChannelBoostStatus?, NoError> { public func getChannelBoostStatus(peerId: EnginePeer.Id) -> Signal<ChannelBoostStatus?, NoError> {
return _internal_getChannelBoostStatus(account: self.account, peerId: peerId) return _internal_getChannelBoostStatus(account: self.account, peerId: peerId)
} }
public func canApplyChannelBoost(peerId: EnginePeer.Id) -> Signal<CanApplyBoostStatus, NoError> {
return _internal_canApplyChannelBoost(account: self.account, peerId: peerId)
}
public func applyChannelBoost(peerId: EnginePeer.Id) -> Signal<Bool, NoError> { public func getMyBoostStatus() -> Signal<MyBoostStatus?, NoError> {
return _internal_applyChannelBoost(account: self.account, peerId: peerId) return _internal_getMyBoostStatus(account: self.account)
}
public func applyChannelBoost(peerId: EnginePeer.Id, slots: [Int32]) -> Signal<Bool, NoError> {
return _internal_applyChannelBoost(account: self.account, peerId: peerId, slots: slots)
} }
} }
} }

View File

@ -835,7 +835,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}), nil) }), nil)
} }
}) })
case let .boost(peerId, status, canApplyStatus): case let .boost(peerId, status, myBoostsStatus):
let _ = myBoostsStatus
var forceDark = false var forceDark = false
if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance { if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {
forceDark = true forceDark = true
@ -847,7 +848,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
} }
var isBoosted = false var isBoosted = false
if case let .error(error) = canApplyStatus, case .peerBoostAlreadyActive = error { if status.boostedByMe {
isBoosted = true isBoosted = true
} }
@ -876,54 +877,56 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
if isBoosted { if isBoosted {
return true return true
} }
var dismiss = false let dismiss = false
switch canApplyStatus { updateImpl?()
case .ok:
updateImpl?() // switch canApplyStatus {
case let .replace(previousPeer): // case .ok:
let controller = replaceBoostConfirmationController(context: context, fromPeers: [previousPeer], toPeer: peer, commit: { // updateImpl?()
updateImpl?() // case let .replace(previousPeer):
}) // let controller = replaceBoostConfirmationController(context: context, fromPeers: [previousPeer], toPeer: peer, commit: {
present(controller, nil) // updateImpl?()
case let .error(error): // })
let title: String? // present(controller, nil)
let text: String // case let .error(error):
// let title: String?
var actions: [TextAlertAction] = [ // let text: String
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) //
] // var actions: [TextAlertAction] = [
// TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
switch error { // ]
case .generic: //
title = nil // switch error {
text = presentationData.strings.Login_UnknownError // case .generic:
case let .floodWait(timeout): // title = nil
title = presentationData.strings.ChannelBoost_Error_BoostTooOftenTitle // text = presentationData.strings.Login_UnknownError
let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime, preferLowerValue: false) // case let .floodWait(timeout):
text = presentationData.strings.ChannelBoost_Error_BoostTooOftenText(valueText).string // title = presentationData.strings.ChannelBoost_Error_BoostTooOftenTitle
dismiss = true // let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime, preferLowerValue: false)
case .premiumRequired: // text = presentationData.strings.ChannelBoost_Error_BoostTooOftenText(valueText).string
title = presentationData.strings.ChannelBoost_Error_PremiumNeededTitle // dismiss = true
text = presentationData.strings.ChannelBoost_Error_PremiumNeededText // case .premiumRequired:
actions = [ // title = presentationData.strings.ChannelBoost_Error_PremiumNeededTitle
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), // text = presentationData.strings.ChannelBoost_Error_PremiumNeededText
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { // actions = [
dismissImpl?() // TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}),
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: false, dismissed: nil) // TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
navigationController?.pushViewController(controller) // dismissImpl?()
}) // let controller = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: false, dismissed: nil)
] // navigationController?.pushViewController(controller)
case .giftedPremiumNotAllowed: // })
title = presentationData.strings.ChannelBoost_Error_GiftedPremiumNotAllowedTitle // ]
text = presentationData.strings.ChannelBoost_Error_GiftedPremiumNotAllowedText // case .giftedPremiumNotAllowed:
dismiss = true // title = presentationData.strings.ChannelBoost_Error_GiftedPremiumNotAllowedTitle
case .peerBoostAlreadyActive: // text = presentationData.strings.ChannelBoost_Error_GiftedPremiumNotAllowedText
return true // dismiss = true
} // case .peerBoostAlreadyActive:
// return true
let controller = textAlertController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, title: title, text: text, actions: actions, parseMarkdown: true) // }
present(controller, nil) //
} // let controller = textAlertController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, title: title, text: text, actions: actions, parseMarkdown: true)
// present(controller, nil)
// }
return dismiss return dismiss
}, },
openPeer: { peer in openPeer: { peer in
@ -942,7 +945,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
updateImpl = { [weak controller] in updateImpl = { [weak controller] in
if let _ = status.nextLevelBoosts { if let _ = status.nextLevelBoosts {
let _ = context.engine.peers.applyChannelBoost(peerId: peerId).startStandalone() let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: []).startStandalone()
controller?.updateSubject(nextSubject, count: nextCount) controller?.updateSubject(nextSubject, count: nextCount)
} else { } else {
dismissImpl?() dismissImpl?()

View File

@ -739,10 +739,10 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
case .boost: case .boost:
return combineLatest( return combineLatest(
context.engine.peers.getChannelBoostStatus(peerId: peer.id), context.engine.peers.getChannelBoostStatus(peerId: peer.id),
context.engine.peers.canApplyChannelBoost(peerId: peer.id) context.engine.peers.getMyBoostStatus()
) )
|> map { boostStatus, canApplyStatus -> ResolvedUrl? in |> map { boostStatus, myBoostStatus -> ResolvedUrl? in
return .boost(peerId: peer.id, status: boostStatus, canApplyStatus: canApplyStatus) return .boost(peerId: peer.id, status: boostStatus, myBoostStatus: myBoostStatus)
} }
} }
} else { } else {