blob: 9ddf0a011e3243e88ac748b8b20799e9cacab94f [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fidl_fuchsia_bluetooth_control/fidl_async.dart' as bt;
import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
import 'package:fuchsia_services/services.dart' show StartupContext;
import 'package:internationalization/strings.dart';
import 'package:quickui/quickui.dart';
/// Defines a [UiSpec] for visualizing bluetooth.
class Bluetooth extends UiSpec {
// Localized strings.
static String get _title => Strings.bluetooth;
static String get _disconnect => Strings.disconnect.toUpperCase();
BluetoothModel model;
Bluetooth({bt.ControlProxy monitor}) {
model = BluetoothModel(
monitor: monitor,
onChange: _onChange,
);
}
factory Bluetooth.fromStartupContext(StartupContext startupContext) {
final bluetoothManager = bt.ControlProxy();
startupContext.incoming.connectToService(bluetoothManager);
return Bluetooth(monitor: bluetoothManager);
}
void _onChange() {
spec = _specForBluetooth(model.remoteDevices);
}
@override
void update(Value value) async {
if (value.$tag == ValueTag.text &&
value.text.action > 0 &&
value.text.text == _disconnect) {
final index = (value.text.action ^ QuickAction.submit.$value) ~/ 2;
await model.disconnectDevice(model.remoteDevices[index]);
}
}
@override
void dispose() {
model.dispose();
}
static Spec _specForBluetooth(List<bt.RemoteDevice> devices) {
if (devices.isEmpty) {
// No connected devices found. Send nullSpec to hide bluetooth settings
return UiSpec.nullSpec;
}
final values = List<TextValue>.generate(devices.length * 2, (index) {
return TextValue(
text: index.isEven ? devices[index ~/ 2].name : _disconnect,
action: index.isEven
? (QuickAction.submit.$value | (index))
: (QuickAction.submit.$value | (index - 1)),
);
});
return Spec(title: _title, groups: [
Group(title: _title, values: [
Value.withIcon(IconValue(codePoint: Icons.bluetooth.codePoint)),
Value.withGrid(GridValue(columns: 2, values: values))
]),
]);
}
}
class BluetoothModel {
final VoidCallback onChange;
bt.ControlProxy _monitor;
StreamSubscription _bluetoothSubscription;
List<bt.RemoteDevice> _bluetoothDevices;
BluetoothModel({
@required this.onChange,
bt.ControlProxy monitor,
}) {
_monitor = monitor ?? bt.ControlProxy();
_bluetoothDevices = <bt.RemoteDevice>[];
// Call initially to get devices connected at login.
loadInitialBluetoothDevices();
// Listen for device updates & check the connection status of bt devices
_bluetoothSubscription = _monitor.onDeviceUpdated.listen((device) {
listConnectedBluetoothDevices();
});
}
void loadInitialBluetoothDevices() async {
final remoteDevices = await _monitor.getKnownRemoteDevices();
await Future.forEach(remoteDevices, (device) async {
if (device != null && device.connected) {
if (device.name != null) {
_bluetoothDevices.add(device);
}
}
});
onChange.call();
}
void listConnectedBluetoothDevices() async {
final remoteDevices = List.from(await _monitor.getKnownRemoteDevices());
await Future.forEach(remoteDevices, (device) async {
final deviceIdentifiers =
_bluetoothDevices.map((btDevice) => btDevice.identifier);
bool isPreviouslyConnectedDevice =
deviceIdentifiers.contains(device.identifier);
if (isPreviouslyConnectedDevice) {
if (!device.connected) {
_bluetoothDevices
.removeWhere((dv) => dv.identifier == device.identifier);
onChange?.call();
}
} else if (device != null && device.connected) {
if (device.name != null) {
_bluetoothDevices.add(device);
onChange?.call();
}
}
});
}
Future<void> disconnectDevice(bt.RemoteDevice device) async =>
await _monitor.disconnect(device.identifier);
void dispose() {
_bluetoothSubscription.cancel();
}
List<bt.RemoteDevice> get remoteDevices => _bluetoothDevices.toList();
set remoteDevices(List<bt.RemoteDevice> values) {
_bluetoothDevices = values;
onChange.call();
}
}