This commit is contained in:
Aditya Pulipaka
2025-07-10 18:52:04 -05:00
commit e0a41761ec
166 changed files with 8444 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import 'utils.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
final Map<DeviceIdentifier, StreamControllerReemit<bool>> _cglobal = {};
final Map<DeviceIdentifier, StreamControllerReemit<bool>> _dglobal = {};
/// connect & disconnect + update stream
extension Extra on BluetoothDevice {
// convenience
StreamControllerReemit<bool> get _cstream {
_cglobal[remoteId] ??= StreamControllerReemit(initialValue: false);
return _cglobal[remoteId]!;
}
// convenience
StreamControllerReemit<bool> get _dstream {
_dglobal[remoteId] ??= StreamControllerReemit(initialValue: false);
return _dglobal[remoteId]!;
}
// get stream
Stream<bool> get isConnecting {
return _cstream.stream;
}
// get stream
Stream<bool> get isDisconnecting {
return _dstream.stream;
}
// connect & update stream
Future<void> connectAndUpdateStream() async {
_cstream.add(true);
try {
await connect(mtu: null);
} finally {
_cstream.add(false);
}
}
// disconnect & update stream
Future<void> disconnectAndUpdateStream({bool queue = true}) async {
_dstream.add(true);
try {
await disconnect(queue: queue);
} finally {
_dstream.add(false);
}
}
}

View File

@@ -0,0 +1,64 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
class ScanResultTile extends StatefulWidget {
const ScanResultTile({super.key, required this.result, this.onTap});
final ScanResult result;
final VoidCallback? onTap;
@override
State<ScanResultTile> createState() => _ScanResultTileState();
}
class _ScanResultTileState extends State<ScanResultTile> {
BluetoothConnectionState _connectionState = BluetoothConnectionState.disconnected;
late StreamSubscription<BluetoothConnectionState> _connectionStateSubscription;
@override
void initState() {
super.initState();
_connectionStateSubscription = widget.result.device.connectionState.listen((state) {
_connectionState = state;
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
_connectionStateSubscription.cancel();
super.dispose();
}
bool get isConnected {
return _connectionState == BluetoothConnectionState.connected;
}
Widget _buildTitle(BuildContext context) {
return Text(
widget.result.advertisementData.advName,
overflow: TextOverflow.ellipsis,
);
}
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: _buildTitle(context),
subtitle: Text(
"Signal Strength (Less = farther): ${widget.result.rssi}",
overflow: TextOverflow.ellipsis,
),
trailing: Icon(Icons.arrow_forward_ios_rounded),
onTap: widget.result.advertisementData.connectable ? widget.onTap : null,
)
);
}
}

View File

@@ -0,0 +1,153 @@
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 as T) : _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 immediately 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> {
/// the initial value to push to the new stream
final T initialValue;
/// controller for the new stream
late StreamController<T> controller;
/// subscription to the original stream
late StreamSubscription<T> subscription;
/// new stream listener count
var listenerCount = 0;
_NewStreamWithInitialValueTransformer(this.initialValue);
@override
Stream<T> bind(Stream<T> stream) {
if (stream.isBroadcast) {
return _bind(stream, broadcast: true);
} else {
return _bind(stream);
}
}
Stream<T> _bind(Stream<T> stream, {bool broadcast = false}) {
/////////////////////////////////////////
/// Original Stream Subscription Callbacks
///
/// When the original stream emits data, forward it to our new stream
void onData(T data) {
controller.add(data);
}
/// When the original stream is done, close our new stream
void onDone() {
controller.close();
}
/// When the original stream has an error, forward it to our new stream
void onError(Object error) {
controller.addError(error);
}
/// When a client listens to our new stream, emit the
/// initial value and subscribe to original stream if needed
void onListen() {
// Emit the initial value to our new stream
controller.add(initialValue);
// listen to the original stream, if needed
if (listenerCount == 0) {
subscription = stream.listen(
onData,
onError: onError,
onDone: onDone,
);
}
// count listeners of the new stream
listenerCount++;
}
//////////////////////////////////////
/// New Stream Controller Callbacks
///
/// (Single Subscription Only) When a client pauses
/// the new stream, pause the original stream
void onPause() {
subscription.pause();
}
/// (Single Subscription Only) When a client resumes
/// the new stream, resume the original stream
void onResume() {
subscription.resume();
}
/// Called when a client cancels their
/// subscription to the new stream,
void onCancel() {
// count listeners of the new stream
listenerCount--;
// when there are no more listeners of the new stream,
// cancel the subscription to the original stream,
// and close the new stream controller
if (listenerCount == 0) {
subscription.cancel();
controller.close();
}
}
//////////////////////////////////////
/// Return New Stream
///
// create a new stream controller
if (broadcast) {
controller = StreamController<T>.broadcast(
onListen: onListen,
onCancel: onCancel,
);
} else {
controller = StreamController<T>(
onListen: onListen,
onPause: onPause,
onResume: onResume,
onCancel: onCancel,
);
}
return controller.stream;
}
}