summaryrefslogtreecommitdiff
path: root/lib/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'lib/widgets')
-rw-r--r--lib/widgets/badge_app.dart16
-rw-r--r--lib/widgets/badge_motive_list.dart74
-rw-r--r--lib/widgets/device_details.dart94
-rw-r--r--lib/widgets/device_scan_select.dart4
-rw-r--r--lib/widgets/notifying_list_widget.dart6
-rw-r--r--lib/widgets/scan_page.dart16
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