1import 'dart:async';
  2
  3import 'package:blood_pressure_app/features/bluetooth/backend/bluetooth_state.dart';
  4import 'package:blood_pressure_app/logging.dart';
  5
  6/// Generic stream data parser base class
  7abstract class StreamDataParser<StreamData, ParsedData> {
  8  /// Method to be implemented by backend that converts the raw bluetooth adapter state to our BluetoothState
  9  ParsedData parse(StreamData rawState);
 10}
 11
 12/// Generic stream data parser base class that caches the last known state returned by the stream
 13abstract class StreamDataParserCached<StreamData, ParsedData> extends StreamDataParser<StreamData, ParsedData> {
 14  /// Initial state when it's unknown, f.e. when stream didn't return any data yet
 15  ParsedData get initialState;
 16  ParsedData? _lastKnownState;
 17
 18  /// The last known adapter state
 19  ParsedData get lastKnownState => _lastKnownState ?? initialState;
 20
 21  /// Internal method to cache the last adapter state value, backends should only implement parse not this method
 22  ParsedData parseAndCache(StreamData rawState) {
 23    _lastKnownState = parse(rawState);
 24    return lastKnownState;
 25  }
 26}
 27
 28/// Transforms the backend's bluetooth adapter state stream to emit [BluetoothAdapterState]'s
 29///
 30/// Can normally be used directly, backends should only inject a customized BluetoothStateParser
 31class StreamDataParserTransformer<StreamData, ParsedData, SD extends StreamDataParser<StreamData, ParsedData>>
 32  extends StreamDataTransformer<StreamData, ParsedData> {
 33  /// Create a BluetoothAdapterStateStreamTransformer
 34  ///
 35  /// [stateParser] The BluetoothStateParser that provides the backend logic to convert BackendState to BluetoothAdapterState
 36  StreamDataParserTransformer({ required SD stateParser, super.sync, super.cancelOnError }) {
 37    _stateParser = stateParser;
 38  }
 39
 40  late SD _stateParser;
 41
 42  @override
 43  void onData(StreamData streamData) {
 44    late ParsedData data;
 45    if (_stateParser is StreamDataParserCached) {
 46      data = (_stateParser as StreamDataParserCached).parseAndCache(streamData);
 47    } else {
 48      data = _stateParser.parse(streamData);
 49    }
 50
 51    sendData(data);
 52  }
 53}
 54
 55
 56/// Generic stream transformer util that should support cancelling & pausing etc
 57///
 58/// Implementations should only need to worry about transforming the data by overriding
 59/// the onData method and sending the transformed data using sendData
 60///
 61/// TODO: move outside bluetooth logic
 62abstract class StreamDataTransformer<S,T> with TypeLogger implements StreamTransformer<S,T> {
 63  /// Create a BluetoothStreamTransformer
 64  ///
 65  /// - [sync] Passed to [StreamController]
 66  /// - [cancelOnError] Passed to [Stream]
 67  StreamDataTransformer({ bool sync = false, bool cancelOnError = false }) {
 68    _cancelOnError = cancelOnError;
 69
 70    _controller = StreamController<T>(
 71      onListen: _onListen,
 72      onCancel: _onCancel,
 73      onPause: () => _subscription?.pause(),
 74      onResume: () => _subscription?.resume(),
 75      sync: sync,
 76    );
 77  }
 78
 79  late StreamController<T> _controller;
 80  StreamSubscription? _subscription;
 81  Stream<S>? _stream;
 82  bool _cancelOnError = false;
 83
 84  void _onListen() {
 85    logger.finest('_onListen');
 86    _subscription = _stream?.listen(
 87      onData,
 88      onError: _controller.addError,
 89      onDone: _controller.close,
 90      cancelOnError: _cancelOnError);
 91  }
 92
 93  void _onCancel() {
 94    logger.finest('_onCancel');
 95    _subscription?.cancel();
 96    _subscription = null;
 97  }
 98
 99  /// Method that actually transforms the data being passed through this stream
100  void onData(S streamData);
101
102  /// Send data to the listening stream, should f.e. be called from within the
103  /// onData call to forward the transformed data
104  void sendData(T data) {
105    _controller.add(data);
106  }
107
108  @override
109  Stream<T> bind(Stream<S> stream) {
110    logger.finest('bind');
111    _stream = stream;
112    return _controller.stream;
113  }
114
115  @override
116  StreamTransformer<RS, RT> cast<RS, RT>() => StreamTransformer.castFrom(this);
117}