Skip to content

Commit 254c1d5

Browse files
authored
Bluetooth page fixes (#176)
1 parent 4f1e311 commit 254c1d5

File tree

4 files changed

+209
-135
lines changed

4 files changed

+209
-135
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import 'package:bluez/bluez.dart';
2+
import 'package:safe_change_notifier/safe_change_notifier.dart';
3+
4+
class BluetoothDeviceModel extends SafeChangeNotifier {
5+
final BlueZDevice _device;
6+
late bool connected;
7+
late String name;
8+
late int appearance;
9+
late int deviceClass;
10+
late String alias;
11+
late bool blocked;
12+
late String address;
13+
late bool paired;
14+
late String errorMessage;
15+
16+
BluetoothDeviceModel(this._device) {
17+
connected = _device.connected;
18+
name = _device.name;
19+
appearance = _device.appearance;
20+
deviceClass = _device.deviceClass;
21+
alias = _device.alias;
22+
blocked = _device.blocked;
23+
address = _device.address;
24+
paired = _device.paired;
25+
errorMessage = '';
26+
}
27+
28+
void init() {
29+
_device.propertiesChanged.listen((event) {
30+
connected = _device.connected;
31+
name = _device.name;
32+
appearance = _device.appearance;
33+
alias = _device.alias;
34+
blocked = _device.blocked;
35+
address = _device.address;
36+
paired = _device.paired;
37+
notifyListeners();
38+
});
39+
}
40+
41+
Future<void> connect() async {
42+
if (!_device.paired) {
43+
await _device.pair().catchError((ioError) {
44+
errorMessage = ioError.toString();
45+
});
46+
notifyListeners();
47+
}
48+
49+
await _device.connect().catchError((ioError) {
50+
errorMessage = ioError.toString();
51+
});
52+
paired = _device.paired;
53+
connected = _device.connected;
54+
notifyListeners();
55+
}
56+
57+
Future<void> disconnect() async {
58+
await _device.disconnect();
59+
connected = _device.connected;
60+
notifyListeners();
61+
}
62+
}
Lines changed: 129 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
import 'package:bluez/bluez.dart';
2+
import 'package:flutter/foundation.dart';
23
import 'package:flutter/material.dart';
4+
import 'package:provider/provider.dart';
5+
import 'package:settings/view/pages/bluetooth/BluetoothDeviceModel.dart';
36
import 'package:settings/view/pages/bluetooth/bluetooth_device_types.dart';
4-
import 'package:settings/view/pages/bluetooth/bluetooth_model.dart';
5-
import 'package:yaru_icons/widgets/yaru_icons.dart';
67
import 'package:yaru_widgets/yaru_widgets.dart';
78

89
class BluetoothDeviceRow extends StatefulWidget {
9-
const BluetoothDeviceRow(
10-
{Key? key, required this.device, required this.model})
10+
const BluetoothDeviceRow({Key? key, required this.removeDevice})
1111
: super(key: key);
1212

13-
final BlueZDevice device;
14-
final BluetoothModel model;
13+
final AsyncCallback removeDevice;
14+
15+
static Widget create(
16+
BuildContext context, BlueZDevice device, AsyncCallback removeDevice) {
17+
return ChangeNotifierProvider(
18+
create: (_) => BluetoothDeviceModel(device),
19+
child: BluetoothDeviceRow(
20+
removeDevice: removeDevice,
21+
),
22+
);
23+
}
1524

1625
@override
1726
State<BluetoothDeviceRow> createState() => _BluetoothDeviceRowState();
@@ -22,140 +31,139 @@ class _BluetoothDeviceRowState extends State<BluetoothDeviceRow> {
2231

2332
@override
2433
void initState() {
25-
status = widget.device.connected ? 'connected' : 'disconnected';
34+
final model = context.read<BluetoothDeviceModel>();
35+
model.init();
2636

2737
super.initState();
2838
}
2939

3040
@override
3141
Widget build(BuildContext context) {
32-
status = widget.device.connected ? 'connected' : 'disconnected';
42+
final model = context.watch<BluetoothDeviceModel>();
3343
return InkWell(
3444
borderRadius: BorderRadius.circular(4.0),
3545
onTap: () => setState(() {
36-
showSimpleDeviceDialog(context);
46+
showDialog(
47+
context: context,
48+
builder: (context) => StatefulBuilder(builder: (context, setState) {
49+
return AlertDialog(
50+
title: Padding(
51+
padding:
52+
const EdgeInsets.only(right: 8, left: 8, bottom: 8),
53+
child: Row(
54+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
55+
children: [
56+
Flexible(
57+
child: RichText(
58+
text: TextSpan(
59+
text: model.name,
60+
style: Theme.of(context).textTheme.headline6),
61+
maxLines: 10,
62+
overflow: TextOverflow.ellipsis,
63+
),
64+
),
65+
Padding(
66+
padding: const EdgeInsets.only(left: 10),
67+
child: Icon(
68+
BluetoothDeviceTypes.getIconForAppearanceCode(
69+
model.appearance)),
70+
)
71+
],
72+
),
73+
),
74+
content: SizedBox(
75+
height: model.errorMessage.isEmpty ? 270 : 320,
76+
width: 300,
77+
child: SingleChildScrollView(
78+
child: Column(
79+
children: [
80+
YaruRow(
81+
trailingWidget: model.connected
82+
? const Text('Connected')
83+
: const Text('Disconnected'),
84+
actionWidget: Switch(
85+
value: model.connected,
86+
onChanged: (connectRequested) async {
87+
connectRequested
88+
? await model.connect()
89+
: await model.disconnect();
90+
setState(() {});
91+
})),
92+
YaruRow(
93+
trailingWidget: const Text('Paired'),
94+
actionWidget: Padding(
95+
padding: const EdgeInsets.only(right: 8),
96+
child: Text(model.paired ? 'Yes' : 'No'),
97+
)),
98+
YaruRow(
99+
trailingWidget: const Text('Address'),
100+
actionWidget: Padding(
101+
padding: const EdgeInsets.only(right: 8),
102+
child: Text(model.address),
103+
)),
104+
YaruRow(
105+
trailingWidget: const Text('Type'),
106+
actionWidget: Padding(
107+
padding: const EdgeInsets.only(right: 8),
108+
child: Text(BluetoothDeviceTypes
109+
.map[model.appearance] ??
110+
'Unkown'),
111+
)),
112+
Padding(
113+
padding: const EdgeInsets.only(
114+
top: 16, bottom: 8, right: 8, left: 8),
115+
child: SizedBox(
116+
width: 300,
117+
child: OutlinedButton(
118+
onPressed: () {
119+
if (BluetoothDeviceTypes.isMouse(
120+
model.appearance)) {
121+
// TODO: get route name from model
122+
Navigator.of(context)
123+
.pushNamed('routeName');
124+
}
125+
},
126+
child: const Text('Open device settings')),
127+
),
128+
),
129+
Padding(
130+
padding: const EdgeInsets.all(8),
131+
child: SizedBox(
132+
width: 300,
133+
child: TextButton(
134+
onPressed: () async {
135+
await model.disconnect();
136+
widget.removeDevice;
137+
138+
Navigator.of(context).pop();
139+
},
140+
child: const Text('Remove device')),
141+
),
142+
),
143+
if (model.errorMessage.isNotEmpty)
144+
Text(
145+
model.errorMessage,
146+
style: TextStyle(
147+
color: Theme.of(context).errorColor),
148+
)
149+
],
150+
),
151+
),
152+
),
153+
);
154+
}));
37155
}),
38156
child: Padding(
39157
padding: const EdgeInsets.all(8.0),
40158
child: YaruRow(
41-
trailingWidget: Text(widget.device.name),
159+
trailingWidget: Text(model.name),
42160
actionWidget: Text(
43-
widget.device.connected ? 'connected' : 'disconnected',
161+
model.connected ? 'connected' : 'disconnected',
44162
style: TextStyle(
45163
color:
46164
Theme.of(context).colorScheme.onSurface.withOpacity(0.7)),
47165
)),
48166
),
49167
);
50168
}
51-
52-
void showSimpleDeviceDialog(BuildContext context) {
53-
showDialog(
54-
context: context,
55-
builder: (context) => StatefulBuilder(builder: (context, setState) {
56-
return AlertDialog(
57-
title: Padding(
58-
padding: const EdgeInsets.only(right: 8, left: 8, bottom: 8),
59-
child: Row(
60-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
61-
children: [
62-
Flexible(
63-
child: RichText(
64-
text: TextSpan(
65-
text: widget.device.name,
66-
style: Theme.of(context).textTheme.headline6),
67-
maxLines: 10,
68-
overflow: TextOverflow.ellipsis,
69-
),
70-
),
71-
Padding(
72-
padding: const EdgeInsets.only(left: 10),
73-
child: Icon(
74-
BluetoothDeviceTypes.getIconForAppearanceCode(
75-
widget.device.appearance)),
76-
)
77-
],
78-
),
79-
),
80-
content: SizedBox(
81-
height: 270,
82-
width: 300,
83-
child: SingleChildScrollView(
84-
child: Column(
85-
children: [
86-
YaruRow(
87-
trailingWidget: widget.device.connected
88-
? const Text('Connected')
89-
: const Text('Disconnected'),
90-
actionWidget: Switch(
91-
value: widget.device.connected,
92-
onChanged: (newValue) async {
93-
widget.device.connected
94-
? await widget.device.disconnect()
95-
: await widget.device
96-
.connect()
97-
.catchError((ioError) => {});
98-
Navigator.of(context).pop();
99-
setState(() {});
100-
})),
101-
YaruRow(
102-
trailingWidget: widget.device.paired
103-
? const Text('Paired')
104-
: const Text('Unpaired'),
105-
actionWidget: Padding(
106-
padding: const EdgeInsets.only(right: 8),
107-
child: Text(widget.device.paired ? 'Yes' : 'No'),
108-
)),
109-
YaruRow(
110-
trailingWidget: const Text('Address'),
111-
actionWidget: Padding(
112-
padding: const EdgeInsets.only(right: 8),
113-
child: Text(widget.device.address),
114-
)),
115-
YaruRow(
116-
trailingWidget: const Text('Type'),
117-
actionWidget: Padding(
118-
padding: const EdgeInsets.only(right: 8),
119-
child: Text(BluetoothDeviceTypes
120-
.map[widget.device.appearance] ??
121-
'Unkown'),
122-
)),
123-
Padding(
124-
padding: const EdgeInsets.only(
125-
top: 16, bottom: 8, right: 8, left: 8),
126-
child: SizedBox(
127-
width: 300,
128-
child: OutlinedButton(
129-
onPressed: () {
130-
if (BluetoothDeviceTypes.isMouse(
131-
widget.device.appearance)) {
132-
// TODO: get route name from model
133-
Navigator.of(context)
134-
.pushNamed('routeName');
135-
}
136-
},
137-
child: const Text('Open device settings')),
138-
),
139-
),
140-
Padding(
141-
padding: const EdgeInsets.all(8),
142-
child: SizedBox(
143-
width: 300,
144-
child: TextButton(
145-
onPressed: () async {
146-
await widget.device.disconnect().then(
147-
(value) => widget.model
148-
.removeDevice(widget.device));
149-
Navigator.of(context).pop();
150-
},
151-
child: const Text('Remove device')),
152-
),
153-
)
154-
],
155-
),
156-
),
157-
),
158-
);
159-
})).then((value) => setState(() {}));
160-
}
161169
}

0 commit comments

Comments
 (0)