import 'package:flutter/services.dart'; import '../models/bluetooth_device_info.dart'; import '../models/gps_speed.dart'; import '../models/location_reading.dart'; import '../models/obd_state.dart'; class TelemetryException implements Exception { TelemetryException(this.message); final String message; @override String toString() => message; } /// Bluetooth OBD and location boundary. Kept separate from LauncherPlatform so /// package/launcher fakes do not need telemetry methods. abstract class TelemetryPlatform { Future> listBondedDevices(); Future isBluetoothEnabled(); Future openBluetoothSettings(); Future ensureBluetoothPermission(); Future ensureLocationPermission(); Future getLocation(); Future connectObd(String address); Future disconnectObd(); Future getObdState(); /// Starts GPS speed updates only when precise location is already allowed. Future startGpsSpeed(); Future stopGpsSpeed(); Future getGpsSpeed(); } class NativeTelemetryBridge implements TelemetryPlatform { static const _channel = MethodChannel('com.wasp.cardash_launcher/telemetry'); @override Future> listBondedDevices() async { try { final raw = await _channel.invokeMethod('listBondedDevices'); if (raw is! List) return const []; return raw .whereType() .map(BluetoothDeviceInfo.fromMap) .where((device) => device.address.isNotEmpty) .toList(growable: false); } on PlatformException catch (error) { throw TelemetryException( error.message ?? 'Could not list Bluetooth devices.', ); } on MissingPluginException { throw TelemetryException('The telemetry bridge is unavailable.'); } } @override Future isBluetoothEnabled() async { try { return await _channel.invokeMethod('isBluetoothEnabled') ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future openBluetoothSettings() async { try { return await _channel.invokeMethod('openBluetoothSettings') ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future ensureBluetoothPermission() async { try { return await _channel.invokeMethod('ensureBluetoothPermission') ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future ensureLocationPermission() async { try { return await _channel.invokeMethod('ensureLocationPermission') ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future getLocation() async { try { final raw = await _channel.invokeMethod('getLocation'); if (raw is! Map) { throw TelemetryException('Could not read the current location.'); } return LocationReading.fromMap(raw); } on PlatformException catch (error) { throw TelemetryException(error.message ?? 'Could not read location.'); } on MissingPluginException { throw TelemetryException('The telemetry bridge is unavailable.'); } } @override Future connectObd(String address) async { try { await _channel.invokeMethod('connectObd', {'address': address}); } on PlatformException catch (error) { throw TelemetryException(error.message ?? 'Could not connect to OBD.'); } on MissingPluginException { throw TelemetryException('The telemetry bridge is unavailable.'); } } @override Future disconnectObd() async { try { await _channel.invokeMethod('disconnectObd'); } on PlatformException { // Nothing useful to do when already disconnected. } on MissingPluginException { // No-op outside Android. } } @override Future startGpsSpeed() async { try { return await _channel.invokeMethod('startGpsSpeed') ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future stopGpsSpeed() async { try { await _channel.invokeMethod('stopGpsSpeed'); } on PlatformException { // Nothing useful to do when already stopped. } on MissingPluginException { // No-op outside Android. } } @override Future getGpsSpeed() async { try { final raw = await _channel.invokeMethod('getGpsSpeed'); return raw is Map ? GpsSpeed.fromMap(raw) : const GpsSpeed(); } on PlatformException { return const GpsSpeed(); } on MissingPluginException { return const GpsSpeed(); } } @override Future getObdState() async { try { final raw = await _channel.invokeMethod('getObdState'); return raw is Map ? ObdState.fromMap(raw) : const ObdState(); } on PlatformException { return const ObdState(); } on MissingPluginException { return const ObdState(); } } }