apple_kit

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

RadrootsAppleExternalActions.swift (3936B)


      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
     83         -> RadrootsExternalActionCapability
     84     {
     85         let url = await resolvedURL(for: destination)
     86         guard let url else {
     87             return RadrootsExternalActionCapability(destination: destination, canOpen: false)
     88         }
     89         let canOpen = await adapters.canOpenURL(url)
     90         return RadrootsExternalActionCapability(
     91             destination: destination,
     92             canOpen: canOpen
     93         )
     94     }
     95 
     96     public func open(_ request: RadrootsExternalActionRequest) async throws {
     97         guard let url = await resolvedURL(for: request.destination) else {
     98             throw RadrootsExternalActionError.unavailable
     99         }
    100         let success = await adapters.openURL(url)
    101         guard success else {
    102             throw RadrootsExternalActionError.transientFailure
    103         }
    104     }
    105 
    106     private func resolvedURL(for destination: RadrootsExternalActionDestination) async -> URL? {
    107         switch destination.kind {
    108         case .appSettings:
    109             await adapters.appSettingsURL()
    110         case .web, .nostr, .appleMaps:
    111             destination.url
    112         }
    113     }
    114 }
    115 
    116 #if canImport(UIKit)
    117     extension RadrootsAppleExternalActionsAdapters {
    118         @MainActor
    119         fileprivate static func openUIKitURL(_ url: URL) async -> Bool {
    120             await withCheckedContinuation { continuation in
    121                 UIApplication.shared.open(url, options: [:]) { success in
    122                     continuation.resume(returning: success)
    123                 }
    124             }
    125         }
    126     }
    127 #endif