Various improvements

This commit is contained in:
Ilya Laktyushin 2022-06-07 15:58:56 +04:00
parent 149805e914
commit 57e7e6906b
20 changed files with 425 additions and 330 deletions

View File

@ -890,7 +890,7 @@ public protocol AccountContext: AnyObject {
public struct PremiumConfiguration { public struct PremiumConfiguration {
public static var defaultValue: PremiumConfiguration { public static var defaultValue: PremiumConfiguration {
return PremiumConfiguration(isPremiumDisabled: false) return PremiumConfiguration(isPremiumDisabled: true)
} }
public let isPremiumDisabled: Bool public let isPremiumDisabled: Bool

View File

@ -1034,14 +1034,14 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in
skipStateAnimation = true skipStateAnimation = true
updateState { state in updateState { state in
var state = state var updatedState = state
state.additionallyIncludePeers = filter.data?.includePeers.peers ?? [] updatedState.additionallyIncludePeers = filter.data?.includePeers.peers ?? []
state.additionallyExcludePeers = filter.data?.excludePeers ?? [] updatedState.additionallyExcludePeers = filter.data?.excludePeers ?? []
state.includeCategories = filter.data?.categories ?? [] updatedState.includeCategories = filter.data?.categories ?? []
state.excludeRead = filter.data?.excludeRead ?? false updatedState.excludeRead = filter.data?.excludeRead ?? false
state.excludeMuted = filter.data?.excludeMuted ?? false updatedState.excludeMuted = filter.data?.excludeMuted ?? false
state.excludeArchived = filter.data?.excludeArchived ?? false updatedState.excludeArchived = filter.data?.excludeArchived ?? false
return state return updatedState
} }
}) })
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
@ -1125,7 +1125,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
var found = false var found = false
for i in 0 ..< filters.count { for i in 0 ..< filters.count {
if filters[i].id == updatedFilter.id, case let .filter(_, _, _, data) = filters[i] { if filters[i].id == updatedFilter.id, case let .filter(_, _, _, data) = filters[i] {
var updatedData = data var updatedData = updatedFilter.data ?? data
var includePeers = updatedData.includePeers var includePeers = updatedData.includePeers
includePeers.setPeers(state.additionallyIncludePeers) includePeers.setPeers(state.additionallyIncludePeers)
updatedData.includePeers = includePeers updatedData.includePeers = includePeers

View File

@ -1484,6 +1484,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme) currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme)
} }
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
if !isPeerGroup && item.index.messageIndex.id.peerId != item.context.account.peerId { if !isPeerGroup && item.index.messageIndex.id.peerId != item.context.account.peerId {
if displayAsMessage { if displayAsMessage {
switch item.content { switch item.content {
@ -1495,7 +1496,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if peer.isPremium { } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
} }
} }
@ -1509,7 +1510,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if peer.isPremium { } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
} }
} }

View File

@ -563,6 +563,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
} }
} }
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
var currentCredibilityIconImage: UIImage? var currentCredibilityIconImage: UIImage?
switch item.peer { switch item.peer {
case let .peer(peer, _): case let .peer(peer, _):
@ -573,7 +575,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if peer.isPremium { } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
} }
} }

View File

