tauri-plugin-haptics 2.3.2

Haptic feedback and vibrations on Android and iOS
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

import SwiftRs
import Tauri
import UIKit
import WebKit
import CoreHaptics
import AudioToolbox

class ImpactFeedbackOptions: Decodable {
  let style: ImpactFeedbackStyle
}

enum ImpactFeedbackStyle: String, Decodable {
  case light, medium, heavy, soft, rigid

  func into() -> UIImpactFeedbackGenerator.FeedbackStyle {
    switch self {
    case .light:
      return .light
    case .medium:
      return .medium
    case .heavy:
      return .heavy
    case .soft:
      return .soft
    case .rigid:
      return .rigid
    }
  }
}

class NotificationFeedbackOptions: Decodable {
  let type: NotificationFeedbackType
}

enum NotificationFeedbackType: String, Decodable {
  case success, warning, error

  func into() -> UINotificationFeedbackGenerator.FeedbackType {
    switch self {
    case .success:
      return .success
    case .warning:
      return .warning
    case .error:
      return .error
    }
  }
}

class VibrateOptions: Decodable {
  // TODO: Array
  let duration: Double
}

class HapticsPlugin: Plugin {
  //
  // Tauri commands
  //

  @objc public func vibrate(_ invoke: Invoke) throws {
    let args = try invoke.parseArgs(VibrateOptions.self)
    if CHHapticEngine.capabilitiesForHardware().supportsHaptics {
      do {
        let engine = try CHHapticEngine()
        try engine.start()
        engine.resetHandler = { [] in
          do {
              try engine.start()
          } catch {
            AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
          }
        }
        // TODO: Make some of this (or all) configurable?
        let intensity: CHHapticEventParameter = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0)
        let sharpness: CHHapticEventParameter = CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
        let continuousEvent = CHHapticEvent(
          eventType: .hapticContinuous,
          parameters: [intensity, sharpness],
          relativeTime: 0.0,
          duration: args.duration/1000
        )
        let pattern = try CHHapticPattern(events: [continuousEvent], parameters: [])
        let player = try engine.makePlayer(with: pattern)

        try player.start(atTime: 0)
      } catch {
        AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
      }
    } else {
      AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
    }

    Logger.error("VIBRATE END")

    invoke.resolve()
  }

  @objc public func impactFeedback(_ invoke: Invoke) throws {
    let args = try invoke.parseArgs(ImpactFeedbackOptions.self)
    let generator = UIImpactFeedbackGenerator(style: args.style.into())
    generator.prepare()
    generator.impactOccurred()

    invoke.resolve()
  }

  @objc public func notificationFeedback(_ invoke: Invoke) throws {
    let args = try invoke.parseArgs(NotificationFeedbackOptions.self)
    let generator = UINotificationFeedbackGenerator()
    generator.prepare()
    generator.notificationOccurred(args.type.into())

    invoke.resolve()
  }

  // TODO: Consider breaking this up into Start,Change,End like capacitor
  @objc public func selectionFeedback(_ invoke: Invoke) throws {
    let generator = UISelectionFeedbackGenerator()
    generator.prepare()
    generator.selectionChanged()

    invoke.resolve()
  }
}

@_cdecl("init_plugin_haptics")
func initPlugin() -> Plugin {
  return HapticsPlugin()
}