From 57e7e6906b611eba8627472d3b03473442da3542 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 7 Jun 2022 15:58:56 +0400 Subject: [PATCH] Various improvements --- .../Sources/AccountContext.swift | 2 +- .../ChatListFilterPresetController.swift | 18 +- .../Sources/Node/ChatListItem.swift | 5 +- .../Sources/ContactsPeerItem.swift | 4 +- submodules/DebugSettingsUI/BUILD | 1 + .../Sources/DebugController.swift | 22 +- .../Sources/InAppPurchaseManager.swift | 53 +- .../Sources/ItemListAvatarAndNameItem.swift | 4 + .../Sources/ItemListPeerItem.swift | 4 +- .../Sources/ChannelVisibilityController.swift | 508 ++++++++++-------- .../Sources/IncreaseLimitHeaderItem.swift | 55 +- .../Sources/PremiumIntroScreen.swift | 11 +- .../Sources/ReactionsCarouselComponent.swift | 2 +- .../Sources/StickersCarouselComponent.swift | 2 +- submodules/TelegramApi/Sources/Api27.swift | 23 +- .../VoiceChatFullscreenParticipantItem.swift | 5 + .../Sources/VoiceChatParticipantItem.swift | 5 + .../TelegramEngine/Payments/AppStore.swift | 22 +- .../Payments/TelegramEnginePayments.swift | 5 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 4 +- 20 files changed, 425 insertions(+), 330 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 3a7df00c3d..a8cc36c4a2 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -890,7 +890,7 @@ public protocol AccountContext: AnyObject { public struct PremiumConfiguration { public static var defaultValue: PremiumConfiguration { - return PremiumConfiguration(isPremiumDisabled: false) + return PremiumConfiguration(isPremiumDisabled: true) } public let isPremiumDisabled: Bool diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 64b2c68e8e..a12cabf1d8 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -1034,14 +1034,14 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in skipStateAnimation = true updateState { state in - var state = state - state.additionallyIncludePeers = filter.data?.includePeers.peers ?? [] - state.additionallyExcludePeers = filter.data?.excludePeers ?? [] - state.includeCategories = filter.data?.categories ?? [] - state.excludeRead = filter.data?.excludeRead ?? false - state.excludeMuted = filter.data?.excludeMuted ?? false - state.excludeArchived = filter.data?.excludeArchived ?? false - return state + var updatedState = state + updatedState.additionallyIncludePeers = filter.data?.includePeers.peers ?? [] + updatedState.additionallyExcludePeers = filter.data?.excludePeers ?? [] + updatedState.includeCategories = filter.data?.categories ?? [] + updatedState.excludeRead = filter.data?.excludeRead ?? false + updatedState.excludeMuted = filter.data?.excludeMuted ?? false + updatedState.excludeArchived = filter.data?.excludeArchived ?? false + return updatedState } }) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) @@ -1125,7 +1125,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat var found = false for i in 0 ..< filters.count { if filters[i].id == updatedFilter.id, case let .filter(_, _, _, data) = filters[i] { - var updatedData = data + var updatedData = updatedFilter.data ?? data var includePeers = updatedData.includePeers includePeers.setPeers(state.additionallyIncludePeers) updatedData.includePeers = includePeers diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 313f0ff86e..2c2f9345b2 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1484,6 +1484,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { 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 displayAsMessage { switch item.content { @@ -1495,7 +1496,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) } else if peer.isVerified { currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) - } else if peer.isPremium { + } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { 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) } else if peer.isVerified { currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) - } else if peer.isPremium { + } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) } } diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index f2692877b9..88f45371b6 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -563,6 +563,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } } + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) + var currentCredibilityIconImage: UIImage? switch item.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) } else if peer.isVerified { currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) - } else if peer.isPremium { + } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) } } diff --git a/submodules/DebugSettingsUI/BUILD b/submodules/DebugSettingsUI/BUILD index 2b0c03d0e3..60710882b0 100644 --- a/submodules/DebugSettingsUI/BUILD +++ b/submodules/DebugSettingsUI/BUILD @@ -24,6 +24,7 @@ swift_library( "//submodules/AppBundle:AppBundle", "//submodules/GZip:GZip", "//third-party/ZipArchive:ZipArchive", + "//submodules/InAppPurchaseManager:InAppPurchaseManager", ], visibility = [ "//visibility:public", diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 0de1fc679f..6fe1dc7143 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -15,6 +15,7 @@ import AccountContext import AppBundle import ZipArchive import WebKit +import InAppPurchaseManager @objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate { public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { @@ -86,13 +87,13 @@ private enum DebugControllerEntry: ItemListNodeEntry { case experimentalBackground(Bool) case inlineStickers(Bool) case localTranscription(Bool) - case snow(Bool) case playerEmbedding(Bool) case playlistPlayback(Bool) case voiceConference case preferredVideoCodec(Int, String, String?, Bool) case disableVideoAspectScaling(Bool) case enableVoipTcp(Bool) + case resetInAppPurchases(PresentationTheme) case hostInfo(PresentationTheme, String) case versionInfo(PresentationTheme) @@ -108,7 +109,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: 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 case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue @@ -187,7 +188,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 31 case .localTranscription: return 32 - case .snow: + case .resetInAppPurchases: return 33 case .playerEmbedding: return 34 @@ -969,16 +970,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).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): return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in @@ -1035,6 +1026,10 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).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): return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section) case .versionInfo: @@ -1096,6 +1091,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.experimentalBackground(experimentalSettings.experimentalBackground)) entries.append(.inlineStickers(experimentalSettings.inlineStickers)) entries.append(.localTranscription(experimentalSettings.localTranscription)) + entries.append(.resetInAppPurchases(presentationData.theme)) entries.append(.playerEmbedding(experimentalSettings.playerEmbedding)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) } diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 367bbdd972..e0df71e041 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -93,6 +93,14 @@ public final class InAppPurchaseManager: NSObject { 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 { let payment = SKPayment(product: product.skProduct) 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 { public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { @@ -167,23 +187,28 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { let transactionState: TransactionState? switch transaction.transactionState { case .purchased: - transactionState = .purchased(transactionId: transaction.transactionIdentifier) - if let transactionIdentifier = transaction.transactionIdentifier { - self.disposableSet.set( - self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier).start(error: { error in - - }, completed: { - queue.finishTransaction(transaction) - }), - forKey: transaction.transactionIdentifier ?? "" - ) + if transaction.original == nil { + transactionState = .purchased(transactionId: transaction.transactionIdentifier) + if let transactionIdentifier = transaction.transactionIdentifier { + self.disposableSet.set( + self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: false).start(error: { _ in + queue.finishTransaction(transaction) + }, completed: { + queue.finishTransaction(transaction) + }), + forKey: transaction.transactionIdentifier ?? "" + ) + } + } else { + transactionState = nil + queue.finishTransaction(transaction) } case .restored: - transactionState = .restored(transactionId: transaction.transactionIdentifier) - if let transactionIdentifier = transaction.transactionIdentifier { + transactionState = .restored(transactionId: transaction.original?.transactionIdentifier) + if let transactionIdentifier = transaction.original?.transactionIdentifier { 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: { queue.finishTransaction(transaction) }), diff --git a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift index 1ab2206d48..2ecd791cfa 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift +++ b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift @@ -358,6 +358,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo updatedTheme = item.presentationData.theme } + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.accountContext.currentAppConfiguration.with { $0 }) + var credibilityIconImage: UIImage? var credibilityIconOffset: CGFloat = 4.0 if let peer = item.peer { @@ -369,6 +371,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo credibilityIconOffset = 2.0 } else if peer.isVerified { credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme) + } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { + credibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) } } diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index f1b15e9355..1bf1f32628 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -603,6 +603,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo var updatedLabelBadgeImage: 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 { } else { @@ -612,7 +614,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) } else if item.peer.isVerified { currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) - } else if item.peer.isPremium { + } else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled { currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) } } diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 5dba73a98c..9019dd5e82 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -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] = [] + let isInitialSetup: Bool + if case .initialSetup = mode { + isInitialSetup = true + } else { + isInitialSetup = false + } + if let peer = view.peers[view.peerId] as? TelegramChannel { var isGroup = false if case .group = peer.info { @@ -721,36 +728,30 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa } } - switch selectedType { - case .publicChannel: - var displayAvailability = false - if peer.addressName == nil { - displayAvailability = publicChannelsToRevoke != nil && !(publicChannelsToRevoke!.isEmpty) - } - - if !"".isEmpty && displayAvailability { - if let publicChannelsToRevoke = publicChannelsToRevoke { -// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount)) - - var index: Int32 = 0 - for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in - var lhsDate: Int32 = 0 - var rhsDate: Int32 = 0 - if let lhs = lhs as? TelegramChannel { - lhsDate = lhs.creationDate - } - if let rhs = rhs as? TelegramChannel { - rhsDate = rhs.creationDate - } - return lhsDate > rhsDate - }) { - 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 - } - } else { - entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true)) + if case .revokeNames = mode { + let count = Int32(publicChannelsToRevoke?.count ?? 0) + entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled)) + + if let publicChannelsToRevoke = publicChannelsToRevoke { + var index: Int32 = 0 + for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in + var lhsDate: Int32 = 0 + var rhsDate: Int32 = 0 + if let lhs = lhs as? TelegramChannel { + lhsDate = lhs.creationDate } - } else { + if let rhs = rhs as? TelegramChannel { + rhsDate = rhs.creationDate + } + return lhsDate > rhsDate + }) { + 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 + } + } + } else { + switch selectedType { + case .publicChannel: entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Group_PublicLink_Placeholder, currentAddressName)) if let status = state.addressNameValidationStatus { let text: String @@ -804,7 +805,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_PublicLink_Info)) } else { entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp)) - } + } } else { entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp)) } @@ -815,199 +816,203 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage)) entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) } - } - case .privateChannel: - let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation - 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)) - if isGroup { - entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) - } else { - entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp)) - } - switch mode { - case .initialSetup, .revokeNames: - break - case .generic, .privateLink: - entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage)) - entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) - } - } - - var isDiscussion = false - if let cachedData = view.cachedData as? CachedChannelData, case .known = cachedData.linkedDiscussionPeerId { - isDiscussion = true - } - - if isGroup && (selectedType == .publicChannel || isDiscussion) { - if isDiscussion { - entries.append(.joinToSendHeader(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Title.uppercased())) - entries.append(.joinToSendEveryone(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Everyone, joinToSend == .everyone)) - entries.append(.joinToSendMembers(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_OnlyMembers, joinToSend == .members)) - } - - if !isDiscussion || joinToSend == .members { - entries.append(.approveMembers(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembers, approveMembers)) - entries.append(.approveMembersInfo(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembersInfo)) - } - } - - 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(.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 { - switch mode { - case .revokeNames: - if let publicChannelsToRevoke = publicChannelsToRevoke { -// 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)) - var index: Int32 = 0 - for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in - var lhsDate: Int32 = 0 - var rhsDate: Int32 = 0 - if let lhs = lhs as? TelegramChannel { - lhsDate = lhs.creationDate - } - if let rhs = rhs as? TelegramChannel { - rhsDate = rhs.creationDate - } - return lhsDate > rhsDate - }) { - 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 - } - } - case .privateLink: - let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation - 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(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help)) - switch mode { - case .initialSetup, .revokeNames: - break - case .generic, .privateLink: - entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage)) - entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) - } - case .generic, .initialSetup: - let selectedType: CurrentChannelType - if let current = state.selectedType { - selectedType = current - } else { - selectedType = .privateChannel - } - - let currentAddressName: String - if let current = state.editingPublicLinkText { - currentAddressName = current - } else { - currentAddressName = "" - } - - entries.append(.typeHeader(presentationData.theme, presentationData.strings.Group_Setup_TypeHeader.uppercased())) - entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel)) - entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel)) - - switch selectedType { - case .publicChannel: - entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp)) case .privateChannel: - entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp)) - } - - switch selectedType { - case .publicChannel: - let displayAvailability = publicChannelsToRevoke == nil || !(publicChannelsToRevoke!.isEmpty) + let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation + 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, !isInitialSetup)) + if isGroup { + entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) + } else { + entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp)) + } + switch mode { + case .initialSetup, .revokeNames: + break + case .generic, .privateLink: + entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage)) + entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) + } + } - if displayAvailability { - if let publicChannelsToRevoke = publicChannelsToRevoke { -// 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)) - var index: Int32 = 0 - for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in - var lhsDate: Int32 = 0 - var rhsDate: Int32 = 0 - if let lhs = lhs as? TelegramChannel { - lhsDate = lhs.creationDate + var isDiscussion = false + if let cachedData = view.cachedData as? CachedChannelData, case .known = cachedData.linkedDiscussionPeerId { + isDiscussion = true + } + + if isGroup && (selectedType == .publicChannel || isDiscussion) { + if isDiscussion { + entries.append(.joinToSendHeader(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Title.uppercased())) + entries.append(.joinToSendEveryone(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Everyone, joinToSend == .everyone)) + entries.append(.joinToSendMembers(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_OnlyMembers, joinToSend == .members)) + } + + if !isDiscussion || joinToSend == .members { + entries.append(.approveMembers(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembers, approveMembers)) + entries.append(.approveMembersInfo(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembersInfo)) + } + } + + 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(.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 { + if case .revokeNames = mode { + let count = Int32(publicChannelsToRevoke?.count ?? 0) + entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled)) + + if let publicChannelsToRevoke = publicChannelsToRevoke { + var index: Int32 = 0 + for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in + var lhsDate: Int32 = 0 + var rhsDate: Int32 = 0 + if let lhs = lhs as? TelegramChannel { + lhsDate = lhs.creationDate + } + if let rhs = rhs as? TelegramChannel { + rhsDate = rhs.creationDate + } + return lhsDate > rhsDate + }) { + 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 + } + } + } else { + switch mode { + case .revokeNames: + break + case .privateLink: + let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation + 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, !isInitialSetup)) + entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help)) + switch mode { + case .initialSetup, .revokeNames: + break + case .generic, .privateLink: + entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage)) + entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) + } + case .generic, .initialSetup: + let selectedType: CurrentChannelType + if let current = state.selectedType { + selectedType = current + } else { + selectedType = .privateChannel + } + + let currentAddressName: String + if let current = state.editingPublicLinkText { + currentAddressName = current + } else { + currentAddressName = "" + } + + entries.append(.typeHeader(presentationData.theme, presentationData.strings.Group_Setup_TypeHeader.uppercased())) + entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel)) + entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel)) + + switch selectedType { + case .publicChannel: + entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp)) + case .privateChannel: + entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp)) + } + + switch selectedType { + case .publicChannel: + let displayAvailability = publicChannelsToRevoke == nil || !(publicChannelsToRevoke!.isEmpty) + + if displayAvailability { + if let publicChannelsToRevoke = publicChannelsToRevoke { + // 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)) + var index: Int32 = 0 + for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in + var lhsDate: Int32 = 0 + var rhsDate: Int32 = 0 + if let lhs = lhs as? TelegramChannel { + lhsDate = lhs.creationDate + } + if let rhs = rhs as? TelegramChannel { + rhsDate = rhs.creationDate + } + return lhsDate > rhsDate + }) { + 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 } - if let rhs = rhs as? TelegramChannel { - rhsDate = rhs.creationDate - } - return lhsDate > rhsDate - }) { - 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 + } else { + entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true)) } } else { - entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true)) - } - } else { - entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentAddressName)) - if let status = state.addressNameValidationStatus { - let text: String - switch status { - case let .invalidFormat(error): - switch error { - case .startsWithDigit: - text = presentationData.strings.Group_Username_InvalidStartsWithNumber - case .startsWithUnderscore: - text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore - case .endsWithUnderscore: - text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore - case .tooShort: - text = presentationData.strings.Group_Username_InvalidTooShort - case .invalidCharacters: + entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentAddressName)) + if let status = state.addressNameValidationStatus { + let text: String + switch status { + case let .invalidFormat(error): + switch error { + case .startsWithDigit: + text = presentationData.strings.Group_Username_InvalidStartsWithNumber + case .startsWithUnderscore: + text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore + case .endsWithUnderscore: + text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore + case .tooShort: + text = presentationData.strings.Group_Username_InvalidTooShort + case .invalidCharacters: + text = presentationData.strings.Channel_Username_InvalidCharacters + } + case let .availability(availability): + switch availability { + case .available: + text = presentationData.strings.Channel_Username_UsernameIsAvailable(currentAddressName).string + case .invalid: text = presentationData.strings.Channel_Username_InvalidCharacters + case .taken: + text = presentationData.strings.Channel_Username_InvalidTaken } - case let .availability(availability): - switch availability { - case .available: - text = presentationData.strings.Channel_Username_UsernameIsAvailable(currentAddressName).string - case .invalid: - text = presentationData.strings.Channel_Username_InvalidCharacters - case .taken: - text = presentationData.strings.Channel_Username_InvalidTaken + case .checking: + text = presentationData.strings.Channel_Username_CheckingUsername } - case .checking: - text = presentationData.strings.Channel_Username_CheckingUsername + + entries.append(.publicLinkStatus(presentationData.theme, text, status)) } - entries.append(.publicLinkStatus(presentationData.theme, text, status)) + entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp)) } - - entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp)) - } - case .privateChannel: - let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation - 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(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) - switch mode { - case .initialSetup, .revokeNames: - break - case .generic, .privateLink: - entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage)) - entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) - } + case .privateChannel: + let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation + 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, !isInitialSetup)) + entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) + switch mode { + case .initialSetup, .revokeNames: + break + case .generic, .privateLink: + entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage)) + entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) + } + } } - } - - let forwardingEnabled: Bool - if let enabled = state.forwardingEnabled { - forwardingEnabled = enabled - } else { - if peer.flags.contains(.copyProtectionEnabled) { - forwardingEnabled = false + + let forwardingEnabled: Bool + if let enabled = state.forwardingEnabled { + forwardingEnabled = enabled } else { - forwardingEnabled = true + if peer.flags.contains(.copyProtectionEnabled) { + forwardingEnabled = false + } else { + forwardingEnabled = true + } } + + entries.append(.forwardingHeader(presentationData.theme, presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased())) + 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(.forwardingHeader(presentationData.theme, presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased())) - 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)) } return entries @@ -1079,16 +1084,24 @@ public enum ChannelVisibilityControllerMode { case initialSetup case generic case privateLink - case revokeNames + case revokeNames([Peer]) } -public func channelVisibilityController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = 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)? = 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 stateValue = Atomic(value: ChannelVisibilityControllerState()) let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in 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]?>() 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 { @@ -1133,8 +1146,42 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta actionsDisposable.add(toggleRequestToJoinDisposable) let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in - updateState { state in - return state.withUpdatedSelectedType(type) + 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 + 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 if text.isEmpty { @@ -1179,11 +1226,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta return state.withUpdatedRevokingPeerId(nil) } }, completed: { - peersDisablingAddressNameAssignment.set(.single([]) |> delay(0.2, queue: Queue.mainQueue()) |> afterNext { _ in - updateState { state in - return state.withUpdatedRevokingPeerId(nil) - } - }) + revokedPeerAddressName?(peerId) + dismissImpl?() })) }, copyLink: { invite in UIPasteboard.general.string = invite.link @@ -1353,7 +1397,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta presentationData, statePromise.get() |> deliverOnMainQueue, peerView, - peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue, + adminedPublicChannels.get(), importersContext, importersState.get(), context.engine.data.get( @@ -1373,7 +1417,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta var rightNavigationButton: ItemListNavigationButton? 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 { if let peer = peer as? TelegramChannel { 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? updateState { state in updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: peer, cachedData: view.cachedData) @@ -1618,14 +1672,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta if selectedType == .publicChannel, let hadNamesToRevoke = hadNamesToRevoke, !crossfade { 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 { animateChanges = hadNamesToRevoke != hasNamesToRevoke } @@ -1645,7 +1692,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta 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? if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil { diff --git a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift index 75efe60da1..0a12f24dcb 100644 --- a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift +++ b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift @@ -81,6 +81,9 @@ private let boldTextFont = Font.semibold(15.0) class IncreaseLimitHeaderItemNode: ListViewItemNode { private var hostView: ComponentHostView? + + private var params: (AnyComponent, CGSize, ListViewItemNodeLayout, CGSize)? + private let titleNode: TextNode private let textNode: TextNode @@ -109,6 +112,19 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { let hostView = ComponentHostView() self.hostView = 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) { @@ -161,31 +177,36 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { ] } + let component = AnyComponent(PremiumLimitDisplayComponent( + inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5), + activeColors: gradientColors, + inactiveTitle: item.strings.Premium_Free, + inactiveValue: item.count > item.limit ? "\(item.limit)" : "", + inactiveTitleColor: item.theme.list.itemPrimaryTextColor, + activeTitle: item.strings.Premium_Premium, + activeValue: item.count >= item.premiumCount ? "" : "\(item.premiumCount)", + activeTitleColor: .white, + badgeIconName: badgeIconName, + badgeText: "\(item.count)", + badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount), + isPremiumDisabled: item.isPremiumDisabled + )) + let containerSize = CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0) + + let _ = textApply() + if let hostView = strongSelf.hostView { let size = hostView.update( transition: .immediate, - component: AnyComponent(PremiumLimitDisplayComponent( - inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5), - activeColors: gradientColors, - inactiveTitle: item.strings.Premium_Free, - inactiveValue: item.count > item.limit ? "\(item.limit)" : "", - inactiveTitleColor: item.theme.list.itemPrimaryTextColor, - activeTitle: item.strings.Premium_Premium, - activeValue: item.count >= item.premiumCount ? "" : "\(item.premiumCount)", - activeTitleColor: .white, - badgeIconName: badgeIconName, - badgeText: "\(item.count)", - badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount), - isPremiumDisabled: item.isPremiumDisabled - )), + component: component, environment: {}, - containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0) + containerSize: containerSize ) hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size) - - let _ = textApply() 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) } }) } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index adfc5f7243..42c3a38bd7 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -807,7 +807,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { super.init() - self.disposable = (context.engine.data.get( + self.disposable = (context.engine.data.subscribe( TelegramEngine.EngineData.Item.Configuration.App(), 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) self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey]) |> deliverOnMainQueue).start(next: { [weak self] views in @@ -1362,9 +1364,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent { self.disposable = combineLatest( queue: Queue.mainQueue(), availableProducts, - context.account.postbox.peerView(id: context.account.peerId) - |> map { view -> Bool in - return view.peers[view.peerId]?.isPremium ?? false + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> Bool in + return peer?.isPremium ?? false }, otherPeerName ).start(next: { [weak self] products, isPremium, otherPeerName in @@ -1418,6 +1420,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { }, completed: { [weak self] in if let strongSelf = self { + let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start() strongSelf.isPremium = true strongSelf.updated(transition: .easeInOut(duration: 0.25)) strongSelf.completion() diff --git a/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift b/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift index 00f89dfa1f..81c60d8fd4 100644 --- a/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift @@ -49,7 +49,7 @@ final class ReactionsCarouselComponent: Component { public func update(component: ReactionsCarouselComponent, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying - if self.node == nil { + if self.node == nil && !component.reactions.isEmpty { let node = ReactionCarouselNode( context: component.context, theme: component.theme, diff --git a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift index b3930ea9c6..03d7dbd945 100644 --- a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift @@ -45,7 +45,7 @@ final class StickersCarouselComponent: Component { public func update(component: StickersCarouselComponent, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying - if self.node == nil { + if self.node == nil && !component.stickers.isEmpty { let node = StickersCarouselNode( context: component.context, stickers: component.stickers diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 99998d1d88..c94edaa4c3 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -6227,11 +6227,13 @@ public extension Api.functions.messages { } } public extension Api.functions.payments { - static func assignAppStoreTransaction(transactionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func assignAppStoreTransaction(flags: Int32, transactionId: String, receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1654235439) + buffer.appendInt32(267129798) + serializeInt32(flags, 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) var result: Api.Updates? 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) { - 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 { static func restorePlayMarketReceipt(receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index 16a2c7b461..d19750bf78 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -495,6 +495,8 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = 58.0 + params.leftInset + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) + var titleIconsWidth: CGFloat = 0.0 var currentCredibilityIconImage: UIImage? var credibilityIconOffset: CGFloat = 0.0 @@ -507,6 +509,9 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { } else if item.peer.isVerified { currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) credibilityIconOffset = 3.0 + } else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled { + currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) + credibilityIconOffset = 3.0 } if let currentCredibilityIconImage = currentCredibilityIconImage { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index fdb6c021fb..fe535608a8 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -829,6 +829,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let verticalInset: CGFloat = 8.0 let verticalOffset: CGFloat = 0.0 + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) + var titleIconsWidth: CGFloat = 0.0 var currentCredibilityIconImage: UIImage? var credibilityIconOffset: CGFloat = 0.0 @@ -841,6 +843,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { } else if item.peer.isVerified { currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) credibilityIconOffset = 3.0 + } else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled { + currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) + credibilityIconOffset = 3.0 } if let currentCredibilityIconImage = currentCredibilityIconImage { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift index 5f6a79b5a6..3bd555de53 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift @@ -8,26 +8,18 @@ public enum AssignAppStoreTransactionError { case generic } -func _internal_assignAppStoreTransaction(account: Account, transactionId: String) -> Signal { - return account.network.request(Api.functions.payments.assignAppStoreTransaction(transactionId: transactionId)) +func _internal_assignAppStoreTransaction(account: Account, transactionId: String, receipt: Data, restore: Bool) -> Signal { + 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 return .generic } |> mapToSignal { updates -> Signal in account.stateManager.addUpdates(updates) - - 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 in - return .never() - } + return .complete() } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index 8669cad00a..cf33ea9cbd 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -1,3 +1,4 @@ +import Foundation import SwiftSignalKit import Postbox @@ -37,8 +38,8 @@ public extension TelegramEngine { return _internal_clearBotPaymentInfo(network: self.account.network, info: info) } - public func assignAppStoreTransaction(transactionId: String) -> Signal { - return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId) + public func assignAppStoreTransaction(transactionId: String, receipt: Data, restore: Bool) -> Signal { + return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId, receipt: receipt, restore: restore) } public func canPurchasePremium() -> Signal { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 4e29814952..979c2598ca 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2300,6 +2300,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { let themeUpdated = self.presentationData?.theme !== presentationData.theme self.presentationData = presentationData + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) + let credibilityIcon: CredibilityIcon if let peer = peer { if peer.isFake { @@ -2308,7 +2310,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { credibilityIcon = .scam } else if peer.isVerified { credibilityIcon = .verified - } else if peer.isPremium { + } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { credibilityIcon = .premium } else { credibilityIcon = .none