apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README

RadrootsAppleExternalActions.swift (3910B)


      1 import Foundation
      2 
      3 #if canImport(AppKit)
      4 @preconcurrency import AppKit
      5 #endif
      6 
      7 #if canImport(UIKit)
      8 @preconcurrency import UIKit
      9 #endif
     10 
     11 public struct RadrootsAppleExternalActionsAdapters: Sendable {
     12     public let appSettingsURL: @Sendable () async -> URL?
     13     public let canOpenURL: @Sendable (URL) async -> Bool
     14     public let openURL: @Sendable (URL) async -> Bool
     15 
     16     public init(
     17         appSettingsURL: @escaping @Sendable () async -> URL?,
     18         canOpenURL: @escaping @Sendable (URL) async -> Bool,
     19         openURL: @escaping @Sendable (URL) async -> Bool
     20     ) {
     21         self.appSettingsURL = appSettingsURL
     22         self.canOpenURL = canOpenURL
     23         self.openURL = openURL
     24     }
     25 
     26     public static var live: Self {
     27         #if canImport(UIKit)
     28         Self(
     29             appSettingsURL: {
     30                 await MainActor.run {
     31                     URL(string: UIApplication.openSettingsURLString)
     32                 }
     33             },
     34             canOpenURL: { url in
     35                 await MainActor.run {
     36                     UIApplication.shared.canOpenURL(url)
     37                 }
     38             },
     39             openURL: { url in
     40                 await Self.openUIKitURL(url)
     41             }
     42         )
     43         #elseif canImport(AppKit)
     44         Self(
     45             appSettingsURL: {
     46                 nil
     47             },
     48             canOpenURL: { url in
     49                 await MainActor.run {
     50                     NSWorkspace.shared.urlForApplication(toOpen: url) != nil
     51                 }
     52             },
     53             openURL: { url in
     54                 await MainActor.run {
     55                     NSWorkspace.shared.open(url)
     56                 }
     57             }
     58         )
     59         #else
     60         Self(
     61             appSettingsURL: {
     62                 nil
     63             },
     64             canOpenURL: { _ in
     65                 false
     66             },
     67             openURL: { _ in
     68                 false
     69             }
     70         )
     71         #endif
     72     }
     73 }
     74 
     75 public final class RadrootsAppleExternalActions: RadrootsExternalActions, Sendable {
     76     private let adapters: RadrootsAppleExternalActionsAdapters
     77 
     78     public init(adapters: RadrootsAppleExternalActionsAdapters = .live) {
     79         self.adapters = adapters
     80     }
     81 
     82     public func canOpen(_ destination: RadrootsExternalActionDestination) async -> RadrootsExternalActionCapability {
     83         let url = await resolvedURL(for: destination)
     84         guard let url else {
     85             return RadrootsExternalActionCapability(destination: destination, canOpen: false)
     86         }
     87         let canOpen = await adapters.canOpenURL(url)
     88         return RadrootsExternalActionCapability(
     89             destination: destination,
     90             canOpen: canOpen
     91         )
     92     }
     93 
     94     public func open(_ request: RadrootsExternalActionRequest) async throws {
     95         guard let url = await resolvedURL(for: request.destination) else {
     96             throw RadrootsExternalActionError.unavailable(
     97                 "\(request.destination.kind.rawValue) external action is unavailable"
     98             )
     99         }
    100         let success = await adapters.openURL(url)
    101         guard success else {
    102             throw RadrootsExternalActionError.transientFailure(
    103                 "failed to open \(request.destination.kind.rawValue) external action"
    104             )
    105         }
    106     }
    107 
    108     private func resolvedURL(for destination: RadrootsExternalActionDestination) async -> URL? {
    109         switch destination.kind {
    110         case .appSettings:
    111             await adapters.appSettingsURL()
    112         case .web, .nostr, .appleMaps:
    113             destination.url
    114         }
    115     }
    116 }
    117 
    118 #if canImport(UIKit)
    119 private extension RadrootsAppleExternalActionsAdapters {
    120     @MainActor
    121     static func openUIKitURL(_ url: URL) async -> Bool {
    122         await withCheckedContinuation { continuation in
    123             UIApplication.shared.open(url, options: [:]) { success in
    124                 continuation.resume(returning: success)
    125             }
    126         }
    127     }
    128 }
    129 #endif