diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 3df82e28b0..272447cd7e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13941,6 +13941,7 @@ Sorry for the inconvenience."; "Stars.Intro.BuyShort" = "Top Up"; "Stars.Intro.Withdraw" = "Withdraw"; +"Stars.Intro.Stats" = "Stats"; "Group.Info.Settings" = "Group Settings"; @@ -13992,3 +13993,6 @@ Sorry for the inconvenience."; "Conversation.VideoTimeLinkCopied" = "Link with start time at %@ copied to clipboard."; "Share.VideoStartAt" = "Start at %@"; "SendStarReactions.SubtitleFrom" = "from %@"; + +"Stars.AccountRevenue.Proceeds.Info" = "Stars from your total balance can be withdrawn as rewards 21 days after they are earned."; +"Stars.AccountRevenue.Withdraw.Info" = "You can collect rewards for Stars using Fragment. You cannot withdraw less than 1000 stars. [Learn More >]()"; diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index f790e0ba78..fc3ea9691c 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -48,6 +48,7 @@ public enum ContactMultiselectionControllerMode { public var onlyUsers: Bool public var disableChannels: Bool public var disableBots: Bool + public var disableContacts: Bool public init( title: String, @@ -59,7 +60,8 @@ public enum ContactMultiselectionControllerMode { displayPresence: Bool = false, onlyUsers: Bool = false, disableChannels: Bool = false, - disableBots: Bool = false + disableBots: Bool = false, + disableContacts: Bool = false ) { self.title = title self.searchPlaceholder = searchPlaceholder @@ -71,6 +73,7 @@ public enum ContactMultiselectionControllerMode { self.onlyUsers = onlyUsers self.disableChannels = disableChannels self.disableBots = disableBots + self.disableContacts = disableContacts } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index 090e6672ed..4f5ddfc578 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -281,8 +281,9 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP chatListFilters: nil, onlyUsers: false, disableChannels: true, - disableBots: false - )), filters: [.excludeSelf])) + disableBots: true, + disableContacts: true + )))) addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] result in @@ -342,7 +343,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP controller.navigationPresentation = .modal pushControllerImpl?(controller) } else { - let controller = selectivePrivacyPeersController(context: context, title: presentationData.strings.Privacy_Messages_Exceptions_Title, footer: presentationData.strings.Privacy_Messages_RemoveFeeInfo, initialPeers: peerIds, initialEnableForPremium: false, displayPremiumCategory: false, initialEnableForBots: false, displayBotsCategory: false, updated: { updatedPeerIds, _, _ in + let controller = selectivePrivacyPeersController(context: context, title: presentationData.strings.Privacy_Messages_Exceptions_Title, footer: presentationData.strings.Privacy_Messages_RemoveFeeInfo, hideContacts: true, initialPeers: peerIds, initialEnableForPremium: false, displayPremiumCategory: false, initialEnableForBots: false, displayBotsCategory: false, updated: { updatedPeerIds, _, _ in updateState { state in var updatedState = state updatedState.disableFor = updatedPeerIds diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index 9d79acf509..e18a0ac730 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -322,7 +322,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati return entries } -public func selectivePrivacyPeersController(context: AccountContext, title: String, footer: String? = nil, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], initialEnableForPremium: Bool, displayPremiumCategory: Bool, initialEnableForBots: Bool, displayBotsCategory: Bool, updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer], Bool, Bool) -> Void) -> ViewController { +public func selectivePrivacyPeersController(context: AccountContext, title: String, footer: String? = nil, hideContacts: Bool = false, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], initialEnableForPremium: Bool, displayPremiumCategory: Bool, initialEnableForBots: Bool, displayBotsCategory: Bool, updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer], Bool, Bool) -> Void) -> ViewController { let initialState = SelectivePrivacyPeersControllerState(enableForPremium: initialEnableForPremium, enableForBots: initialEnableForBots, editing: false, peerIdWithRevealedOptions: nil) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -428,7 +428,8 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri chatListFilters: nil, onlyUsers: false, disableChannels: true, - disableBots: false + disableBots: hideContacts, + disableContacts: hideContacts )), alwaysEnabled: true)) addPeerDisposable.set((controller.result |> take(1) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index ea9e2ce6c5..626f027cb0 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -29,6 +29,7 @@ final class StarsStatisticsScreenComponent: Component { let peerId: EnginePeer.Id let revenueContext: StarsRevenueStatsContext let openTransaction: (StarsContext.State.Transaction) -> Void + let buy: () -> Void let withdraw: () -> Void let showTimeoutTooltip: (Int32) -> Void let buyAds: () -> Void @@ -38,6 +39,7 @@ final class StarsStatisticsScreenComponent: Component { peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext, openTransaction: @escaping (StarsContext.State.Transaction) -> Void, + buy: @escaping () -> Void, withdraw: @escaping () -> Void, showTimeoutTooltip: @escaping (Int32) -> Void, buyAds: @escaping () -> Void @@ -46,6 +48,7 @@ final class StarsStatisticsScreenComponent: Component { self.peerId = peerId self.revenueContext = revenueContext self.openTransaction = openTransaction + self.buy = buy self.withdraw = withdraw self.showTimeoutTooltip = showTimeoutTooltip self.buyAds = buyAds @@ -508,7 +511,7 @@ final class StarsStatisticsScreenComponent: Component { )), footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: strings.Stars_BotRevenue_Proceeds_Info, + string: component.peerId == component.context.account.peerId ? strings.Stars_AccountRevenue_Proceeds_Info : strings.Stars_BotRevenue_Proceeds_Info, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor )), @@ -558,8 +561,8 @@ final class StarsStatisticsScreenComponent: Component { return (TelegramTextAttributes.URL, contents) }) - let balanceInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(strings.Stars_BotRevenue_Withdraw_Info, attributes: termsMarkdownAttributes, textAlignment: .natural - )) + let balanceRawString = component.peerId == component.context.account.peerId ? strings.Stars_AccountRevenue_Withdraw_Info : strings.Stars_BotRevenue_Withdraw_Info + let balanceInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(balanceRawString, attributes: termsMarkdownAttributes, textAlignment: .natural)) if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme { self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme) } @@ -567,6 +570,96 @@ final class StarsStatisticsScreenComponent: Component { balanceInfoString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: balanceInfoString.string)) } + var balanceItems: [AnyComponentWithIdentity] = [] + if component.peerId == component.context.account.peerId { + let withdrawEnabled = self.starsState?.balances.withdrawEnabled ?? false + balanceItems = [ + AnyComponentWithIdentity(id: 0, component: AnyComponent( + StarsBalanceComponent( + theme: environment.theme, + strings: strings, + dateTimeFormat: environment.dateTimeFormat, + count: self.starsState?.balances.availableBalance ?? StarsAmount.zero, + rate: self.starsState?.usdRate ?? 0, + actionTitle: strings.Stars_Intro_BuyShort, + actionAvailable: true, + actionIsEnabled: true, + actionIcon: PresentationResourcesItemList.itemListRoundTopupIcon(environment.theme), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.buy() + }, + secondaryActionTitle: withdrawEnabled ? strings.Stars_Intro_Withdraw : nil, + secondaryActionIcon: PresentationResourcesItemList.itemListRoundWithdrawIcon(environment.theme), + secondaryActionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp, + secondaryAction: { [weak self] in + guard let self, let component = self.component else { + return + } + var remainingCooldownSeconds: Int32 = 0 + if let cooldownUntilTimestamp = self.starsState?.balances.nextWithdrawalTimestamp { + remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) + remainingCooldownSeconds = max(0, remainingCooldownSeconds) + + if remainingCooldownSeconds > 0 { + component.showTimeoutTooltip(cooldownUntilTimestamp) + } else { + component.withdraw() + } + } else { + component.withdraw() + } + } + ) + )) + ] + } else { + balanceItems = [ + AnyComponentWithIdentity(id: 0, component: AnyComponent( + StarsBalanceComponent( + theme: environment.theme, + strings: strings, + dateTimeFormat: environment.dateTimeFormat, + count: self.starsState?.balances.availableBalance ?? StarsAmount.zero, + rate: self.starsState?.usdRate ?? 0, + actionTitle: strings.Stars_BotRevenue_Withdraw_WithdrawShort, + actionAvailable: true, + actionIsEnabled: self.starsState?.balances.withdrawEnabled ?? true, + actionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp, + actionIcon: PresentationResourcesItemList.itemListRoundTopupIcon(environment.theme), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + var remainingCooldownSeconds: Int32 = 0 + if let cooldownUntilTimestamp = self.starsState?.balances.nextWithdrawalTimestamp { + remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) + remainingCooldownSeconds = max(0, remainingCooldownSeconds) + + if remainingCooldownSeconds > 0 { + component.showTimeoutTooltip(cooldownUntilTimestamp) + } else { + component.withdraw() + } + } else { + component.withdraw() + } + }, + secondaryActionTitle: strings.Stars_BotRevenue_Withdraw_BuyAds, + secondaryActionIcon: PresentationResourcesItemList.itemListRoundWithdrawIcon(environment.theme), + secondaryAction: { [weak self] in + guard let self, let component = self.component else { + return + } + component.buyAds() + } + ) + )) + ] + } + let balanceSize = self.balanceView.update( transition: .immediate, component: AnyComponent(ListSectionComponent( @@ -597,44 +690,7 @@ final class StarsStatisticsScreenComponent: Component { } } )), - items: [AnyComponentWithIdentity(id: 0, component: AnyComponent( - StarsBalanceComponent( - theme: environment.theme, - strings: strings, - dateTimeFormat: environment.dateTimeFormat, - count: self.starsState?.balances.availableBalance ?? StarsAmount.zero, - rate: self.starsState?.usdRate ?? 0, - actionTitle: strings.Stars_BotRevenue_Withdraw_WithdrawShort, - actionAvailable: true, - actionIsEnabled: self.starsState?.balances.withdrawEnabled ?? true, - actionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp, - action: { [weak self] in - guard let self, let component = self.component else { - return - } - var remainingCooldownSeconds: Int32 = 0 - if let cooldownUntilTimestamp = self.starsState?.balances.nextWithdrawalTimestamp { - remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) - remainingCooldownSeconds = max(0, remainingCooldownSeconds) - - if remainingCooldownSeconds > 0 { - component.showTimeoutTooltip(cooldownUntilTimestamp) - } else { - component.withdraw() - } - } else { - component.withdraw() - } - }, - secondaryActionTitle: strings.Stars_BotRevenue_Withdraw_BuyAds, - secondaryAction: { [weak self] in - guard let self, let component = self.component else { - return - } - component.buyAds() - } - ) - ))] + items: balanceItems )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInsets, height: availableSize.height) @@ -799,11 +855,14 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { private weak var tooltipScreen: UndoOverlayController? private var timer: Foundation.Timer? + private let options = Promise<[StarsTopUpOption]>() + public init(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) { self.context = context self.peerId = peerId self.revenueContext = revenueContext + var buyImpl: (() -> Void)? var withdrawImpl: (() -> Void)? var buyAdsImpl: (() -> Void)? var showTimeoutTooltipImpl: ((Int32) -> Void)? @@ -815,6 +874,9 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { openTransaction: { transaction in openTransactionImpl?(transaction) }, + buy: { + buyImpl?() + }, withdraw: { withdrawImpl?() }, @@ -842,6 +904,46 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { }) } + if peerId == context.account.peerId { + self.options.set(.single([]) |> then(context.engine.payments.starsTopUpOptions())) + } + + buyImpl = { [weak self] in + guard let self else { + return + } + let _ = (self.options.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] options in + guard let self, let starsContext = context.starsContext else { + return + } + let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic, completion: { [weak self] stars in + guard let self else { + return + } + starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let resultController = UndoOverlayController( + presentationData: presentationData, + content: .universal( + animation: "StarsBuy", + scale: 0.066, + colors: [:], + title: presentationData.strings.Stars_Intro_PurchasedTitle, + text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string, + customUndoText: nil, + timeout: nil + ), + elevatedLayout: false, + action: { _ in return true}) + self.present(resultController, in: .window(.root)) + }) + self.push(controller) + }) + } + withdrawImpl = { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 6894f31d53..7bfd83869e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -630,7 +630,7 @@ final class StarsTransactionsScreenComponent: Component { contentHeight += descriptionSize.height contentHeight += 29.0 - let withdrawAvailable = self.revenueState?.balances.withdrawEnabled ?? false + let withdrawAvailable = (self.revenueState?.balances.overallRevenue.value ?? 0) > 0 let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) let balanceSize = self.balanceView.update( @@ -656,26 +656,14 @@ final class StarsTransactionsScreenComponent: Component { } component.buy() }, - secondaryActionTitle: withdrawAvailable ? environment.strings.Stars_Intro_Withdraw : nil, - secondaryActionIcon: withdrawAvailable ? PresentationResourcesItemList.itemListRoundWithdrawIcon(environment.theme) : nil, + secondaryActionTitle: withdrawAvailable ? environment.strings.Stars_Intro_Stats : nil, + secondaryActionIcon: withdrawAvailable ? PresentationResourcesItemList.itemListStatsIcon(environment.theme) : nil, secondaryActionCooldownUntilTimestamp: self.revenueState?.balances.nextWithdrawalTimestamp, secondaryAction: withdrawAvailable ? { [weak self] in guard let self, let component = self.component else { return } - var remainingCooldownSeconds: Int32 = 0 - if let cooldownUntilTimestamp = self.revenueState?.balances.nextWithdrawalTimestamp { - remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) - remainingCooldownSeconds = max(0, remainingCooldownSeconds) - - if remainingCooldownSeconds > 0 { - component.showTimeoutTooltip(cooldownUntilTimestamp) - } else { - component.withdraw() - } - } else { - component.withdraw() - } + component.withdraw() } : nil, additionalAction: (premiumConfiguration.starsGiftsPurchaseAvailable && !premiumConfiguration.isPremiumDisabled) ? AnyComponent( Button( @@ -739,7 +727,7 @@ final class StarsTransactionsScreenComponent: Component { return } let _ = (component.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: component.context, peerId: component.context.account.peerId, mode: .connectedPrograms) - |> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in + |> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in guard let self, let component = self.component else { return } @@ -1236,48 +1224,52 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { guard let self else { return } - let _ = (context.engine.peers.checkStarsRevenueWithdrawalAvailability() - |> deliverOnMainQueue).start(error: { [weak self] error in - guard let self else { - return - } - switch error { - case .serverProvided: - return - case .requestPassword: - let _ = (self.starsRevenueStatsContext.state - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] state in - guard let self else { - return - } - let controller = self.context.sharedContext.makeStarsWithdrawalScreen(context: context, completion: { [weak self] amount in - guard let self else { - return - } - let controller = confirmStarsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: amount, present: { [weak self] c, a in - self?.present(c, in: .window(.root)) - }, completion: { [weak self] url in - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) - - Queue.mainQueue().after(2.0) { - self?.starsRevenueStatsContext.reload() - } - }) - self.present(controller, in: .window(.root)) - }) - self.push(controller) - }) - default: - let controller = starsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: 0, initialError: error, present: { [weak self] c, a in - self?.present(c, in: .window(.root)) - }, completion: { _ in - - }) - self.present(controller, in: .window(.root)) - } - }) + + let controller = self.context.sharedContext.makeStarsStatisticsScreen(context: context, peerId: context.account.peerId, revenueContext: self.starsRevenueStatsContext) + self.push(controller) + +// let _ = (context.engine.peers.checkStarsRevenueWithdrawalAvailability() +// |> deliverOnMainQueue).start(error: { [weak self] error in +// guard let self else { +// return +// } +// switch error { +// case .serverProvided: +// return +// case .requestPassword: +// let _ = (self.starsRevenueStatsContext.state +// |> take(1) +// |> deliverOnMainQueue).start(next: { [weak self] state in +// guard let self else { +// return +// } +// let controller = self.context.sharedContext.makeStarsWithdrawalScreen(context: context, completion: { [weak self] amount in +// guard let self else { +// return +// } +// let controller = confirmStarsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: amount, present: { [weak self] c, a in +// self?.present(c, in: .window(.root)) +// }, completion: { [weak self] url in +// let presentationData = context.sharedContext.currentPresentationData.with { $0 } +// context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) +// +// Queue.mainQueue().after(2.0) { +// self?.starsRevenueStatsContext.reload() +// } +// }) +// self.present(controller, in: .window(.root)) +// }) +// self.push(controller) +// }) +// default: +// let controller = starsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: 0, initialError: error, present: { [weak self] c, a in +// self?.present(c, in: .window(.root)) +// }, completion: { _ in +// +// }) +// self.present(controller, in: .window(.root)) +// } +// }) } showTimeoutTooltipImpl = { [weak self] cooldownUntilTimestamp in diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Stats.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Stats.imageset/Contents.json new file mode 100644 index 0000000000..a243b957ee --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Stats.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stats_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Stats.imageset/stats_24.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Stats.imageset/stats_24.pdf new file mode 100644 index 0000000000..c054ff1aa7 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Stats.imageset/stats_24.pdf differ diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index 26f6d8b768..d2c4b95f4c 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -146,6 +146,9 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { )) } else if chatSelection.disableChannels || chatSelection.disableBots { var categories: ChatListFilterPeerCategories = [.contacts, .nonContacts, .groups, .bots, .channels] + if chatSelection.disableContacts { + categories.remove(.contacts) + } if chatSelection.disableChannels { categories.remove(.channels) }