diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 1c7a30d823..216168d964 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7326,3 +7326,5 @@ Sorry for the inconvenience."; "Attachment.DeselectedItems_any" = "%@ items deselected"; "Attachment.DeselectedItems_many" = "%@ items deselected"; "Attachment.DeselectedItems_0" = "%@ items deselected"; + +"PrivacyPhoneNumberSettings.CustomPublicLink" = "Users who have your number saved in their contacts will also see it on Telegram.\n\nThis public link opens a chat with you:\n[https://t.me/%@]()"; diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 95c5d74c56..d2b9df3430 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -220,6 +220,7 @@ public class AttachmentController: ViewController { guard self.currentType != type else { if let controller = self.currentController { controller.scrollToTopWithTabBar?() + controller.requestAttachmentMenuExpansion() } return } @@ -255,7 +256,7 @@ public class AttachmentController: ViewController { strongSelf.container.container.view.layer.animatePosition(from: CGPoint(x: ascending ? 70.0 : -70.0, y: 0.0), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() - previousController?.prepareForReuse() + previousController?.resetForReuse() }) }) } diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift index 85e8baf38b..f999855e59 100644 --- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift +++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift @@ -193,7 +193,7 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, model.interfaceView.doneLongPressed = { [weak selectionContext, weak editingContext, weak legacyController, weak model] item in if let legacyController = legacyController, let item = item as? TGMediaPickerGalleryItem, let model = model, let selectionContext = selectionContext { var effectiveHasSchedule = hasSchedule - + if let editingContext = editingContext { for item in selectionContext.selectedItems() { if let editableItem = item as? TGMediaEditableItem, let timer = editingContext.timer(for: editableItem)?.intValue, timer > 0 { @@ -202,9 +202,8 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, } } } - + let legacySheetController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) - let controller = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer) let dismissImpl = { [weak model] in model?.dismiss(true, false) @@ -241,8 +240,10 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, legacySheetController?.dismiss() } legacySheetController.bind(controller: controller) - present(legacySheetController, nil) + + let hapticFeedback = HapticFeedback() + hapticFeedback.impact() } } model.interfaceView.setThumbnailSignalForItem { item in diff --git a/submodules/PeerInfoUI/Sources/PeerReportController.swift b/submodules/PeerInfoUI/Sources/PeerReportController.swift index 99497fe7a6..a8c0e71276 100644 --- a/submodules/PeerInfoUI/Sources/PeerReportController.swift +++ b/submodules/PeerInfoUI/Sources/PeerReportController.swift @@ -66,7 +66,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro icon = UIImage(bundleImageName: "Chat/Context Menu/ReportDrugs") case .personalDetails: title = presentationData.strings.ReportPeer_ReasonPersonalDetails - icon = UIImage(bundleImageName: "Chat/Context Menu/User") + icon = UIImage(bundleImageName: "Chat/Context Menu/ReportPersonal") case .other: title = presentationData.strings.ReportPeer_ReasonOther icon = UIImage(bundleImageName: "Chat/Context Menu/Report") diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index b50c1c749f..75c576c524 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -9,6 +9,7 @@ import TelegramUIPreferences import ItemListUI import PresentationDataUtils import AccountContext +import UndoUI enum SelectivePrivacySettingsKind { case presence @@ -50,8 +51,9 @@ private final class SelectivePrivacySettingsControllerArguments { let updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)? let updateCallIntegrationEnabled: ((Bool) -> Void)? let updatePhoneDiscovery: ((Bool) -> Void)? + let copyPhoneLink: ((String) -> Void)? - init(context: AccountContext, updateType: @escaping (SelectivePrivacySettingType) -> Void, openSelective: @escaping (SelectivePrivacySettingsPeerTarget, Bool) -> Void, updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)?, updateCallIntegrationEnabled: ((Bool) -> Void)?, updatePhoneDiscovery: ((Bool) -> Void)?) { + init(context: AccountContext, updateType: @escaping (SelectivePrivacySettingType) -> Void, openSelective: @escaping (SelectivePrivacySettingsPeerTarget, Bool) -> Void, updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)?, updateCallIntegrationEnabled: ((Bool) -> Void)?, updatePhoneDiscovery: ((Bool) -> Void)?, copyPhoneLink: ((String) -> Void)?) { self.context = context self.updateType = updateType self.openSelective = openSelective @@ -59,6 +61,7 @@ private final class SelectivePrivacySettingsControllerArguments { self.updateCallP2PMode = updateCallP2PMode self.updateCallIntegrationEnabled = updateCallIntegrationEnabled self.updatePhoneDiscovery = updatePhoneDiscovery + self.copyPhoneLink = copyPhoneLink } } @@ -91,7 +94,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case everybody(PresentationTheme, String, Bool) case contacts(PresentationTheme, String, Bool) case nobody(PresentationTheme, String, Bool) - case settingInfo(PresentationTheme, String) + case settingInfo(PresentationTheme, String, String) case exceptionsHeader(PresentationTheme, String) case disableFor(PresentationTheme, String, String) case enableFor(PresentationTheme, String, String) @@ -109,7 +112,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case phoneDiscoveryHeader(PresentationTheme, String) case phoneDiscoveryEverybody(PresentationTheme, String, Bool) case phoneDiscoveryMyContacts(PresentationTheme, String, Bool) - case phoneDiscoveryInfo(PresentationTheme, String) + case phoneDiscoveryInfo(PresentationTheme, String, String) var section: ItemListSectionId { switch self { @@ -229,8 +232,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } else { return false } - case let .settingInfo(lhsTheme, lhsText): - if case let .settingInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .settingInfo(lhsTheme, lhsText, lhsLink): + if case let .settingInfo(rhsTheme, rhsText, rhsLink) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLink == rhsLink { return true } else { return false @@ -331,8 +334,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } else { return false } - case let .phoneDiscoveryInfo(lhsTheme, lhsText): - if case let .phoneDiscoveryInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .phoneDiscoveryInfo(lhsTheme, lhsText, lhsLink): + if case let .phoneDiscoveryInfo(rhsTheme, rhsText, rhsLink) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLink == rhsLink { return true } else { return false @@ -365,8 +368,10 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateType(.nobody) }) - case let .settingInfo(_, text): - return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .settingInfo(_, text, link): + return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in + arguments.copyPhoneLink?(link) + }) case let .exceptionsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .disableFor(_, title, value): @@ -421,8 +426,10 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updatePhoneDiscovery?(false) }) - case let .phoneDiscoveryInfo(_, text): - return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .phoneDiscoveryInfo(_, text, link): + return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in + arguments.copyPhoneLink?(link) + }) } } } @@ -531,7 +538,7 @@ private struct SelectivePrivacySettingsControllerState: Equatable { } } -private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String) -> [SelectivePrivacySettingsEntry] { +private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String, phoneNumber: String) -> [SelectivePrivacySettingsEntry] { var entries: [SelectivePrivacySettingsEntry] = [] let settingTitle: String @@ -569,7 +576,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present if state.setting == .nobody { settingInfoText = nil } else { - settingInfoText = presentationData.strings.PrivacyPhoneNumberSettings_CustomHelp + settingInfoText = presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string } disableForText = presentationData.strings.PrivacyLastSeenSettings_NeverShareWith enableForText = presentationData.strings.PrivacyLastSeenSettings_AlwaysShareWith @@ -603,15 +610,16 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present case .groupInvitations, .profilePhoto: break } + let phoneLink = "https://t.me/+\(phoneNumber)" if let settingInfoText = settingInfoText { - entries.append(.settingInfo(presentationData.theme, settingInfoText)) + entries.append(.settingInfo(presentationData.theme, settingInfoText, phoneLink)) } if case .phoneNumber = kind, state.setting == .nobody { entries.append(.phoneDiscoveryHeader(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_DiscoveryHeader)) entries.append(.phoneDiscoveryEverybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.phoneDiscoveryEnabled != false)) entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false)) - entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomHelp : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp)) + entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink)) } entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) @@ -930,6 +938,11 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective updateState { state in return state.withUpdatedPhoneDiscoveryEnabled(value) } + }, copyPhoneLink: { link in + UIPasteboard.general.string = link + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) }) let peer = context.account.postbox.transaction { transaction -> Peer? in @@ -939,6 +952,7 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peer) |> deliverOnMainQueue |> map { presentationData, state, peer -> (ItemListControllerState, (ItemListNodeState, Any)) in let peerName = peer.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let phoneNumber = (peer as? TelegramUser)?.phone ?? "" let title: String switch kind { @@ -956,7 +970,7 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective title = presentationData.strings.Privacy_PhoneNumber } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: selectivePrivacySettingsControllerEntries(presentationData: presentationData, kind: kind, state: state, peerName: peerName ?? ""), style: .blocks, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: selectivePrivacySettingsControllerEntries(presentationData: presentationData, kind: kind, state: state, peerName: peerName ?? "", phoneNumber: phoneNumber), style: .blocks, animateChanges: false) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedResolvedByPhonePeer.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedResolvedByPhonePeer.swift new file mode 100644 index 0000000000..f76fa82cff --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedResolvedByPhonePeer.swift @@ -0,0 +1,41 @@ +import Foundation +import Postbox + +public final class CachedResolvedByPhonePeer: Codable { + public let peerId: PeerId? + public let timestamp: Int32 + + public static func key(name: String) -> ValueBoxKey { + let key: ValueBoxKey + if let nameData = name.data(using: .utf8) { + key = ValueBoxKey(length: nameData.count) + nameData.withUnsafeBytes { rawBytes -> Void in + let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self) + + memcpy(key.memory, bytes, nameData.count) + } + } else { + key = ValueBoxKey(length: 0) + } + return key + } + + public init(peerId: PeerId?, timestamp: Int32) { + self.peerId = peerId + self.timestamp = timestamp + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.peerId = (try container.decodeIfPresent(Int64.self, forKey: "p")).flatMap(PeerId.init) + self.timestamp = try container.decode(Int32.self, forKey: "t") + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encodeIfPresent(self.peerId?.toInt64(), forKey: "p") + try container.encode(self.timestamp, forKey: "t") + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index aaea82f8af..5cb52c2d64 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -82,6 +82,7 @@ public struct Namespaces { public static let cachedPeerExportedInvitations: Int8 = 17 public static let cachedSendAsPeers: Int8 = 18 public static let availableReactions: Int8 = 19 + public static let resolvedByPhonePeers: Int8 = 20 } public struct UnorderedItemList { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift index c35dd5f68c..d179804a00 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift @@ -77,3 +77,66 @@ func _internal_resolvePeerByName(account: Account, name: String, ageLimit: Int32 } } } + +private let resolvedByPhonePeersCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 150, highWaterItemCount: 200) + +func _internal_resolvePeerByPhone(account: Account, phone: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal { + var normalizedPhone = phone + if normalizedPhone.hasPrefix("+") { + normalizedPhone = String(normalizedPhone[normalizedPhone.index(after: normalizedPhone.startIndex)...]) + } + + return account.postbox.transaction { transaction -> CachedResolvedByPhonePeer? in + return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.resolvedByPhonePeers, key: CachedResolvedByPhonePeer.key(name: normalizedPhone)))?.get(CachedResolvedByPhonePeer.self) + } |> mapToSignal { cachedEntry -> Signal in + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + if let cachedEntry = cachedEntry, cachedEntry.timestamp <= timestamp && cachedEntry.timestamp >= timestamp - ageLimit { + return .single(cachedEntry.peerId) + } else { + return account.network.request(Api.functions.contacts.resolvePhone(phone: normalizedPhone)) + |> mapError { _ -> Void in + return Void() + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> PeerId? in + var peerId: PeerId? = nil + + switch result { + case let .resolvedPeer(apiPeer, chats, users): + var peers: [PeerId: Peer] = [:] + + for user in users { + if let user = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { + peers[user.id] = user + } + } + + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers[groupOrChannel.id] = groupOrChannel + } + } + + if let peer = peers[apiPeer.peerId] { + peerId = peer.id + + updatePeers(transaction: transaction, peers: Array(peers.values), update: { _, updated -> Peer in + return updated + }) + } + } + + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + if let entry = CodableEntry(CachedResolvedByPhonePeer(peerId: peerId, timestamp: timestamp)) { + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.resolvedByPhonePeers, key: CachedResolvedByPhonePeer.key(name: normalizedPhone)), entry: entry, collectionSpec: resolvedByNamePeersCollectionSpec) + } + return peerId + } + |> castError(Void.self) + } + |> `catch` { _ -> Signal in + return .single(nil) + } + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 80d83c7b0f..ffc1e598d6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -97,6 +97,18 @@ public extension TelegramEngine { } } } + + public func resolvePeerByPhone(phone: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal { + return _internal_resolvePeerByPhone(account: self.account, phone: phone, ageLimit: ageLimit) + |> mapToSignal { peerId -> Signal in + guard let peerId = peerId else { + return .single(nil) + } + return self.account.postbox.transaction { transaction -> EnginePeer? in + return transaction.getPeer(peerId).flatMap(EnginePeer.init) + } + } + } public func updatedRemotePeer(peer: PeerReference) -> Signal { return _internal_updatedRemotePeer(postbox: self.account.postbox, network: self.account.network, peer: peer) diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/Contents.json new file mode 100644 index 0000000000..a19a549220 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 94e9556918..40d308e359 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -605,6 +605,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if parsedUrl.host == "resolve" { if let components = URLComponents(string: "/?" + query) { + var phone: String? var domain: String? var start: String? var startGroup: String? @@ -614,7 +615,9 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { - if queryItem.name == "domain" { + if queryItem.name == "phone" { + phone = value + } else if queryItem.name == "domain" { domain = value } else if queryItem.name == "start" { start = value @@ -633,7 +636,9 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur } } - if let domain = domain { + if let phone = phone { + convertedUrl = "https://t.me/+\(phone)" + } else if let domain = domain { var result = "https://t.me/\(domain)" if let post = post, let postValue = Int(post) { result += "/\(postValue)" diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index f8e29dd1a8..a423819fa0 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -41,6 +41,7 @@ public enum ParsedInternalUrl { case share(url: String?, text: String?, to: String?) case wallpaper(WallpaperUrlParameter) case theme(String) + case phone(String) } private enum ParsedUrl { @@ -154,7 +155,12 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { } else if pathComponents[0].hasPrefix(phonebookUsernamePathPrefix), let idValue = Int64(String(pathComponents[0][pathComponents[0].index(pathComponents[0].startIndex, offsetBy: phonebookUsernamePathPrefix.count)...])) { return .peerId(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(idValue))) } else if pathComponents[0].hasPrefix("+") || pathComponents[0].hasPrefix("%20") { - return .join(String(pathComponents[0].dropFirst())) + let component = pathComponents[0].replacingOccurrences(of: "%20", with: "+") + if component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789+").inverted) == nil { + return .phone(component.replacingOccurrences(of: "+", with: "")) + } else { + return .join(String(component.dropFirst())) + } } return .peerName(peerName, nil) } else if pathComponents.count == 2 || pathComponents.count == 3 { @@ -350,6 +356,16 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) -> Signal { switch url { + case let .phone(phone): + return context.engine.peers.resolvePeerByPhone(phone: phone) + |> take(1) + |> map { peer -> ResolvedUrl? in + if let peer = peer?._asPeer() { + return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)) + } else { + return .peer(nil, .info) + } + } case let .peerName(name, parameter): return context.engine.peers.resolvePeerByName(name: name) |> take(1) @@ -383,11 +399,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return .single(.joinVoiceChat(peer.id, invite)) } } else { - if let peer = peer as? TelegramUser, peer.botInfo == nil { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) - } else { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) - } + return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) } } else { return .single(.peer(nil, .info))