Skip to content

Commit

Permalink
Merge branch 'main' into leaderboard
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian-Boraks committed Oct 14, 2023
2 parents 57e8060 + d2fb7e6 commit 46aa023
Show file tree
Hide file tree
Showing 9 changed files with 411 additions and 104 deletions.
93 changes: 0 additions & 93 deletions app/watt_wizard/lib/device_connection_card.dart

This file was deleted.

44 changes: 39 additions & 5 deletions app/watt_wizard/lib/profile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:watt_wizard/device_connection_card.dart';
import 'package:watt_wizard/widgets/connected_device_tile.dart';
import 'package:watt_wizard/widgets/scan_result_tile.dart';
import 'package:watt_wizard/utils/extra.dart';

class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key, required this.username});
Expand All @@ -25,14 +27,21 @@ class _ProfileScreenState extends State<ProfileScreen> {
super.initState();

FlutterBluePlus.connectedSystemDevices.then((devices) {
_connectedDevices = devices;
List<BluetoothDevice> device = [];
for (BluetoothDevice d in devices) {
if (d.platformName == "Light Control") {
device.add(d);
}
}
_connectedDevices = device;
setState(() {});
});

_scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
List<ScanResult> result = [];
for (ScanResult r in results) {
if (r.device.platformName == "Light Control") {
if (r.device.platformName == "Light Control" &&
!_connectedDevices.contains(r.device)) {
result.add(r);
}
}
Expand Down Expand Up @@ -62,6 +71,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
FlutterBluePlus.stopScan();
}

void onConnectPressed(BluetoothDevice device) {
device.connectAndUpdateStream();
_connectedDevices.add(device);
setState(() {});
}

Future onRefresh() {
if (_isScanning == false) {
FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
Expand All @@ -70,11 +85,27 @@ class _ProfileScreenState extends State<ProfileScreen> {
return Future.delayed(const Duration(milliseconds: 500));
}

List<Widget> _buildConnectedDeviceTiles(BuildContext context) {
return _connectedDevices
.map(
(d) => ConnectedDeviceTile(
device: d,
onConnect: () => onConnectPressed(d),
),
)
.toList();
}

List<Widget> _buildScanResultTiles(BuildContext context) {
return _scanResults
.map(
(r) => Text(
r.device.platformName,
(r) => ScanResultTile(
result: r,
onTap: () {
_scanResults.remove(r);
setState(() {});
onConnectPressed(r.device);
},
),
)
.toList();
Expand Down Expand Up @@ -110,6 +141,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
onRefresh: onRefresh,
child: ListView(
children: <Widget>[
const Text("Connected Devices"),
..._buildConnectedDeviceTiles(context),
const Text("Scanned Devices:"),
..._buildScanResultTiles(context),
],
),
Expand Down
39 changes: 39 additions & 0 deletions app/watt_wizard/lib/utils/extra.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'utils.dart';

import 'package:flutter_blue_plus/flutter_blue_plus.dart';

final Map<DeviceIdentifier, StreamControllerReemit<bool>> _global = {};

/// connect & disconnect + update stream
extension Extra on BluetoothDevice {
// convenience
StreamControllerReemit<bool> get _stream {
_global[remoteId] ??= StreamControllerReemit(initialValue: false);
return _global[remoteId]!;
}

// get stream
Stream<bool> get isConnectingOrDisconnecting {
return _stream.stream;
}

// connect & update stream
Future<void> connectAndUpdateStream() async {
_stream.add(true);
try {
await connect();
} finally {
_stream.add(false);
}
}

// disconnect & update stream
Future<void> disconnectAndUpdateStream() async {
_stream.add(true);
try {
await disconnect();
} finally {
_stream.add(false);
}
}
}
86 changes: 86 additions & 0 deletions app/watt_wizard/lib/utils/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'dart:async';

// It is essentially a stream but:
// 1. we cache the latestValue of the stream
// 2. the "latestValue" is re-emitted whenever the stream is listened to
class StreamControllerReemit<T> {
T? _latestValue;

final StreamController<T> _controller = StreamController<T>.broadcast();

StreamControllerReemit({T? initialValue}) : _latestValue = initialValue;

Stream<T> get stream {
return _latestValue != null
? _controller.stream.newStreamWithInitialValue(_latestValue!)
: _controller.stream;
}

T? get value => _latestValue;

void add(T newValue) {
_latestValue = newValue;
_controller.add(newValue);
}

Future<void> close() {
return _controller.close();
}
}

// return a new stream that imediately emits an initial value
extension _StreamNewStreamWithInitialValue<T> on Stream<T> {
Stream<T> newStreamWithInitialValue(T initialValue) {
return transform(_NewStreamWithInitialValueTransformer(initialValue));
}
}

// Helper for 'newStreamWithInitialValue' method for streams.
class _NewStreamWithInitialValueTransformer<T>
extends StreamTransformerBase<T, T> {
final T initialValue;

_NewStreamWithInitialValueTransformer(this.initialValue);

@override
Stream<T> bind(Stream<T> stream) {
if (stream.isBroadcast) {
return _bind(stream).asBroadcastStream();
} else {
return _bind(stream);
}
}

Stream<T> _bind(Stream<T> stream) {
StreamController<T>? controller;
StreamSubscription<T>? subscription;

controller = StreamController<T>(
onListen: () {
// Emit the initial value
controller?.add(initialValue);

subscription = stream.listen(
controller?.add,
onError: (Object error) {
controller?.addError(error);
controller?.close();
},
onDone: controller?.close,
);
},
onPause: ([Future<dynamic>? resumeSignal]) {
subscription?.pause(resumeSignal);
},
onResume: () {
subscription?.resume();
},
onCancel: () {
return subscription?.cancel();
},
sync: true,
);

return controller.stream;
}
}
59 changes: 59 additions & 0 deletions app/watt_wizard/lib/widgets/connected_device_tile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';

class ConnectedDeviceTile extends StatefulWidget {
final BluetoothDevice device;
final VoidCallback onConnect;

const ConnectedDeviceTile({
required this.device,
required this.onConnect,
Key? key,
}) : super(key: key);

@override
State<ConnectedDeviceTile> createState() => _ConnectedDeviceTileState();
}

class _ConnectedDeviceTileState extends State<ConnectedDeviceTile> {
BluetoothConnectionState _connectionState =
BluetoothConnectionState.disconnected;

late StreamSubscription<BluetoothConnectionState>
_connectionStateSubscription;

@override
void initState() {
super.initState();

_connectionStateSubscription =
widget.device.connectionState.listen((state) {
_connectionState = state;
setState(() {});
});
}

@override
void dispose() {
_connectionStateSubscription.cancel();
super.dispose();
}

bool get isConnected {
return _connectionState == BluetoothConnectionState.connected;
}

@override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.device.platformName),
subtitle: Text(widget.device.remoteId.toString()),
trailing: ElevatedButton(
onPressed: isConnected ? null : widget.onConnect,
child: isConnected ? const Text('OPEN') : const Text('CONNECT'),
),
);
}
}
Loading

0 comments on commit 46aa023

Please sign in to comment.