Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions lib/view/pages/bluetooth/BluetoothDeviceModel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:bluez/bluez.dart';
import 'package:safe_change_notifier/safe_change_notifier.dart';

class BluetoothDeviceModel extends SafeChangeNotifier {
final BlueZDevice _device;
late bool connected;
late String name;
late int appearance;
late int deviceClass;
late String alias;
late bool blocked;
late String address;
late bool paired;
late String errorMessage;

BluetoothDeviceModel(this._device) {
connected = _device.connected;
name = _device.name;
appearance = _device.appearance;
deviceClass = _device.deviceClass;
alias = _device.alias;
blocked = _device.blocked;
address = _device.address;
paired = _device.paired;
errorMessage = '';
}

void init() {
_device.propertiesChanged.listen((event) {
connected = _device.connected;
name = _device.name;
appearance = _device.appearance;
alias = _device.alias;
blocked = _device.blocked;
address = _device.address;
paired = _device.paired;
notifyListeners();
});
}

Future<void> connect() async {
if (!_device.paired) {
await _device.pair().catchError((ioError) {
errorMessage = ioError.toString();
});
notifyListeners();
}

await _device.connect().catchError((ioError) {
errorMessage = ioError.toString();
});
paired = _device.paired;
connected = _device.connected;
notifyListeners();
}

Future<void> disconnect() async {
await _device.disconnect();
connected = _device.connected;
notifyListeners();
}
}
250 changes: 129 additions & 121 deletions lib/view/pages/bluetooth/bluetooth_device_row.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import 'package:bluez/bluez.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:settings/view/pages/bluetooth/BluetoothDeviceModel.dart';
import 'package:settings/view/pages/bluetooth/bluetooth_device_types.dart';
import 'package:settings/view/pages/bluetooth/bluetooth_model.dart';
import 'package:yaru_icons/widgets/yaru_icons.dart';
import 'package:yaru_widgets/yaru_widgets.dart';

class BluetoothDeviceRow extends StatefulWidget {
const BluetoothDeviceRow(
{Key? key, required this.device, required this.model})
const BluetoothDeviceRow({Key? key, required this.removeDevice})
: super(key: key);

final BlueZDevice device;
final BluetoothModel model;
final AsyncCallback removeDevice;

static Widget create(
BuildContext context, BlueZDevice device, AsyncCallback removeDevice) {
return ChangeNotifierProvider(
create: (_) => BluetoothDeviceModel(device),
child: BluetoothDeviceRow(
removeDevice: removeDevice,
),
);
}

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

@override
void initState() {
status = widget.device.connected ? 'connected' : 'disconnected';
final model = context.read<BluetoothDeviceModel>();
model.init();

super.initState();
}

@override
Widget build(BuildContext context) {
status = widget.device.connected ? 'connected' : 'disconnected';
final model = context.watch<BluetoothDeviceModel>();
return InkWell(
borderRadius: BorderRadius.circular(4.0),
onTap: () => setState(() {
showSimpleDeviceDialog(context);
showDialog(
context: context,
builder: (context) => StatefulBuilder(builder: (context, setState) {
return AlertDialog(
title: Padding(
padding:
const EdgeInsets.only(right: 8, left: 8, bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: RichText(
text: TextSpan(
text: model.name,
style: Theme.of(context).textTheme.headline6),
maxLines: 10,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Icon(
BluetoothDeviceTypes.getIconForAppearanceCode(
model.appearance)),
)
],
),
),
content: SizedBox(
height: model.errorMessage.isEmpty ? 270 : 320,
width: 300,
child: SingleChildScrollView(
child: Column(
children: [
YaruRow(
trailingWidget: model.connected
? const Text('Connected')
: const Text('Disconnected'),
actionWidget: Switch(
value: model.connected,
onChanged: (connectRequested) async {
connectRequested
? await model.connect()
: await model.disconnect();
setState(() {});
})),
YaruRow(
trailingWidget: const Text('Paired'),
actionWidget: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(model.paired ? 'Yes' : 'No'),
)),
YaruRow(
trailingWidget: const Text('Address'),
actionWidget: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(model.address),
)),
YaruRow(
trailingWidget: const Text('Type'),
actionWidget: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(BluetoothDeviceTypes
.map[model.appearance] ??
'Unkown'),
)),
Padding(
padding: const EdgeInsets.only(
top: 16, bottom: 8, right: 8, left: 8),
child: SizedBox(
width: 300,
child: OutlinedButton(
onPressed: () {
if (BluetoothDeviceTypes.isMouse(
model.appearance)) {
// TODO: get route name from model
Navigator.of(context)
.pushNamed('routeName');
}
},
child: const Text('Open device settings')),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: SizedBox(
width: 300,
child: TextButton(
onPressed: () async {
await model.disconnect();
widget.removeDevice;

Navigator.of(context).pop();
},
child: const Text('Remove device')),
),
),
if (model.errorMessage.isNotEmpty)
Text(
model.errorMessage,
style: TextStyle(
color: Theme.of(context).errorColor),
)
],
),
),
),
);
}));
}),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: YaruRow(
trailingWidget: Text(widget.device.name),
trailingWidget: Text(model.name),
actionWidget: Text(
widget.device.connected ? 'connected' : 'disconnected',
model.connected ? 'connected' : 'disconnected',
style: TextStyle(
color:
Theme.of(context).colorScheme.onSurface.withOpacity(0.7)),
)),
),
);
}

void showSimpleDeviceDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => StatefulBuilder(builder: (context, setState) {
return AlertDialog(
title: Padding(
padding: const EdgeInsets.only(right: 8, left: 8, bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: RichText(
text: TextSpan(
text: widget.device.name,
style: Theme.of(context).textTheme.headline6),
maxLines: 10,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Icon(
BluetoothDeviceTypes.getIconForAppearanceCode(
widget.device.appearance)),
)
],
),
),
content: SizedBox(
height: 270,
width: 300,
child: SingleChildScrollView(
child: Column(
children: [
YaruRow(
trailingWidget: widget.device.connected
? const Text('Connected')
: const Text('Disconnected'),
actionWidget: Switch(
value: widget.device.connected,
onChanged: (newValue) async {
widget.device.connected
? await widget.device.disconnect()
: await widget.device
.connect()
.catchError((ioError) => {});
Navigator.of(context).pop();
setState(() {});
})),
YaruRow(
trailingWidget: widget.device.paired
? const Text('Paired')
: const Text('Unpaired'),
actionWidget: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(widget.device.paired ? 'Yes' : 'No'),
)),
YaruRow(
trailingWidget: const Text('Address'),
actionWidget: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(widget.device.address),
)),
YaruRow(
trailingWidget: const Text('Type'),
actionWidget: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(BluetoothDeviceTypes
.map[widget.device.appearance] ??
'Unkown'),
)),
Padding(
padding: const EdgeInsets.only(
top: 16, bottom: 8, right: 8, left: 8),
child: SizedBox(
width: 300,
child: OutlinedButton(
onPressed: () {
if (BluetoothDeviceTypes.isMouse(
widget.device.appearance)) {
// TODO: get route name from model
Navigator.of(context)
.pushNamed('routeName');
}
},
child: const Text('Open device settings')),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: SizedBox(
width: 300,
child: TextButton(
onPressed: () async {
await widget.device.disconnect().then(
(value) => widget.model
.removeDevice(widget.device));
Navigator.of(context).pop();
},
child: const Text('Remove device')),
),
)
],
),
),
),
);
})).then((value) => setState(() {}));
}
}
Loading