@ -24,6 +24,7 @@ swift_library(
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",
"//submodules/GZip:GZip", "//submodules/GZip:GZip",
"//third-party/ZipArchive:ZipArchive", "//third-party/ZipArchive:ZipArchive",
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -15,6 +15,7 @@ import AccountContext
import AppBundle import AppBundle
import ZipArchive import ZipArchive
import WebKit import WebKit
import InAppPurchaseManager
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate { @objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
@ -86,13 +87,13 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case experimentalBackground(Bool) case experimentalBackground(Bool)
case inlineStickers(Bool) case inlineStickers(Bool)
case localTranscription(Bool) case localTranscription(Bool)
case snow(Bool)
case playerEmbedding(Bool) case playerEmbedding(Bool)
case playlistPlayback(Bool) case playlistPlayback(Bool)
case voiceConference case voiceConference
case preferredVideoCodec(Int, String, String?, Bool) case preferredVideoCodec(Int, String, String?, Bool)
case disableVideoAspectScaling(Bool) case disableVideoAspectScaling(Bool)
case enableVoipTcp(Bool) case enableVoipTcp(Bool)
case resetInAppPurchases(PresentationTheme)
case hostInfo(PresentationTheme, String) case hostInfo(PresentationTheme, String)
case versionInfo(PresentationTheme) case versionInfo(PresentationTheme)
@ -108,7 +109,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .localTranscription, .snow: case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .localTranscription, .resetInAppPurchases:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec: case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue return DebugControllerSection.videoExperiments.rawValue
@ -187,7 +188,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 31 return 31
case .localTranscription: case .localTranscription:
return 32 return 32
case .snow: case .resetInAppPurchases:
return 33 return 33
case .playerEmbedding: case .playerEmbedding:
return 34 return 34
@ -969,16 +970,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case let .snow(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Snow", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.snow = value
return PreferencesEntry(settings)
})
}).start()
})
case let .playerEmbedding(value): case let .playerEmbedding(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -1035,6 +1026,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case .resetInAppPurchases:
return ItemListActionItem(presentationData: presentationData, title: "Reset IAP Transactions", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.context?.inAppPurchaseManager?.finishAllTransactions()
})
case let .hostInfo(_, string): case let .hostInfo(_, string):
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
case .versionInfo: case .versionInfo:
@ -1096,6 +1091,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground)) entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
entries.append(.inlineStickers(experimentalSettings.inlineStickers)) entries.append(.inlineStickers(experimentalSettings.inlineStickers))
entries.append(.localTranscription(experimentalSettings.localTranscription)) entries.append(.localTranscription(experimentalSettings.localTranscription))
entries.append(.resetInAppPurchases(presentationData.theme))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding)) entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
} }

View File

@ -93,6 +93,14 @@ public final class InAppPurchaseManager: NSObject {
return self.productsPromise.get() return self.productsPromise.get()
} }
public func finishAllTransactions() {
let paymentQueue = SKPaymentQueue.default()
let transactions = paymentQueue.transactions
for transaction in transactions {
paymentQueue.finishTransaction(transaction)
}
}
public func buyProduct(_ product: Product, account: Account) -> Signal<PurchaseState, PurchaseError> { public func buyProduct(_ product: Product, account: Account) -> Signal<PurchaseState, PurchaseError> {
let payment = SKPayment(product: product.skProduct) let payment = SKPayment(product: product.skProduct)
SKPaymentQueue.default().add(payment) SKPaymentQueue.default().add(payment)
@ -159,6 +167,18 @@ extension InAppPurchaseManager: SKProductsRequestDelegate {
} }
} }
private func getReceiptData() -> Data? {
var receiptData: Data?
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
} catch {
Logger.shared.log("InAppPurchaseManager", "Couldn't read receipt data with error: \(error.localizedDescription)")
}
}
return receiptData
}
extension InAppPurchaseManager: SKPaymentTransactionObserver { extension InAppPurchaseManager: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions { for transaction in transactions {
@ -167,23 +187,28 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
let transactionState: TransactionState? let transactionState: TransactionState?
switch transaction.transactionState { switch transaction.transactionState {
case .purchased: case .purchased:
if transaction.original == nil {
transactionState = .purchased(transactionId: transaction.transactionIdentifier) transactionState = .purchased(transactionId: transaction.transactionIdentifier)
if let transactionIdentifier = transaction.transactionIdentifier { if let transactionIdentifier = transaction.transactionIdentifier {
self.disposableSet.set( self.disposableSet.set(
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier).start(error: { error in self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: false).start(error: { _ in
queue.finishTransaction(transaction)
}, completed: { }, completed: {
queue.finishTransaction(transaction) queue.finishTransaction(transaction)
}), }),
forKey: transaction.transactionIdentifier ?? "" forKey: transaction.transactionIdentifier ?? ""
) )
} }
} else {
transactionState = nil
queue.finishTransaction(transaction)
}
case .restored: case .restored:
transactionState = .restored(transactionId: transaction.transactionIdentifier) transactionState = .restored(transactionId: transaction.original?.transactionIdentifier)
if let transactionIdentifier = transaction.transactionIdentifier { if let transactionIdentifier = transaction.original?.transactionIdentifier {
self.disposableSet.set( self.disposableSet.set(
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier).start(error: { error in self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: true).start(error: { _ in
queue.finishTransaction(transaction)
}, completed: { }, completed: {
queue.finishTransaction(transaction) queue.finishTransaction(transaction)
}), }),

View File

@ -358,6 +358,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
updatedTheme = item.presentationData.theme updatedTheme = item.presentationData.theme
} }
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.accountContext.currentAppConfiguration.with { $0 })
var credibilityIconImage: UIImage? var credibilityIconImage: UIImage?
var credibilityIconOffset: CGFloat = 4.0 var credibilityIconOffset: CGFloat = 4.0
if let peer = item.peer { if let peer = item.peer {
@ -369,6 +371,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
credibilityIconOffset = 2.0 credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme) credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
credibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
} }
} }

