diff options
| author | uvok | 2025-07-30 20:25:03 +0200 | 
|---|---|---|
| committer | uvok | 2025-07-30 20:25:03 +0200 | 
| commit | 87e1dfffd2c17ef0e3d0711394122456e9d0c7c8 (patch) | |
| tree | 50ca1fa6cf89d271ad319720e8691f6d49332f20 | |
| parent | c84d5947b8650a022dc7a0032e3de1996b92307e (diff) | |
Move FlutterBleCode to new classes
| -rw-r--r-- | lib/control/flutter_blue_plus_scanner_controller.dart | 10 | ||||
| -rw-r--r-- | lib/control/mock_scanner_controller.dart | 12 | ||||
| -rw-r--r-- | lib/control/scanner_controller_impl.dart | 21 | ||||
| -rw-r--r-- | lib/device_details.dart | 78 | ||||
| -rw-r--r-- | lib/model/device.dart | 2 | ||||
| -rw-r--r-- | lib/model/device_connection.dart | 29 | ||||
| -rw-r--r-- | lib/model/device_connection_factory.dart | 32 | ||||
| -rw-r--r-- | lib/model/flutter_blue_plus_device_connection.dart | 109 | ||||
| -rw-r--r-- | lib/model/mock_device_connection.dart | 33 | ||||
| -rw-r--r-- | lib/scan_page.dart | 12 | ||||
| -rw-r--r-- | test/widget_test.dart | 2 | 
11 files changed, 248 insertions, 92 deletions
| diff --git a/lib/control/flutter_blue_plus_scanner_controller.dart b/lib/control/flutter_blue_plus_scanner_controller.dart index b859129..fc570b2 100644 --- a/lib/control/flutter_blue_plus_scanner_controller.dart +++ b/lib/control/flutter_blue_plus_scanner_controller.dart @@ -9,12 +9,6 @@ import 'package:logger/logger.dart';  var logger = Logger();  class FlutterBluePlusScannerController extends ScannerControllerImpl { -  final StreamController<List<Device>> _scanResultsController = -      StreamController<List<Device>>.broadcast(); - -  @override -  Stream<List<Device>> get scanResultsStream => _scanResultsController.stream; -    @override    Future<void> startScan({      Duration timeout = const Duration(seconds: 5), @@ -33,7 +27,7 @@ class FlutterBluePlusScannerController extends ScannerControllerImpl {          List<Device> devices = results              .map((d) => FlutterBluePlusDevice.fromScan(d))              .toList(); -        _scanResultsController.add(devices); +        super.setDevices(devices);        },        onError: (err) {          logger.e(err); @@ -57,7 +51,7 @@ class FlutterBluePlusScannerController extends ScannerControllerImpl {    @override    void dispose() {      stopScan().ignore(); -    _scanResultsController.close(); +    super.dispose();    }    List<ScanResult> _scanResults = []; diff --git a/lib/control/mock_scanner_controller.dart b/lib/control/mock_scanner_controller.dart index b210c0a..ec893cd 100644 --- a/lib/control/mock_scanner_controller.dart +++ b/lib/control/mock_scanner_controller.dart @@ -13,14 +13,6 @@ class MockScannerController extends ScannerControllerImpl {      MockDevice(3, "Fourth"),    ]; -  @override -  void dispose() {} - -  @override -  Stream<List<Device>> get scanResultsStream => _deviceContoller.stream; -  final StreamController<List<Device>> _deviceContoller = -      StreamController<List<Device>>.broadcast(); -    bool _isScanning = false;    @override @@ -33,9 +25,7 @@ class MockScannerController extends ScannerControllerImpl {      for (int i = 0; i < fakedDevices.length && _isScanning; i++) {        await Future.delayed(Duration(milliseconds: 300)); -      _deviceContoller.add( -        fakedDevices.getRange(0, i + 1).toList(growable: false), -      ); +      super.setDevices(fakedDevices.getRange(0, i + 1).toList(growable: false));      }      _isScanning = false; diff --git a/lib/control/scanner_controller_impl.dart b/lib/control/scanner_controller_impl.dart index b0a7f79..7033542 100644 --- a/lib/control/scanner_controller_impl.dart +++ b/lib/control/scanner_controller_impl.dart @@ -1,17 +1,38 @@  import 'dart:async';  import 'package:uvok_epaper_badge/control/scanner_controller.dart';  import 'package:meta/meta.dart'; +import 'package:uvok_epaper_badge/model/device.dart';  /// Helper class which provides the setStatus method.  abstract class ScannerControllerImpl extends ScannerController {    final StreamController<ScanStatus> _scanStatusController =        StreamController<ScanStatus>.broadcast(); +  final StreamController<List<Device>> _deviceContoller = +      StreamController<List<Device>>.broadcast();    @override    Stream<ScanStatus> get statusStream => _scanStatusController.stream; +  @override +  Stream<List<Device>> get scanResultsStream => _deviceContoller.stream; +    @protected    void setStatus(ScanStatus newStatus) { +    if (_scanStatusController.isClosed) return; +      _scanStatusController.add(newStatus);    } + +  @protected +  void setDevices(List<Device> devices) { +    if (_deviceContoller.isClosed) return; + +    _deviceContoller.add(devices); +  } + +  @override +  void dispose() { +    _scanStatusController.close(); +    _deviceContoller.close(); +  }  } diff --git a/lib/device_details.dart b/lib/device_details.dart index 11a5f39..f3754d3 100644 --- a/lib/device_details.dart +++ b/lib/device_details.dart @@ -16,13 +16,19 @@  import 'package:uvok_epaper_badge/model/device.dart';  import 'package:flutter/material.dart';  import 'package:logger/logger.dart'; +import 'package:uvok_epaper_badge/model/device_connection.dart';  var logger = Logger();  class DeviceDetailsScreen extends StatefulWidget {    final Device device; +  final DeviceConnection deviceConnection; -  const DeviceDetailsScreen({super.key, required this.device}); +  const DeviceDetailsScreen({ +    super.key, +    required this.device, +    required this.deviceConnection, +  });    @override    State<StatefulWidget> createState() { @@ -32,16 +38,10 @@ class DeviceDetailsScreen extends StatefulWidget {  class DeviceDetailsState extends State<DeviceDetailsScreen> {    String connectStatus = "<Status>"; -  // Just to have a resonable default subscription? -  // StreamSubscription<BluetoothConnectionState> subs = -  //     Stream<BluetoothConnectionState>.empty().listen((e) => ());    /// Whether the back button should be active.    bool backActive = false; -  // BluetoothCharacteristic? current; -  // BluetoothCharacteristic? available; -    @override    Widget build(BuildContext context) {      return Scaffold( @@ -74,51 +74,22 @@ class DeviceDetailsState extends State<DeviceDetailsScreen> {      Navigator.pop(context);    } -  void onConnStateChange(ConnectionStatus event) { -    setState(() { -      connectStatus = event.toString(); -    }); -    logger.i("New conn state: ${event.toString()}"); -  } -    @override    void deactivate() {      super.deactivate();      logger.i("Closing state"); -    // subs.cancel().ignore();      // widget.device.disconnect().ignore();    }    void _doConnect() async {      final dev = widget.device; -    // subs.cancel().ignore(); -    // subs = dev.connectionState.listen(onConnStateChange); +      try {        logger.i("Try to connect..."); -      // connect timeout doesn't work under Linux -      // await dev.connect().timeout(Duration(seconds: 2)); -      // logger.i("Connected!"); - -      // connectStatus = "Connected"; - -      // // ???? WTF ???? -      // List<BluetoothService> svcs = await dev.discoverServices(); -      // dev.onServicesReset.listen((_) async { -      //   logger.i("Services Reset"); -      //   try { -      //     List<BluetoothService> svcs = await dev.discoverServices(); -      //     findCharac(svcs); -      //   } catch (e) { -      //     logger.e(e); -      //   } -      // }); - -      // logger.i("services discovered"); - -      // findCharac(svcs); +      await widget.deviceConnection.connect();      } catch (e) {        logger.e(e); -      // dev.disconnect().ignore(); +      await widget.deviceConnection.disconnect();        connectStatus = e.toString();      } finally {        backActive = true; @@ -127,33 +98,4 @@ class DeviceDetailsState extends State<DeviceDetailsScreen> {        }      }    } - -  // void findCharac(List<BluetoothService> svcs) { -  //   if (svcs.isEmpty) { -  //     connectStatus += ", No services found!"; -  //     return; -  //   } -  //   connectStatus += ", Services found!"; -  //   BluetoothService? badgeService = svcs.firstWhereOrNull( -  //     (s) => s.serviceUuid.str == "ca260000-b4bb-46b2-bd06-b7b7a61ea990", -  //   ); - -  //   if (badgeService == null) { -  //   } else { -  //     logger.i("badge service found"); -  //     current = badgeService.characteristics.firstWhereOrNull( -  //       (c) => -  //           c.characteristicUuid.str == "ca260001-b4bb-46b2-bd06-b7b7a61ea990", -  //     ); -  //     available = badgeService.characteristics.firstWhereOrNull( -  //       (c) => -  //           c.characteristicUuid.str == "ca260002-b4bb-46b2-bd06-b7b7a61ea990", -  //     ); -  //   } - -  //   if (current == null || available == null) { -  //   } else { -  //     logger.i("characteristics found"); -  //   } -  // }  } diff --git a/lib/model/device.dart b/lib/model/device.dart index 27d33dd..98445fa 100644 --- a/lib/model/device.dart +++ b/lib/model/device.dart @@ -1,5 +1,3 @@ -enum ConnectionStatus { disconnected, connected, error } -  /// Represents a (badge) device to be connected to.  abstract class Device {    String? get name; diff --git a/lib/model/device_connection.dart b/lib/model/device_connection.dart new file mode 100644 index 0000000..6d5c248 --- /dev/null +++ b/lib/model/device_connection.dart @@ -0,0 +1,29 @@ +// Copyright (C) 2025, uvok cheetah +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <https://www.gnu.org/licenses/>. + +import 'package:uvok_epaper_badge/model/device.dart'; +import 'package:uvok_epaper_badge/model/mock_device_connection.dart'; + +enum ConnectionStatus { disconnected, connected, error } + +abstract class DeviceConnection { +  Future<void> connect(); +  Future<void> disconnect(); +  ConnectionStatus get status; + +  // Future<Uint8List> read(String endpoint); +  // Future<void> write(String endpoint, Uint8List data); +  // Stream<Uint8List> subscribe(String endpoint); +} diff --git a/lib/model/device_connection_factory.dart b/lib/model/device_connection_factory.dart new file mode 100644 index 0000000..23a186e --- /dev/null +++ b/lib/model/device_connection_factory.dart @@ -0,0 +1,32 @@ +// Copyright (C) 2025, uvok cheetah +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <https://www.gnu.org/licenses/>. + +import 'package:uvok_epaper_badge/model/device.dart'; +import 'package:uvok_epaper_badge/model/device_connection.dart'; +import 'package:uvok_epaper_badge/model/mock_device_connection.dart'; + +class DeviceConnectionFactory { +  static DeviceConnection createConnection(Device device) { +    // switch (device.type) { +    //   case DeviceType.ble: +    // return BleDeviceConnection(device); +    //   case DeviceType.tcp: +    // return TcpDeviceConnection(device); +    //   case DeviceType.http: +    // return HttpDeviceConnection(device); +    // } +    return MockDeviceConnection(); +  } +} diff --git a/lib/model/flutter_blue_plus_device_connection.dart b/lib/model/flutter_blue_plus_device_connection.dart new file mode 100644 index 0000000..93aa67e --- /dev/null +++ b/lib/model/flutter_blue_plus_device_connection.dart @@ -0,0 +1,109 @@ +// Copyright (C) 2025, uvok cheetah +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; + +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:logger/logger.dart'; +import 'package:uvok_epaper_badge/first_where_ext.dart'; +import 'package:uvok_epaper_badge/model/device.dart'; +import 'package:uvok_epaper_badge/model/device_connection.dart'; +import 'package:uvok_epaper_badge/model/flutter_blue_plus_device.dart'; + +var logger = Logger(); + +class FlutterBluePlusDeviceConnection implements DeviceConnection { +  ConnectionStatus _status = ConnectionStatus.disconnected; +  // Just to have a resonable default subscription? +  StreamSubscription<BluetoothConnectionState> subs = +      Stream<BluetoothConnectionState>.empty().listen((e) => ()); +  BluetoothCharacteristic? current; +  BluetoothCharacteristic? available; + +  final FlutterBluePlusDevice device; + +  FlutterBluePlusDeviceConnection({required this.device}); + +  @override +  Future<void> connect() async { +    subs.cancel().ignore(); +    final dev = device.scanResult.device; +    subs = dev.connectionState.listen(_onConnStateChange); +    // connect timeout doesn't work under Linux +    await dev.connect().timeout(Duration(seconds: 2)); +    // // ???? WTF ???? +    List<BluetoothService> svcs = await dev.discoverServices(); +    dev.onServicesReset.listen((_) async { +      logger.i("Services Reset"); +      try { +        List<BluetoothService> svcs = await dev.discoverServices(); +        findCharac(svcs); +      } catch (e) { +        logger.e(e); +      } +    }); + +    logger.i("services discovered"); + +    findCharac(svcs); + +    _status = ConnectionStatus.connected; +  } + +  @override +  Future<void> disconnect() async { +    _status = ConnectionStatus.disconnected; +  } + +  void dispose() { +    subs.cancel().ignore(); +  } + +  @override +  ConnectionStatus get status => _status; + +  void _onConnStateChange(BluetoothConnectionState event) { +    logger.i("New conn state: ${event.toString()}"); +  } + +  void findCharac(List<BluetoothService> svcs) { +    if (svcs.isEmpty) { +      logger.w("No services found!"); +      return; +    } +    logger.i("Services found!"); +    BluetoothService? badgeService = svcs.firstWhereOrNull( +      (s) => s.serviceUuid.str == "ca260000-b4bb-46b2-bd06-b7b7a61ea990", +    ); + +    if (badgeService == null) { +    } else { +      logger.i("badge service found"); +      current = badgeService.characteristics.firstWhereOrNull( +        (c) => +            c.characteristicUuid.str == "ca260001-b4bb-46b2-bd06-b7b7a61ea990", +      ); +      available = badgeService.characteristics.firstWhereOrNull( +        (c) => +            c.characteristicUuid.str == "ca260002-b4bb-46b2-bd06-b7b7a61ea990", +      ); +    } + +    if (current == null || available == null) { +    } else { +      logger.i("characteristics found"); +    } +  } +} diff --git a/lib/model/mock_device_connection.dart b/lib/model/mock_device_connection.dart new file mode 100644 index 0000000..3153387 --- /dev/null +++ b/lib/model/mock_device_connection.dart @@ -0,0 +1,33 @@ +// Copyright (C) 2025, uvok cheetah +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <https://www.gnu.org/licenses/>. + +import 'package:uvok_epaper_badge/model/device_connection.dart'; + +class MockDeviceConnection implements DeviceConnection { +  ConnectionStatus _status = ConnectionStatus.disconnected; + +  @override +  Future<void> connect() async { +    _status = ConnectionStatus.connected; +  } + +  @override +  Future<void> disconnect() async { +    _status = ConnectionStatus.disconnected; +  } + +  @override +  ConnectionStatus get status => _status; +} diff --git a/lib/scan_page.dart b/lib/scan_page.dart index f523c38..6256728 100644 --- a/lib/scan_page.dart +++ b/lib/scan_page.dart @@ -20,6 +20,8 @@ import 'package:uvok_epaper_badge/model/device.dart';  import 'package:flutter/material.dart';  import 'package:logger/logger.dart';  import 'package:permission_handler/permission_handler.dart'; +import 'package:uvok_epaper_badge/model/device_connection.dart'; +import 'package:uvok_epaper_badge/model/device_connection_factory.dart';  var logger = Logger(); @@ -39,12 +41,18 @@ class _ScanPageState extends State<ScanPage> {    Device? selectedDevice;    void _doConnect() async { -    Device? dev = selectedDevice; +    final Device? dev = selectedDevice;      if (dev == null) return; + +    final DeviceConnection connection = +        DeviceConnectionFactory.createConnection(dev);      //???      Navigator.push(        context, -      MaterialPageRoute(builder: (context) => DeviceDetailsScreen(device: dev)), +      MaterialPageRoute( +        builder: (context) => +            DeviceDetailsScreen(device: dev, deviceConnection: connection), +      ),      );    } diff --git a/test/widget_test.dart b/test/widget_test.dart index 58c70d5..3a794df 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,7 +5,7 @@  // gestures. You can also use WidgetTester to find child widgets in the widget  // tree, read text, and verify that the values of widget properties are correct. -import 'package:badge/badge_app.dart'; +import 'package:uvok_epaper_badge/badge_app.dart';  import 'package:flutter/material.dart';  import 'package:flutter_test/flutter_test.dart'; | 
