diff options
author | uvok | 2025-07-29 16:30:42 +0200 |
---|---|---|
committer | uvok | 2025-07-29 16:30:42 +0200 |
commit | 0e92f3c889f7a9f8832aa706aa9c3bfce1bb7891 (patch) | |
tree | 2545a15022c42ac9adc9e34e691bc9c1caf13b85 /lib | |
parent | 8dc4939e2eb055e4ba1463f931c9c69284687973 (diff) |
Reorder classes
Diffstat (limited to 'lib')
-rw-r--r-- | lib/badge_app.dart | 32 | ||||
-rw-r--r-- | lib/device_details.dart | 9 | ||||
-rw-r--r-- | lib/main.dart | 239 | ||||
-rw-r--r-- | lib/scan_page.dart | 173 |
4 files changed, 214 insertions, 239 deletions
diff --git a/lib/badge_app.dart b/lib/badge_app.dart new file mode 100644 index 0000000..edc1897 --- /dev/null +++ b/lib/badge_app.dart @@ -0,0 +1,32 @@ +import 'package:badge/scan_page.dart'; +import 'package:flutter/material.dart'; + +class BadgeApp extends StatelessWidget { + const BadgeApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Scanner', + theme: ThemeData( + // This is the theme of your application. + // + // TRY THIS: Try running your application with "flutter run". You'll see + // the application has a purple toolbar. Then, without quitting the app, + // try changing the seedColor in the colorScheme below to Colors.green + // and then invoke "hot reload" (save your changes or press the "hot + // reload" button in a Flutter-supported IDE, or press "r" if you used + // the command line to start the app). + // + // Notice that the counter didn't reset back to zero; the application + // state is not lost during the reload. To reset the state, use hot + // restart instead. + // + // This works for code too, not just values: Most code changes can be + // tested with just a hot reload. + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + ), + home: const ScanPage(title: 'Badge Scanner'), + ); + } +} diff --git a/lib/device_details.dart b/lib/device_details.dart index 4f96ed8..d677b29 100644 --- a/lib/device_details.dart +++ b/lib/device_details.dart @@ -19,15 +19,20 @@ 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; @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text("Device details")), + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text("Device details"), + ), body: Center( child: Column( spacing: 20, diff --git a/lib/main.dart b/lib/main.dart index 6310660..64aab35 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,245 +1,10 @@ -import 'dart:io' show Platform; - -import 'package:badge/device_details.dart'; -import 'package:badge/device_scan_select.dart'; +import 'package:badge/badge_app.dart'; import 'package:flutter/material.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:logger/logger.dart'; var logger = Logger(printer: PrettyPrinter()); void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Scanner', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Badge Scanner'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State<MyHomePage> createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State<MyHomePage> { - List<ScanResult> scanResults = []; - bool isScanning = false; - - ScanResult? selectedDevice; - - void _doConnect() async { - var dev = selectedDevice?.device; - if (dev == null) return; - //??? - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DeviceDetailsScreen(btDevice: dev), - ), - ); - } - - void _doScan() async { - var system = await FlutterBluePlus.systemDevices([]); - for (var d in system) { - logger.i('${d.platformName} already connected to! ${d.remoteId}'); - // if (d.platformName == "myBleDevice") { - // await r.connect(); // must connect our app - // } - } - - setState(() { - selectedDevice = null; - scanResults = []; - isScanning = true; - }); - var subscription = FlutterBluePlus.scanResults.listen( - onScanResult, - onError: (e) => logger.e(e), - ); - // should probably use this! - FlutterBluePlus.cancelWhenScanComplete(subscription); - - // Ehhhh... can't have both - try { - if (Platform.isAndroid) { - await FlutterBluePlus.startScan( - withKeywords: ["NimBLE"], - timeout: Duration(seconds: 5), - ); - } else { - // for Linux, which can't do advNames - // msd doesn't work, either???? - await FlutterBluePlus.startScan( - //withMsd: [MsdFilter(0xffff, data: ascii.encode("uvok"))], - timeout: Duration(seconds: 5), - ); - } - - // wait for scanning to stop - await FlutterBluePlus.isScanning.where((val) => val == false).first; - } finally { - subscription.cancel(); - setState(() { - isScanning = false; - }); - } - } - - void onScanResult(List<ScanResult> results) { - if (results.isNotEmpty) { - //ScanResult r = results.last; // the most recently found device - for (var r in results.where( - (d) => - d.rssi > -90 && - !scanResults.any((ed) => ed.device.remoteId == d.device.remoteId), - )) { - logger.i( - '${r.device.remoteId}: "${r.device.platformName}" / "${r.device.advName}" / "${r.advertisementData.advName}" found!', - ); - scanResults.add(r); - } - setState(() {}); - } - } - - Future getPermissions() async { - try { - await Permission.bluetooth.request(); - } catch (e) { - logger.e(e.toString()); - } - } - - void btHandler(BluetoothAdapterState event) { - logger.i(event); - switch (event) { - case BluetoothAdapterState.on: - break; - default: - break; - } - } - - @override - void initState() { - super.initState(); - getPermissions(); - FlutterBluePlus.adapterState.listen(btHandler); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - spacing: 24, - children: <Widget>[ - SizedBox(height: 15), - Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 15.0, - children: [ - ElevatedButton( - onPressed: isScanning ? null : _doScan, - child: isScanning ? Text("Scanning...") : Text("Start scan"), - ), - ElevatedButton( - onPressed: (selectedDevice == null || isScanning) - ? null - : _doConnect, - child: Text("Connect"), - ), - ], - ), - Expanded( - child: DeviceScanSelection( - items: scanResults, - onItemSelected: (item) { - setState(() => selectedDevice = item); - }, - ), - ), - ], - ), - ), - ); - } -} - -String firstGiven(List<String> list) { - return list.firstWhere((s) => s.isNotEmpty, orElse: () => ""); + runApp(const BadgeApp()); } diff --git a/lib/scan_page.dart b/lib/scan_page.dart new file mode 100644 index 0000000..d21ade7 --- /dev/null +++ b/lib/scan_page.dart @@ -0,0 +1,173 @@ +import 'dart:io' show Platform; + +import 'package:badge/device_details.dart'; +import 'package:badge/device_scan_select.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:logger/logger.dart'; +import 'package:permission_handler/permission_handler.dart'; + +var logger = Logger(printer: PrettyPrinter()); + +class ScanPage extends StatefulWidget { + const ScanPage({super.key, required this.title}); + + // Original doc: Fields in a Widget subclass are always marked "final". + + final String title; + + @override + State<ScanPage> createState() => _ScanPageState(); +} + +class _ScanPageState extends State<ScanPage> { + List<ScanResult> scanResults = []; + bool isScanning = false; + + ScanResult? selectedDevice; + + void _doConnect() async { + var dev = selectedDevice?.device; + if (dev == null) return; + //??? + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DeviceDetailsScreen(btDevice: dev), + ), + ); + } + + void _doScan() async { + var system = await FlutterBluePlus.systemDevices([]); + for (var d in system) { + logger.i('${d.platformName} already connected to! ${d.remoteId}'); + } + + setState(() { + selectedDevice = null; + scanResults = []; + isScanning = true; + }); + var subscription = FlutterBluePlus.scanResults.listen( + onScanResult, + onError: (e) => logger.e(e), + ); + // either this, or the cancel in the finally block, should do the same? + FlutterBluePlus.cancelWhenScanComplete(subscription); + + try { + if (Platform.isAndroid) { + // Ehhhh... can't have both keyword/services + await FlutterBluePlus.startScan( + withKeywords: ["NimBLE"], + timeout: Duration(seconds: 5), + ); + } else { + // for Linux, which can't do advNames (but platformname, for whatever reason) + // msd doesn't work, either???? + await FlutterBluePlus.startScan( + //withMsd: [MsdFilter(0xffff, data: ascii.encode("uvok"))], + timeout: Duration(seconds: 5), + ); + } + + // wait for scanning to stop + await FlutterBluePlus.isScanning.where((val) => val == false).first; + } finally { + subscription.cancel(); + setState(() { + isScanning = false; + }); + } + } + + void onScanResult(List<ScanResult> results) { + if (results.isNotEmpty) { + for (var r in results.where( + (d) => d.rssi > -90 && !_deviceInResults(d), + )) { + logger.i( + '${r.device.remoteId}: "${r.device.platformName}" / "${r.device.advName}" / "${r.advertisementData.advName}" found!', + ); + scanResults.add(r); + } + setState(() {}); + } + } + + bool _deviceInResults(ScanResult incomingDev) => scanResults.any( + (existingDev) => existingDev.device.remoteId == incomingDev.device.remoteId, + ); + + Future getPermissions() async { + try { + await Permission.bluetooth.request(); + } catch (e) { + logger.e(e.toString()); + } + } + + void btHandler(BluetoothAdapterState event) { + logger.i(event); + switch (event) { + case BluetoothAdapterState.on: + break; + default: + break; + } + } + + @override + void initState() { + super.initState(); + getPermissions(); + FlutterBluePlus.adapterState.listen(btHandler); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(widget.title), + ), + body: Center( + child: Column( + // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" + // action in the IDE, or press "p" in the console), to see the + // wireframe for each widget. + mainAxisAlignment: MainAxisAlignment.center, + spacing: 24, + children: <Widget>[ + SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 15.0, + children: [ + ElevatedButton( + onPressed: isScanning ? null : _doScan, + child: isScanning ? Text("Scanning...") : Text("Start scan"), + ), + ElevatedButton( + onPressed: (selectedDevice == null || isScanning) + ? null + : _doConnect, + child: Text("Connect"), + ), + ], + ), + Expanded( + child: DeviceScanSelection( + items: scanResults, + onItemSelected: (item) { + setState(() => selectedDevice = item); + }, + ), + ), + ], + ), + ), + ); + } +} |