diff --git a/app/lib/backend/preferences.dart b/app/lib/backend/preferences.dart index b51c3ea61..ce8d90b44 100644 --- a/app/lib/backend/preferences.dart +++ b/app/lib/backend/preferences.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:collection/collection.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; import 'package:friend_private/backend/schema/memory.dart'; import 'package:friend_private/backend/schema/message.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/backend/schema/person.dart'; import 'package:friend_private/backend/schema/plugin.dart'; import 'package:friend_private/backend/schema/transcript_segment.dart'; @@ -27,18 +27,18 @@ class SharedPreferencesUtil { String get uid => getString('uid') ?? ''; - set btDeviceStruct(BTDeviceStruct value) { - saveString('btDeviceStruct', jsonEncode(value.toJson())); + set btDevice(BtDevice value) { + saveString('btDevice', jsonEncode(value.toJson())); } - Future btDeviceStructSet(BTDeviceStruct value) async { - await saveString('btDeviceStruct', jsonEncode(value.toJson())); + Future btDeviceSet(BtDevice value) async { + await saveString('btDevice', jsonEncode(value.toJson())); } - BTDeviceStruct get btDeviceStruct { - final String device = getString('btDeviceStruct') ?? ''; - if (device.isEmpty) return BTDeviceStruct(id: '', name: ''); - return BTDeviceStruct.fromJson(jsonDecode(device)); + BtDevice get btDevice { + final String device = getString('btDevice') ?? ''; + if (device.isEmpty) return BtDevice(id: '', name: '', type: DeviceType.friend, rssi: 0); + return BtDevice.fromJson(jsonDecode(device)); } set deviceName(String value) => saveString('deviceName', value); diff --git a/app/lib/backend/schema/bt_device.dart b/app/lib/backend/schema/bt_device.dart deleted file mode 100644 index 967b40317..000000000 --- a/app/lib/backend/schema/bt_device.dart +++ /dev/null @@ -1,250 +0,0 @@ -import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'package:friend_private/services/devices/device_connection.dart'; -import 'package:friend_private/services/devices/frame_connection.dart'; -import 'package:friend_private/services/devices/models.dart'; - -enum BleAudioCodec { - pcm16, - pcm8, - mulaw16, - mulaw8, - opus, - unknown; - - @override - String toString() => mapCodecToName(this); -} - -String mapCodecToName(BleAudioCodec codec) { - switch (codec) { - case BleAudioCodec.opus: - return 'opus'; - case BleAudioCodec.pcm16: - return 'pcm16'; - case BleAudioCodec.pcm8: - return 'pcm8'; - default: - return 'pcm8'; - } -} - -BleAudioCodec mapNameToCodec(String codec) { - switch (codec) { - case 'opus': - return BleAudioCodec.opus; - case 'pcm16': - return BleAudioCodec.pcm16; - case 'pcm8': - return BleAudioCodec.pcm8; - default: - return BleAudioCodec.pcm8; - } -} - -int mapCodecToSampleRate(BleAudioCodec codec) { - switch (codec) { - case BleAudioCodec.opus: - return 16000; - case BleAudioCodec.pcm16: - return 16000; - case BleAudioCodec.pcm8: - return 16000; - default: - return 16000; - } -} - -int mapCodecToBitDepth(BleAudioCodec codec) { - switch (codec) { - case BleAudioCodec.opus: - return 16; - case BleAudioCodec.pcm16: - return 16; - case BleAudioCodec.pcm8: - return 8; - default: - return 16; - } -} - -Future getTypeOfBluetoothDevice(BluetoothDevice device) async { - if (deviceTypeMap.containsKey(device.remoteId.toString())) { - return deviceTypeMap[device.remoteId.toString()]; - } - DeviceType? deviceType; - await device.discoverServices(); - if (device.servicesList.where((s) => s.uuid == Guid(friendServiceUuid)).isNotEmpty) { - // Check if the device has the image data stream characteristic - final hasImageStream = device.servicesList - .where((s) => s.uuid == friendServiceUuid) - .expand((s) => s.characteristics) - .any((c) => c.uuid.toString().toLowerCase() == imageDataStreamCharacteristicUuid.toLowerCase()); - deviceType = hasImageStream ? DeviceType.openglass : DeviceType.friend; - } else if (device.servicesList.where((s) => s.uuid == Guid(frameServiceUuid)).isNotEmpty) { - deviceType = DeviceType.frame; - } - if (deviceType != null) { - deviceTypeMap[device.remoteId.toString()] = deviceType; - } - return deviceType; -} - -enum DeviceType { - friend, - openglass, - frame, -} - -Map deviceTypeMap = {}; - -class BTDeviceStruct { - String name; - String id; - int? rssi; - List? fwver; - DeviceType? type; - - BTDeviceStruct({required this.id, required this.name, this.rssi, this.fwver, this.type}) { - if (type != null) { - deviceTypeMap[id] = type!; - } else if (deviceTypeMap.containsKey(id)) { - type = deviceTypeMap[id]; - } - } - - String getShortId() => BTDeviceStruct.shortId(id); - - static shortId(String id) { - try { - return id.replaceAll(':', '').split('-').last.substring(0, 6); - } catch (e) { - return id.length > 6 ? id.substring(0, 6) : id; - } - } - - factory BTDeviceStruct.fromJson(Map json) { - var fwver = json['fwver'] as List?; - if (fwver != null) { - if (fwver.firstOrNull is int) { - fwver = fwver.map((e) => e as int).toList(); - } else if (fwver.firstOrNull is String) { - fwver = (fwver.firstOrNull as String).split('.').map((e) => int.parse(e.replaceFirst('v', ''))).toList(); - } else { - fwver = null; - } - } - return BTDeviceStruct( - id: json['id'] as String, - name: json['name'] as String, - rssi: json['rssi'] as int?, - fwver: fwver as List?, - type: json['type'] == null - ? null - : DeviceType.values.firstWhere((e) => e.name.toLowerCase() == json['type'].toLowerCase()), - ); - } - - Map toJson({bool fwverAsString = false}) => - {'id': id, 'name': name, 'rssi': rssi, 'fwver': fwverAsString ? fwver?.join('.') : fwver, 'type': type?.name}; -} - -class DeviceInfo { - String modelNumber; - String firmwareRevision; - String hardwareRevision; - String manufacturerName; - DeviceType type; - - DeviceInfo(this.modelNumber, this.firmwareRevision, this.hardwareRevision, this.manufacturerName, this.type); - - static Future getDeviceInfo(BTDeviceStruct? device, DeviceConnection? conn) async { - if (device == null) { - return DeviceInfo('Unknown', 'Unknown', 'Unknown', 'Unknown', DeviceType.friend); - } - if (conn == null) { - return DeviceInfo('Unknown', 'Unknown', 'Unknown', 'Unknown', DeviceType.friend); - } - - device.type ??= await getTypeOfBluetoothDevice(conn.bleDevice); - - if (device.type == DeviceType.friend) { - return _getDeviceInfoFromFriend(device, conn); - } else if (device.type == DeviceType.openglass) { - return _getDeviceInfoFromFriend(device, conn); - } else if (device.type == DeviceType.frame) { - return _getDeviceInfoFromFrame(device, conn as FrameDeviceConnection); - } else { - return _getDeviceInfoFromFriend(device, conn); - } - } - - static Future _getDeviceInfoFromFriend(BTDeviceStruct? device, DeviceConnection conn) async { - var modelNumber = 'Friend'; - var firmwareRevision = '1.0.2'; - var hardwareRevision = 'Seeed Xiao BLE Sense'; - var manufacturerName = 'Based Hardware'; - - if (device == null) { - return DeviceInfo(modelNumber, firmwareRevision, hardwareRevision, manufacturerName, DeviceType.friend); - } - - var deviceInformationService = await conn.getService(deviceInformationServiceUuid); - if (deviceInformationService != null) { - var modelNumberCharacteristic = conn.getCharacteristic(deviceInformationService, modelNumberCharacteristicUuid); - if (modelNumberCharacteristic != null) { - modelNumber = String.fromCharCodes(await modelNumberCharacteristic.read()); - } - - var firmwareRevisionCharacteristic = - conn.getCharacteristic(deviceInformationService, firmwareRevisionCharacteristicUuid); - if (firmwareRevisionCharacteristic != null) { - firmwareRevision = String.fromCharCodes(await firmwareRevisionCharacteristic.read()); - } - - var hardwareRevisionCharacteristic = - conn.getCharacteristic(deviceInformationService, hardwareRevisionCharacteristicUuid); - if (hardwareRevisionCharacteristic != null) { - hardwareRevision = String.fromCharCodes(await hardwareRevisionCharacteristic.read()); - } - - var manufacturerNameCharacteristic = - conn.getCharacteristic(deviceInformationService, manufacturerNameCharacteristicUuid); - if (manufacturerNameCharacteristic != null) { - manufacturerName = String.fromCharCodes(await manufacturerNameCharacteristic.read()); - } - } - - var type = DeviceType.friend; - if (device.type == DeviceType.openglass) { - type = DeviceType.openglass; - } else { - final friendService = await conn.getService(friendServiceUuid); - if (friendService != null) { - var imageCaptureControlCharacteristic = - conn.getCharacteristic(friendService, imageDataStreamCharacteristicUuid); - if (imageCaptureControlCharacteristic != null) { - type = DeviceType.openglass; - } - } - } - - return DeviceInfo( - modelNumber, - firmwareRevision, - hardwareRevision, - manufacturerName, - type, - ); - } - - static Future _getDeviceInfoFromFrame(BTDeviceStruct? device, FrameDeviceConnection conn) async { - await conn.init(); - return DeviceInfo( - conn.modelNumber, - conn.firmwareRevision, - conn.hardwareRevision, - conn.manufacturerName, - DeviceType.frame, - ); - } -} diff --git a/app/lib/backend/schema/bt_device/bt_device.dart b/app/lib/backend/schema/bt_device/bt_device.dart new file mode 100644 index 000000000..36ad66d3f --- /dev/null +++ b/app/lib/backend/schema/bt_device/bt_device.dart @@ -0,0 +1,342 @@ +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:friend_private/backend/preferences.dart'; +import 'package:friend_private/services/devices/device_connection.dart'; +import 'package:friend_private/services/devices/frame_connection.dart'; +import 'package:friend_private/services/devices/models.dart'; + +enum BleAudioCodec { + pcm16, + pcm8, + mulaw16, + mulaw8, + opus, + unknown; + + @override + String toString() => mapCodecToName(this); +} + +String mapCodecToName(BleAudioCodec codec) { + switch (codec) { + case BleAudioCodec.opus: + return 'opus'; + case BleAudioCodec.pcm16: + return 'pcm16'; + case BleAudioCodec.pcm8: + return 'pcm8'; + default: + return 'pcm8'; + } +} + +BleAudioCodec mapNameToCodec(String codec) { + switch (codec) { + case 'opus': + return BleAudioCodec.opus; + case 'pcm16': + return BleAudioCodec.pcm16; + case 'pcm8': + return BleAudioCodec.pcm8; + default: + return BleAudioCodec.pcm8; + } +} + +int mapCodecToSampleRate(BleAudioCodec codec) { + switch (codec) { + case BleAudioCodec.opus: + return 16000; + case BleAudioCodec.pcm16: + return 16000; + case BleAudioCodec.pcm8: + return 16000; + default: + return 16000; + } +} + +int mapCodecToBitDepth(BleAudioCodec codec) { + switch (codec) { + case BleAudioCodec.opus: + return 16; + case BleAudioCodec.pcm16: + return 16; + case BleAudioCodec.pcm8: + return 8; + default: + return 16; + } +} + +Future getTypeOfBluetoothDevice(BluetoothDevice device) async { + if (cachedDevicesMap.containsKey(device.remoteId.toString())) { + return cachedDevicesMap[device.remoteId.toString()]; + } + DeviceType? deviceType; + await device.discoverServices(); + if (device.servicesList.where((s) => s.uuid == Guid(friendServiceUuid)).isNotEmpty) { + // Check if the device has the image data stream characteristic + final hasImageStream = device.servicesList + .where((s) => s.uuid == Guid.fromString(friendServiceUuid)) + .expand((s) => s.characteristics) + .any((c) => c.uuid.toString().toLowerCase() == imageDataStreamCharacteristicUuid.toLowerCase()); + deviceType = hasImageStream ? DeviceType.openglass : DeviceType.friend; + } else if (device.servicesList.where((s) => s.uuid == Guid(frameServiceUuid)).isNotEmpty) { + deviceType = DeviceType.frame; + } + if (deviceType != null) { + cachedDevicesMap[device.remoteId.toString()] = deviceType; + } + return deviceType; +} + +enum DeviceType { + friend, + openglass, + frame, +} + +Map cachedDevicesMap = {}; + +class BtDevice { + String name; + String id; + DeviceType type; + int rssi; + String? _modelNumber; + String? _firmwareRevision; + String? _hardwareRevision; + String? _manufacturerName; + + BtDevice( + {required this.name, + required this.id, + required this.type, + required this.rssi, + String? modelNumber, + String? firmwareRevision, + String? hardwareRevision, + String? manufacturerName}) { + _modelNumber = modelNumber; + _firmwareRevision = firmwareRevision; + _hardwareRevision = hardwareRevision; + _manufacturerName = manufacturerName; + } + + // create an empty device + BtDevice.empty() + : name = '', + id = '', + type = DeviceType.friend, + rssi = 0, + _modelNumber = '', + _firmwareRevision = '', + _hardwareRevision = '', + _manufacturerName = ''; + + // getters + String get modelNumber => _modelNumber ?? 'Unknown11'; + String get firmwareRevision => _firmwareRevision ?? 'Unknown'; + String get hardwareRevision => _hardwareRevision ?? 'Unknown'; + String get manufacturerName => _manufacturerName ?? 'Unknown'; + + // set details + set modelNumber(String modelNumber) => _modelNumber = modelNumber; + set firmwareRevision(String firmwareRevision) => _firmwareRevision = firmwareRevision; + set hardwareRevision(String hardwareRevision) => _hardwareRevision = hardwareRevision; + set manufacturerName(String manufacturerName) => _manufacturerName = manufacturerName; + + String getShortId() => BtDevice.shortId(id); + + static shortId(String id) { + try { + return id.replaceAll(':', '').split('-').last.substring(0, 6); + } catch (e) { + return id.length > 6 ? id.substring(0, 6) : id; + } + } + + BtDevice copyWith( + {String? name, + String? id, + DeviceType? type, + int? rssi, + String? modelNumber, + String? firmwareRevision, + String? hardwareRevision, + String? manufacturerName}) { + return BtDevice( + name: name ?? this.name, + id: id ?? this.id, + type: type ?? this.type, + rssi: rssi ?? this.rssi, + modelNumber: modelNumber ?? _modelNumber, + firmwareRevision: firmwareRevision ?? _firmwareRevision, + hardwareRevision: hardwareRevision ?? _hardwareRevision, + manufacturerName: manufacturerName ?? _manufacturerName, + ); + } + + Future updateDeviceInfo(DeviceConnection? conn) async { + if (conn == null) { + return this; + } + return await getDeviceInfo(conn); + } + + Future getDeviceInfo(DeviceConnection? conn) async { + if (conn == null) { + if (SharedPreferencesUtil().btDevice.id.isNotEmpty) { + var device = SharedPreferencesUtil().btDevice; + return copyWith( + id: device.id, + name: device.name, + type: device.type, + rssi: device.rssi, + modelNumber: device.modelNumber, + firmwareRevision: device.firmwareRevision, + hardwareRevision: device.hardwareRevision, + manufacturerName: device.manufacturerName, + ); + } else { + return BtDevice.empty(); + } + } + + if (type == DeviceType.friend) { + return await _getDeviceInfoFromFriend(conn); + } else if (type == DeviceType.openglass) { + return await _getDeviceInfoFromFriend(conn); + } else if (type == DeviceType.frame) { + return await _getDeviceInfoFromFrame(conn as FrameDeviceConnection); + } else { + return await _getDeviceInfoFromFriend(conn); + } + } + + Future _getDeviceInfoFromFriend(DeviceConnection conn) async { + var modelNumber = 'Friend'; + var firmwareRevision = '1.0.2'; + var hardwareRevision = 'Seeed Xiao BLE Sense'; + var manufacturerName = 'Based Hardware'; + + var deviceInformationService = await conn.getService(deviceInformationServiceUuid); + if (deviceInformationService != null) { + var modelNumberCharacteristic = conn.getCharacteristic(deviceInformationService, modelNumberCharacteristicUuid); + if (modelNumberCharacteristic != null) { + modelNumber = String.fromCharCodes(await modelNumberCharacteristic.read()); + } + + var firmwareRevisionCharacteristic = + conn.getCharacteristic(deviceInformationService, firmwareRevisionCharacteristicUuid); + if (firmwareRevisionCharacteristic != null) { + firmwareRevision = String.fromCharCodes(await firmwareRevisionCharacteristic.read()); + } + + var hardwareRevisionCharacteristic = + conn.getCharacteristic(deviceInformationService, hardwareRevisionCharacteristicUuid); + if (hardwareRevisionCharacteristic != null) { + hardwareRevision = String.fromCharCodes(await hardwareRevisionCharacteristic.read()); + } + + var manufacturerNameCharacteristic = + conn.getCharacteristic(deviceInformationService, manufacturerNameCharacteristicUuid); + if (manufacturerNameCharacteristic != null) { + manufacturerName = String.fromCharCodes(await manufacturerNameCharacteristic.read()); + } + } + + var t = DeviceType.friend; + if (type == DeviceType.openglass) { + t = DeviceType.openglass; + } else { + final friendService = await conn.getService(friendServiceUuid); + if (friendService != null) { + var imageCaptureControlCharacteristic = + conn.getCharacteristic(friendService, imageDataStreamCharacteristicUuid); + if (imageCaptureControlCharacteristic != null) { + t = DeviceType.openglass; + } + } + } + + return copyWith( + modelNumber: modelNumber, + firmwareRevision: firmwareRevision, + hardwareRevision: hardwareRevision, + manufacturerName: manufacturerName, + type: t, + ); + } + + Future _getDeviceInfoFromFrame(FrameDeviceConnection conn) async { + await conn.init(); + return copyWith( + modelNumber: conn.modelNumber, + firmwareRevision: conn.firmwareRevision, + hardwareRevision: conn.hardwareRevision, + manufacturerName: conn.manufacturerName, + type: DeviceType.frame, + ); + } + + // from BluetoothDevice + Future fromBluetoothDevice(BluetoothDevice device) async { + var rssi = await device.readRssi(); + return BtDevice( + name: device.platformName, + id: device.remoteId.str, + type: DeviceType.friend, + rssi: rssi, + ); + } + + // from ScanResult + static fromScanResult(ScanResult result) { + DeviceType? deviceType; + if (result.advertisementData.serviceUuids.contains(Guid(friendServiceUuid))) { + deviceType = DeviceType.friend; + } else if (result.advertisementData.serviceUuids.contains(Guid(frameServiceUuid))) { + deviceType = DeviceType.frame; + } + if (deviceType != null) { + cachedDevicesMap[result.device.remoteId.toString()] = deviceType; + } else if (cachedDevicesMap.containsKey(result.device.remoteId.toString())) { + deviceType = cachedDevicesMap[result.device.remoteId.toString()]; + } + return BtDevice( + name: result.device.platformName, + id: result.device.remoteId.str, + type: deviceType ?? DeviceType.friend, + rssi: result.rssi, + ); + } + + // from json + static fromJson(Map json) { + return BtDevice( + name: json['name'], + id: json['id'], + type: DeviceType.values[json['type']], + rssi: json['rssi'], + modelNumber: json['modelNumber'], + firmwareRevision: json['firmwareRevision'], + hardwareRevision: json['hardwareRevision'], + manufacturerName: json['manufacturerName'], + ); + } + + // to json + Map toJson() { + return { + 'name': name, + 'id': id, + 'type': type.index, + 'rssi': rssi, + 'modelNumber': modelNumber, + 'firmwareRevision': firmwareRevision, + 'hardwareRevision': hardwareRevision, + 'manufacturerName': manufacturerName, + }; + } +} diff --git a/app/lib/pages/capture/logic/openglass_mixin.dart b/app/lib/pages/capture/logic/openglass_mixin.dart index eb8443c95..3fe0d1672 100644 --- a/app/lib/pages/capture/logic/openglass_mixin.dart +++ b/app/lib/pages/capture/logic/openglass_mixin.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:friend_private/backend/http/openai.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/services/services.dart'; import 'package:friend_private/utils/audio/wav_bytes.dart'; import 'package:tuple/tuple.dart'; @@ -44,7 +44,7 @@ mixin OpenGlassMixin { } Future openGlassProcessing( - BTDeviceStruct device, + BtDevice device, Function(List>) onPhotosUpdated, Function(bool) setHasTranscripts, ) async { diff --git a/app/lib/pages/capture/widgets/widgets.dart b/app/lib/pages/capture/widgets/widgets.dart index 12d6669bd..b67d3d0ea 100644 --- a/app/lib/pages/capture/widgets/widgets.dart +++ b/app/lib/pages/capture/widgets/widgets.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/backend/schema/transcript_segment.dart'; import 'package:friend_private/pages/speech_profile/page.dart'; import 'package:friend_private/providers/capture_provider.dart'; @@ -26,9 +26,9 @@ class SpeechProfileCardWidget extends StatelessWidget { return provider.hasSpeakerProfile ? const SizedBox() : Consumer(builder: (context, device, child) { - if (device.deviceInfo == null || + if (device.pairedDevice == null || !device.isConnected || - device.deviceInfo?.firmwareRevision == '1.0.2') { + device.pairedDevice?.firmwareRevision == '1.0.2') { return const SizedBox(); } return Stack( @@ -95,9 +95,9 @@ class UpdateFirmwareCardWidget extends StatelessWidget { Widget build(BuildContext context) { return Consumer( builder: (context, provider, child) { - return (provider.deviceInfo == null || !provider.isConnected) + return (provider.pairedDevice == null || !provider.isConnected) ? const SizedBox() - : (provider.deviceInfo?.firmwareRevision != '1.0.2') + : (provider.pairedDevice?.firmwareRevision != '1.0.2') ? const SizedBox() : Stack( children: [ @@ -153,7 +153,7 @@ getTranscriptWidget( bool memoryCreating, List segments, List> photos, - BTDeviceStruct? btDevice, + BtDevice? btDevice, ) { if (memoryCreating) { return const Padding( @@ -173,7 +173,7 @@ getTranscriptWidget( getLiteTranscriptWidget( List segments, List> photos, - BTDeviceStruct? btDevice, + BtDevice? btDevice, ) { return Column( children: [ @@ -188,7 +188,7 @@ getLiteTranscriptWidget( } getPhoneMicRecordingButton(VoidCallback recordingToggled, RecordingState state) { - if (SharedPreferencesUtil().btDeviceStruct.id.isNotEmpty) return const SizedBox.shrink(); + if (SharedPreferencesUtil().btDevice.id.isNotEmpty) return const SizedBox.shrink(); return Visibility( visible: true, child: Padding( diff --git a/app/lib/pages/home/device.dart b/app/lib/pages/home/device.dart index c0125dc21..1eaec96fe 100644 --- a/app/lib/pages/home/device.dart +++ b/app/lib/pages/home/device.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/providers/device_provider.dart'; import 'package:friend_private/services/services.dart'; import 'package:friend_private/utils/analytics/intercom.dart'; @@ -10,11 +10,7 @@ import 'package:gradient_borders/box_borders/gradient_box_border.dart'; import 'package:provider/provider.dart'; class ConnectedDevice extends StatefulWidget { - // TODO: retrieve this from here instead of params - final BTDeviceStruct? device; - final int batteryLevel; - - const ConnectedDevice({super.key, required this.device, required this.batteryLevel}); + const ConnectedDevice({super.key}); @override State createState() => _ConnectedDeviceState(); @@ -22,7 +18,7 @@ class ConnectedDevice extends StatefulWidget { class _ConnectedDeviceState extends State { // TODO: thinh, use connection directly - Future _bleDisconnectDevice(BTDeviceStruct btDevice) async { + Future _bleDisconnectDevice(BtDevice btDevice) async { var connection = await ServiceManager.instance().device.ensureConnection(btDevice.id); if (connection == null) { return Future.value(null); @@ -32,150 +28,145 @@ class _ConnectedDeviceState extends State { @override Widget build(BuildContext context) { - var deviceName = widget.device?.name ?? SharedPreferencesUtil().deviceName; - var deviceConnected = widget.device != null; - - return FutureBuilder( - future: context.read().getDeviceInfo(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - return Scaffold( + return Consumer(builder: (context, provider, child) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.primary, + appBar: AppBar( + title: Text(provider.connectedDevice != null ? 'Connected Device' : 'Paired Device'), backgroundColor: Theme.of(context).colorScheme.primary, - appBar: AppBar( - title: Text(deviceConnected ? 'Connected Device' : 'Paired Device'), - backgroundColor: Theme.of(context).colorScheme.primary, - ), - body: Column( - children: [ - const SizedBox(height: 32), - const DeviceAnimationWidget(), - Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - '$deviceName (${widget.device?.getShortId() ?? SharedPreferencesUtil().btDeviceStruct.getShortId()})', - style: const TextStyle( - color: Colors.white, - fontSize: 16.0, - fontWeight: FontWeight.w500, - height: 1.5, - ), - textAlign: TextAlign.center, + ), + body: Column( + children: [ + const SizedBox(height: 32), + const DeviceAnimationWidget(), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + '${provider.pairedDevice?.name} (${provider.pairedDevice?.getShortId() ?? SharedPreferencesUtil().btDevice.getShortId()})', + style: const TextStyle( + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + height: 1.5, ), - const SizedBox(height: 12), - if (snapshot.hasData) - Column( - children: [ - Text( - '${snapshot.data?.modelNumber}, firmware ${snapshot.data?.firmwareRevision}', - style: const TextStyle( - color: Colors.white, - fontSize: 10.0, - fontWeight: FontWeight.w500, - height: 1, - ), - textAlign: TextAlign.center, + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + if (provider.pairedDevice != null) + Column( + children: [ + Text( + '${provider.pairedDevice?.modelNumber}, firmware ${provider.pairedDevice?.firmwareRevision}', + style: const TextStyle( + color: Colors.white, + fontSize: 10.0, + fontWeight: FontWeight.w500, + height: 1, ), - const SizedBox(height: 12), - Text( - 'by ${snapshot.data?.manufacturerName}', - style: const TextStyle( - color: Colors.white, - fontSize: 10.0, - fontWeight: FontWeight.w500, - height: 1, - ), - textAlign: TextAlign.center, + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + Text( + 'by ${provider.pairedDevice?.manufacturerName}', + style: const TextStyle( + color: Colors.white, + fontSize: 10.0, + fontWeight: FontWeight.w500, + height: 1, ), - const SizedBox(height: 12), - ], - ), - widget.device != null - ? Container( - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(10), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 10, - height: 10, - decoration: BoxDecoration( - color: widget.batteryLevel > 75 - ? const Color.fromARGB(255, 0, 255, 8) - : widget.batteryLevel > 20 - ? Colors.yellow.shade700 - : Colors.red, - shape: BoxShape.circle, - ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + ], + ), + provider.connectedDevice != null + ? Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: provider.batteryLevel > 75 + ? const Color.fromARGB(255, 0, 255, 8) + : provider.batteryLevel > 20 + ? Colors.yellow.shade700 + : Colors.red, + shape: BoxShape.circle, ), - const SizedBox(width: 8.0), - Text( - '${widget.batteryLevel.toString()}% Battery', - style: const TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: FontWeight.w600, - ), + ), + const SizedBox(width: 8.0), + Text( + '${provider.batteryLevel.toString()}% Battery', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, ), - ], - )) - : const SizedBox.shrink() - ], - ), - const SizedBox(height: 32), - Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0), - decoration: BoxDecoration( - border: const GradientBoxBorder( - gradient: LinearGradient(colors: [ - Color.fromARGB(127, 208, 208, 208), - Color.fromARGB(127, 188, 99, 121), - Color.fromARGB(127, 86, 101, 182), - Color.fromARGB(127, 126, 190, 236) - ]), - width: 2, - ), - borderRadius: BorderRadius.circular(12), - ), - child: TextButton( - onPressed: () async { - await SharedPreferencesUtil().btDeviceStructSet(BTDeviceStruct(id: '', name: '')); - SharedPreferencesUtil().deviceName = ''; - if (widget.device != null) { - await _bleDisconnectDevice(widget.device!); - } - context.read().setIsConnected(false); - context.read().setConnectedDevice(null); - context.read().updateConnectingStatus(false); - Navigator.of(context).pop(); - MixpanelManager().disconnectFriendClicked(); - }, - child: Text( - widget.device == null ? "Unpair" : "Disconnect", - style: const TextStyle(color: Colors.white, fontSize: 16), - ), + ), + ], + )) + : const SizedBox.shrink() + ], + ), + const SizedBox(height: 32), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0), + decoration: BoxDecoration( + border: const GradientBoxBorder( + gradient: LinearGradient(colors: [ + Color.fromARGB(127, 208, 208, 208), + Color.fromARGB(127, 188, 99, 121), + Color.fromARGB(127, 86, 101, 182), + Color.fromARGB(127, 126, 190, 236) + ]), + width: 2, ), + borderRadius: BorderRadius.circular(12), ), - const SizedBox(height: 8), - TextButton( + child: TextButton( onPressed: () async { - await IntercomManager.instance.displayChargingArticle(); + await SharedPreferencesUtil() + .btDeviceSet(BtDevice(id: '', name: '', type: DeviceType.friend, rssi: 0)); + SharedPreferencesUtil().deviceName = ''; + if (provider.connectedDevice != null) { + await _bleDisconnectDevice(provider.connectedDevice!); + } + context.read().setIsConnected(false); + context.read().setConnectedDevice(null); + context.read().updateConnectingStatus(false); + Navigator.of(context).pop(); + MixpanelManager().disconnectFriendClicked(); }, - child: const Text( - 'Issues charging?', - style: TextStyle( - color: Colors.white, - decoration: TextDecoration.underline, - ), + child: Text( + provider.connectedDevice == null ? "Unpair" : "Disconnect", + style: const TextStyle(color: Colors.white, fontSize: 16), + ), + ), + ), + const SizedBox(height: 8), + TextButton( + onPressed: () async { + await IntercomManager.instance.displayChargingArticle(); + }, + child: const Text( + 'Issues charging?', + style: TextStyle( + color: Colors.white, + decoration: TextDecoration.underline, ), ), - ], - ), - ); - }, - ); + ), + ], + ), + ); + }); } } diff --git a/app/lib/pages/home/firmware_mixin.dart b/app/lib/pages/home/firmware_mixin.dart index c4f03eec9..cb8adc13f 100644 --- a/app/lib/pages/home/firmware_mixin.dart +++ b/app/lib/pages/home/firmware_mixin.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'package:flutter/widgets.dart'; import 'package:friend_private/backend/http/shared.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/env/env.dart'; import 'package:friend_private/services/services.dart'; import 'package:nordic_dfu/nordic_dfu.dart'; @@ -23,7 +23,7 @@ mixin FirmwareMixin on State { int installProgress = 1; // TODO: thinh, use connection directly - Future _bleDisconnectDevice(BTDeviceStruct btDevice) async { + Future _bleDisconnectDevice(BtDevice btDevice) async { var connection = await ServiceManager.instance().device.ensureConnection(btDevice.id); if (connection == null) { return Future.value(null); @@ -31,7 +31,7 @@ mixin FirmwareMixin on State { return await connection.disconnect(); } - Future startDfu(BTDeviceStruct btDevice, {bool fileInAssets = false}) async { + Future startDfu(BtDevice btDevice, {bool fileInAssets = false}) async { setState(() { isInstalling = true; }); diff --git a/app/lib/pages/home/firmware_update.dart b/app/lib/pages/home/firmware_update.dart index 2fd0807af..68f3a7b83 100644 --- a/app/lib/pages/home/firmware_update.dart +++ b/app/lib/pages/home/firmware_update.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/pages/home/firmware_mixin.dart'; import 'package:friend_private/pages/home/page.dart'; import 'package:friend_private/utils/other/temp.dart'; @@ -7,10 +7,9 @@ import 'package:gradient_borders/gradient_borders.dart'; import 'package:intercom_flutter/intercom_flutter.dart'; class FirmwareUpdate extends StatefulWidget { - final DeviceInfo deviceInfo; - final BTDeviceStruct? device; + final BtDevice? device; - const FirmwareUpdate({super.key, required this.deviceInfo, this.device}); + const FirmwareUpdate({super.key, this.device}); @override State createState() => _FirmwareUpdateState(); @@ -28,8 +27,8 @@ class _FirmwareUpdateState extends State with FirmwareMixin { isLoading = true; }); await getLatestVersion(deviceName: widget.device!.name); - var (a, b) = await shouldUpdateFirmware( - currentFirmware: widget.deviceInfo.firmwareRevision, deviceName: widget.device!.name); + var (a, b) = + await shouldUpdateFirmware(currentFirmware: widget.device!.firmwareRevision, deviceName: widget.device!.name); if (mounted) { setState(() { shouldUpdate = b; @@ -131,7 +130,7 @@ class _FirmwareUpdateState extends State with FirmwareMixin { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'Current Version: ${widget.deviceInfo.firmwareRevision}', + 'Current Version: ${widget.device!.firmwareRevision}', style: const TextStyle(color: Colors.white, fontSize: 16), ), const SizedBox(height: 8), diff --git a/app/lib/pages/home/page.dart b/app/lib/pages/home/page.dart index 9f56a2d23..ced780a1c 100644 --- a/app/lib/pages/home/page.dart +++ b/app/lib/pages/home/page.dart @@ -375,10 +375,7 @@ class _HomePageState extends State with WidgetsBindingObserver, Ticker : () { routeToPage( context, - ConnectedDevice( - device: deviceProvider.connectedDevice!, - batteryLevel: deviceProvider.batteryLevel, - ), + const ConnectedDevice(), ); MixpanelManager().batteryIndicatorClicked(); }, @@ -426,16 +423,12 @@ class _HomePageState extends State with WidgetsBindingObserver, Ticker } else { return GestureDetector( onTap: () async { - if (SharedPreferencesUtil().btDeviceStruct.id.isEmpty) { + if (SharedPreferencesUtil().btDevice.id.isEmpty) { routeToPage(context, const ConnectDevicePage()); MixpanelManager().connectFriendClicked(); } else { - await routeToPage( - context, - ConnectedDevice( - device: deviceProvider.connectedDevice, batteryLevel: deviceProvider.batteryLevel)); + await routeToPage(context, const ConnectedDevice()); } - // setState(() {}); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), diff --git a/app/lib/pages/memories/widgets/capture.dart b/app/lib/pages/memories/widgets/capture.dart index 15a121dd5..a54de1a66 100644 --- a/app/lib/pages/memories/widgets/capture.dart +++ b/app/lib/pages/memories/widgets/capture.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; import 'package:friend_private/backend/schema/geolocation.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/pages/capture/widgets/widgets.dart'; import 'package:friend_private/providers/capture_provider.dart'; import 'package:friend_private/providers/connectivity_provider.dart'; diff --git a/app/lib/pages/memories/widgets/processing_capture.dart b/app/lib/pages/memories/widgets/processing_capture.dart index 9d0617700..b615cf740 100644 --- a/app/lib/pages/memories/widgets/processing_capture.dart +++ b/app/lib/pages/memories/widgets/processing_capture.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; import 'package:friend_private/backend/schema/memory.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/pages/capture/widgets/widgets.dart'; import 'package:friend_private/pages/memories/widgets/capture.dart'; import 'package:friend_private/pages/memory_capturing/page.dart'; @@ -292,7 +292,7 @@ class _RecordingStatusIndicatorState extends State wit } getPhoneMicRecordingButton(BuildContext context, toggleRecording, RecordingState state) { - if (SharedPreferencesUtil().btDeviceStruct.id.isNotEmpty) return const SizedBox.shrink(); + if (SharedPreferencesUtil().btDevice.id.isNotEmpty) return const SizedBox.shrink(); return MaterialButton( onPressed: state == RecordingState.initialising ? null : toggleRecording, child: Row( diff --git a/app/lib/pages/onboarding/find_device/found_devices.dart b/app/lib/pages/onboarding/find_device/found_devices.dart index 0946dfcce..5d40d7f99 100644 --- a/app/lib/pages/onboarding/find_device/found_devices.dart +++ b/app/lib/pages/onboarding/find_device/found_devices.dart @@ -1,7 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/providers/onboarding_provider.dart'; import 'package:friend_private/widgets/dialog.dart'; import 'package:gradient_borders/gradient_borders.dart'; @@ -94,7 +94,7 @@ class _FoundDevicesState extends State { if (!provider.isConnected) ..._devicesList(provider), if (provider.isConnected) Text( - '${provider.deviceName} (${BTDeviceStruct.shortId(provider.deviceId)})', + '${provider.deviceName} (${BtDevice.shortId(provider.deviceId)})', textAlign: TextAlign.center, style: const TextStyle( fontWeight: FontWeight.w500, diff --git a/app/lib/pages/onboarding/wrapper.dart b/app/lib/pages/onboarding/wrapper.dart index 7763cba84..172c1b2f1 100644 --- a/app/lib/pages/onboarding/wrapper.dart +++ b/app/lib/pages/onboarding/wrapper.dart @@ -4,7 +4,7 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:friend_private/backend/auth.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/pages/home/page.dart'; import 'package:friend_private/pages/onboarding/auth.dart'; import 'package:friend_private/pages/onboarding/find_device/page.dart'; diff --git a/app/lib/pages/settings/device_settings.dart b/app/lib/pages/settings/device_settings.dart index d5d416333..d0642da4d 100644 --- a/app/lib/pages/settings/device_settings.dart +++ b/app/lib/pages/settings/device_settings.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/pages/home/firmware_update.dart'; import 'package:friend_private/pages/sdcard/page.dart'; import 'package:friend_private/providers/device_provider.dart'; @@ -24,7 +24,7 @@ class DeviceSettings extends StatefulWidget { class _DeviceSettingsState extends State { // TODO: thinh, use connection directly - Future _bleDisconnectDevice(BTDeviceStruct btDevice) async { + Future _bleDisconnectDevice(BtDevice btDevice) async { var connection = await ServiceManager.instance().device.ensureConnection(btDevice.id); if (connection == null) { return Future.value(null); @@ -35,7 +35,9 @@ class _DeviceSettingsState extends State { @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) { - context.read().getDeviceInfo(); + if (context.read().connectedDevice!.modelNumber == 'Unknown') { + context.read().getDeviceInfo(); + } }); super.initState(); } @@ -56,7 +58,7 @@ class _DeviceSettingsState extends State { Stack( children: [ Column( - children: deviceSettingsWidgets(provider.deviceInfo, provider.connectedDevice, context), + children: deviceSettingsWidgets(provider.pairedDevice, context), ), if (!provider.isConnected) ClipRRect( @@ -127,7 +129,8 @@ class _DeviceSettingsState extends State { ), child: TextButton( onPressed: () async { - await SharedPreferencesUtil().btDeviceStructSet(BTDeviceStruct(id: '', name: '')); + await SharedPreferencesUtil() + .btDeviceSet(BtDevice(id: '', name: '', type: DeviceType.friend, rssi: 0)); SharedPreferencesUtil().deviceName = ''; if (provider.connectedDevice != null) { await _bleDisconnectDevice(provider.connectedDevice!); @@ -157,7 +160,7 @@ class _DeviceSettingsState extends State { } } -List deviceSettingsWidgets(DeviceInfo? deviceInfo, BTDeviceStruct? device, BuildContext context) { +List deviceSettingsWidgets(BtDevice? device, BuildContext context) { return [ ListTile( title: const Text('Device Name'), @@ -169,11 +172,11 @@ List deviceSettingsWidgets(DeviceInfo? deviceInfo, BTDeviceStruct? devic ), GestureDetector( onTap: () { - routeToPage(context, FirmwareUpdate(deviceInfo: deviceInfo!, device: device)); + routeToPage(context, FirmwareUpdate(device: device)); }, child: ListTile( title: const Text('Update Latest Version'), - subtitle: Text('Current: ${deviceInfo?.firmwareRevision ?? '1.0.2'}'), + subtitle: Text('Current: ${device?.firmwareRevision ?? '1.0.2'}'), trailing: const Icon( Icons.arrow_forward_ios, size: 16, @@ -212,15 +215,15 @@ List deviceSettingsWidgets(DeviceInfo? deviceInfo, BTDeviceStruct? devic ListTile( title: const Text('Hardware Revision'), - subtitle: Text(deviceInfo?.hardwareRevision ?? 'XIAO'), + subtitle: Text(device?.hardwareRevision ?? 'XIAO'), ), ListTile( title: const Text('Model Number'), - subtitle: Text(deviceInfo?.modelNumber ?? 'Friend'), + subtitle: Text(device?.modelNumber ?? 'Friend'), ), ListTile( title: const Text('Manufacturer Name'), - subtitle: Text(deviceInfo?.manufacturerName ?? 'Based Hardware'), + subtitle: Text(device?.manufacturerName ?? 'Based Hardware'), ), ]; } diff --git a/app/lib/pages/speech_profile/page.dart b/app/lib/pages/speech_profile/page.dart index abc45626d..ecfd01941 100644 --- a/app/lib/pages/speech_profile/page.dart +++ b/app/lib/pages/speech_profile/page.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/pages/home/page.dart'; import 'package:friend_private/pages/speech_profile/user_speech_samples.dart'; import 'package:friend_private/providers/capture_provider.dart'; diff --git a/app/lib/providers/capture_provider.dart b/app/lib/providers/capture_provider.dart index 7c712260c..d7d11df9a 100644 --- a/app/lib/providers/capture_provider.dart +++ b/app/lib/providers/capture_provider.dart @@ -8,11 +8,11 @@ import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; import 'package:friend_private/backend/http/api/memories.dart'; import 'package:friend_private/backend/http/api/processing_memories.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; import 'package:friend_private/backend/schema/geolocation.dart'; import 'package:friend_private/backend/schema/memory.dart'; import 'package:friend_private/backend/schema/message.dart'; import 'package:friend_private/backend/schema/message_event.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/backend/schema/structured.dart'; import 'package:friend_private/backend/schema/transcript_segment.dart'; import 'package:friend_private/providers/memory_provider.dart'; @@ -46,7 +46,7 @@ class CaptureProvider extends ChangeNotifier notifyListeners(); } - BTDeviceStruct? _recordingDevice; + BtDevice? _recordingDevice; List segments = []; Geolocation? geolocation; @@ -126,7 +126,7 @@ class CaptureProvider extends ChangeNotifier notifyListeners(); } - void _updateRecordingDevice(BTDeviceStruct? device) { + void _updateRecordingDevice(BtDevice? device) { debugPrint('connected device changed from ${_recordingDevice?.id} to ${device?.id}'); _recordingDevice = device; notifyListeners(); @@ -194,7 +194,7 @@ class CaptureProvider extends ChangeNotifier // Notify capturing if (capturingProcessingMemory != null) { - memoryProvider?.onNewCapturingMemory(capturingProcessingMemory!); + // memoryProvider?.onNewCapturingMemory(capturingProcessingMemory!); } // Update processing memory @@ -753,7 +753,7 @@ class CaptureProvider extends ChangeNotifier } Future streamDeviceRecording({ - BTDeviceStruct? device, + BtDevice? device, bool restartBytesProcessing = true, }) async { debugPrint("streamDeviceRecording $device $restartBytesProcessing"); diff --git a/app/lib/providers/device_provider.dart b/app/lib/providers/device_provider.dart index ee4b1a82a..517dc8776 100644 --- a/app/lib/providers/device_provider.dart +++ b/app/lib/providers/device_provider.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/providers/capture_provider.dart'; import 'package:friend_private/services/devices.dart'; import 'package:friend_private/services/notifications.dart'; @@ -15,8 +15,8 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption bool isConnecting = false; bool isConnected = false; - BTDeviceStruct? connectedDevice; - DeviceInfo? deviceInfo; + BtDevice? connectedDevice; + BtDevice? pairedDevice; StreamSubscription>? _bleBatteryLevelListener; int batteryLevel = -1; Timer? _reconnectionTimer; @@ -33,23 +33,27 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption notifyListeners(); } - void setConnectedDevice(BTDeviceStruct? device) { + void setConnectedDevice(BtDevice? device) async { connectedDevice = device; + pairedDevice = device; + await getDeviceInfo(); print('setConnectedDevice: $device'); notifyListeners(); } - Future getDeviceInfo() async { - if (connectedDevice == null) { - return DeviceInfo.getDeviceInfo(null, null); - } - var connection = await ServiceManager.instance().device.ensureConnection(connectedDevice!.id); - if (connection == null) { - return DeviceInfo.getDeviceInfo(null, null); + Future getDeviceInfo() async { + if (connectedDevice != null) { + var connection = await ServiceManager.instance().device.ensureConnection(connectedDevice!.id); + pairedDevice = await connectedDevice!.getDeviceInfo(connection); + SharedPreferencesUtil().btDevice = pairedDevice!; + } else { + if (SharedPreferencesUtil().btDevice.id.isEmpty) { + pairedDevice = BtDevice.empty(); + } else { + pairedDevice = SharedPreferencesUtil().btDevice; + } } - deviceInfo = await DeviceInfo.getDeviceInfo(connection.device, connection); notifyListeners(); - return deviceInfo!; } // TODO: thinh, use connection directly @@ -66,8 +70,8 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption } } - Future _getConnectedDevice() async { - var deviceId = SharedPreferencesUtil().btDeviceStruct.id; + Future _getConnectedDevice() async { + var deviceId = SharedPreferencesUtil().btDevice.id; if (deviceId.isEmpty) { return null; } @@ -96,7 +100,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption print('seconds: $connectionCheckSeconds'); print('triggered timer at ${DateTime.now()}'); - if (SharedPreferencesUtil().btDeviceStruct.id.isEmpty) { + if (SharedPreferencesUtil().btDevice.id.isEmpty) { return; } print("isConnected: $isConnected, isConnecting: $isConnecting, connectedDevice: $connectedDevice"); @@ -111,7 +115,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption }); } - Future _scanAndConnectDevice({bool autoConnect = true, bool timeout = false}) async { + Future _scanAndConnectDevice({bool autoConnect = true, bool timeout = false}) async { var device = await _getConnectedDevice(); if (device != null) { return device; @@ -136,7 +140,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption if (isConnected) { if (connectedDevice == null) { connectedDevice = await _getConnectedDevice(); - SharedPreferencesUtil().btDeviceStruct = connectedDevice!; + // SharedPreferencesUtil().btDevice = connectedDevice!; SharedPreferencesUtil().deviceName = connectedDevice!.name; MixpanelManager().deviceConnected(); } @@ -150,7 +154,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption var cDevice = await _getConnectedDevice(); if (cDevice != null) { setConnectedDevice(cDevice); - SharedPreferencesUtil().btDeviceStruct = cDevice; + // SharedPreferencesUtil().btDevice = cDevice; SharedPreferencesUtil().deviceName = cDevice.name; MixpanelManager().deviceConnected(); setIsConnected(true); @@ -215,7 +219,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption }); } - void onDeviceReconnected(BTDeviceStruct device) async { + void onDeviceReconnected(BtDevice device) async { debugPrint('_onConnected inside: $connectedDevice'); _disconnectNotificationTimer?.cancel(); NotificationService.instance.clearNotification(1); @@ -227,7 +231,8 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption // The device is still disconnected for some reason if (connectedDevice != null) { MixpanelManager().deviceConnected(); - SharedPreferencesUtil().btDeviceStruct = connectedDevice!; + await getDeviceInfo(); + // SharedPreferencesUtil().btDevice = connectedDevice!; SharedPreferencesUtil().deviceName = connectedDevice!.name; } notifyListeners(); @@ -254,7 +259,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption } @override - void onDevices(List devices) async { + void onDevices(List devices) async { if (connectedDevice != null) { return; } @@ -264,7 +269,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption } // Connect to first founded device - var force = devices.first.id == SharedPreferencesUtil().btDeviceStruct.id; + var force = devices.first.id == SharedPreferencesUtil().btDevice.id; var connection = await ServiceManager.instance().device.ensureConnection(devices.first.id, force: force); if (connection == null) { return; diff --git a/app/lib/providers/onboarding_provider.dart b/app/lib/providers/onboarding_provider.dart index 85af36013..7013c4b72 100644 --- a/app/lib/providers/onboarding_provider.dart +++ b/app/lib/providers/onboarding_provider.dart @@ -8,7 +8,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/providers/base_provider.dart'; import 'package:friend_private/providers/device_provider.dart'; import 'package:friend_private/services/devices.dart'; @@ -27,11 +27,11 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen String deviceId = ''; String? connectingToDeviceId; Timer? connectionStateTimer; - List deviceList = []; + List deviceList = []; late Timer _didNotMakeItTimer; Timer? _findDevicesTimer; bool enableInstructions = false; - Map foundDevicesMap = {}; + Map foundDevicesMap = {}; //----------------- Onboarding Permissions ----------------- bool hasBluetoothPermission = false; @@ -157,7 +157,7 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen // Method to handle taps on devices Future handleTap({ - required BTDeviceStruct device, + required BtDevice device, required bool isFromOnboarding, VoidCallback? goNext, }) async { @@ -170,15 +170,16 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen isClicked = true; // Prevent further clicks connectingToDeviceId = device.id; // Mark this device as being connected to notifyListeners(); - await ServiceManager.instance().device.ensureConnection(device.id, force: true); + var c = await ServiceManager.instance().device.ensureConnection(device.id, force: true); print('Connected to device: ${device.name}'); deviceId = device.id; - await SharedPreferencesUtil().btDeviceStructSet(device); + // device = await device.getDeviceInfo(c); + await SharedPreferencesUtil().btDeviceSet(device); deviceName = device.name; var cDevice = await _getConnectedDevice(deviceId); if (cDevice != null) { deviceProvider!.setConnectedDevice(cDevice); - SharedPreferencesUtil().btDeviceStruct = cDevice; + // SharedPreferencesUtil().btDevice = cDevice; SharedPreferencesUtil().deviceName = cDevice.name; deviceProvider!.setIsConnected(true); } @@ -195,7 +196,7 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen notifyListeners(); stopFindDeviceTimer(); await Future.delayed(const Duration(seconds: 2)); - SharedPreferencesUtil().btDeviceStruct = connectedDevice!; + SharedPreferencesUtil().btDevice = connectedDevice!; SharedPreferencesUtil().deviceName = connectedDevice.name; foundDevicesMap.clear(); deviceList.clear(); @@ -228,7 +229,7 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen Future scanDevices({ required VoidCallback onShowDialog, }) async { - if (SharedPreferencesUtil().btDeviceStruct.id.isEmpty) { + if (SharedPreferencesUtil().btDevice.id.isEmpty) { // it means the device has been unpaired deviceAlreadyUnpaired(); } @@ -253,7 +254,7 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen } // TODO: thinh, use connection directly - Future _getConnectedDevice(String deviceId) async { + Future _getConnectedDevice(String deviceId) async { if (deviceId.isEmpty) { return null; } @@ -285,11 +286,11 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen } @override - void onDevices(List devices) { - List foundDevices = devices; + void onDevices(List devices) { + List foundDevices = devices; // Update foundDevicesMap with new devices and remove the ones not found anymore - Map updatedDevicesMap = {}; + Map updatedDevicesMap = {}; for (final device in foundDevices) { // If it's a new device, add it to the map. If it already exists, this will just update the entry. updatedDevicesMap[device.id] = device; @@ -300,7 +301,7 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen // Merge the new devices into the current map to maintain order foundDevicesMap.addAll(updatedDevicesMap); // Convert the values of the map back to a list - List orderedDevices = foundDevicesMap.values.toList(); + List orderedDevices = foundDevicesMap.values.toList(); if (orderedDevices.isNotEmpty) { deviceList = orderedDevices; notifyListeners(); diff --git a/app/lib/providers/speech_profile_provider.dart b/app/lib/providers/speech_profile_provider.dart index 199d8af1e..554ddbff8 100644 --- a/app/lib/providers/speech_profile_provider.dart +++ b/app/lib/providers/speech_profile_provider.dart @@ -7,9 +7,9 @@ import 'package:friend_private/backend/http/api/memories.dart'; import 'package:friend_private/backend/http/api/speech_profile.dart'; import 'package:friend_private/backend/http/api/users.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; import 'package:friend_private/backend/schema/memory.dart'; import 'package:friend_private/backend/schema/message_event.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/backend/schema/transcript_segment.dart'; import 'package:friend_private/providers/device_provider.dart'; import 'package:friend_private/services/devices.dart'; @@ -23,7 +23,7 @@ class SpeechProfileProvider extends ChangeNotifier DeviceProvider? deviceProvider; bool? permissionEnabled; bool loading = false; - BTDeviceStruct? device; + BtDevice? device; final targetWordsCount = 70; final maxDuration = 90; @@ -324,7 +324,7 @@ class SpeechProfileProvider extends ChangeNotifier } @override - void onDevices(List devices) {} + void onDevices(List devices) {} @override void onStatusChanged(DeviceServiceStatus status) {} diff --git a/app/lib/services/devices.dart b/app/lib/services/devices.dart index 9bc5a4811..8739e2774 100644 --- a/app/lib/services/devices.dart +++ b/app/lib/services/devices.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/services/devices/device_connection.dart'; import 'package:friend_private/services/devices/models.dart'; @@ -31,21 +31,21 @@ enum DeviceConnectionState { } abstract class IDeviceServiceSubsciption { - void onDevices(List devices); + void onDevices(List devices); void onStatusChanged(DeviceServiceStatus status); void onDeviceConnectionStateChanged(String deviceId, DeviceConnectionState state); } class DeviceService implements IDeviceService { DeviceServiceStatus _status = DeviceServiceStatus.init; - List _devices = []; + List _devices = []; List _bleDevices = []; final Map _subscriptions = {}; DeviceConnection? _connection; - List get devices => _devices; + List get devices => _devices; DeviceServiceStatus get status => _status; @@ -92,27 +92,28 @@ class DeviceService implements IDeviceService { Future _onBleDiscovered(List results, String? desirableDeviceId) async { _bleDevices = results.where((r) => r.device.platformName.isNotEmpty).toList(); _bleDevices.sort((a, b) => b.rssi.compareTo(a.rssi)); - // Set devices - _devices = _bleDevices.map((deviceResult) { - DeviceType? deviceType; - if (deviceResult.advertisementData.serviceUuids.contains(Guid(friendServiceUuid))) { - deviceType = DeviceType.friend; - } else if (deviceResult.advertisementData.serviceUuids.contains(Guid(frameServiceUuid))) { - deviceType = DeviceType.frame; - } - if (deviceType != null) { - deviceTypeMap[deviceResult.device.remoteId.toString()] = deviceType; - } else if (deviceTypeMap.containsKey(deviceResult.device.remoteId.toString())) { - deviceType = deviceTypeMap[deviceResult.device.remoteId.toString()]; - } - return BTDeviceStruct( - name: deviceResult.device.platformName, - id: deviceResult.device.remoteId.str, - rssi: deviceResult.rssi, - type: deviceType, - ); - }).toList(); + + // _devices = _bleDevices.map((deviceResult) { + // DeviceType? deviceType; + // if (deviceResult.advertisementData.serviceUuids.contains(Guid(friendServiceUuid))) { + // deviceType = DeviceType.friend; + // } else if (deviceResult.advertisementData.serviceUuids.contains(Guid(frameServiceUuid))) { + // deviceType = DeviceType.frame; + // } + // if (deviceType != null) { + // deviceTypeMap[deviceResult.device.remoteId.toString()] = deviceType; + // } else if (deviceTypeMap.containsKey(deviceResult.device.remoteId.toString())) { + // deviceType = deviceTypeMap[deviceResult.device.remoteId.toString()]; + // } + // return BtDevice( + // name: deviceResult.device.platformName, + // id: deviceResult.device.remoteId.str, + // rssi: deviceResult.rssi, + // type: deviceType, + // ); + // }).toList(); + _devices = _bleDevices.map((e) => BtDevice.fromScanResult(e)).toList(); onDevices(devices); // Check desirable device @@ -194,7 +195,7 @@ class DeviceService implements IDeviceService { } } - void onDevices(List devices) { + void onDevices(List devices) { for (var s in _subscriptions.values) { s.onDevices(devices); } diff --git a/app/lib/services/devices/device_connection.dart b/app/lib/services/devices/device_connection.dart index dd9a460e1..0b0d4c963 100644 --- a/app/lib/services/devices/device_connection.dart +++ b/app/lib/services/devices/device_connection.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/services/devices.dart'; import 'package:friend_private/services/devices/frame_connection.dart'; import 'package:friend_private/services/devices/friend_connection.dart'; @@ -12,7 +12,7 @@ import 'package:friend_private/services/notifications.dart'; class DeviceConnectionFactory { static DeviceConnection? create( - BTDeviceStruct device, + BtDevice device, BluetoothDevice bleDevice, ) { if (device.type == null) { @@ -32,7 +32,7 @@ class DeviceConnectionFactory { } abstract class DeviceConnection { - BTDeviceStruct device; + BtDevice device; BluetoothDevice bleDevice; DateTime? _pongAt; diff --git a/app/lib/services/devices/frame_connection.dart b/app/lib/services/devices/frame_connection.dart index 2febd698b..7e3b4eae9 100644 --- a/app/lib/services/devices/frame_connection.dart +++ b/app/lib/services/devices/frame_connection.dart @@ -6,7 +6,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:frame_sdk/bluetooth.dart'; import 'package:frame_sdk/frame_sdk.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/services/devices.dart'; import 'package:friend_private/services/devices/device_connection.dart'; diff --git a/app/lib/services/devices/friend_connection.dart b/app/lib/services/devices/friend_connection.dart index b517d55a7..7cbc2dadc 100644 --- a/app/lib/services/devices/friend_connection.dart +++ b/app/lib/services/devices/friend_connection.dart @@ -5,7 +5,7 @@ import 'dart:math'; import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/services/devices.dart'; import 'package:friend_private/services/devices/device_connection.dart'; import 'package:friend_private/services/devices/errors.dart'; diff --git a/app/lib/services/sockets.dart b/app/lib/services/sockets.dart index 92ac5fb17..860059a97 100644 --- a/app/lib/services/sockets.dart +++ b/app/lib/services/sockets.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/services/sockets/transcription_connection.dart'; abstract class ISocketService { diff --git a/app/lib/services/sockets/transcription_connection.dart b/app/lib/services/sockets/transcription_connection.dart index 6601fe2bb..8f5d08a46 100644 --- a/app/lib/services/sockets/transcription_connection.dart +++ b/app/lib/services/sockets/transcription_connection.dart @@ -5,8 +5,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; import 'package:friend_private/backend/schema/message_event.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/backend/schema/transcript_segment.dart'; import 'package:friend_private/env/env.dart'; import 'package:friend_private/services/notifications.dart'; diff --git a/app/lib/utils/analytics/mixpanel.dart b/app/lib/utils/analytics/mixpanel.dart index 541be07a2..d2cb0bd4d 100644 --- a/app/lib/utils/analytics/mixpanel.dart +++ b/app/lib/utils/analytics/mixpanel.dart @@ -141,7 +141,9 @@ class MixpanelManager { void bottomNavigationTabClicked(String tab) => track('Bottom Navigation Tab Clicked', properties: {'tab': tab}); void deviceConnected() => track('Device Connected', properties: { - ..._preferences.btDeviceStruct.toJson(fwverAsString: true), + //TODO; Mohsin + // ..._preferences.btDeviceStruct.toJson(fwverAsString: true), + ..._preferences.btDevice.toJson(), }); void deviceDisconnected() => track('Device Disconnected'); diff --git a/app/lib/utils/audio/wav_bytes.dart b/app/lib/utils/audio/wav_bytes.dart index 8ae443983..a940deedb 100644 --- a/app/lib/utils/audio/wav_bytes.dart +++ b/app/lib/utils/audio/wav_bytes.dart @@ -4,7 +4,7 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; +import 'package:friend_private/backend/schema/bt_device/bt_device.dart'; import 'package:friend_private/utils/logger.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; import 'package:intl/intl.dart';