diff options
Diffstat (limited to 'lib/widgets')
-rw-r--r-- | lib/widgets/badge_app.dart | 16 | ||||
-rw-r--r-- | lib/widgets/badge_motive_list.dart | 74 | ||||
-rw-r--r-- | lib/widgets/device_details.dart | 94 | ||||
-rw-r--r-- | lib/widgets/device_scan_select.dart | 4 | ||||
-rw-r--r-- | lib/widgets/notifying_list_widget.dart | 6 | ||||
-rw-r--r-- | lib/widgets/scan_page.dart | 16 |
6 files changed, 178 insertions, 32 deletions
diff --git a/lib/widgets/badge_app.dart b/lib/widgets/badge_app.dart index f4d1fcd..23c63aa 100644 --- a/lib/widgets/badge_app.dart +++ b/lib/widgets/badge_app.dart @@ -13,6 +13,8 @@ // 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:ui' show AppExitResponse; + import 'package:uvok_epaper_badge/control/scanner_controller.dart'; import 'package:uvok_epaper_badge/widgets/scan_page.dart'; import 'package:flutter/material.dart'; @@ -20,7 +22,15 @@ import 'package:flutter/material.dart'; class BadgeApp extends StatelessWidget { final ScannerController selectedScanner; - const BadgeApp({super.key, required this.selectedScanner}); + BadgeApp({super.key, required this.selectedScanner}) { + AppLifecycleListener( + onExitRequested: () async { + logger.i("Exit requested"); + dispose(); + return AppExitResponse.exit; + }, + ); + } @override Widget build(BuildContext context) { @@ -47,4 +57,8 @@ class BadgeApp extends StatelessWidget { home: ScanPage(title: 'Badge Scanner', deviceScanner: selectedScanner), ); } + + void dispose() { + selectedScanner.dispose(); + } } diff --git a/lib/widgets/badge_motive_list.dart b/lib/widgets/badge_motive_list.dart new file mode 100644 index 0000000..989b155 --- /dev/null +++ b/lib/widgets/badge_motive_list.dart @@ -0,0 +1,74 @@ +// 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:flutter/material.dart'; +import 'package:uvok_epaper_badge/model/badge_motive.dart'; +import 'package:uvok_epaper_badge/view_model/badge_motive_view_model.dart'; +import 'package:uvok_epaper_badge/widgets/notifying_list_widget.dart'; + +class BadgeMotiveList extends NotifyingListWidget<BadgeMotive> { + final BadgeMotiveViewModel _motiveVM; + + const BadgeMotiveList({super.key, required BadgeMotiveViewModel motiveVM}) + : _motiveVM = motiveVM, + super(items: const []); + + @override + State<StatefulWidget> createState() => _BadgeMotiveListState(); +} + +class _BadgeMotiveListState extends State<BadgeMotiveList> { + @override + void initState() { + // I have no idea about the connection state here... + //widget._motiveVM.getCurrentMotive().ignore(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + var mytheme = Theme.of(context); + + return Expanded( + child: ListenableBuilder( + listenable: widget._motiveVM, + builder: (context, child) { + return ListView.separated( + itemBuilder: (context, index) { + final item = widget._motiveVM.motives[index]; + final selected = widget._motiveVM.currentMotive?.id == item.id; + return ListTile( + title: Text(item.description), + selectedTileColor: mytheme.primaryColorLight, + selectedColor: Colors.black, + onTap: !widget._motiveVM.allowSelection + ? null + : () async { + widget.onItemSelected(item); + await widget._motiveVM.setMotive(item); + }, + selected: selected, + ); + }, + separatorBuilder: (context, index) { + return Divider(); + }, + itemCount: widget._motiveVM.motives.length, + ); + }, + ), + ); + } +} diff --git a/lib/widgets/device_details.dart b/lib/widgets/device_details.dart index 79f76cf..0463522 100644 --- a/lib/widgets/device_details.dart +++ b/lib/widgets/device_details.dart @@ -13,10 +13,16 @@ // 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:ui'; + +import 'package:uvok_epaper_badge/model/badge_motive_selection_factory.dart'; import 'package:uvok_epaper_badge/model/device/device.dart'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; import 'package:uvok_epaper_badge/model/connection/device_connection.dart'; +import 'package:uvok_epaper_badge/model/motive_selection/badge_motive_selection.dart'; +import 'package:uvok_epaper_badge/view_model/badge_motive_view_model.dart'; +import 'package:uvok_epaper_badge/widgets/badge_motive_list.dart'; var logger = Logger(); @@ -24,11 +30,19 @@ class DeviceDetailsScreen extends StatefulWidget { final Device device; final DeviceConnection deviceConnection; - const DeviceDetailsScreen({ + late final BadgeMotiveSelection _motiveSelection; + late final BadgeMotiveViewModel _motiveVM; + + DeviceDetailsScreen({ super.key, required this.device, required this.deviceConnection, - }); + }) { + _motiveSelection = BadgeMotiveSelectionFactory.createBadgeMotiveSelection( + device, + ); + _motiveVM = BadgeMotiveViewModel(motivSelect: _motiveSelection); + } @override State<StatefulWidget> createState() { @@ -37,11 +51,21 @@ class DeviceDetailsScreen extends StatefulWidget { } class DeviceDetailsState extends State<DeviceDetailsScreen> { - String connectStatus = "<Status>"; + late final AppLifecycleListener appLL; /// Whether the back button should be active. bool backActive = false; + DeviceDetailsState() { + appLL = AppLifecycleListener( + onExitRequested: () async { + logger.i("Exit requested"); + dispose(); + return AppExitResponse.exit; + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -50,15 +74,41 @@ class DeviceDetailsState extends State<DeviceDetailsScreen> { title: Text("Device details"), ), body: Center( - child: Column( - spacing: 20, - children: [ - Text(connectStatus), - ElevatedButton( - onPressed: backActive ? backClick : null, - child: Text("Back"), - ), - ], + child: ValueListenableBuilder( + valueListenable: widget.deviceConnection.status, + builder: (connCtx, ConnectionStatus value, child) { + return Column( + spacing: 20, + children: [ + SizedBox(height: 20), + Text("Connection state: ${value.toString()}"), + ElevatedButton( + child: Text("Refresh"), + onPressed: () async { + await widget._motiveVM.updateMotives(); + await widget._motiveVM.getCurrentMotive(); + }, + ), + ListenableBuilder( + listenable: widget._motiveVM, + builder: (errorCtx, child) { + if (widget._motiveVM.errorMessage != null) { + var theme = Theme.of(errorCtx); + return Text( + widget._motiveVM.errorMessage!, + style: TextStyle( + color: theme.colorScheme.error, + fontWeight: FontWeight.bold, + ), + ); + } + return Container(); + }, + ), + BadgeMotiveList(motiveVM: widget._motiveVM), + ], + ); + }, ), ), ); @@ -70,33 +120,29 @@ class DeviceDetailsState extends State<DeviceDetailsScreen> { _doConnect(); } - void backClick() { - Navigator.pop(context); - } - @override void deactivate() { - widget.deviceConnection.disconnect().ignore(); - logger.i("Closing state"); + logger.i("(widget deactivate)"); // widget.device.disconnect().ignore(); super.deactivate(); } - void _doConnect() async { - final dev = widget.device; + @override + void dispose() { + logger.i("(widget dispose)"); + widget.deviceConnection.disconnect().ignore(); + super.dispose(); + } + void _doConnect() async { try { logger.i("Try to connect..."); await widget.deviceConnection.connect(); } catch (e) { logger.e(e); await widget.deviceConnection.disconnect(); - connectStatus = e.toString(); } finally { backActive = true; - if (mounted) { - setState(() {}); - } } } } diff --git a/lib/widgets/device_scan_select.dart b/lib/widgets/device_scan_select.dart index cd65eac..a2e7fc5 100644 --- a/lib/widgets/device_scan_select.dart +++ b/lib/widgets/device_scan_select.dart @@ -33,6 +33,8 @@ class _DeviceScanSelectionState extends State<DeviceScanSelection> { @override Widget build(BuildContext context) { + var mytheme = Theme.of(context); + return Expanded( child: ListView.separated( itemCount: widget.items.length, @@ -45,7 +47,7 @@ class _DeviceScanSelectionState extends State<DeviceScanSelection> { title: Text(name), subtitle: Text(result.address ?? "???"), trailing: Text('RSSI: ${result.rssi}'), - selectedTileColor: Colors.amber, + selectedTileColor: mytheme.primaryColorLight, selectedColor: Colors.black, onTap: () { setState(() { diff --git a/lib/widgets/notifying_list_widget.dart b/lib/widgets/notifying_list_widget.dart index 7fdc2b1..b53cb2f 100644 --- a/lib/widgets/notifying_list_widget.dart +++ b/lib/widgets/notifying_list_widget.dart @@ -22,6 +22,8 @@ abstract class NotifyingListWidget<T> extends StatefulWidget { const NotifyingListWidget({ super.key, required this.items, - required this.onItemSelected, - }); + ValueChanged<T?>? onItemSelected, + }) : onItemSelected = onItemSelected ?? (_noOp); + + static _noOp(_) {} } diff --git a/lib/widgets/scan_page.dart b/lib/widgets/scan_page.dart index bcb3525..f6695fe 100644 --- a/lib/widgets/scan_page.dart +++ b/lib/widgets/scan_page.dart @@ -13,6 +13,8 @@ // 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:io'; + import 'package:uvok_epaper_badge/control/scanner_controller.dart'; import 'package:uvok_epaper_badge/widgets/device_details.dart'; import 'package:uvok_epaper_badge/widgets/device_scan_select.dart'; @@ -61,13 +63,21 @@ class _ScanPageState extends State<ScanPage> { selectedDevice = null; }); - // ... + await getPermissions(); await widget.deviceScanner.startScan(); } Future getPermissions() async { + // avoid spamming log with "unsupported" messages. + if (Platform.isLinux) return; + try { - await Permission.bluetooth.request(); + var status = await Permission.bluetooth.request(); + logger.i("New BLE permission status: $status"); + status = await Permission.bluetoothScan.request(); + logger.i("New BLE scan permission status: $status"); + status = await Permission.bluetoothConnect.request(); + logger.i("New BLE connect permission status: $status"); } catch (e) { logger.e(e.toString()); } @@ -76,8 +86,6 @@ class _ScanPageState extends State<ScanPage> { @override void initState() { super.initState(); - // ehhh... - getPermissions().ignore(); } @override |