diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 91729a1da9..7a950aff16 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12132,3 +12132,5 @@ Sorry for the inconvenience."; "Channel.AdminLogFilter.Section.SettingsGroup" = "Group Settings"; "Channel.AdminLogFilter.Section.SettingsChannel" = "Channel Settings"; "Channel.AdminLogFilter.Section.Messages" = "Messages"; + +"Premium.Gift.ContactSelection.AddBirthday" = "Add Your Birthday"; diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index 5de19b6719..8bbc7dd68d 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -93,7 +93,7 @@ public final class ContactMultiselectionControllerParams { public let context: AccountContext public let updatedPresentationData: (initial: PresentationData, signal: Signal)? public let mode: ContactMultiselectionControllerMode - public let options: [ContactListAdditionalOption] + public let options: Signal<[ContactListAdditionalOption], NoError> public let filters: [ContactListFilter] public let onlyWriteable: Bool public let isGroupInvitation: Bool @@ -105,7 +105,7 @@ public final class ContactMultiselectionControllerParams { public let openProfile: ((EnginePeer) -> Void)? public let sendMessage: ((EnginePeer) -> Void)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ContactMultiselectionControllerMode, options: Signal<[ContactListAdditionalOption], NoError> = .single([]), filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) { self.context = context self.updatedPresentationData = updatedPresentationData self.mode = mode diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 662f8cfe9c..cdb77cad55 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -760,7 +760,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f selectedChats: Set(filterData.includePeers.peers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters - )), options: [], filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in + )), filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in if count >= premiumLimit { let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: min(premiumLimit, count), action: { return true @@ -913,7 +913,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex selectedChats: Set(filterData.excludePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters - )), options: [], filters: [], alwaysEnabled: true, limit: 100)) + )), filters: [], alwaysEnabled: true, limit: 100)) controller.navigationPresentation = .modal let _ = (controller.result |> take(1) diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index fbfffde7a1..25970a199a 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -1770,25 +1770,32 @@ public final class ContactListNode: ASDisplayNode { if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty { isEmpty = true } + let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers.map { $0.peer }, topPeersPresentation: displayTopPeers, interaction: interaction) let previous = previousEntries.swap(entries) let previousSelection = previousSelectionState.swap(selectionState) let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds) var hadPermissionInfo = false + var previousOptionsCount = 0 if let previous = previous { for entry in previous { if case .permissionInfo = entry { hadPermissionInfo = true - break + } + if case .option = entry { + previousOptionsCount += 1 } } } var hasPermissionInfo = false + var optionsCount = 0 for entry in entries { if case .permissionInfo = entry { hasPermissionInfo = true - break + } + if case .option = entry { + optionsCount += 1 } } @@ -1799,6 +1806,8 @@ public final class ContactListNode: ASDisplayNode { animation = .insertion } else if hadPermissionInfo != hasPermissionInfo { animation = .insertion + } else if optionsCount < previousOptionsCount { + animation = .insertion } else { animation = .none } diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index a585475f19..0a849c07b4 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -313,7 +313,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { } } else if let file = content.file { if content.type == "telegram_background" { - if let wallpaper = parseWallpaperUrl(content.url) { + if let wallpaper = parseWallpaperUrl(sharedContext: item.context.sharedContext, url: content.url) { switch wallpaper { case let .slug(slug, _, colors, intensity, angle): previewWallpaperFileReference = .message(message: MessageReference(message), media: file) diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index 5fa0cbcae1..752e0a5f37 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -38,6 +38,7 @@ private struct LocationViewTransaction { let updates: [ListViewUpdateItem] let gotTravelTimes: Bool let count: Int + let animated: Bool } private enum LocationViewEntryId: Hashable { @@ -197,14 +198,14 @@ private enum LocationViewEntry: Comparable, Identifiable { } } -private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?, gotTravelTimes: Bool) -> LocationViewTransaction { +private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?, gotTravelTimes: Bool, animated: Bool) -> LocationViewTransaction { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) } - return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes, count: toEntries.count) + return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes, count: toEntries.count, animated: animated) } enum LocationViewLocation: Equatable { @@ -573,7 +574,27 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan let previousState = previousState.swap(state) let previousHadTravelTimes = previousHadTravelTimes.swap(!travelTimes.isEmpty) - let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction, gotTravelTimes: !travelTimes.isEmpty && !previousHadTravelTimes) + var animated = false + var previousActionsCount = 0 + var actionsCount = 0 + if let previousEntries { + for entry in previousEntries { + if case .toggleLiveLocation = entry { + previousActionsCount += 1 + } + } + } + for entry in entries { + if case .toggleLiveLocation = entry { + actionsCount += 1 + } + } + + if actionsCount < previousActionsCount { + animated = true + } + + let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction, gotTravelTimes: !travelTimes.isEmpty && !previousHadTravelTimes, animated: animated) strongSelf.enqueueTransition(transition) strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: state.trackingMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: proximityNotification, animated: false) @@ -787,7 +808,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan scrollToItem = nil } - let options = ListViewDeleteAndInsertOptions() + var options = ListViewDeleteAndInsertOptions() + if transition.animated { + options.insert(.AnimateInsertion) + } self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in }) } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index cbc787a359..282c219f2f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -518,7 +518,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio ) |> deliverOnMainQueue).start(next: { chatPeer, exportedInvitation, members in let disabledIds = members?.compactMap({$0.peer.id}) ?? [] - let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: [], filters: [.excludeSelf, .disable(disabledIds)], onlyWriteable: true, isGroupInvitation: true)) + let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), filters: [.excludeSelf, .disable(disabledIds)], onlyWriteable: true, isGroupInvitation: true)) addMembersDisposable.set(( contactsController.result diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 7b768293f3..87898ed215 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -2276,7 +2276,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta nextImpl = { [weak controller] in if let controller = controller { if case .initialSetup = mode { - let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .channelCreation, options: [], onlyWriteable: true)) + let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .channelCreation, onlyWriteable: true)) (controller.navigationController as? NavigationController)?.replaceAllButRootController(selectionController, animated: true) let _ = (selectionController.result |> deliverOnMainQueue).start(next: { [weak selectionController] result in diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift index 56fa4ceb78..48ea6da9db 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift @@ -310,10 +310,10 @@ public enum ProxySettingsControllerMode { public func proxySettingsController(context: AccountContext, mode: ProxySettingsControllerMode = .default) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - return proxySettingsController(accountManager: context.sharedContext.accountManager, context: context, postbox: context.account.postbox, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData) + return proxySettingsController(accountManager: context.sharedContext.accountManager, sharedContext: context.sharedContext, context: context, postbox: context.account.postbox, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData) } -public func proxySettingsController(accountManager: AccountManager, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal) -> ViewController { +public func proxySettingsController(accountManager: AccountManager, sharedContext: SharedAccountContext, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal) -> ViewController { var pushControllerImpl: ((ViewController) -> Void)? var dismissImpl: (() -> Void)? let stateValue = Atomic(value: ProxySettingsControllerState()) @@ -341,7 +341,7 @@ public func proxySettingsController(accountManager: AccountManager ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - return proxyServerSettingsController(context: context, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, accountManager: context.sharedContext.accountManager, network: context.account.network, currentSettings: currentSettings) + return proxyServerSettingsController(sharedContext: context.sharedContext, context: context, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, accountManager: context.sharedContext.accountManager, network: context.account.network, currentSettings: currentSettings) } -func proxyServerSettingsController(context: AccountContext? = nil, presentationData: PresentationData, updatedPresentationData: Signal, accountManager: AccountManager, network: Network, currentSettings: ProxyServerSettings?) -> ViewController { +func proxyServerSettingsController(sharedContext: SharedAccountContext, context: AccountContext? = nil, presentationData: PresentationData, updatedPresentationData: Signal, accountManager: AccountManager, network: Network, currentSettings: ProxyServerSettings?) -> ViewController { var currentMode: ProxyServerSettingsControllerMode = .socks5 var currentUsername: String? var currentPassword: String? @@ -285,7 +285,7 @@ func proxyServerSettingsController(context: AccountContext? = nil, presentationD currentMode = .mtp } } else { - if let proxy = parseProxyUrl(UIPasteboard.general.string ?? "") { + if let proxy = parseProxyUrl(sharedContext: sharedContext, url: UIPasteboard.general.string ?? "") { if let secret = proxy.secret, let parsedSecret = MTProxySecret.parseData(secret) { pasteboardSettings = ProxyServerSettings(host: proxy.host, port: proxy.port, connection: .mtp(secret: parsedSecret.serialize())) } else { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift index ca723fc519..203bc2f9c3 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift @@ -315,7 +315,6 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, chatListFilters: nil, displayAutoremoveTimeout: true )), - options: [], filters: [.excludeSelf], isPeerEnabled: { peer in var canManage = false diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index b08f6a4d62..4d02db8394 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -1141,7 +1141,7 @@ public func selectivePrivacySettingsController( onlyUsers: false, disableChannels: true, disableBots: false - )), options: [], filters: [.excludeSelf])) + )), filters: [.excludeSelf])) addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] result in diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index 3f7fc5c00b..81ecb8434b 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -355,7 +355,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri onlyUsers: false, disableChannels: true, disableBots: false - )), options: [], alwaysEnabled: true)) + )), alwaysEnabled: true)) addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] result in diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 66f0bd7d58..7f92d7df97 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -93,8 +93,7 @@ final class AccountTaskManager { tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .emoji).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .status).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .avatar).start()) - tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .chatStickers).start()) - tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .greetingStickers).start()) + tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .combinedChatStickers).start()) tasks.add(managedSynchronizeAttachMenuBots(accountPeerId: self.accountPeerId, postbox: self.stateManager.postbox, network: self.stateManager.network, force: true).start()) tasks.add(managedSynchronizeNotificationSoundList(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedChatListFilters(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start()) diff --git a/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift b/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift index a6d3722352..c0af005c09 100644 --- a/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift +++ b/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift @@ -8,8 +8,7 @@ public final class EmojiSearchCategories: Equatable, Codable { case emoji = 0 case status = 1 case avatar = 2 - case chatStickers = 3 - case greetingStickers = 4 + case combinedChatStickers = 3 } public struct Group: Codable, Equatable { @@ -17,16 +16,25 @@ public final class EmojiSearchCategories: Equatable, Codable { case id case title case identifiers + case kind + } + + public enum Kind: Int32, Codable { + case generic + case greeting + case premium } public var id: Int64 public var title: String public var identifiers: [String] + public var kind: Kind - public init(id: Int64, title: String, identifiers: [String]) { + public init(id: Int64, title: String, identifiers: [String], kind: Kind) { self.id = id self.title = title self.identifiers = identifiers + self.kind = kind } public init(from decoder: Decoder) throws { @@ -35,6 +43,16 @@ public final class EmojiSearchCategories: Equatable, Codable { self.id = try container.decode(Int64.self, forKey: .id) self.title = try container.decode(String.self, forKey: .title) self.identifiers = try container.decode([String].self, forKey: .identifiers) + self.kind = ((try container.decodeIfPresent(Int32.self, forKey: .kind)).flatMap(Kind.init(rawValue:))) ?? .generic + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.id, forKey: .id) + try container.encode(self.title, forKey: .title) + try container.encode(self.identifiers, forKey: .identifiers) + try container.encode(self.kind.rawValue, forKey: .kind) } } @@ -130,9 +148,8 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network, |> `catch` { _ -> Signal in return .single(.emojiGroupsNotModified) } - //TODO:localize - case .chatStickers, .greetingStickers: - signal = network.request(Api.functions.messages.getEmojiGroups(hash: current?.hash ?? 0)) + case .combinedChatStickers: + signal = network.request(Api.functions.messages.getEmojiStickerGroups(hash: current?.hash ?? 0)) |> `catch` { _ -> Signal in return .single(.emojiGroupsNotModified) } @@ -150,10 +167,24 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network, case let .emojiGroup(title, iconEmojiId, emoticons): return EmojiSearchCategories.Group( id: iconEmojiId, - title: title, identifiers: emoticons + title: title, + identifiers: emoticons, + kind: .generic + ) + case let .emojiGroupGreeting(title, iconEmojiId, emoticons): + return EmojiSearchCategories.Group( + id: iconEmojiId, + title: title, + identifiers: emoticons, + kind: .greeting + ) + case let .emojiGroupPremium(title, iconEmojiId): + return EmojiSearchCategories.Group( + id: iconEmojiId, + title: title, + identifiers: [], + kind: .premium ) - case .emojiGroupGreeting, .emojiGroupPremium: - return nil } } ) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 36f9827a9a..1933796a33 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1193,21 +1193,23 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } recognizer.highlight = { [weak self] point in if let strongSelf = self { - if let replyInfoNode = strongSelf.replyInfoNode { - var translatedPoint: CGPoint? - let convertedNodeFrame = replyInfoNode.view.convert(replyInfoNode.bounds, to: strongSelf.view) - if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) { - translatedPoint = strongSelf.view.convert(point, to: replyInfoNode.view) + if strongSelf.selectionNode == nil { + if let replyInfoNode = strongSelf.replyInfoNode { + var translatedPoint: CGPoint? + let convertedNodeFrame = replyInfoNode.view.convert(replyInfoNode.bounds, to: strongSelf.view) + if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) { + translatedPoint = strongSelf.view.convert(point, to: replyInfoNode.view) + } + replyInfoNode.updateTouchesAtPoint(translatedPoint) } - replyInfoNode.updateTouchesAtPoint(translatedPoint) - } - if let forwardInfoNode = strongSelf.forwardInfoNode { - var translatedPoint: CGPoint? - let convertedNodeFrame = forwardInfoNode.view.convert(forwardInfoNode.bounds, to: strongSelf.view) - if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) { - translatedPoint = strongSelf.view.convert(point, to: forwardInfoNode.view) + if let forwardInfoNode = strongSelf.forwardInfoNode { + var translatedPoint: CGPoint? + let convertedNodeFrame = forwardInfoNode.view.convert(forwardInfoNode.bounds, to: strongSelf.view) + if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) { + translatedPoint = strongSelf.view.convert(point, to: forwardInfoNode.view) + } + forwardInfoNode.updateTouchesAtPoint(translatedPoint) } - forwardInfoNode.updateTouchesAtPoint(translatedPoint) } for contentNode in strongSelf.contentNodes { var translatedPoint: CGPoint? diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift index fc64728f86..7347c9ce43 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift @@ -85,6 +85,8 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { private var highlightColor: UIColor? private var linkHighlightingNode: LinkHighlightingNode? + private var previousPeer: Peer? + public var openPsa: ((String, ASDisplayNode) -> Void)? override public init() { @@ -114,6 +116,32 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { } } + public func getBoundingRects() -> [CGRect] { + var initialRects: [CGRect] = [] + let addRects: (TextNode, CGPoint, CGFloat) -> Void = { textNode, offset, additionalWidth in + guard let cachedLayout = textNode.cachedLayout else { + return + } + for rect in cachedLayout.linesRects() { + var rect = rect + rect.size.width += rect.origin.x + additionalWidth + rect.origin.x = 0.0 + initialRects.append(rect.offsetBy(dx: offset.x, dy: offset.y)) + } + } + + let offsetY: CGFloat = -12.0 + if let titleNode = self.titleNode { + addRects(titleNode, CGPoint(x: titleNode.frame.minX, y: offsetY + titleNode.frame.minY), 0.0) + + if let nameNode = self.nameNode { + addRects(nameNode, CGPoint(x: titleNode.frame.minX, y: offsetY + nameNode.frame.minY), nameNode.frame.minX - titleNode.frame.minX) + } + } + + return initialRects + } + public func updateTouchesAtPoint(_ point: CGPoint?) { var isHighlighted = false if point != nil { @@ -167,14 +195,19 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode) let nameNodeLayout = TextNode.asyncLayout(maybeNode?.nameNode) + let previousPeer = maybeNode?.previousPeer + return { context, presentationData, strings, type, peer, authorName, psaType, storyData, constrainedSize in + let originalPeer = peer + let peer = peer ?? previousPeer + let fontSize = floor(presentationData.fontSize.baseDisplaySize * 14.0 / 17.0) let prefixFont = Font.regular(fontSize) let peerFont = Font.medium(fontSize) let peerString: String if let peer = peer { - if let authorName = authorName { + if let authorName = authorName, originalPeer === peer { peerString = "\(EnginePeer(peer).displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)) (\(authorName))" } else { peerString = EnginePeer(peer).displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) @@ -343,18 +376,16 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth, height: constrainedSize.height), alignment: .natural, cutout: cutout, insets: UIEdgeInsets())) + var authorAvatarInset: CGFloat = 0.0 + authorAvatarInset = 20.0 + var nameLayoutAndApply: (TextNodeLayout, () -> TextNode)? if let authorString { - nameLayoutAndApply = nameNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: authorString, font: peerFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth, height: constrainedSize.height), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + nameLayoutAndApply = nameNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: authorString, font: peer != nil ? peerFont : prefixFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth - authorAvatarInset, height: constrainedSize.height), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) } let titleAuthorSpacing: CGFloat = 0.0 - var authorAvatarInset: CGFloat = 0.0 - if peer != nil { - authorAvatarInset = 20.0 - } - let resultSize: CGSize if let nameLayoutAndApply { resultSize = CGSize( @@ -379,6 +410,8 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { node.theme = presentationData.theme.theme node.highlightColor = titleColor.withMultipliedAlpha(0.1) + node.previousPeer = peer + let titleNode = titleApply() titleNode.displaysAsynchronously = !presentationData.isPreview @@ -398,7 +431,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { } nameNode.frame = CGRect(origin: CGPoint(x: leftOffset + authorAvatarInset, y: titleLayout.size.height + titleAuthorSpacing), size: nameLayout.size) - if let peer, authorAvatarInset != 0.0 { + if authorAvatarInset != 0.0 { let avatarNode: AvatarNode if let current = node.avatarNode { avatarNode = current @@ -410,7 +443,13 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { let avatarSize = CGSize(width: 16.0, height: 16.0) avatarNode.frame = CGRect(origin: CGPoint(x: leftOffset, y: titleLayout.size.height + titleAuthorSpacing), size: avatarSize) avatarNode.updateSize(size: avatarSize) - avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize) + if let peer { + avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize) + } else if let authorName, !authorName.isEmpty { + avatarNode.setCustomLetters([String(authorName[authorName.startIndex])]) + } else { + avatarNode.setCustomLetters([" "]) + } } else { if let avatarNode = node.avatarNode { node.avatarNode = nil diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index d52eb4e31c..d4fa8b85bc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -58,6 +58,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { private var replyBackgroundContent: WallpaperBubbleBackgroundNode? private var forwardInfoNode: ChatMessageForwardInfoNode? private var forwardBackgroundContent: WallpaperBubbleBackgroundNode? + private var forwardBackgroundMaskNode: LinkHighlightingNode? private var actionButtonsNode: ChatMessageActionButtonsNode? private var reactionButtonsNode: ChatMessageReactionButtonsNode? @@ -1110,7 +1111,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { var forwardBackgroundFrame: CGRect? if let forwardAreaFrame { - forwardBackgroundFrame = forwardAreaFrame.insetBy(dx: -6.0, dy: -3.0) + var forwardBackgroundFrameValue = forwardAreaFrame.insetBy(dx: -6.0, dy: -3.0) + forwardBackgroundFrameValue.size.height += 2.0 + forwardBackgroundFrame = forwardBackgroundFrameValue } var replyBackgroundFrame: CGRect? @@ -1147,7 +1150,17 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } if let backgroundContent = strongSelf.forwardBackgroundContent, let forwardBackgroundFrame { - backgroundContent.cornerRadius = 4.0 + let forwardBackgroundMaskNode: LinkHighlightingNode + if let current = strongSelf.forwardBackgroundMaskNode { + forwardBackgroundMaskNode = current + } else { + forwardBackgroundMaskNode = LinkHighlightingNode(color: .black) + forwardBackgroundMaskNode.inset = 4.0 + forwardBackgroundMaskNode.outerRadius = 12.0 + strongSelf.forwardBackgroundMaskNode = forwardBackgroundMaskNode + backgroundContent.view.mask = forwardBackgroundMaskNode.view + } + backgroundContent.frame = forwardBackgroundFrame if let (rect, containerSize) = strongSelf.absoluteRect { var backgroundFrame = backgroundContent.frame @@ -1155,6 +1168,28 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { backgroundFrame.origin.y += rect.minY backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) } + + if let forwardInfoNode = strongSelf.forwardInfoNode { + forwardBackgroundMaskNode.frame = backgroundContent.bounds.offsetBy(dx: forwardInfoNode.frame.minX - backgroundContent.frame.minX, dy: forwardInfoNode.frame.minY - backgroundContent.frame.minY) + var backgroundRects = forwardInfoNode.getBoundingRects() + for i in 0 ..< backgroundRects.count { + backgroundRects[i].origin.x -= 2.0 + backgroundRects[i].size.width += 4.0 + } + for i in 0 ..< backgroundRects.count { + if i != 0 { + if abs(backgroundRects[i - 1].maxX - backgroundRects[i].maxX) < 16.0 { + let maxMaxX = max(backgroundRects[i - 1].maxX, backgroundRects[i].maxX) + backgroundRects[i - 1].size.width = max(0.0, maxMaxX - backgroundRects[i - 1].origin.x) + backgroundRects[i].size.width = max(0.0, maxMaxX - backgroundRects[i].origin.x) + } + } + } + forwardBackgroundMaskNode.updateRects(backgroundRects) + } + } else if let forwardBackgroundMaskNode = strongSelf.forwardBackgroundMaskNode { + strongSelf.forwardBackgroundMaskNode = nil + forwardBackgroundMaskNode.view.removeFromSuperview() } let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index cf5f1c7dcc..e05cc37df0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -315,7 +315,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent var colors: [UInt32] = [] var rotation: Int32? var intensity: Int32? - if let wallpaper = parseWallpaperUrl(webpage.url), case let .slug(_, _, colorsValue, intensityValue, rotationValue) = wallpaper { + if let wallpaper = parseWallpaperUrl(sharedContext: item.context.sharedContext, url: webpage.url), case let .slug(_, _, colorsValue, intensityValue, rotationValue) = wallpaper { colors = colorsValue rotation = rotationValue intensity = intensityValue @@ -353,7 +353,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent if type == "telegram_background" { var colors: [UInt32] = [] var rotation: Int32? - if let wallpaper = parseWallpaperUrl(webpage.url) { + if let wallpaper = parseWallpaperUrl(sharedContext: item.context.sharedContext, url: webpage.url) { if case let .color(color) = wallpaper { colors = [color.rgb] } else if case let .gradient(colorsValue, rotationValue) = wallpaper { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index b5b8755e77..6c95270a34 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1663,10 +1663,36 @@ public extension EmojiPagerContentComponent { switch subject { case .groupPhotoEmojiSelection, .profilePhotoEmojiSelection: searchCategories = context.engine.stickers.emojiSearchCategories(kind: .avatar) - case .chatStickers: - searchCategories = context.engine.stickers.emojiSearchCategories(kind: .chatStickers) - case .greetingStickers: - searchCategories = context.engine.stickers.emojiSearchCategories(kind: .greetingStickers) + case .chatStickers, .greetingStickers: + searchCategories = context.engine.stickers.emojiSearchCategories(kind: .combinedChatStickers) + |> map { result -> EmojiSearchCategories? in + guard let result else { + return nil + } + + var groups: [EmojiSearchCategories.Group] = [] + groups = result.groups + if case .greetingStickers = subject { + if let index = groups.firstIndex(where: { group in + return group.kind == .greeting + }) { + let group = groups.remove(at: index) + groups.insert(group, at: 0) + } + } else if case .chatStickers = subject { + if let index = groups.firstIndex(where: { group in + return group.kind == .premium + }) { + let group = groups.remove(at: index) + groups.append(group) + } + } + + return EmojiSearchCategories( + hash: result.hash, + groups: groups + ) + } } return combineLatest( diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 0e9db22acf..0fb1d0616c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -13097,7 +13097,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa }, clearHighlightAutomatically: true)) } - let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: options, filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true)) + let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: .single(options), filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true)) contactsController.navigationPresentation = .modal confirmationImpl = { [weak contactsController] peerId in diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift index 9035470244..2db55b4b3b 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift @@ -384,7 +384,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: nil, onlyUsers: true - )), options: [], filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in + )), filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in })) controller.navigationPresentation = .modal diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift index 8b26c01139..cd44260599 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift @@ -242,7 +242,7 @@ final class BusinessRecipientListScreenComponent: Component { additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: nil, onlyUsers: true - )), options: [], filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in + )), filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in })) controller.navigationPresentation = .modal diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index df1e5706d5..27840d2f14 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -2165,13 +2165,13 @@ private func extractAccountManagerState(records: AccountRecordsView deliverOnMainQueue).start(next: { _, context, authContext in - if let authContext = authContext, let confirmationCode = parseConfirmationCodeUrl(url) { + |> deliverOnMainQueue).start(next: { sharedContext, context, authContext in + if let authContext = authContext, let confirmationCode = parseConfirmationCodeUrl(sharedContext: sharedContext, url: url) { authContext.rootController.applyConfirmationCode(confirmationCode) } else if let context = context { context.openUrl(url) } else if let authContext = authContext { - if let proxyData = parseProxyUrl(url) { + if let proxyData = parseProxyUrl(sharedContext: sharedContext, url: url) { authContext.rootController.view.endEditing(true) let presentationData = authContext.sharedContext.currentPresentationData.with { $0 } let controller = ProxyServerActionSheetController(presentationData: presentationData, accountManager: authContext.sharedContext.accountManager, postbox: authContext.account.postbox, network: authContext.account.network, server: proxyData, updatedPresentationData: nil) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index fa8c96f733..33236f8406 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4156,7 +4156,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .user = peerType, maxQuantity > 1 { let presentationData = self.presentationData var reachedLimitImpl: ((Int32) -> Void)? - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, options: [], isPeerEnabled: { peer in + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, isPeerEnabled: { peer in if case let .user(user) = peer, user.botInfo == nil { return true } else { diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 229268d614..8f6a31dd57 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -856,7 +856,7 @@ extension ChatControllerImpl { } }, recognizedQRCode: { [weak self] code in if let strongSelf = self { - if let (host, port, username, password, secret) = parseProxyUrl(code) { + if let (host, port, username, password, secret) = parseProxyUrl(sharedContext: strongSelf.context.sharedContext, url: code) { strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil) } } @@ -1697,7 +1697,7 @@ extension ChatControllerImpl { } }, recognizedQRCode: { [weak self] code in if let strongSelf = self { - if let (host, port, username, password, secret) = parseProxyUrl(code) { + if let (host, port, username, password, secret) = parseProxyUrl(sharedContext: strongSelf.context.sharedContext, url: code) { strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil) } } diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index 4d0571e71f..d62b23f868 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -124,7 +124,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { self.contactsNode.openCreateNewGroup = { [weak self] in if let strongSelf = self { - let controller = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .groupCreation, options: [], onlyWriteable: true)) + let controller = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .groupCreation, onlyWriteable: true)) (strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: { [weak self] in if let strongSelf = self { strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index b4433818ba..5b1f86bbe6 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -81,7 +81,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection private var limitsConfiguration: LimitsConfiguration? private var limitsConfigurationDisposable: Disposable? private var initialPeersDisposable: Disposable? - private let options: [ContactListAdditionalOption] + private let options: Signal<[ContactListAdditionalOption], NoError> private let filters: [ContactListFilter] private let onlyWriteable: Bool private let isGroupInvitation: Bool diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index 93377b49e7..93863b9988 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -82,7 +82,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { private let onlyWriteable: Bool private let isGroupInvitation: Bool - init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: [ContactListAdditionalOption], filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) { + init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: Signal<[ContactListAdditionalOption], NoError>, filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) { self.navigationBar = navigationBar self.context = context @@ -235,7 +235,13 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } else { displayTopPeers = .none } - let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, onlyWriteable: onlyWriteable, isGroupInvitation: isGroupInvitation, selectionState: ContactListNodeGroupSelectionState()) + + let presentation: Signal = options + |> map { options in + return .natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers) + } + + let contactListNode = ContactListNode(context: context, presentation: presentation, filters: filters, onlyWriteable: onlyWriteable, isGroupInvitation: isGroupInvitation, selectionState: ContactListNodeGroupSelectionState()) self.contentNode = .contacts(contactListNode) if !selectedPeers.isEmpty { diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index f6b415e8c1..584574c71a 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -25,8 +25,8 @@ public struct ParsedSecureIdUrl { public let opaqueNonce: Data } -public func parseProxyUrl(_ url: URL) -> ProxyServerSettings? { - guard let proxy = parseProxyUrl(url.absoluteString) else { +public func parseProxyUrl(sharedContext: SharedAccountContext, url: URL) -> ProxyServerSettings? { + guard let proxy = parseProxyUrl(sharedContext: sharedContext, url: url.absoluteString) else { return nil } if let secret = proxy.secret, let _ = MTProxySecret.parseData(secret) { @@ -107,14 +107,14 @@ public func parseSecureIdUrl(_ url: URL) -> ParsedSecureIdUrl? { return nil } -public func parseConfirmationCodeUrl(_ url: URL) -> Int? { +public func parseConfirmationCodeUrl(sharedContext: SharedAccountContext, url: URL) -> Int? { if url.pathComponents.count == 3 && url.pathComponents[1].lowercased() == "login" { if let code = Int(url.pathComponents[2]) { return code } } if url.scheme == "tg" { - if let host = url.host, let query = url.query, let parsedUrl = parseInternalUrl(query: host + "?" + query) { + if let host = url.host, let query = url.query, let parsedUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query) { switch parsedUrl { case let .confirmationCode(code): return code @@ -168,7 +168,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur return } - guard let parsedUrl = parsedUrlValue else { + guard var parsedUrl = parsedUrlValue else { return } @@ -249,6 +249,20 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur |> deliverOnMainQueue).startStandalone(next: handleResolvedUrl) } + if context.sharedContext.immediateExperimentalUISettings.browserExperiment { + if let scheme = parsedUrl.scheme, (scheme == "tg" || scheme == context.sharedContext.applicationBindings.appSpecificScheme) { + if parsedUrl.host == "ipfs" { + if let value = URL(string: "ipfs:/" + parsedUrl.path) { + parsedUrl = value + } + } + } else if let scheme = parsedUrl.scheme, scheme == "https", parsedUrl.host == "t.me", parsedUrl.path.hasPrefix("/ipfs/") { + if let value = URL(string: "ipfs://" + String(parsedUrl.path[parsedUrl.path.index(parsedUrl.path.startIndex, offsetBy: "/ipfs/".count)...])) { + parsedUrl = value + } + } + } + if let scheme = parsedUrl.scheme, (scheme == "tg" || scheme == context.sharedContext.applicationBindings.appSpecificScheme) { var convertedUrl: String? if let query = parsedUrl.query { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 06bf87b584..ff9467d3dd 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2165,16 +2165,26 @@ public final class SharedAccountContextImpl: SharedAccountContext { mode = .premiumGifting(birthdays: nil, selectToday: false) } - var contactOptions: [ContactListAdditionalOption] = [] + let contactOptions: Signal<[ContactListAdditionalOption], NoError> if currentBirthdays != nil || "".isEmpty { - contactOptions = [ContactListAdditionalOption( - title: "Add Your Birthday", - icon: .generic(UIImage(bundleImageName: "Contact List/AddBirthdayIcon")!), - action: { - presentBirthdayPickerImpl?() - }, - clearHighlightAutomatically: true - )] + contactOptions = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)) + |> map { birthday in + if birthday == nil { + return [ContactListAdditionalOption( + title: presentationData.strings.Premium_Gift_ContactSelection_AddBirthday, + icon: .generic(UIImage(bundleImageName: "Contact List/AddBirthdayIcon")!), + action: { + presentBirthdayPickerImpl?() + }, + clearHighlightAutomatically: true + )] + } else { + return [] + } + } + |> deliverOnMainQueue + } else { + contactOptions = .single([]) } var openProfileImpl: ((EnginePeer) -> Void)? @@ -2544,7 +2554,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController { - return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) + return proxySettingsController(accountManager: sharedContext.accountManager, sharedContext: sharedContext, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) } public func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index a98c33ba18..eb30208939 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -103,6 +103,7 @@ public enum ParsedInternalUrl { case chatFolder(slug: String) case premiumGiftCode(slug: String) case messageLink(slug: String) + case externalUrl(url: String) } private enum ParsedUrl { @@ -110,7 +111,7 @@ private enum ParsedUrl { case internalUrl(ParsedInternalUrl) } -public func parseInternalUrl(query: String) -> ParsedInternalUrl? { +public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) -> ParsedInternalUrl? { var query = query if query.hasPrefix("s/") { query = String(query[query.index(query.startIndex, offsetBy: 2)...]) @@ -129,6 +130,12 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { if !pathComponents.isEmpty && !pathComponents[0].isEmpty { let peerName: String = pathComponents[0] + if sharedContext.immediateExperimentalUISettings.browserExperiment { + if query.hasPrefix("ipfs/") { + return .externalUrl(url: "ipfs://" + String(query[query.index(query.startIndex, offsetBy: "ipfs/".count)...])) + } + } + if pathComponents[0].hasPrefix("+") || pathComponents[0].hasPrefix("%20") { let component = pathComponents[0].replacingOccurrences(of: "%20", with: "+") if component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789+").inverted) == nil { @@ -1016,7 +1023,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return .single(.result(.messageLink(link: result))) } }) - + case let .externalUrl(url): + return .single(.result(.externalUrl(url))) } } @@ -1046,20 +1054,20 @@ public func isTelegraPhLink(_ url: String) -> Bool { return false } -public func parseProxyUrl(_ url: String) -> (host: String, port: Int32, username: String?, password: String?, secret: Data?)? { +public func parseProxyUrl(sharedContext: SharedAccountContext, url: String) -> (host: String, port: Int32, username: String?, password: String?, secret: Data?)? { let schemes = ["http://", "https://", ""] for basePath in baseTelegramMePaths { for scheme in schemes { let basePrefix = scheme + basePath + "/" if url.lowercased().hasPrefix(basePrefix) { - if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])), case let .proxy(host, port, username, password, secret) = internalUrl { + if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .proxy(host, port, username, password, secret) = internalUrl { return (host, port, username, password, secret) } } } } if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query { - if let internalUrl = parseInternalUrl(query: host + "?" + query), case let .proxy(host, port, username, password, secret) = internalUrl { + if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .proxy(host, port, username, password, secret) = internalUrl { return (host, port, username, password, secret) } } @@ -1067,20 +1075,20 @@ public func parseProxyUrl(_ url: String) -> (host: String, port: Int32, username return nil } -public func parseStickerPackUrl(_ url: String) -> String? { +public func parseStickerPackUrl(sharedContext: SharedAccountContext, url: String) -> String? { let schemes = ["http://", "https://", ""] for basePath in baseTelegramMePaths { for scheme in schemes { let basePrefix = scheme + basePath + "/" if url.lowercased().hasPrefix(basePrefix) { - if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])), case let .stickerPack(name, _) = internalUrl { + if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .stickerPack(name, _) = internalUrl { return name } } } } if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query { - if let internalUrl = parseInternalUrl(query: host + "?" + query), case let .stickerPack(name, _) = internalUrl { + if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .stickerPack(name, _) = internalUrl { return name } } @@ -1088,20 +1096,20 @@ public func parseStickerPackUrl(_ url: String) -> String? { return nil } -public func parseWallpaperUrl(_ url: String) -> WallpaperUrlParameter? { +public func parseWallpaperUrl(sharedContext: SharedAccountContext, url: String) -> WallpaperUrlParameter? { let schemes = ["http://", "https://", ""] for basePath in baseTelegramMePaths { for scheme in schemes { let basePrefix = scheme + basePath + "/" if url.lowercased().hasPrefix(basePrefix) { - if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])), case let .wallpaper(wallpaper) = internalUrl { + if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .wallpaper(wallpaper) = internalUrl { return wallpaper } } } } if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query { - if let internalUrl = parseInternalUrl(query: host + "?" + query), case let .wallpaper(wallpaper) = internalUrl { + if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .wallpaper(wallpaper) = internalUrl { return wallpaper } } @@ -1178,7 +1186,7 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String url = basePrefix + String(url[scheme.endIndex...]).replacingOccurrences(of: ".\(basePath)/", with: "").replacingOccurrences(of: ".\(basePath)", with: "") } if url.lowercased().hasPrefix(basePrefix) { - if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) { + if let internalUrl = parseInternalUrl(sharedContext: context.sharedContext, query: String(url[basePrefix.endIndex...])) { return resolveInternalUrl(context: context, url: internalUrl) |> map { result -> ResolveUrlResult in switch result {