From aff54859eb34d4e0560573aac9f3684a198a4d4f Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 10 Jan 2023 19:26:46 +0300 Subject: [PATCH] feat: complete deeplink api --- Package.swift | 8 +- .../BaseNavigationStackDeeplinkHandler.swift | 48 +++++++++++ .../DeeplinkHandler/DeeplinkHandler.swift | 28 ++++++ .../UIViewController+DeeplinkHandler.swift | 37 ++++++++ TIDeepLink/Sources/DeeplinkMapper.swift | 27 ++++++ TIDeepLink/Sources/DeeplinkType.swift | 24 ++++++ TIDeepLink/Sources/TIDeepLinkService.swift | 85 ++++++++++++++++++- TIDeepLink/TIDeepLink.podspec | 7 +- 8 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift create mode 100644 TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift create mode 100644 TIDeepLink/Sources/DeeplinkHandler/Helpers/UIViewController+DeeplinkHandler.swift create mode 100644 TIDeepLink/Sources/DeeplinkMapper.swift create mode 100644 TIDeepLink/Sources/DeeplinkType.swift diff --git a/Package.swift b/Package.swift index d77d173e..b892c9e9 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( .library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]), .library(name: "TITableKitUtils", targets: ["TITableKitUtils"]), .library(name: "TILogging", targets: ["TILogging"]), - .library(name: "TIDeepLink", targets: ["TIDeepLink"]), + .library(name: "TIDeeplink", targets: ["TIDeeplink"]), // MARK: - Networking @@ -33,7 +33,7 @@ let package = Package( .library(name: "TIMapUtils", targets: ["TIMapUtils"]), .library(name: "TIAppleMapUtils", targets: ["TIAppleMapUtils"]), - + // MARK: - Elements .library(name: "OTPSwiftView", targets: ["OTPSwiftView"]), .library(name: "TITransitions", targets: ["TITransitions"]), @@ -66,7 +66,7 @@ let package = Package( .target(name: "TIKeychainUtils", dependencies: ["TIFoundationUtils", "KeychainAccess"], path: "TIKeychainUtils/Sources"), .target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"), .target(name: "TILogging", dependencies: ["TIUIElements", "TISwiftUtils", "TIUIKitCore"], path: "TILogging/Sources"), - .target(name: "TIDeepLink", dependencies: [], path: "TIDeepLink/Sources"), + .target(name: "TIDeeplink", dependencies: ["TIFoundationUtils"], path: "TIDeeplink/Sources"), // MARK: - Networking .target(name: "TINetworking", dependencies: ["TIFoundationUtils", "Alamofire"], path: "TINetworking/Sources"), @@ -76,7 +76,7 @@ let package = Package( // MARK: - Maps .target(name: "TIMapUtils", dependencies: [], path: "TIMapUtils/Sources"), .target(name: "TIAppleMapUtils", dependencies: ["TIMapUtils"], path: "TIAppleMapUtils/Sources"), - + // MARK: - Elements .target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"), .target(name: "TITransitions", path: "TITransitions/Sources"), diff --git a/TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift b/TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift new file mode 100644 index 00000000..db7f8671 --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift @@ -0,0 +1,48 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +open class BaseNavigationStackDeeplinkHandler: DeeplinkHandler { + + // MARK: - DeeplinkHandler + + open func canHandle(deeplink: DeeplinkType) -> Bool { + findHandler(for: deeplink) != nil + } + + open func handle(deeplink: DeeplinkType) -> Operation? { + let handler = findHandler(for: deeplink) + return handler?.handle(deeplink: deeplink) + } + + // MARK: - Open methods + + open func findHandler(for deeplink: DeeplinkType) -> DeeplinkHandler? { + guard let rootController = UIApplication.shared.keyWindow?.rootViewController else { + return nil + } + + return rootController.findHandler(for: deeplink) + } +} diff --git a/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift b/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift new file mode 100644 index 00000000..bb874296 --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol DeeplinkHandler { + func canHandle(deeplink: DeeplinkType) -> Bool + func handle(deeplink: DeeplinkType) -> Operation? +} diff --git a/TIDeepLink/Sources/DeeplinkHandler/Helpers/UIViewController+DeeplinkHandler.swift b/TIDeepLink/Sources/DeeplinkHandler/Helpers/UIViewController+DeeplinkHandler.swift new file mode 100644 index 00000000..aba552c3 --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkHandler/Helpers/UIViewController+DeeplinkHandler.swift @@ -0,0 +1,37 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +public typealias DeeplinkHandlerViewController = DeeplinkHandler & UIViewController + +public extension UIViewController { + func findHandler(for deeplink: DeeplinkType) -> DeeplinkHandlerViewController? { + if let deeplinksHandler = self as? DeeplinkHandlerViewController, + deeplinksHandler.canHandle(deeplink: deeplink) { + return deeplinksHandler + } + + let deeplinksHandler = presentedViewController?.findHandler(for: deeplink) + return deeplinksHandler + } +} diff --git a/TIDeepLink/Sources/DeeplinkMapper.swift b/TIDeepLink/Sources/DeeplinkMapper.swift new file mode 100644 index 00000000..60f3262e --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkMapper.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol DeeplinkMapper { + func map(url: URL) -> DeeplinkType? +} diff --git a/TIDeepLink/Sources/DeeplinkType.swift b/TIDeepLink/Sources/DeeplinkType.swift new file mode 100644 index 00000000..1d317f71 --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkType.swift @@ -0,0 +1,24 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol DeeplinkType { +} diff --git a/TIDeepLink/Sources/TIDeepLinkService.swift b/TIDeepLink/Sources/TIDeepLinkService.swift index e3b71823..c4d1fcf2 100644 --- a/TIDeepLink/Sources/TIDeepLinkService.swift +++ b/TIDeepLink/Sources/TIDeepLinkService.swift @@ -1,9 +1,88 @@ -final class DeeplinksService { +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// - static let shared = DeeplinksService() +import Foundation +import TIFoundationUtils + +public final class TIDeeplinksService { + + public static let shared = TIDeeplinksService() + + // MARK: - Private properties + + private let operationQueue = OperationQueue.main + + private var pendingDeeplink: DeeplinkType? + + private(set) var isProcessDeeplink = false + + // MARK: - Public properties + + public var deeplinkMapper: DeeplinkMapper? + public var deeplinkHandler = BaseNavigationStackDeeplinkHandler() + + // MARK: - Init private init() { } -} + // MARK: - Public methods + + @discardableResult + public func deferredHandle(url: URL) -> Bool { + pendingDeeplink = deeplinkMapper?.map(url: url) + return pendingDeeplink != nil + } + + public func reset() { + operationQueue.cancelAllOperations() + pendingDeeplink = nil + isProcessDeeplink = false + } + + public func tryHandle() { + guard let deeplink = pendingDeeplink, + deeplinkHandler.canHandle(deeplink: deeplink) else { + return + } + + handle() + } + + public func handle() { + guard let deeplink = pendingDeeplink, + let lastOperation = deeplinkHandler.handle(deeplink: deeplink) else { + return + } + + operationQueue.addOperation { [weak self] in + self?.isProcessDeeplink = true + self?.pendingDeeplink = nil + } + operationQueue.addOperations(lastOperation.flattenDependencies + [lastOperation], + waitUntilFinished: false) + operationQueue.addOperation { [weak self] in + self?.isProcessDeeplink = false + } + } +} diff --git a/TIDeepLink/TIDeepLink.podspec b/TIDeepLink/TIDeepLink.podspec index 0e35e6dd..7f8d4b4a 100644 --- a/TIDeepLink/TIDeepLink.podspec +++ b/TIDeepLink/TIDeepLink.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| - s.name = 'TIDeepLink' + s.name = 'TIDeeplink' s.version = '1.33.0' - s.summary = 'Deep link service API' + s.summary = 'Deeplink service API' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru', @@ -12,4 +12,7 @@ Pod::Spec.new do |s| s.swift_versions = ['5.3'] s.source_files = s.name + '/Sources/**/*' + + s.dependency 'TIFoundationUtils', s.version.to_s + end