View File

@ -603,6 +603,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
var updatedLabelBadgeImage: UIImage? var updatedLabelBadgeImage: UIImage?
var currentCredibilityIconImage: UIImage? var currentCredibilityIconImage: UIImage?
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
if case .threatSelfAsSaved = item.aliasHandling, item.peer.id == item.context.account.peerId { if case .threatSelfAsSaved = item.aliasHandling, item.peer.id == item.context.account.peerId {
} else { } else {
@ -612,7 +614,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if item.peer.isVerified { } else if item.peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if item.peer.isPremium { } else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
} }
} }

View File

@ -622,9 +622,16 @@ private struct ChannelVisibilityControllerState: Equatable {
} }
} }
private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool) -> [ChannelVisibilityEntry] { private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool) -> [ChannelVisibilityEntry] {
var entries: [ChannelVisibilityEntry] = [] var entries: [ChannelVisibilityEntry] = []
let isInitialSetup: Bool
if case .initialSetup = mode {
isInitialSetup = true
} else {
isInitialSetup = false
}
if let peer = view.peers[view.peerId] as? TelegramChannel { if let peer = view.peers[view.peerId] as? TelegramChannel {
var isGroup = false var isGroup = false
if case .group = peer.info { if case .group = peer.info {
@ -721,17 +728,11 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
} }
} }
switch selectedType { if case .revokeNames = mode {
case .publicChannel: let count = Int32(publicChannelsToRevoke?.count ?? 0)
var displayAvailability = false entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled))
if peer.addressName == nil {
displayAvailability = publicChannelsToRevoke != nil && !(publicChannelsToRevoke!.isEmpty)
}
if !"".isEmpty && displayAvailability {
if let publicChannelsToRevoke = publicChannelsToRevoke { if let publicChannelsToRevoke = publicChannelsToRevoke {
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
var index: Int32 = 0 var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0 var lhsDate: Int32 = 0
@ -747,10 +748,10 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil)) entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1 index += 1
} }
} else {
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
} }
} else { } else {
switch selectedType {
case .publicChannel:
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Group_PublicLink_Placeholder, currentAddressName)) entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Group_PublicLink_Placeholder, currentAddressName))
if let status = state.addressNameValidationStatus { if let status = state.addressNameValidationStatus {
let text: String let text: String
@ -815,11 +816,10 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage)) entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
} }
}
case .privateChannel: case .privateChannel:
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased())) entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup)) entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
if isGroup { if isGroup {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
} else { } else {
@ -855,13 +855,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased())) entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled)) entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingChannelInfo) : (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled : presentationData.strings.Group_Setup_ForwardingChannelInfoDisabled))) entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingChannelInfo) : (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled : presentationData.strings.Group_Setup_ForwardingChannelInfoDisabled)))
}
} else if let peer = view.peers[view.peerId] as? TelegramGroup { } else if let peer = view.peers[view.peerId] as? TelegramGroup {
switch mode { if case .revokeNames = mode {
case .revokeNames: let count = Int32(publicChannelsToRevoke?.count ?? 0)
if let publicChannelsToRevoke = publicChannelsToRevoke { entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled))
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false)) if let publicChannelsToRevoke = publicChannelsToRevoke {
var index: Int32 = 0 var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0 var lhsDate: Int32 = 0
@ -878,10 +878,14 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
index += 1 index += 1
} }
} }
} else {
switch mode {
case .revokeNames:
break
case .privateLink: case .privateLink:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased())) entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup)) entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help)) entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
switch mode { switch mode {
case .initialSetup, .revokeNames: case .initialSetup, .revokeNames:
@ -922,7 +926,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
if displayAvailability { if displayAvailability {
if let publicChannelsToRevoke = publicChannelsToRevoke { if let publicChannelsToRevoke = publicChannelsToRevoke {
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount)) // entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false)) entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
var index: Int32 = 0 var index: Int32 = 0
@ -982,7 +986,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
case .privateChannel: case .privateChannel:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased())) entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup)) entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
switch mode { switch mode {
case .initialSetup, .revokeNames: case .initialSetup, .revokeNames:
@ -1009,6 +1013,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled)) entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled)) entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled))
} }
}
return entries return entries
} }
@ -1079,16 +1084,24 @@ public enum ChannelVisibilityControllerMode {
case initialSetup case initialSetup
case generic case generic
case privateLink case privateLink
case revokeNames case revokeNames([Peer])
} }
public func channelVisibilityController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil) -> ViewController { public func channelVisibilityController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil, revokedPeerAddressName: ((PeerId) -> Void)? = nil) -> ViewController {
let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true) let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelVisibilityControllerState()) let stateValue = Atomic(value: ChannelVisibilityControllerState())
let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) }) statePromise.set(stateValue.modify { f($0) })
} }
let adminedPublicChannels = Promise<[Peer]?>()
if case let .revokeNames(peers) = mode {
adminedPublicChannels.set(.single(peers))
} else {
adminedPublicChannels.set(context.engine.peers.adminedPublicChannels(scope: .all)
|> map(Optional.init))
}
let peersDisablingAddressNameAssignment = Promise<[Peer]?>() let peersDisablingAddressNameAssignment = Promise<[Peer]?>()
peersDisablingAddressNameAssignment.set(.single(nil) |> then(context.engine.peers.channelAddressNameAssignmentAvailability(peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[Peer]?, NoError> in peersDisablingAddressNameAssignment.set(.single(nil) |> then(context.engine.peers.channelAddressNameAssignmentAvailability(peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[Peer]?, NoError> in
if case .addressNameLimitReached = result { if case .addressNameLimitReached = result {
@ -1133,9 +1146,43 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
actionsDisposable.add(toggleRequestToJoinDisposable) actionsDisposable.add(toggleRequestToJoinDisposable)
let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in
if type == .publicChannel {
let _ = combineLatest(
queue: Queue.mainQueue(),
adminedPublicChannels.get() |> filter { $0 != nil } |> take(1),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
)
).start(next: { peers, accountPeer, data in
let (limits, premiumLimits) = data
let isPremium = accountPeer?.isPremium ?? false
if let peers = peers {
let count = Int32(peers.count)
if count < limits.maxPublicLinksCount || (count < premiumLimits.maxPublicLinksCount && isPremium) {
updateState { state in updateState { state in
return state.withUpdatedSelectedType(type) return state.withUpdatedSelectedType(type)
} }
} else {
let controller = channelVisibilityController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, mode: .revokeNames(peers), upgradedToSupergroup: { _, _ in }, revokedPeerAddressName: { revokedPeerId in
let updatedPublicChannels = peers.filter { $0.id != revokedPeerId }
adminedPublicChannels.set(.single(updatedPublicChannels) |> then(
context.engine.peers.adminedPublicChannels(scope: .all) |> map(Optional.init))
)
})
controller.navigationPresentation = .modal
pushControllerImpl?(controller)
}
} else {
}
})
} else {
updateState { state in
return state.withUpdatedSelectedType(type)
}
}
}, updatePublicLinkText: { currentText, text in }, updatePublicLinkText: { currentText, text in
if text.isEmpty { if text.isEmpty {
checkAddressNameDisposable.set(nil) checkAddressNameDisposable.set(nil)
@ -1179,11 +1226,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
return state.withUpdatedRevokingPeerId(nil) return state.withUpdatedRevokingPeerId(nil)
} }
}, completed: { }, completed: {
peersDisablingAddressNameAssignment.set(.single([]) |> delay(0.2, queue: Queue.mainQueue()) |> afterNext { _ in revokedPeerAddressName?(peerId)
updateState { state in dismissImpl?()
return state.withUpdatedRevokingPeerId(nil)
}
})
})) }))
}, copyLink: { invite in }, copyLink: { invite in
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
@ -1353,7 +1397,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
presentationData, presentationData,
statePromise.get() |> deliverOnMainQueue, statePromise.get() |> deliverOnMainQueue,
peerView, peerView,
peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue, adminedPublicChannels.get(),
importersContext, importersContext,
importersState.get(), importersState.get(),
context.engine.data.get( context.engine.data.get(
@ -1373,7 +1417,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
if case .revokeNames = mode { if case .revokeNames = mode {
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Premium_IncreaseLimit, colorful: true, action: {
let controller = PremiumIntroScreen(context: context, source: .publicLinks)
pushControllerImpl?(controller)
})
} else { } else {
if let peer = peer as? TelegramChannel { if let peer = peer as? TelegramChannel {
var doneEnabled = true var doneEnabled = true
@ -1400,7 +1447,14 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
} }
} }
rightNavigationButton = ItemListNavigationButton(content: .text(mode == .initialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { let isInitialSetup: Bool
if case .initialSetup = mode {
isInitialSetup = true
} else {
isInitialSetup = false
}
rightNavigationButton = ItemListNavigationButton(content: .text(isInitialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
var updatedAddressNameValue: String? var updatedAddressNameValue: String?
updateState { state in updateState { state in
updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: peer, cachedData: view.cachedData) updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: peer, cachedData: view.cachedData)
@ -1619,13 +1673,6 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
crossfade = hadNamesToRevoke != hasNamesToRevoke crossfade = hadNamesToRevoke != hasNamesToRevoke
} }
if hasNamesToRevoke && selectedType == .publicChannel {
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Premium_IncreaseLimit, colorful: true, action: {
let controller = PremiumIntroScreen(context: context, source: .publicLinks)
pushControllerImpl?(controller)
})
}
if let hadNamesToRevoke = hadNamesToRevoke { if let hadNamesToRevoke = hadNamesToRevoke {
animateChanges = hadNamesToRevoke != hasNamesToRevoke animateChanges = hadNamesToRevoke != hasNamesToRevoke
} }
@ -1645,7 +1692,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
title = presentationData.strings.Premium_LimitReached title = presentationData.strings.Premium_LimitReached
} }
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, importers: importers, state: state, limits: limits, premiumLimits: premiumLimits, isPremium: isPremium) let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, importers: importers, state: state, limits: limits, premiumLimits: premiumLimits, isPremium: isPremium, isPremiumDisabled: premiumConfiguration.isPremiumDisabled)
var focusItemTag: ItemListItemTag? var focusItemTag: ItemListItemTag?
if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil { if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {

View File

@ -81,6 +81,9 @@ private let boldTextFont = Font.semibold(15.0)
class IncreaseLimitHeaderItemNode: ListViewItemNode { class IncreaseLimitHeaderItemNode: ListViewItemNode {
private var hostView: ComponentHostView<Empty>? private var hostView: ComponentHostView<Empty>?
private var params: (AnyComponent<Empty>, CGSize, ListViewItemNodeLayout, CGSize)?
private let titleNode: TextNode private let titleNode: TextNode
private let textNode: TextNode private let textNode: TextNode
@ -109,6 +112,19 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
let hostView = ComponentHostView<Empty>() let hostView = ComponentHostView<Empty>()
self.hostView = hostView self.hostView = hostView
self.view.addSubview(hostView) self.view.addSubview(hostView)
if let (component, containerSize, layout, textSize) = self.params {
let size = hostView.update(
transition: .immediate,
component: component,
environment: {},
containerSize: containerSize
)
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size)
let textSpacing: CGFloat = -6.0
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textSize.width) / 2.0), y: size.height + textSpacing), size: textSize)
}
} }
func asyncLayout() -> (_ item: IncreaseLimitHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { func asyncLayout() -> (_ item: IncreaseLimitHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@ -161,10 +177,7 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
] ]
} }
if let hostView = strongSelf.hostView { let component = AnyComponent(PremiumLimitDisplayComponent(
let size = hostView.update(
transition: .immediate,
component: AnyComponent(PremiumLimitDisplayComponent(
inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5), inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
activeColors: gradientColors, activeColors: gradientColors,
inactiveTitle: item.strings.Premium_Free, inactiveTitle: item.strings.Premium_Free,
@ -177,15 +190,23 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
badgeText: "\(item.count)", badgeText: "\(item.count)",
badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount), badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount),
isPremiumDisabled: item.isPremiumDisabled isPremiumDisabled: item.isPremiumDisabled
)), ))
environment: {}, let containerSize = CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0)
containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0)
)
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size)
let _ = textApply() let _ = textApply()
if let hostView = strongSelf.hostView {
let size = hostView.update(
transition: .immediate,
component: component,
environment: {},
containerSize: containerSize
)
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size)
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: size.height + textSpacing), size: textLayout.size) strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: size.height + textSpacing), size: textLayout.size)
} }
strongSelf.params = (component, containerSize, layout, textLayout.size)
} }
}) })
} }

