From 1c0af395dbd7c38bed369f1427d18688f76d5225 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 8 Nov 2022 15:11:37 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 28 +- .../ImportStickerPackTitleController.swift | 2 +- .../Items/ItemListActivityTextItem.swift | 71 ++++- .../Sources/ChannelVisibilityController.swift | 59 ++-- .../PremiumUI/Sources/PremiumDemoScreen.swift | 6 +- .../Sources/Search/SettingsSearchItem.swift | 4 + .../Search/SettingsSearchableItems.swift | 274 +++++++++++++----- .../Sources/UsernameSetupController.swift | 41 ++- .../TelegramEngine/Peers/AddressNames.swift | 23 +- .../TelegramUI/Sources/ChatController.swift | 11 +- 10 files changed, 383 insertions(+), 136 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index df99dbe54a..3c103b5c24 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -4351,6 +4351,27 @@ Unused sets are archived when you add more."; "SettingsSearch.Synonyms.Support" = "Support"; "SettingsSearch.Synonyms.FAQ" = " "; +"SettingsSearch.Synonyms.Devices.TerminateOtherSessions" = " "; +"SettingsSearch.Synonyms.Devices.LinkDesktopDevice" = " "; + +"SettingsSearch.Synonyms.Language.ShowTranslateButton" = " "; +"SettingsSearch.Synonyms.Language.DoNotTranslate" = " "; + +"SettingsSearch.Synonyms.Premium" = " "; +"SettingsSearch.Synonyms.Premium.DoubledLimits" = " "; +"SettingsSearch.Synonyms.Premium.UploadSize" = " "; +"SettingsSearch.Synonyms.Premium.FasterSpeed" = " "; +"SettingsSearch.Synonyms.Premium.VoiceToText" = "Transcribe"; +"SettingsSearch.Synonyms.Premium.NoAds" = " "; +"SettingsSearch.Synonyms.Premium.EmojiStatus" = " "; +"SettingsSearch.Synonyms.Premium.Reactions" = " "; +"SettingsSearch.Synonyms.Premium.Stickers" = " "; +"SettingsSearch.Synonyms.Premium.AnimatedEmoji" = " "; +"SettingsSearch.Synonyms.Premium.ChatManagement" = " "; +"SettingsSearch.Synonyms.Premium.Badge" = "Star"; +"SettingsSearch.Synonyms.Premium.Avatar" = "Video Avatar"; +"SettingsSearch.Synonyms.Premium.AppIcon" = " "; + "ChatList.DeleteForCurrentUser" = "Delete just for me"; "ChatList.DeleteForEveryone" = "Delete for me and %@"; "ChatList.DeleteForEveryoneConfirmationTitle" = "Warning!"; @@ -7645,13 +7666,14 @@ Sorry for the inconvenience."; "Premium.FasterSpeed" = "Faster Download Speed"; "Premium.FasterSpeedInfo" = "No more limits on the speed with which media and documents are downloaded."; +"Premium.FasterSpeedStandaloneInfo" = "Subscribe to **Telegram Premium** to download media and files at the fastest possible speed."; "Premium.VoiceToText" = "Voice-to-Text Conversion"; "Premium.VoiceToTextInfo" = "Ability to read the transcript of any incoming voice message."; +"Premium.VoiceToTextStandaloneInfo" = "Subscribe to **Telegram Premium** to be able to convert voice and video messages to text."; "Premium.NoAds" = "No Ads"; "Premium.NoAdsInfo" = "No more ads in public channels where Telegram sometimes shows ads."; - "Premium.NoAdsStandaloneInfo" = "Remove ads such as this one by subscribing to **Telegram Premium**."; "Premium.Reactions" = "Unique Reactions"; @@ -7665,6 +7687,7 @@ Sorry for the inconvenience."; "Premium.ChatManagement" = "Advanced Chat Management"; "Premium.ChatManagementInfo" = "Tools to set the default folder, auto-archive and hide new chats from non-contacts."; +"Premium.ChatManagementStandaloneInfo" = "Subscribers of **Telegram Premium** can set the default folder, auto-archive and hide new chats from non-contacts."; "Premium.Badge" = "Profile Badge"; "Premium.BadgeInfo" = "A badge next to your name showing that you are helping support Telegram."; @@ -8247,3 +8270,6 @@ Sorry for the inconvenience."; "EmojiSearch.SearchTopicIconsPlaceholder" = "Search Topic Icons"; "EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found"; + +"Username.UsernamePurchaseAvailable" = "Sorry, this username is occupied by someone. But it's available for purchase through official @auction."; +"Channel.Username.UsernamePurchaseAvailable" = "Sorry, this link is occupied by someone. But it's available for purchase through official @auction."; diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift index eced81ab2c..5a56c97acc 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift @@ -840,7 +840,7 @@ func importStickerPackShortNameController(context: AccountContext, title: String case .taken: contentNode?.infoText = .taken contentNode?.actionNodes.last?.actionEnabled = false - case .invalid: + case .invalid, .purchaseAvailable: contentNode?.infoText = .info contentNode?.actionNodes.last?.actionEnabled = false } diff --git a/submodules/ItemListUI/Sources/Items/ItemListActivityTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListActivityTextItem.swift index f65e1fbcc9..db68050cbc 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListActivityTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListActivityTextItem.swift @@ -5,19 +5,31 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramPresentationData import ActivityIndicator +import TextFormat +import Markdown public class ItemListActivityTextItem: ListViewItem, ItemListItem { + public enum TextColor { + case generic + case constructive + case destructive + } + let displayActivity: Bool let presentationData: ItemListPresentationData - let text: NSAttributedString + let text: String + let color: TextColor + let linkAction: ((ItemListTextItemLinkAction) -> Void)? public let sectionId: ItemListSectionId public let isAlwaysPlain: Bool = true - public init(displayActivity: Bool, presentationData: ItemListPresentationData, text: NSAttributedString, sectionId: ItemListSectionId) { + public init(displayActivity: Bool, presentationData: ItemListPresentationData, text: String, color: TextColor, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, sectionId: ItemListSectionId) { self.displayActivity = displayActivity self.presentationData = presentationData self.text = text + self.color = color + self.linkAction = linkAction self.sectionId = sectionId } @@ -78,28 +90,46 @@ public class ItemListActivityTextItemNode: ListViewItemNode { self.addSubnode(self.activityIndicator) } + override public func didLoad() { + super.didLoad() + + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) + recognizer.tapActionAtPoint = { _ in + return .waitForSingleTap + } + self.view.addGestureRecognizer(recognizer) + } + public func asyncLayout() -> (_ item: ItemListActivityTextItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) return { item, params, neighbors in - let leftInset: CGFloat = 12.0 + params.leftInset + let leftInset: CGFloat = 15.0 + params.leftInset let verticalInset: CGFloat = 7.0 let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) + let titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize) var activityWidth: CGFloat = 0.0 if item.displayActivity { activityWidth = 25.0 } - let titleString = NSMutableAttributedString(attributedString: item.text) - let hasFont = titleString.attribute(.font, at: 0, effectiveRange: nil) != nil - if !hasFont { - titleString.removeAttribute(NSAttributedString.Key.font, range: NSMakeRange(0, titleString.length)) - titleString.addAttributes([NSAttributedString.Key.font: titleFont], range: NSMakeRange(0, titleString.length)) + let textColor: UIColor + switch item.color { + case .generic: + textColor = item.presentationData.theme.list.freeTextColor + case .constructive: + textColor = item.presentationData.theme.list.freeTextSuccessColor + case .destructive: + textColor = item.presentationData.theme.list.freeTextErrorColor } - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - 22.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: TextNodeCutout(topLeft: CGSize(width: activityWidth, height: 22.0)), insets: UIEdgeInsets())) + let attributedString = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: textColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + })) + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - 22.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: TextNodeCutout(topLeft: CGSize(width: activityWidth, height: 22.0)), insets: UIEdgeInsets())) let contentSize: CGSize let insets: UIEdgeInsets @@ -137,4 +167,27 @@ public class ItemListActivityTextItemNode: ListViewItemNode { override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } + + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap: + let titleFrame = self.titleNode.frame + if let item = self.item, titleFrame.contains(location) { + if let (_, attributes) = self.titleNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) { + if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + item.linkAction?(.tap(url)) + } + } + } + default: + break + } + } + default: + break + } + } } diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 6a67d2c401..d71e5880a1 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -23,6 +23,7 @@ import ContextUI import UndoUI import QrCodeUI import PremiumUI +import TextFormat private final class ChannelVisibilityControllerArguments { let context: AccountContext @@ -629,10 +630,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .publicLinkHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) - case let .publicLinkAvailability(theme, text, value): - let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor) - attr.addAttribute(.font, value: Font.regular(13), range: NSMakeRange(0, attr.length)) - return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section) + case let .publicLinkAvailability(_, text, value): + return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: text, color: value ? .generic : .destructive, sectionId: self.section) case let .linksLimitInfo(theme, text, count, limit, premiumLimit, isPremiumDisabled): return IncreaseLimitHeaderItem(theme: theme, strings: presentationData.strings, icon: .link, count: count, limit: limit, premiumCount: premiumLimit, text: text, isPremiumDisabled: isPremiumDisabled, sectionId: self.section) case let .privateLinkHeader(_, title): @@ -672,26 +671,28 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .publicLinkInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) - case let .publicLinkStatus(theme, text, status): + case let .publicLinkStatus(_, text, status): var displayActivity = false - let color: UIColor + let textColor: ItemListActivityTextItem.TextColor switch status { - case .invalidFormat: - color = theme.list.freeTextErrorColor - case let .availability(availability): - switch availability { - case .available: - color = theme.list.freeTextSuccessColor - case .invalid: - color = theme.list.freeTextErrorColor - case .taken: - color = theme.list.freeTextErrorColor - } - case .checking: - color = theme.list.freeTextColor - displayActivity = true + case .invalidFormat: + textColor = .destructive + case let .availability(availability): + switch availability { + case .available: + textColor = .constructive + case .invalid: + textColor = .destructive + case .taken: + textColor = .destructive + case .purchaseAvailable: + textColor = .generic + } + case .checking: + textColor = .generic + displayActivity = true } - return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: NSAttributedString(string: text, textColor: color), sectionId: self.section) + return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in }, sectionId: self.section) case let .existingLinksInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): @@ -1060,6 +1061,14 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa text = presentationData.strings.Channel_Username_InvalidCharacters case .taken: text = presentationData.strings.Channel_Username_InvalidTaken + case .purchaseAvailable: + var markdownString = presentationData.strings.Channel_Username_UsernamePurchaseAvailable + let entities = generateTextEntities(markdownString, enabledTypes: [.mention]) + if let entity = entities.first { + markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound)) + markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound)) + } + text = markdownString } case .checking: text = presentationData.strings.Channel_Username_CheckingUsername @@ -1253,6 +1262,14 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa text = presentationData.strings.Channel_Username_InvalidCharacters case .taken: text = presentationData.strings.Channel_Username_InvalidTaken + case .purchaseAvailable: + var markdownString = presentationData.strings.Channel_Username_UsernamePurchaseAvailable + let entities = generateTextEntities(markdownString, enabledTypes: [.mention]) + if let entity = entities.first { + markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound)) + markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound)) + } + text = markdownString } case .checking: text = presentationData.strings.Channel_Username_CheckingUsername diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index f8e8dd2eff..28f6b09005 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -718,7 +718,7 @@ private final class DemoSheetContent: CombinedComponent { decoration: .fasterStars )), title: strings.Premium_FasterSpeed, - text: strings.Premium_FasterSpeedInfo, + text: isStandalone ? strings.Premium_FasterSpeedStandaloneInfo : strings.Premium_FasterSpeedInfo, textColor: textColor ) ) @@ -736,7 +736,7 @@ private final class DemoSheetContent: CombinedComponent { decoration: .badgeStars )), title: strings.Premium_VoiceToText, - text: strings.Premium_VoiceToTextInfo, + text: isStandalone ? strings.Premium_VoiceToTextStandaloneInfo : strings.Premium_VoiceToTextInfo, textColor: textColor ) ) @@ -826,7 +826,7 @@ private final class DemoSheetContent: CombinedComponent { decoration: .swirlStars )), title: strings.Premium_ChatManagement, - text: strings.Premium_ChatManagementInfo, + text: isStandalone ? strings.Premium_ChatManagementStandaloneInfo : strings.Premium_ChatManagementInfo, textColor: textColor ) ) diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index 2f671c912a..9ecbfff22c 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -51,6 +51,10 @@ extension SettingsSearchableItemIcon { return PresentationResourcesSettings.chatFolders case .deleteAccount: return PresentationResourcesSettings.deleteAccount + case .devices: + return PresentationResourcesSettings.devices + case .premium: + return PresentationResourcesSettings.premium } } } diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index 7b5fa9f98b..c88e4a8361 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -18,6 +18,8 @@ import PhoneNumberFormat import AccountUtils import InstantPageCache import NotificationPeerExceptionController +import QrCodeUI +import PremiumUI enum SettingsSearchableItemIcon { case profile @@ -37,6 +39,8 @@ enum SettingsSearchableItemIcon { case faq case chatFolders case deleteAccount + case devices + case premium } public enum SettingsSearchableItemId: Hashable { @@ -57,43 +61,49 @@ public enum SettingsSearchableItemId: Hashable { case faq(Int32) case chatFolders(Int32) case deleteAccount(Int32) + case devices(Int32) + case premium(Int32) private var namespace: Int32 { switch self { - case .profile: - return 1 - case .proxy: - return 2 - case .savedMessages: - return 3 - case .calls: - return 4 - case .stickers: - return 5 - case .notifications: - return 6 - case .privacy: - return 7 - case .data: - return 8 - case .appearance: - return 9 - case .language: - return 10 - case .watch: - return 11 - case .passport: - return 12 - case .wallet: - return 13 - case .support: - return 14 - case .faq: - return 15 - case .chatFolders: - return 16 - case .deleteAccount: - return 17 + case .profile: + return 1 + case .proxy: + return 2 + case .savedMessages: + return 3 + case .calls: + return 4 + case .stickers: + return 5 + case .notifications: + return 6 + case .privacy: + return 7 + case .data: + return 8 + case .appearance: + return 9 + case .language: + return 10 + case .watch: + return 11 + case .passport: + return 12 + case .wallet: + return 13 + case .support: + return 14 + case .faq: + return 15 + case .chatFolders: + return 16 + case .deleteAccount: + return 17 + case .devices: + return 18 + case .premium: + return 19 } } @@ -115,7 +125,9 @@ public enum SettingsSearchableItemId: Hashable { let .support(id), let .faq(id), let .chatFolders(id), - let .deleteAccount(id): + let .deleteAccount(id), + let .devices(id), + let .premium(id): return id } } @@ -128,42 +140,46 @@ public enum SettingsSearchableItemId: Hashable { let namespace = Int32((index >> 32) & 0x7fffffff) let id = Int32(bitPattern: UInt32(index & 0xffffffff)) switch namespace { - case 1: - self = .profile(id) - case 2: - self = .proxy(id) - case 3: - self = .savedMessages(id) - case 4: - self = .calls(id) - case 5: - self = .stickers(id) - case 6: - self = .notifications(id) - case 7: - self = .privacy(id) - case 8: - self = .data(id) - case 9: - self = .appearance(id) - case 10: - self = .language(id) - case 11: - self = .watch(id) - case 12: - self = .passport(id) - case 13: - self = .wallet(id) - case 14: - self = .support(id) - case 15: - self = .faq(id) - case 16: - self = .chatFolders(id) - case 17: - self = .deleteAccount(id) - default: - return nil + case 1: + self = .profile(id) + case 2: + self = .proxy(id) + case 3: + self = .savedMessages(id) + case 4: + self = .calls(id) + case 5: + self = .stickers(id) + case 6: + self = .notifications(id) + case 7: + self = .privacy(id) + case 8: + self = .data(id) + case 9: + self = .appearance(id) + case 10: + self = .language(id) + case 11: + self = .watch(id) + case 12: + self = .passport(id) + case 13: + self = .wallet(id) + case 14: + self = .support(id) + case 15: + self = .faq(id) + case 16: + self = .chatFolders(id) + case 17: + self = .deleteAccount(id) + case 18: + self = .devices(id) + case 19: + self = .premium(id) + default: + return nil } } } @@ -233,6 +249,103 @@ private func profileSearchableItems(context: AccountContext, canAddAccount: Bool return items } +private func devicesSearchableItems(context: AccountContext, activeSessionsContext: ActiveSessionsContext?, webSessionsContext: WebSessionsContext?) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .devices + let strings = context.sharedContext.currentPresentationData.with { $0 }.strings + + var result: [SettingsSearchableItem] = [] + if let activeSessionsContext = activeSessionsContext { + result.append(SettingsSearchableItem(id: .devices(0), title: strings.Settings_Devices, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions) + [strings.PrivacySettings_AuthSessions], icon: icon, breadcrumbs: [], present: { context, _, present in + present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: false)) + })) + result.append(SettingsSearchableItem(id: .devices(1), title: strings.AuthSessions_TerminateOtherSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Devices_TerminateOtherSessions), icon: icon, breadcrumbs: [strings.Settings_Devices], present: { context, _, present in + present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: false)) + })) + result.append(SettingsSearchableItem(id: .devices(2), title: strings.AuthSessions_LinkDesktopDevice, alternate: synonyms(strings.SettingsSearch_Synonyms_Devices_LinkDesktopDevice), icon: icon, breadcrumbs: [strings.Settings_Devices], present: { context, _, present in + + present(.push, QrCodeScanScreen(context: context, subject: .authTransfer(activeSessionsContext: activeSessionsContext))) + })) + } + return result +} + +private func premiumSearchableItems(context: AccountContext) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .premium + let strings = context.sharedContext.currentPresentationData.with { $0 }.strings + + var result: [SettingsSearchableItem] = [] + + result.append(SettingsSearchableItem(id: .premium(0), title: strings.Settings_Premium, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in + present(.push, PremiumIntroScreen(context: context, modal: false, source: .settings)) + })) + + let presentDemo: (PremiumDemoScreen.Subject, (SettingsSearchableItemPresentation, ViewController?) -> Void) -> Void = { subject, present in + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: subject, action: { + let controller = PremiumIntroScreen(context: context, modal: false, source: .settings) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + present(.push, controller) + } + + result.append(SettingsSearchableItem(id: .premium(1), title: strings.Premium_DoubledLimits, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_DoubledLimits), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { _, _, present in + presentDemo(.doubleLimits, present) + })) + + result.append(SettingsSearchableItem(id: .premium(2), title: strings.Premium_UploadSize, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_UploadSize), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.moreUpload, present) + })) + + result.append(SettingsSearchableItem(id: .premium(3), title: strings.Premium_FasterSpeed, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_FasterSpeed), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.fasterDownload, present) + })) + + result.append(SettingsSearchableItem(id: .premium(4), title: strings.Premium_VoiceToText, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_VoiceToText), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.voiceToText, present) + })) + + result.append(SettingsSearchableItem(id: .premium(5), title: strings.Premium_NoAds, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_NoAds), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.noAds, present) + })) + + result.append(SettingsSearchableItem(id: .premium(6), title: strings.Premium_EmojiStatus, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_EmojiStatus), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.emojiStatus, present) + })) + + result.append(SettingsSearchableItem(id: .premium(7), title: strings.Premium_Reactions, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_Reactions), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.uniqueReactions, present) + })) + + result.append(SettingsSearchableItem(id: .premium(8), title: strings.Premium_Stickers, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_Stickers), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.premiumStickers, present) + })) + + result.append(SettingsSearchableItem(id: .premium(9), title: strings.Premium_AnimatedEmoji, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_AnimatedEmoji), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.animatedEmoji, present) + })) + + result.append(SettingsSearchableItem(id: .premium(10), title: strings.Premium_ChatManagement, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_ChatManagement), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.advancedChatManagement, present) + })) + + result.append(SettingsSearchableItem(id: .premium(11), title: strings.Premium_Badge, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_Badge), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.profileBadge, present) + })) + + result.append(SettingsSearchableItem(id: .premium(12), title: strings.Premium_Avatar, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_Avatar), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.animatedUserpics, present) + })) + + result.append(SettingsSearchableItem(id: .premium(13), title: strings.Premium_AppIcon, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_AppIcon), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in + presentDemo(.appIcons, present) + })) + + return result +} + private func callSearchableItems(context: AccountContext) -> [SettingsSearchableItem] { let icon: SettingsSearchableItemIcon = .calls let strings = context.sharedContext.currentPresentationData.with { $0 }.strings @@ -532,9 +645,6 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac SettingsSearchableItem(id: .privacy(8), title: strings.PrivacySettings_TwoStepAuth, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_TwoStepAuth), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in present(.push, twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: true, data: nil))) }), - activeSessionsContext == nil ? nil : SettingsSearchableItem(id: .privacy(9), title: strings.Settings_Devices, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions) + [strings.PrivacySettings_AuthSessions], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in - present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext!, webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: false)) - }), webSessionsContext == nil ? nil : SettingsSearchableItem(id: .privacy(10), title: strings.PrivacySettings_WebSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext ?? context.engine.privacy.activeSessions(), webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: true)) }), @@ -724,6 +834,14 @@ private func languageSearchableItems(context: AccountContext, localizations: [Lo })) index += 1 } + + items.append(SettingsSearchableItem(id: .language(1000), title: strings.Localization_ShowTranslate, alternate: synonyms(strings.SettingsSearch_Synonyms_Language_ShowTranslateButton), icon: icon, breadcrumbs: [strings.Settings_AppLanguage], present: { context, _, present in + present(.push, LocalizationListController(context: context)) + })) + items.append(SettingsSearchableItem(id: .language(1001), title: strings.Localization_DoNotTranslate, alternate: synonyms(strings.SettingsSearch_Synonyms_Language_DoNotTranslate), icon: icon, breadcrumbs: [strings.Settings_AppLanguage], present: { context, _, present in + present(.push, LocalizationListController(context: context)) + })) + return items } @@ -838,6 +956,9 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList }) allItems.append(savedMessages) + let devicesItems = devicesSearchableItems(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: activeWebSessionsContext) + allItems.append(contentsOf: devicesItems) + let callItems = callSearchableItems(context: context) allItems.append(contentsOf: callItems) @@ -867,6 +988,9 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList let languageItems = languageSearchableItems(context: context, localizations: localizations) allItems.append(contentsOf: languageItems) + let premiumItems = premiumSearchableItems(context: context) + allItems.append(contentsOf: premiumItems) + if watchAppInstalled { let watch = SettingsSearchableItem(id: .watch(0), title: strings.Settings_AppleWatch, alternate: synonyms(strings.SettingsSearch_Synonyms_Watch), icon: .watch, breadcrumbs: [], present: { context, _, present in present(.push, watchSettingsController(context: context)) @@ -880,7 +1004,7 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList }) allItems.append(passport) } - + let support = SettingsSearchableItem(id: .support(0), title: strings.Settings_Support, alternate: synonyms(strings.SettingsSearch_Synonyms_Support), icon: .support, breadcrumbs: [], present: { context, _, present in let _ = (context.engine.peers.supportPeerId() |> deliverOnMainQueue).start(next: { peerId in diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index 0878b6a2e6..96bd2c9301 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -11,6 +11,7 @@ import AccountContext import ShareController import UndoUI import InviteLinksUI +import TextFormat private final class UsernameSetupControllerArguments { let account: Account @@ -207,24 +208,26 @@ private enum UsernameSetupEntry: ItemListNodeEntry { arguments.shareLink() } }) - case let .publicLinkStatus(theme, _, status, text): + case let .publicLinkStatus(_, _, status, text): var displayActivity = false - let string: NSAttributedString + let textColor: ItemListActivityTextItem.TextColor switch status { - case .invalidFormat: - string = NSAttributedString(string: text, textColor: theme.list.freeTextErrorColor) - case let .availability(availability): - switch availability { - case .available: - string = NSAttributedString(string: text, textColor: theme.list.freeTextSuccessColor) - case .invalid, .taken: - string = NSAttributedString(string: text, textColor: theme.list.freeTextErrorColor) - } - case .checking: - string = NSAttributedString(string: text, textColor: theme.list.freeTextColor) - displayActivity = true + case .invalidFormat: + textColor = .destructive + case let .availability(availability): + switch availability { + case .available: + textColor = .constructive + case .purchaseAvailable: + textColor = .generic + case .invalid, .taken: + textColor = .destructive + } + case .checking: + textColor = .generic + displayActivity = true } - return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: string, sectionId: self.section) + return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in }, sectionId: self.section) case let .additionalLinkHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .additionalLink(_, link, _): @@ -328,6 +331,14 @@ private func usernameSetupControllerEntries(presentationData: PresentationData, statusText = presentationData.strings.Username_InvalidCharacters case .taken: statusText = presentationData.strings.Username_InvalidTaken + case .purchaseAvailable: + var markdownString = presentationData.strings.Username_UsernamePurchaseAvailable + let entities = generateTextEntities(markdownString, enabledTypes: [.mention]) + if let entity = entities.first { + markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound)) + markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound)) + } + statusText = markdownString } case .checking: statusText = presentationData.strings.Username_CheckingUsername diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift index e68255f7ae..ddc5739b26 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift @@ -17,6 +17,7 @@ public enum AddressNameAvailability: Equatable { case available case invalid case taken + case purchaseAvailable } public enum AddressNameDomain { @@ -33,7 +34,7 @@ func _internal_checkAddressNameFormat(_ value: String, canEmpty: Bool = false) - if index == 0 { return .startsWithUnderscore } else if index == length - 1 { - return length < 5 ? .tooShort : .endsWithUnderscore + return length < 4 ? .tooShort : .endsWithUnderscore } } if index == 0 && char >= "0" && char <= "9" { @@ -45,7 +46,7 @@ func _internal_checkAddressNameFormat(_ value: String, canEmpty: Bool = false) - index += 1 } - if length < 5 && (!canEmpty || length != 0) { + if length < 4 && (!canEmpty || length != 0) { return .tooShort } return nil @@ -65,7 +66,11 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma } } |> `catch` { error -> Signal in - return .single(.invalid) + if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" { + return .single(.purchaseAvailable) + } else { + return .single(.invalid) + } } case let .peer(peerId): if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) { @@ -79,7 +84,11 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma } } |> `catch` { error -> Signal in - return .single(.invalid) + if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" { + return .single(.purchaseAvailable) + } else { + return .single(.invalid) + } } } else if peerId.namespace == Namespaces.Peer.CloudGroup { return account.network.request(Api.functions.channels.checkUsername(channel: .inputChannelEmpty, username: name)) @@ -92,7 +101,11 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma } } |> `catch` { error -> Signal in - return .single(.invalid) + if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" { + return .single(.purchaseAvailable) + } else { + return .single(.invalid) + } } } else { return .single(.invalid) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2624561041..38fa38fbf6 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1057,6 +1057,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var tip: ContextController.Tip? if tip == nil { + let isAd = message.adAttribute != nil + var isAction = false for media in message.media { if media is TelegramMediaAction { @@ -1064,7 +1066,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G break } } - if strongSelf.presentationInterfaceState.copyProtectionEnabled && !isAction { + if strongSelf.presentationInterfaceState.copyProtectionEnabled && !isAction && !isAd { if case .scheduledMessages = strongSelf.subject { } else { var isChannel = false @@ -1075,7 +1077,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } else { let numberOfComponents = message.text.components(separatedBy: CharacterSet.whitespacesAndNewlines).count - let displayTextSelectionTip = numberOfComponents >= 3 && !message.text.isEmpty && chatTextSelectionTips < 3 + let displayTextSelectionTip = numberOfComponents >= 3 && !message.text.isEmpty && chatTextSelectionTips < 3 && !isAd if displayTextSelectionTip { let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start() tip = .textSelection @@ -1088,11 +1090,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } actions.context = strongSelf.context - actions.animationCache = strongSelf.controllerInteraction?.presentationContext.animationCache - - //let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) - + if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty { actions.reactionItems = topReactions.map(ReactionContextItem.reaction) actions.selectedReactionItems = selectedReactions.reactions