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 chatFolder(slug: String)
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)
}

View File

@ -115,7 +115,7 @@ private enum StatsEntry: ItemListNodeEntry {
case boostersTitle(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 boostersInfo(PresentationTheme, String)
@ -534,10 +534,16 @@ private enum StatsEntry: ItemListNodeEntry {
arguments.contextAction(message.id, node, gesture)
})
case let .booster(_, _, dateTimeFormat, peer, expires):
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 != arguments.context.account.peerId, sectionId: self.section, action: {
arguments.openPeer(peer)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
let _ = dateTimeFormat
let _ = peer
let _ = expires
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):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandBoosters()
@ -732,7 +738,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
if let boostersState {
var boosterIndex: Int32 = 0
var boosters: [ChannelBoostersContext.State.Booster] = boostersState.boosters
var boosters: [ChannelBoostersContext.State.Boost] = boostersState.boosts
var effectiveExpanded = state.boostersExpanded
if boosters.count > maxUsersDisplayedLimit && !state.boostersExpanded {
boosters = Array(boosters.prefix(Int(maxUsersDisplayedLimit)))

View File

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

View File

@ -1196,13 +1196,13 @@ public extension TelegramEngine {
public func getChannelBoostStatus(peerId: EnginePeer.Id) -> Signal<ChannelBoostStatus?, NoError> {
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> {
return _internal_applyChannelBoost(account: self.account, peerId: peerId)
public func getMyBoostStatus() -> Signal<MyBoostStatus?, NoError> {
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)
}
})
case let .boost(peerId, status, canApplyStatus):
case let .boost(peerId, status, myBoostsStatus):
let _ = myBoostsStatus
var forceDark = false
if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {
forceDark = true
@ -847,7 +848,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}
var isBoosted = false
if case let .error(error) = canApplyStatus, case .peerBoostAlreadyActive = error {
if status.boostedByMe {
isBoosted = true
}
@ -876,54 +877,56 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
if isBoosted {
return true
}
var dismiss = false
switch canApplyStatus {
case .ok:
updateImpl?()
case let .replace(previousPeer):
let controller = replaceBoostConfirmationController(context: context, fromPeers: [previousPeer], toPeer: peer, commit: {
updateImpl?()
})
present(controller, nil)
case let .error(error):
let title: String?
let text: String
var actions: [TextAlertAction] = [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
]
switch error {
case .generic:
title = nil
text = presentationData.strings.Login_UnknownError
case let .floodWait(timeout):
title = presentationData.strings.ChannelBoost_Error_BoostTooOftenTitle
let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime, preferLowerValue: false)
text = presentationData.strings.ChannelBoost_Error_BoostTooOftenText(valueText).string
dismiss = true
case .premiumRequired:
title = presentationData.strings.ChannelBoost_Error_PremiumNeededTitle
text = presentationData.strings.ChannelBoost_Error_PremiumNeededText
actions = [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
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
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 dismiss = false
updateImpl?()
// switch canApplyStatus {
// case .ok:
// updateImpl?()
// case let .replace(previousPeer):
// let controller = replaceBoostConfirmationController(context: context, fromPeers: [previousPeer], toPeer: peer, commit: {
// updateImpl?()
// })
// present(controller, nil)
// case let .error(error):
// let title: String?
// let text: String
//
// var actions: [TextAlertAction] = [
// TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
// ]
//
// switch error {
// case .generic:
// title = nil
// text = presentationData.strings.Login_UnknownError
// case let .floodWait(timeout):
// title = presentationData.strings.ChannelBoost_Error_BoostTooOftenTitle
// let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime, preferLowerValue: false)
// text = presentationData.strings.ChannelBoost_Error_BoostTooOftenText(valueText).string
// dismiss = true
// case .premiumRequired:
// title = presentationData.strings.ChannelBoost_Error_PremiumNeededTitle
// text = presentationData.strings.ChannelBoost_Error_PremiumNeededText
// actions = [
// TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}),
// TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
// 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
// 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)
// }
return dismiss
},
openPeer: { peer in
@ -942,7 +945,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
updateImpl = { [weak controller] in
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)
} else {
dismissImpl?()

View File

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