From 97c0dbebb9c066cc4e87862345ddefb01f9836c5 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 25 Aug 2020 21:06:42 +0300 Subject: [PATCH] Improve country selection search --- submodules/CountrySelectionUI/BUCK | 1 + submodules/CountrySelectionUI/BUILD | 1 + ...onSequenceCountrySelectionController.swift | 1 + ...quenceCountrySelectionControllerNode.swift | 85 ++++++++++++++++--- .../Sources/ShareController.swift | 80 ++++++++++++----- 5 files changed, 134 insertions(+), 34 deletions(-) diff --git a/submodules/CountrySelectionUI/BUCK b/submodules/CountrySelectionUI/BUCK index 03763af9f3..06b334d255 100644 --- a/submodules/CountrySelectionUI/BUCK +++ b/submodules/CountrySelectionUI/BUCK @@ -9,6 +9,7 @@ static_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", "//submodules/Display:Display#shared", + "//submodules/Postbox:Postbox#shared", "//submodules/TelegramCore:TelegramCore#shared", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramStringFormatting:TelegramStringFormatting", diff --git a/submodules/CountrySelectionUI/BUILD b/submodules/CountrySelectionUI/BUILD index 368cd0b914..b5284ea80d 100644 --- a/submodules/CountrySelectionUI/BUILD +++ b/submodules/CountrySelectionUI/BUILD @@ -10,6 +10,7 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", + "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramStringFormatting:TelegramStringFormatting", diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift index 66c79dbfa7..653cf32759 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift @@ -236,6 +236,7 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll } public static func lookupPatternByNumber(_ number: String, preferredCountries: [String: String]) -> String? { + let number = removePlus(number) if let (_, code) = lookupCountryIdByNumber(number, preferredCountries: preferredCountries), !code.patterns.isEmpty { var prefixes: [String: String] = [:] for pattern in code.patterns { diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift index f7d22cf3aa..82b9b60a54 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import Postbox import TelegramCore import SyncCore import TelegramPresentationData @@ -73,6 +74,75 @@ func localizedCountryNamesAndCodes(strings: PresentationStrings) -> [((String, S return result } +private func stringTokens(_ string: String) -> [ValueBoxKey] { + let nsString = string.replacingOccurrences(of: ".", with: "").folding(options: .diacriticInsensitive, locale: .current).lowercased() as NSString + + let flag = UInt(kCFStringTokenizerUnitWord) + let tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, nsString, CFRangeMake(0, nsString.length), flag, CFLocaleCopyCurrent()) + var tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer) + var tokens: [ValueBoxKey] = [] + + var addedTokens = Set() + while tokenType != [] { + let currentTokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer) + + if currentTokenRange.location >= 0 && currentTokenRange.length != 0 { + let token = ValueBoxKey(length: currentTokenRange.length * 2) + nsString.getCharacters(token.memory.assumingMemoryBound(to: unichar.self), range: NSMakeRange(currentTokenRange.location, currentTokenRange.length)) + if !addedTokens.contains(token) { + tokens.append(token) + addedTokens.insert(token) + } + } + tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer) + } + + return tokens +} + +private func matchStringTokens(_ tokens: [ValueBoxKey], with other: [ValueBoxKey]) -> Bool { + if other.isEmpty { + return false + } else if other.count == 1 { + let otherToken = other[0] + for token in tokens { + if otherToken.isPrefix(to: token) { + return true + } + } + } else { + for otherToken in other { + var found = false + for token in tokens { + if otherToken.isPrefix(to: token) { + found = true + break + } + } + if !found { + return false + } + } + return true + } + return false +} + +private func searchCountries(items: [((String, String), String, Int)], query: String) -> [((String, String), String, Int)] { + let queryTokens = stringTokens(query.lowercased()) + + var result: [((String, String), String, Int)] = [] + for item in items { + let string = "\(item.0) \(item.1)" + let tokens = stringTokens(string) + if matchStringTokens(tokens, with: queryTokens) { + result.append(item) + } + } + + return result +} + final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, UITableViewDelegate, UITableViewDataSource { let itemSelected: (((String, String), String, Int)) -> Void @@ -88,6 +158,7 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, private let sectionTitles: [String] private var searchResults: [((String, String), String, Int)] = [] + private let countryNamesAndCodes: [((String, String), String, Int)] init(theme: PresentationTheme, strings: PresentationStrings, displayCodes: Bool, itemSelected: @escaping (((String, String), String, Int)) -> Void) { self.theme = theme @@ -107,6 +178,7 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, } let countryNamesAndCodes = localizedCountryNamesAndCodes(strings: strings) + self.countryNamesAndCodes = countryNamesAndCodes var sections: [(String, [((String, String), String, Int)])] = [] for (names, id, code) in countryNamesAndCodes.sorted(by: { lhs, rhs in @@ -176,18 +248,7 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, self.searchTableView.reloadData() self.searchTableView.isHidden = true } else { - let normalizedQuery = query.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) - - var results: [((String, String), String, Int)] = [] - for (_, items) in self.sections { - for item in items { - if item.0.0.lowercased().hasPrefix(normalizedQuery) || item.0.1.lowercased().hasPrefix(normalizedQuery) { - results.append(item) - } - } - } - - self.searchResults = results + self.searchResults = searchCountries(items: self.countryNamesAndCodes, query: query) self.searchTableView.isHidden = false self.searchTableView.reloadData() } diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 78e8de6d3f..173abe52b1 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -593,27 +593,6 @@ public final class ShareController: ViewController { } self.controllerNode.shareExternal = { [weak self] in if let strongSelf = self { -// if case let .messages(messages) = strongSelf.subject, let message = messages.first, let peer = message.peers[message.id.peerId] { -// let renderer = MessageStoryRenderer(context: strongSelf.currentContext, messages: messages) -// -// let layout = ContainerViewLayout(size: CGSize(width: 414.0, height: 896.0), metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: 0.0, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false) -// renderer.update(layout: layout) { image in -// if let data = image?.pngData() { -// let pasteboardItems: [[String: Any]] = [["com.instagram.sharedSticker.backgroundImage": data, -// "com.instagram.sharedSticker.contentURL": "https://t.me/\(peer.addressName ?? "")/\(message.id.id)"]] -// if #available(iOS 10.0, *) { -// UIPasteboard.general.setItems(pasteboardItems, options: [.expirationDate: Date().addingTimeInterval(5 * 60)]) -// } else { -//// UIPasteboard.general.setItems(pasteboardItems) -// } -// strongSelf.sharedContext.applicationBindings.openUrl("instagram-stories://share") -// } -// } -// -// return .complete() -// } - - var collectableItems: [CollectableExternalShareItem] = [] switch strongSelf.subject { case let .url(text): @@ -695,7 +674,25 @@ public final class ShareController: ViewController { activityItems.append(url) } } - let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + + var activities: [UIActivity]? + if false, #available(iOS 10.0, *), strongSelf.sharedContext.applicationBindings.canOpenUrl("instagram-stories://"), case let .messages(messages) = strongSelf.subject, let message = messages.first, let peer = message.peers[message.id.peerId] { + let shareToInstagram = ShareToInstagramActivity(action: { sharedItems in + let renderer = MessageStoryRenderer(context: strongSelf.currentContext, messages: messages) + + let layout = ContainerViewLayout(size: CGSize(width: 414.0, height: 896.0), metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: 0.0, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false) + renderer.update(layout: layout) { image in + if let data = image?.pngData() { + let pasteboardItems: [[String: Any]] = [["com.instagram.sharedSticker.backgroundImage": data, + "com.instagram.sharedSticker.contentURL": "https://t.me/\(peer.addressName ?? "")/\(message.id.id)"]] + UIPasteboard.general.setItems(pasteboardItems, options: [.expirationDate: Date().addingTimeInterval(5 * 60)]) + strongSelf.sharedContext.applicationBindings.openUrl("instagram-stories://share") + } + } + }) + activities = [shareToInstagram] + } + let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities) if let window = strongSelf.view.window, let rootViewController = window.rootViewController { activityController.popoverPresentationController?.sourceView = window @@ -1015,3 +1012,42 @@ final class MessageStoryRenderer { dateHeaderNode.updateLayout(size: self.containerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right) } } + +private class ShareToInstagramActivity: UIActivity { + private var activityItems = [Any]() + private var action: ([Any]) -> Void + + init(action: @escaping ([Any]) -> Void) { + self.action = action + super.init() + } + + override var activityTitle: String? { + return "Share to Instagram Stories" + } + + override var activityImage: UIImage? { + return nil + } + + override var activityType: UIActivity.ActivityType? { + return UIActivity.ActivityType(rawValue: "org.telegram.Telegram.ShareToInstagram") + } + + override class var activityCategory: UIActivity.Category { + return .action + } + + override func canPerform(withActivityItems activityItems: [Any]) -> Bool { + return true + } + + override func prepare(withActivityItems activityItems: [Any]) { + self.activityItems = activityItems + } + + override func perform() { + self.action(self.activityItems) + activityDidFinish(true) + } +}