View File

@ -807,7 +807,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
super.init() super.init()
self.disposable = (context.engine.data.get( self.disposable = (context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Configuration.App(), TelegramEngine.EngineData.Item.Configuration.App(),
TelegramEngine.EngineData.Item.Configuration.PremiumPromo() TelegramEngine.EngineData.Item.Configuration.PremiumPromo()
) )
@ -841,6 +841,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
} }
}) })
let _ = updatePremiumPromoConfigurationOnce(account: context.account).start()
let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers)
self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey]) self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey])
|> deliverOnMainQueue).start(next: { [weak self] views in |> deliverOnMainQueue).start(next: { [weak self] views in
@ -1362,9 +1364,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
self.disposable = combineLatest( self.disposable = combineLatest(
queue: Queue.mainQueue(), queue: Queue.mainQueue(),
availableProducts, availableProducts,
context.account.postbox.peerView(id: context.account.peerId) context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { view -> Bool in |> map { peer -> Bool in
return view.peers[view.peerId]?.isPremium ?? false return peer?.isPremium ?? false
}, },
otherPeerName otherPeerName
).start(next: { [weak self] products, isPremium, otherPeerName in ).start(next: { [weak self] products, isPremium, otherPeerName in
@ -1418,6 +1420,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
}, completed: { [weak self] in }, completed: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start()
strongSelf.isPremium = true strongSelf.isPremium = true
strongSelf.updated(transition: .easeInOut(duration: 0.25)) strongSelf.updated(transition: .easeInOut(duration: 0.25))
strongSelf.completion() strongSelf.completion()

View File

@ -49,7 +49,7 @@ final class ReactionsCarouselComponent: Component {
public func update(component: ReactionsCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize { public func update(component: ReactionsCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying
if self.node == nil { if self.node == nil && !component.reactions.isEmpty {
let node = ReactionCarouselNode( let node = ReactionCarouselNode(
context: component.context, context: component.context,
theme: component.theme, theme: component.theme,

View File

@ -45,7 +45,7 @@ final class StickersCarouselComponent: Component {
public func update(component: StickersCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize { public func update(component: StickersCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying
if self.node == nil { if self.node == nil && !component.stickers.isEmpty {
let node = StickersCarouselNode( let node = StickersCarouselNode(
context: component.context, context: component.context,
stickers: component.stickers stickers: component.stickers

View File

@ -6227,11 +6227,13 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.payments { public extension Api.functions.payments {
static func assignAppStoreTransaction(transactionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func assignAppStoreTransaction(flags: Int32, transactionId: String, receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1654235439) buffer.appendInt32(267129798)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(transactionId, buffer: buffer, boxed: false) serializeString(transactionId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("transactionId", String(describing: transactionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in serializeBytes(receipt, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("flags", String(describing: flags)), ("transactionId", String(describing: transactionId)), ("receipt", String(describing: receipt))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -6381,21 +6383,6 @@ public extension Api.functions.payments {
}) })
} }
} }
public extension Api.functions.payments {
static func restoreAppStoreReceipt(receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-2132923705)
serializeBytes(receipt, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.restoreAppStoreReceipt", parameters: [("receipt", String(describing: receipt))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.payments { public extension Api.functions.payments {
static func restorePlayMarketReceipt(receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func restorePlayMarketReceipt(receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -495,6 +495,8 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
let leftInset: CGFloat = 58.0 + params.leftInset let leftInset: CGFloat = 58.0 + params.leftInset
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
var titleIconsWidth: CGFloat = 0.0 var titleIconsWidth: CGFloat = 0.0
var currentCredibilityIconImage: UIImage? var currentCredibilityIconImage: UIImage?
var credibilityIconOffset: CGFloat = 0.0 var credibilityIconOffset: CGFloat = 0.0
@ -507,6 +509,9 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
} else if item.peer.isVerified { } else if item.peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0 credibilityIconOffset = 3.0
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
} }
if let currentCredibilityIconImage = currentCredibilityIconImage { if let currentCredibilityIconImage = currentCredibilityIconImage {

View File

@ -829,6 +829,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let verticalInset: CGFloat = 8.0 let verticalInset: CGFloat = 8.0
let verticalOffset: CGFloat = 0.0 let verticalOffset: CGFloat = 0.0
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
var titleIconsWidth: CGFloat = 0.0 var titleIconsWidth: CGFloat = 0.0
var currentCredibilityIconImage: UIImage? var currentCredibilityIconImage: UIImage?
var credibilityIconOffset: CGFloat = 0.0 var credibilityIconOffset: CGFloat = 0.0
@ -841,6 +843,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} else if item.peer.isVerified { } else if item.peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0 credibilityIconOffset = 3.0
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
} }
if let currentCredibilityIconImage = currentCredibilityIconImage { if let currentCredibilityIconImage = currentCredibilityIconImage {

View File

@ -8,26 +8,18 @@ public enum AssignAppStoreTransactionError {
case generic case generic
} }
func _internal_assignAppStoreTransaction(account: Account, transactionId: String) -> Signal<Never, AssignAppStoreTransactionError> { func _internal_assignAppStoreTransaction(account: Account, transactionId: String, receipt: Data, restore: Bool) -> Signal<Never, AssignAppStoreTransactionError> {
return account.network.request(Api.functions.payments.assignAppStoreTransaction(transactionId: transactionId)) var flags: Int32 = 0
if restore {
flags |= (1 << 0)
}
return account.network.request(Api.functions.payments.assignAppStoreTransaction(flags: flags, transactionId: transactionId, receipt: Buffer(data: receipt)))
|> mapError { _ -> AssignAppStoreTransactionError in |> mapError { _ -> AssignAppStoreTransactionError in
return .generic return .generic
} }
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in |> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
account.stateManager.addUpdates(updates) account.stateManager.addUpdates(updates)
return .complete()
return account.postbox.peerView(id: account.peerId)
|> castError(AssignAppStoreTransactionError.self)
|> take(until: { view in
if let peer = view.peers[view.peerId], peer.isPremium {
return SignalTakeAction(passthrough: false, complete: true)
} else {
return SignalTakeAction(passthrough: false, complete: false)
}
})
|> mapToSignal { _ -> Signal<Never, AssignAppStoreTransactionError> in
return .never()
}
} }
} }

View File

@ -1,3 +1,4 @@
import Foundation
import SwiftSignalKit import SwiftSignalKit
import Postbox import Postbox
@ -37,8 +38,8 @@ public extension TelegramEngine {
return _internal_clearBotPaymentInfo(network: self.account.network, info: info) return _internal_clearBotPaymentInfo(network: self.account.network, info: info)
} }
public func assignAppStoreTransaction(transactionId: String) -> Signal<Never, AssignAppStoreTransactionError> { public func assignAppStoreTransaction(transactionId: String, receipt: Data, restore: Bool) -> Signal<Never, AssignAppStoreTransactionError> {
return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId) return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId, receipt: receipt, restore: restore)
} }
public func canPurchasePremium() -> Signal<Bool, NoError> { public func canPurchasePremium() -> Signal<Bool, NoError> {

View File

@ -2300,6 +2300,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let themeUpdated = self.presentationData?.theme !== presentationData.theme let themeUpdated = self.presentationData?.theme !== presentationData.theme
self.presentationData = presentationData self.presentationData = presentationData
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
let credibilityIcon: CredibilityIcon let credibilityIcon: CredibilityIcon
if let peer = peer { if let peer = peer {
if peer.isFake { if peer.isFake {
@ -2308,7 +2310,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
credibilityIcon = .scam credibilityIcon = .scam
} else if peer.isVerified { } else if peer.isVerified {
credibilityIcon = .verified credibilityIcon = .verified
} else if peer.isPremium { } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
credibilityIcon = .premium credibilityIcon = .premium
} else { } else {
credibilityIcon = .none credibilityIcon = .none