diff --git a/packages/sensors_plus/sensors_plus/ios/sensors_plus/Sources/sensors_plus/FPPSensorsPlusPlugin.swift b/packages/sensors_plus/sensors_plus/ios/sensors_plus/Sources/sensors_plus/FPPSensorsPlusPlugin.swift index 2b2695ff08..1becc4b1d2 100644 --- a/packages/sensors_plus/sensors_plus/ios/sensors_plus/Sources/sensors_plus/FPPSensorsPlusPlugin.swift +++ b/packages/sensors_plus/sensors_plus/ios/sensors_plus/Sources/sensors_plus/FPPSensorsPlusPlugin.swift @@ -4,103 +4,72 @@ import Flutter -var _eventChannels: [String: FlutterEventChannel] = [:] -var _streamHandlers: [String: MotionStreamHandler] = [:] -var _isCleanUp = false - public class FPPSensorsPlusPlugin: NSObject, FlutterPlugin { + private var methodChannel: FlutterMethodChannel? + private var eventChannels: [String: FlutterEventChannel] = [:] + private var streamHandlers: [String: MotionStreamHandler] = [:] public static func register(with registrar: FlutterPluginRegistrar) { - let accelerometerStreamHandler = FPPAccelerometerStreamHandlerPlus() - let accelerometerStreamHandlerName = "dev.fluttercommunity.plus/sensors/accelerometer" - let accelerometerChannel = FlutterEventChannel( - name: accelerometerStreamHandlerName, - binaryMessenger: registrar.messenger() - ) - accelerometerChannel.setStreamHandler(accelerometerStreamHandler) - _eventChannels[accelerometerStreamHandlerName] = accelerometerChannel - _streamHandlers[accelerometerStreamHandlerName] = accelerometerStreamHandler - - let userAccelerometerStreamHandler = FPPUserAccelStreamHandlerPlus() - let userAccelerometerStreamHandlerName = "dev.fluttercommunity.plus/sensors/user_accel" - let userAccelerometerChannel = FlutterEventChannel( - name: userAccelerometerStreamHandlerName, - binaryMessenger: registrar.messenger() - ) - userAccelerometerChannel.setStreamHandler(userAccelerometerStreamHandler) - _eventChannels[userAccelerometerStreamHandlerName] = userAccelerometerChannel - _streamHandlers[userAccelerometerStreamHandlerName] = userAccelerometerStreamHandler + let instance = FPPSensorsPlusPlugin() + instance.setUpChannels(registrar: registrar) + registrar.publish(instance) + } - let gyroscopeStreamHandler = FPPGyroscopeStreamHandlerPlus() - let gyroscopeStreamHandlerName = "dev.fluttercommunity.plus/sensors/gyroscope" - let gyroscopeChannel = FlutterEventChannel( - name: gyroscopeStreamHandlerName, - binaryMessenger: registrar.messenger() - ) - gyroscopeChannel.setStreamHandler(gyroscopeStreamHandler) - _eventChannels[gyroscopeStreamHandlerName] = gyroscopeChannel - _streamHandlers[gyroscopeStreamHandlerName] = gyroscopeStreamHandler + public func detachFromEngine(for registrar: FlutterPluginRegistrar) { + cleanUp() + } - let magnetometerStreamHandler = FPPMagnetometerStreamHandlerPlus() - let magnetometerStreamHandlerName = "dev.fluttercommunity.plus/sensors/magnetometer" - let magnetometerChannel = FlutterEventChannel( - name: magnetometerStreamHandlerName, - binaryMessenger: registrar.messenger() - ) - magnetometerChannel.setStreamHandler(magnetometerStreamHandler) - _eventChannels[magnetometerStreamHandlerName] = magnetometerChannel - _streamHandlers[magnetometerStreamHandlerName] = magnetometerStreamHandler + private func setUpChannels(registrar: FlutterPluginRegistrar) { + let sensorConfigurations: [(String, MotionStreamHandler)] = [ + ("dev.fluttercommunity.plus/sensors/accelerometer", FPPAccelerometerStreamHandlerPlus()), + ("dev.fluttercommunity.plus/sensors/user_accel", FPPUserAccelStreamHandlerPlus()), + ("dev.fluttercommunity.plus/sensors/gyroscope", FPPGyroscopeStreamHandlerPlus()), + ("dev.fluttercommunity.plus/sensors/magnetometer", FPPMagnetometerStreamHandlerPlus()), + ("dev.fluttercommunity.plus/sensors/barometer", FPPBarometerStreamHandlerPlus()), + ] - let barometerStreamHandler = FPPBarometerStreamHandlerPlus() - let barometerStreamHandlerName = "dev.fluttercommunity.plus/sensors/barometer" - let barometerChannel = FlutterEventChannel( - name: barometerStreamHandlerName, - binaryMessenger: registrar.messenger() - ) - barometerChannel.setStreamHandler(barometerStreamHandler) - _eventChannels[barometerStreamHandlerName] = barometerChannel - _streamHandlers[barometerStreamHandlerName] = barometerStreamHandler + for (name, handler) in sensorConfigurations { + let channel = FlutterEventChannel(name: name, binaryMessenger: registrar.messenger()) + channel.setStreamHandler(handler) + eventChannels[name] = channel + streamHandlers[name] = handler + } - let methodChannel = FlutterMethodChannel( - name: "dev.fluttercommunity.plus/sensors/method", - binaryMessenger: registrar.messenger() + methodChannel = FlutterMethodChannel( + name: "dev.fluttercommunity.plus/sensors/method", + binaryMessenger: registrar.messenger() ) - methodChannel.setMethodCallHandler { call, result in - let streamHandler: MotionStreamHandler!; - switch (call.method) { - case "setAccelerationSamplingPeriod": - streamHandler = _streamHandlers[accelerometerStreamHandlerName] - case "setUserAccelerometerSamplingPeriod": - streamHandler = _streamHandlers[userAccelerometerStreamHandlerName] - case "setGyroscopeSamplingPeriod": - streamHandler = _streamHandlers[gyroscopeStreamHandlerName] - case "setMagnetometerSamplingPeriod": - streamHandler = _streamHandlers[magnetometerStreamHandlerName] - case "setBarometerSamplingPeriod": - streamHandler = _streamHandlers[barometerStreamHandlerName] - default: - return result(FlutterMethodNotImplemented) + methodChannel?.setMethodCallHandler { [weak self] call, result in + guard let self, let handler = self.handler(for: call.method) else { + result(FlutterMethodNotImplemented) + return } - streamHandler.samplingPeriod = call.arguments as! Int + handler.samplingPeriod = call.arguments as! Int result(nil) } - - _isCleanUp = false } - func detachFromEngineForRegistrar(registrar: NSObject!) { - FPPSensorsPlusPlugin._cleanUp() + private func handler(for method: String) -> MotionStreamHandler? { + let names = [ + "setAccelerationSamplingPeriod": "dev.fluttercommunity.plus/sensors/accelerometer", + "setUserAccelerometerSamplingPeriod": "dev.fluttercommunity.plus/sensors/user_accel", + "setGyroscopeSamplingPeriod": "dev.fluttercommunity.plus/sensors/gyroscope", + "setMagnetometerSamplingPeriod": "dev.fluttercommunity.plus/sensors/magnetometer", + "setBarometerSamplingPeriod": "dev.fluttercommunity.plus/sensors/barometer", + ] + return names[method].flatMap { streamHandlers[$0] } } - static func _cleanUp() { - _isCleanUp = true - for channel in _eventChannels.values { - channel.setStreamHandler(nil) - } - _eventChannels.removeAll() - for handler in _streamHandlers.values { + private func cleanUp() { + methodChannel?.setMethodCallHandler(nil) + methodChannel = nil + for handler in streamHandlers.values { handler.onCancel(withArguments: nil) } - _streamHandlers.removeAll() + for channel in eventChannels.values { + channel.setStreamHandler(nil) + } + streamHandlers.removeAll() + eventChannels.removeAll() } } diff --git a/packages/sensors_plus/sensors_plus/ios/sensors_plus/Sources/sensors_plus/FPPStreamHandlerPlus.swift b/packages/sensors_plus/sensors_plus/ios/sensors_plus/Sources/sensors_plus/FPPStreamHandlerPlus.swift index 25c54b20b4..51e7fc1f9c 100644 --- a/packages/sensors_plus/sensors_plus/ios/sensors_plus/Sources/sensors_plus/FPPStreamHandlerPlus.swift +++ b/packages/sensors_plus/sensors_plus/ios/sensors_plus/Sources/sensors_plus/FPPStreamHandlerPlus.swift @@ -2,301 +2,172 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import Foundation -import Flutter -import UIKit import CoreMotion +import Flutter +import Foundation let GRAVITY = 9.81 -var _motionManager: CMMotionManager! -var _altimeter: CMAltimeter! +let timestampMicroAtBoot = (Date().timeIntervalSince1970 - ProcessInfo.processInfo.systemUptime) * 1000000 -public protocol MotionStreamHandler: FlutterStreamHandler { +public protocol MotionStreamHandler: NSObjectProtocol, FlutterStreamHandler { var samplingPeriod: Int { get set } } -let timestampMicroAtBoot = (Date().timeIntervalSince1970 - ProcessInfo.processInfo.systemUptime) * 1000000 +private class FPPStreamHandlerBase: NSObject, MotionStreamHandler { + var samplingPeriod = 200000 + var eventSink: FlutterEventSink? -func _initMotionManager() { - if (_motionManager == nil) { - _motionManager = CMMotionManager() - _motionManager.accelerometerUpdateInterval = 0.2 - _motionManager.deviceMotionUpdateInterval = 0.2 - _motionManager.gyroUpdateInterval = 0.2 - _motionManager.magnetometerUpdateInterval = 0.2 + func onListen(withArguments arguments: Any?, eventSink sink: @escaping FlutterEventSink) -> FlutterError? { + self.eventSink = sink + return nil } -} -func _initAltimeter() { - if (_altimeter == nil) { - _altimeter = CMAltimeter() + func onCancel(withArguments arguments: Any?) -> FlutterError? { + eventSink = nil + return nil } -} -func sendFlutter(x: Float64, y: Float64, z: Float64, timestamp: TimeInterval, sink: @escaping FlutterEventSink) { - if _isCleanUp { - return + func sendError(_ error: Error, to sink: @escaping FlutterEventSink) { + DispatchQueue.main.async { [weak self] in + guard self?.eventSink != nil else { return } + sink(FlutterError(code: "UNAVAILABLE", message: error.localizedDescription, details: nil)) + } } - // Even after [detachFromEngineForRegistrar] some events may still be received - // and fired until fully detached. - DispatchQueue.main.async { - let timestampSince1970Micro = timestampMicroAtBoot + (timestamp * 1000000) - let triplet = [x, y, z, timestampSince1970Micro] - triplet.withUnsafeBufferPointer { buffer in - sink(FlutterStandardTypedData.init(float64: Data(buffer: buffer))) + + func sendValues(_ values: [Double], timestamp: TimeInterval, to sink: @escaping FlutterEventSink) { + DispatchQueue.main.async { [weak self] in + guard self?.eventSink != nil else { return } + let payload = values + [timestampMicroAtBoot + timestamp * 1000000] + payload.withUnsafeBufferPointer { buffer in + sink(FlutterStandardTypedData(float64: Data(buffer: buffer))) + } } } } -class FPPAccelerometerStreamHandlerPlus: NSObject, MotionStreamHandler { +class FPPAccelerometerStreamHandlerPlus: FPPStreamHandlerBase { + private let motionManager = CMMotionManager() - var samplingPeriod = 200000 { - didSet { - _initMotionManager() - _motionManager.accelerometerUpdateInterval = Double(samplingPeriod) * 0.000001 - } + override var samplingPeriod: Int { + didSet { motionManager.accelerometerUpdateInterval = Double(samplingPeriod) * 0.000001 } } - func onListen( - withArguments arguments: Any?, - eventSink sink: @escaping FlutterEventSink - ) -> FlutterError? { - _initMotionManager() - _motionManager.startAccelerometerUpdates(to: OperationQueue()) { data, error in - if _isCleanUp { - return - } - if (error != nil) { - sink(FlutterError.init( - code: "UNAVAILABLE", - message: error!.localizedDescription, - details: nil - )) - return - } - // Multiply by gravity, and adjust sign values to - // align with Android. - let acceleration = data!.acceleration - sendFlutter( - x: -acceleration.x * GRAVITY, - y: -acceleration.y * GRAVITY, - z: -acceleration.z * GRAVITY, - timestamp: data!.timestamp, - sink: sink - ) + override func onListen(withArguments arguments: Any?, eventSink sink: @escaping FlutterEventSink) -> FlutterError? { + super.onListen(withArguments: arguments, eventSink: sink) + motionManager.accelerometerUpdateInterval = Double(samplingPeriod) * 0.000001 + motionManager.startAccelerometerUpdates(to: OperationQueue()) { [weak self] data, error in + guard let self, let sink = self.eventSink else { return } + if let error { self.sendError(error, to: sink); return } + guard let data else { return } + let acceleration = data.acceleration + self.sendValues([-acceleration.x * GRAVITY, -acceleration.y * GRAVITY, -acceleration.z * GRAVITY], timestamp: data.timestamp, to: sink) } return nil } - func onCancel(withArguments arguments: Any?) -> FlutterError? { - _motionManager.stopAccelerometerUpdates() - return nil - } - - func dealloc() { - FPPSensorsPlusPlugin._cleanUp() + override func onCancel(withArguments arguments: Any?) -> FlutterError? { + motionManager.stopAccelerometerUpdates() + return super.onCancel(withArguments: arguments) } } -class FPPUserAccelStreamHandlerPlus: NSObject, MotionStreamHandler { +class FPPUserAccelStreamHandlerPlus: FPPStreamHandlerBase { + private let motionManager = CMMotionManager() - var samplingPeriod = 200000 { - didSet { - _initMotionManager() - _motionManager.deviceMotionUpdateInterval = Double(samplingPeriod) * 0.000001 - } + override var samplingPeriod: Int { + didSet { motionManager.deviceMotionUpdateInterval = Double(samplingPeriod) * 0.000001 } } - func onListen( - withArguments arguments: Any?, - eventSink sink: @escaping FlutterEventSink - ) -> FlutterError? { - _initMotionManager() - _motionManager.startDeviceMotionUpdates(to: OperationQueue()) { data, error in - if _isCleanUp { - return - } - if (error != nil) { - sink(FlutterError.init( - code: "UNAVAILABLE", - message: error!.localizedDescription, - details: nil - )) - return - } - // Multiply by gravity, and adjust sign values to - // align with Android. - let acceleration = data!.userAcceleration - sendFlutter( - x: -acceleration.x * GRAVITY, - y: -acceleration.y * GRAVITY, - z: -acceleration.z * GRAVITY, - timestamp: data!.timestamp, - sink: sink - ) + override func onListen(withArguments arguments: Any?, eventSink sink: @escaping FlutterEventSink) -> FlutterError? { + super.onListen(withArguments: arguments, eventSink: sink) + motionManager.deviceMotionUpdateInterval = Double(samplingPeriod) * 0.000001 + motionManager.startDeviceMotionUpdates(to: OperationQueue()) { [weak self] data, error in + guard let self, let sink = self.eventSink else { return } + if let error { self.sendError(error, to: sink); return } + guard let data else { return } + let acceleration = data.userAcceleration + self.sendValues([-acceleration.x * GRAVITY, -acceleration.y * GRAVITY, -acceleration.z * GRAVITY], timestamp: data.timestamp, to: sink) } return nil } - func onCancel(withArguments arguments: Any?) -> FlutterError? { - _motionManager.stopDeviceMotionUpdates() - return nil - } - - func dealloc() { - FPPSensorsPlusPlugin._cleanUp() + override func onCancel(withArguments arguments: Any?) -> FlutterError? { + motionManager.stopDeviceMotionUpdates() + return super.onCancel(withArguments: arguments) } } -class FPPGyroscopeStreamHandlerPlus: NSObject, MotionStreamHandler { +class FPPGyroscopeStreamHandlerPlus: FPPStreamHandlerBase { + private let motionManager = CMMotionManager() - var samplingPeriod = 200000 { - didSet { - _initMotionManager() - _motionManager.gyroUpdateInterval = Double(samplingPeriod) * 0.000001 - } + override var samplingPeriod: Int { + didSet { motionManager.gyroUpdateInterval = Double(samplingPeriod) * 0.000001 } } - func onListen( - withArguments arguments: Any?, - eventSink sink: @escaping FlutterEventSink - ) -> FlutterError? { - _initMotionManager() - _motionManager.startGyroUpdates(to: OperationQueue()) { data, error in - if _isCleanUp { - return - } - if (error != nil) { - sink(FlutterError( - code: "UNAVAILABLE", - message: error!.localizedDescription, - details: nil - )) - return - } - let rotationRate = data!.rotationRate - sendFlutter( - x: rotationRate.x, - y: rotationRate.y, - z: rotationRate.z, - timestamp: data!.timestamp, - sink: sink - ) + override func onListen(withArguments arguments: Any?, eventSink sink: @escaping FlutterEventSink) -> FlutterError? { + super.onListen(withArguments: arguments, eventSink: sink) + motionManager.gyroUpdateInterval = Double(samplingPeriod) * 0.000001 + motionManager.startGyroUpdates(to: OperationQueue()) { [weak self] data, error in + guard let self, let sink = self.eventSink else { return } + if let error { self.sendError(error, to: sink); return } + guard let data else { return } + let rotation = data.rotationRate + self.sendValues([rotation.x, rotation.y, rotation.z], timestamp: data.timestamp, to: sink) } return nil } - func onCancel(withArguments arguments: Any?) -> FlutterError? { - _motionManager.stopGyroUpdates() - return nil - } - - func dealloc() { - FPPSensorsPlusPlugin._cleanUp() + override func onCancel(withArguments arguments: Any?) -> FlutterError? { + motionManager.stopGyroUpdates() + return super.onCancel(withArguments: arguments) } } -class FPPMagnetometerStreamHandlerPlus: NSObject, MotionStreamHandler { +class FPPMagnetometerStreamHandlerPlus: FPPStreamHandlerBase { + private let motionManager = CMMotionManager() - var samplingPeriod = 200000 { - didSet { - _initMotionManager() - _motionManager.magnetometerUpdateInterval = Double(samplingPeriod) * 0.000001 - } + override var samplingPeriod: Int { + didSet { motionManager.magnetometerUpdateInterval = Double(samplingPeriod) * 0.000001 } } - func onListen( - withArguments arguments: Any?, - eventSink sink: @escaping FlutterEventSink - ) -> FlutterError? { - _initMotionManager() - _motionManager.startMagnetometerUpdates(to: OperationQueue()) { data, error in - if _isCleanUp { - return - } - if (error != nil) { - sink(FlutterError( - code: "UNAVAILABLE", - message: error!.localizedDescription, - details: nil - )) - return - } - let magneticField = data!.magneticField - sendFlutter( - x: magneticField.x, - y: magneticField.y, - z: magneticField.z, - timestamp: data!.timestamp, - sink: sink - ) + override func onListen(withArguments arguments: Any?, eventSink sink: @escaping FlutterEventSink) -> FlutterError? { + super.onListen(withArguments: arguments, eventSink: sink) + motionManager.magnetometerUpdateInterval = Double(samplingPeriod) * 0.000001 + motionManager.startMagnetometerUpdates(to: OperationQueue()) { [weak self] data, error in + guard let self, let sink = self.eventSink else { return } + if let error { self.sendError(error, to: sink); return } + guard let data else { return } + let field = data.magneticField + self.sendValues([field.x, field.y, field.z], timestamp: data.timestamp, to: sink) } return nil } - func onCancel(withArguments arguments: Any?) -> FlutterError? { - _motionManager.stopMagnetometerUpdates() - return nil - } - - func dealloc() { - FPPSensorsPlusPlugin._cleanUp() + override func onCancel(withArguments arguments: Any?) -> FlutterError? { + motionManager.stopMagnetometerUpdates() + return super.onCancel(withArguments: arguments) } } -class FPPBarometerStreamHandlerPlus: NSObject, MotionStreamHandler { +class FPPBarometerStreamHandlerPlus: FPPStreamHandlerBase { + private let altimeter = CMAltimeter() - var samplingPeriod = 200000 { - didSet { - _initAltimeter() - // Note: CMAltimeter does not provide a way to set the sampling period directly. - // The sampling period would typically be managed by starting/stopping the updates. + override func onListen(withArguments arguments: Any?, eventSink sink: @escaping FlutterEventSink) -> FlutterError? { + super.onListen(withArguments: arguments, eventSink: sink) + guard CMAltimeter.isRelativeAltitudeAvailable() else { + return FlutterError(code: "UNAVAILABLE", message: "Barometer is not available on this device", details: nil) } - } - - func onListen( - withArguments arguments: Any?, - eventSink sink: @escaping FlutterEventSink - ) -> FlutterError? { - _initAltimeter() - if CMAltimeter.isRelativeAltitudeAvailable() { - _altimeter.startRelativeAltitudeUpdates(to: OperationQueue()) { data, error in - if _isCleanUp { - return - } - if (error != nil) { - sink(FlutterError( - code: "UNAVAILABLE", - message: error!.localizedDescription, - details: nil - )) - return - } - let pressure = data!.pressure.doubleValue * 10.0 // kPa to hPa (hectopascals) - DispatchQueue.main.async { - let timestampSince1970Micro = timestampMicroAtBoot + (data!.timestamp * 1000000) - let pressureArray: [Double] = [pressure, timestampSince1970Micro] - pressureArray.withUnsafeBufferPointer { buffer in - sink(FlutterStandardTypedData.init(float64: Data(buffer: buffer))) - } - } - } - } else { - return FlutterError( - code: "UNAVAILABLE", - message: "Barometer is not available on this device", - details: nil - ) + altimeter.startRelativeAltitudeUpdates(to: OperationQueue()) { [weak self] data, error in + guard let self, let sink = self.eventSink else { return } + if let error { self.sendError(error, to: sink); return } + guard let data else { return } + self.sendValues([data.pressure.doubleValue * 10.0], timestamp: data.timestamp, to: sink) } return nil } - func onCancel(withArguments arguments: Any?) -> FlutterError? { - _altimeter.stopRelativeAltitudeUpdates() - return nil - } - - func dealloc() { - FPPSensorsPlusPlugin._cleanUp() + override func onCancel(withArguments arguments: Any?) -> FlutterError? { + altimeter.stopRelativeAltitudeUpdates() + return super.onCancel(withArguments: arguments) } }