From 878c7f8da717462a189dc34bdaa50bdebc93627f Mon Sep 17 00:00:00 2001 From: Rexios80 Date: Sun, 2 May 2021 21:35:04 -0400 Subject: [PATCH] Feature/cross communication (#57) * Improved message broadcast code * Initial work * Fixed heart beat animation and sound * Fixed animation and sound weirdness when changing settings * Fixed even more dumb things with the heart beat sound * Did some view refactoring to make it less dumb * Fixed text color property not working for heart rate widget * Start/stop the server on lifecycle events * Don't allow adding widget with empty data source * Added setting for client name * Added server ip editor to settings page * Actually save client name * Added code to connect to servers * Improved socket connection code * Close server connections on server stop * Different way of telling if server connections close * Sending data to other servers works * Fixed widget selector not showing data --- lib/controllers/data_widget_controller.dart | 3 +- lib/controllers/end_drawer_controller.dart | 5 +- .../heart_rate_widget_controller.dart | 3 +- lib/controllers/socket_server_controller.dart | 39 +-- .../widget_selector_controller.dart | 6 + lib/hive/data_widget_properties.dart | 10 + lib/hive/data_widget_properties.g.dart | 9 +- lib/hive/hive_utils.dart | 7 +- lib/hive/settings.dart | 19 ++ lib/hive/settings.g.dart | 12 +- lib/main.dart | 2 + lib/model/data_source.dart | 8 +- lib/model/message.dart | 8 +- lib/services/discord_rich_presence.dart | 0 lib/services/socket_server.dart | 58 +++- lib/view/screens/data_view.dart | 42 ++- lib/view/screens/settings_view.dart | 133 ++++++++- lib/view/widgets/data/data_widget.dart | 51 ++-- lib/view/widgets/data/heart_rate_widget.dart | 101 ++++--- lib/view/widgets/drawers/end_drawer.dart | 3 +- lib/view/widgets/heart_rate_range_editor.dart | 9 +- lib/view/widgets/overlay.dart | 7 +- lib/view/widgets/widget_editor.dart | 269 +++++++++--------- .../widgets/widget_editor_text_field.dart | 5 +- lib/view/widgets/widget_selector.dart | 101 +++++-- pubspec.lock | 9 +- pubspec.yaml | 2 + 27 files changed, 616 insertions(+), 305 deletions(-) create mode 100644 lib/controllers/widget_selector_controller.dart delete mode 100644 lib/services/discord_rich_presence.dart diff --git a/lib/controllers/data_widget_controller.dart b/lib/controllers/data_widget_controller.dart index a31c13d..9ad28af 100644 --- a/lib/controllers/data_widget_controller.dart +++ b/lib/controllers/data_widget_controller.dart @@ -1,9 +1,10 @@ import 'package:get/get.dart'; import 'package:hds_overlay/hive/data_type.dart'; import 'package:hds_overlay/hive/data_widget_properties.dart'; +import 'package:tuple/tuple.dart'; class DataWidgetController extends GetxController { - final RxMap> propertiesMap; + final RxMap, Rx> propertiesMap; DataWidgetController(this.propertiesMap); } diff --git a/lib/controllers/end_drawer_controller.dart b/lib/controllers/end_drawer_controller.dart index 95bceb4..980dc89 100644 --- a/lib/controllers/end_drawer_controller.dart +++ b/lib/controllers/end_drawer_controller.dart @@ -1,7 +1,10 @@ import 'package:get/get.dart'; import 'package:hds_overlay/hive/data_type.dart'; +import 'package:hds_overlay/model/data_source.dart'; +import 'package:tuple/tuple.dart'; class EndDrawerController extends GetxController { RxBool open = false.obs; - Rx selectedDataType = DataType.unknown.obs; + Rx> selectedDataTypeSource = + Tuple2(DataType.unknown, DataSource.watch).obs; } diff --git a/lib/controllers/heart_rate_widget_controller.dart b/lib/controllers/heart_rate_widget_controller.dart index 4701e88..a9d8b64 100644 --- a/lib/controllers/heart_rate_widget_controller.dart +++ b/lib/controllers/heart_rate_widget_controller.dart @@ -1,7 +1,6 @@ -import 'package:get/get.dart'; - class HeartRateWidgetController { bool animating = false; bool sounding = false; int currentHeartRate = 0; + bool visible = true; } diff --git a/lib/controllers/socket_server_controller.dart b/lib/controllers/socket_server_controller.dart index 580c689..be47d71 100644 --- a/lib/controllers/socket_server_controller.dart +++ b/lib/controllers/socket_server_controller.dart @@ -1,34 +1,20 @@ import 'package:get/get.dart'; import 'package:hds_overlay/controllers/settings_controller.dart'; import 'package:hds_overlay/hive/data_type.dart'; -import 'package:hds_overlay/hive/settings.dart'; import 'package:hds_overlay/model/log_message.dart'; import 'package:hds_overlay/model/message.dart'; import 'package:hds_overlay/services/socket_server.dart'; +import 'package:tuple/tuple.dart'; class SocketServerController extends GetxService { final SettingsController settingsController = Get.find(); final server = SocketServer(); - final messages = Map().obs; + final messages = Map, DataMessage>().obs; final logs = [].obs; - late int port; - SocketServerController() { - this.port = settingsController.settings.value.port; - server.start(port); - - // Restart the server if the port changes - debounce(settingsController.settings, (Settings settings) async { - if (port != settings.port) { - port = settings.port; - await server.stop(); - server.start(port); - } - }); - server.messageStream.listen((message) { - final log = '(${message.source.name}) ${message.name}: ${message.value}'; + final log = '(${message.source}) ${message.name}: ${message.value}'; if (message is UnknownDataMessage) { // Don't do anything with these @@ -37,7 +23,7 @@ class SocketServerController extends GetxService { } message as DataMessage; - messages[message.dataType] = message; + messages[Tuple2(message.dataType, message.source)] = message; logs.add(LogMessage(LogLevel.data, log)); }); @@ -46,4 +32,21 @@ class SocketServerController extends GetxService { logs.add(log); }); } + + void stopServer() { + server.stop(); + } + + void startServer() { + // If the log is modified here the view will be in a bad state + Future.delayed( + Duration(milliseconds: 500), + () => logs.add(LogMessage(LogLevel.info, + 'Client name: ${settingsController.settings.value.clientName}'))); + server.start( + settingsController.settings.value.port, + settingsController.settings.value.clientName, + settingsController.settings.value.serverIps, + ); + } } diff --git a/lib/controllers/widget_selector_controller.dart b/lib/controllers/widget_selector_controller.dart new file mode 100644 index 0000000..a99ce42 --- /dev/null +++ b/lib/controllers/widget_selector_controller.dart @@ -0,0 +1,6 @@ +import 'package:get/get.dart'; +import 'package:hds_overlay/model/data_source.dart'; + +class WidgetSelectorController extends GetxController { + RxString dataSource = DataSource.watch.obs; +} diff --git a/lib/hive/data_widget_properties.dart b/lib/hive/data_widget_properties.dart index 2334b28..0251ad7 100644 --- a/lib/hive/data_widget_properties.dart +++ b/lib/hive/data_widget_properties.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:hds_overlay/hive/tuple2_double.dart'; +import 'package:hds_overlay/model/data_source.dart'; import 'package:hive/hive.dart'; import 'data_type.dart'; @@ -81,4 +82,13 @@ class DataWidgetProperties extends HiveObject { set textInsideImage(bool value) { _textInsideImage = value; } + + @HiveField(22) + String? _dataSource; + + String get dataSource => _dataSource ?? DataSource.watch; + + set dataSource(String value) { + _dataSource = value; + } } diff --git a/lib/hive/data_widget_properties.g.dart b/lib/hive/data_widget_properties.g.dart index 642d989..0bfeeeb 100644 --- a/lib/hive/data_widget_properties.g.dart +++ b/lib/hive/data_widget_properties.g.dart @@ -38,13 +38,14 @@ class DataWidgetPropertiesAdapter extends TypeAdapter { ..animated = fields[18] as bool ..heartRateRanges = (fields[19] as Map).cast() ..heartBeatSound = fields[20] as Uint8List? - .._textInsideImage = fields[21] as bool?; + .._textInsideImage = fields[21] as bool? + .._dataSource = fields[22] as String?; } @override void write(BinaryWriter writer, DataWidgetProperties obj) { writer - ..writeByte(22) + ..writeByte(23) ..writeByte(0) ..write(obj.dataType) ..writeByte(1) @@ -88,7 +89,9 @@ class DataWidgetPropertiesAdapter extends TypeAdapter { ..writeByte(20) ..write(obj.heartBeatSound) ..writeByte(21) - ..write(obj._textInsideImage); + ..write(obj._textInsideImage) + ..writeByte(22) + ..write(obj._dataSource); } @override diff --git a/lib/hive/hive_utils.dart b/lib/hive/hive_utils.dart index 261a741..40b4adc 100644 --- a/lib/hive/hive_utils.dart +++ b/lib/hive/hive_utils.dart @@ -8,6 +8,7 @@ import 'package:hds_overlay/hive/settings.dart'; import 'package:hds_overlay/hive/tuple2_double.dart'; import 'package:hive/hive.dart'; import 'package:hive_flutter/hive_flutter.dart'; +import 'package:tuple/tuple.dart'; import 'data_type.dart'; @@ -66,10 +67,10 @@ class HiveUtils { return Future.value(); } - static Map> createDwpMap( + static Map, Rx> createDwpMap( Box dwpBox) { - final map = Map>(); - dwpBox.values.forEach((e) => map[e.dataType] = e.obs); + final map = Map, Rx>(); + dwpBox.values.forEach((e) => map[Tuple2(e.dataType, e.dataSource)] = e.obs); return map; } } diff --git a/lib/hive/settings.dart b/lib/hive/settings.dart index 86fcfe3..527f1a9 100644 --- a/lib/hive/settings.dart +++ b/lib/hive/settings.dart @@ -1,5 +1,6 @@ import 'package:hds_overlay/utils/colors.dart'; import 'package:hive/hive.dart'; +import 'package:uuid/uuid.dart'; part 'settings.g.dart'; @@ -40,4 +41,22 @@ class Settings extends HiveObject { set overlayHeight(double value) { _overlayHeight = value; } + + @HiveField(5) + String? _clientName; + + String get clientName => _clientName ?? 'HDS-${Uuid().v4()}'; + + set clientName(String value) { + _clientName = value; + } + + @HiveField(6) + List? _serverIps; + + List get serverIps => _serverIps ?? []; + + set serverIps(List value) { + _serverIps = value; + } } diff --git a/lib/hive/settings.g.dart b/lib/hive/settings.g.dart index 26d2d6c..5a014d3 100644 --- a/lib/hive/settings.g.dart +++ b/lib/hive/settings.g.dart @@ -21,13 +21,15 @@ class SettingsAdapter extends TypeAdapter { ..overlayBackgroundColor = fields[1] as int ..darkMode = fields[2] as bool .._overlayWidth = fields[3] as double? - .._overlayHeight = fields[4] as double?; + .._overlayHeight = fields[4] as double? + .._clientName = fields[5] as String? + .._serverIps = (fields[6] as List?)?.cast(); } @override void write(BinaryWriter writer, Settings obj) { writer - ..writeByte(5) + ..writeByte(7) ..writeByte(0) ..write(obj.port) ..writeByte(1) @@ -37,7 +39,11 @@ class SettingsAdapter extends TypeAdapter { ..writeByte(3) ..write(obj._overlayWidth) ..writeByte(4) - ..write(obj._overlayHeight); + ..write(obj._overlayHeight) + ..writeByte(5) + ..write(obj._clientName) + ..writeByte(6) + ..write(obj._serverIps); } @override diff --git a/lib/main.dart b/lib/main.dart index a5e9eda..c04e213 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:hds_overlay/utils/themes.dart'; import 'package:hds_overlay/view/routes.dart'; import 'package:hds_overlay/view/screens/settings_view.dart'; import 'package:hds_overlay/view/widgets/overlay.dart'; +import 'package:lifecycle/lifecycle.dart'; import 'package:provider/provider.dart'; import 'controllers/settings_controller.dart'; @@ -26,6 +27,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( + navigatorObservers: [defaultLifecycleObserver], title: 'Health Data Server', theme: Themes.light, darkTheme: Themes.dark, diff --git a/lib/model/data_source.dart b/lib/model/data_source.dart index aed7ef5..caaa13f 100644 --- a/lib/model/data_source.dart +++ b/lib/model/data_source.dart @@ -1,8 +1,4 @@ class DataSource { - static final watch = DataSource('watch'); - static final unknown = DataSource('unknown'); - - final String name; - - DataSource(this.name); + static final watch = 'watch'; + static final unknown = 'unknown'; } \ No newline at end of file diff --git a/lib/model/message.dart b/lib/model/message.dart index 209b873..cba0de7 100644 --- a/lib/model/message.dart +++ b/lib/model/message.dart @@ -1,8 +1,6 @@ import 'package:enum_to_string/enum_to_string.dart'; import 'package:hds_overlay/hive/data_type.dart'; -import 'data_source.dart'; - // This might get annoying extension DataTypeExtension on DataType { static DataType fromString(String string) { @@ -22,7 +20,7 @@ abstract class MessageBase { } abstract class DataMessageBase extends MessageBase { - final DataSource source; + final String source; final dynamic value; String get name; @@ -37,7 +35,7 @@ class DataMessage extends DataMessageBase { return EnumToString.convertToString(dataType); } - DataMessage(DataSource source, this.dataType, dynamic value) + DataMessage(String source, this.dataType, dynamic value) : super(source, value); } @@ -48,6 +46,6 @@ class UnknownDataMessage extends DataMessageBase { return 'Unknown data type $_name'; } - UnknownDataMessage(DataSource source, this._name, dynamic value) + UnknownDataMessage(String source, this._name, dynamic value) : super(source, value); } diff --git a/lib/services/discord_rich_presence.dart b/lib/services/discord_rich_presence.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/services/socket_server.dart b/lib/services/socket_server.dart index 4e0ffb9..822ba89 100644 --- a/lib/services/socket_server.dart +++ b/lib/services/socket_server.dart @@ -21,6 +21,7 @@ class SocketServer { Stream get messageStream => _messageStreamController.stream; final Map clients = Map(); + final List servers = []; SocketServer() { NetworkInterface.list(type: InternetAddressType.IPv4).then((interfaces) { @@ -35,7 +36,8 @@ class SocketServer { }); } - Future start(int port) async { + Future start( + int port, String clientName, List serverIps) async { var handler = webSocketHandler( (WebSocketChannel webSocket) { webSocket.stream @@ -43,7 +45,7 @@ class SocketServer { .onDone(() { clients.remove(webSocket); _logStreamController.add(LogMessage(LogLevel.warn, - 'Client disconnected: ${clients[webSocket] ?? DataSource.unknown.name}')); + 'Client disconnected: ${clients[webSocket] ?? DataSource.unknown}')); }); }, pingInterval: Duration(seconds: 15), @@ -53,15 +55,48 @@ class SocketServer { server = await shelf_io.serve(handler, InternetAddress.anyIPv4, port); _logStreamController.add( LogMessage(LogLevel.info, 'Server started on port ${server?.port}')); - return Future.value(); } catch (error) { _logStreamController.add(LogMessage(LogLevel.error, error.toString())); return Future.error(error); } + + // Set up server connections + serverIps.forEach((ip) => connectToServer(clientName, ip)); + + return Future.value(); + } + + void connectToServer(String clientName, String ip) async { + try { + var uri = Uri.parse('ws://$ip'); + if (!uri.hasPort) { + uri = Uri.parse('${uri.toString()}:3476'); + } + final channel = WebSocketChannel.connect(uri); + channel.sink.add('clientName:$clientName'); + servers.add(channel); + _logStreamController + .add(LogMessage(LogLevel.good, 'Connecting to server: $ip')); + await channel.stream.listen((_) {}).asFuture(); + _logStreamController + .add(LogMessage(LogLevel.warn, 'Disconnected from server: $ip')); + connectToServer(clientName, ip); + } catch (e) { + print(e.toString()); + _logStreamController + .add(LogMessage(LogLevel.error, 'Unable to connect to server: $ip')); + Future.delayed( + Duration(seconds: 10), () => connectToServer(clientName, ip)); + } } Future stop() async { _logStreamController.add(LogMessage(LogLevel.warn, 'Server stopped')); + + // Close connection to all servers + servers.forEach((server) => server.sink.close()); + servers.clear(); + return server?.close(); } @@ -76,8 +111,8 @@ class SocketServer { return; } - final source = DataSource(clients[client] ?? DataSource.unknown.name); - if (source.name == DataSource.unknown.name) { + final source = clients[client] ?? DataSource.unknown; + if (source == DataSource.unknown) { // Ignore messages from unidentified clients return; } @@ -91,9 +126,16 @@ class SocketServer { .add(UnknownDataMessage(source, parts[0], parts[1])); } - // Broadcast to all clients that aren't the watch - final externalClients = - clients.entries.toList().where((e) => e.value != DataSource.watch.name); + // Only broadcast messages from the watch + if (source != DataSource.watch) return; + + // Broadcast to all clients that aren't the watch or the source the data came from + final externalClients = clients.entries + .toList() + .where((e) => e.value != DataSource.watch && e.value != source); externalClients.forEach((e) => e.key.sink.add(message)); + + // Broadcast to all servers + servers.forEach((e) => e.sink.add(message)); } } diff --git a/lib/view/screens/data_view.dart b/lib/view/screens/data_view.dart index dd7504d..5444b6a 100644 --- a/lib/view/screens/data_view.dart +++ b/lib/view/screens/data_view.dart @@ -11,7 +11,9 @@ import 'package:hds_overlay/hive/data_widget_properties.dart'; import 'package:hds_overlay/utils/themes.dart'; import 'package:hds_overlay/view/widgets/data/data_widget.dart'; import 'package:hds_overlay/view/widgets/data/heart_rate_widget.dart'; +import 'package:lifecycle/lifecycle.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; class DataView extends StatelessWidget { final endDrawerController = Get.put(EndDrawerController()); @@ -33,8 +35,9 @@ class DataView extends StatelessWidget { DesktopWindow.setMinWindowSize(Size(width, height)); // This needs to be in here or the Scaffold can't be found - ever(endDrawerController.selectedDataType, (DataType dataType) { - if (dataType != DataType.unknown) { + ever(endDrawerController.selectedDataTypeSource, + (Tuple2 dataType) { + if (dataType.item1 != DataType.unknown) { Scaffold.of(context).openEndDrawer(); } }); @@ -45,19 +48,21 @@ class DataView extends StatelessWidget { children: dwc.propertiesMap.values.map((dwp) { return Obx( () { + final typeSource = + Tuple2(dwp.value.dataType, dwp.value.dataSource); final DataWidgetProperties properties = - dwc.propertiesMap[dwp.value.dataType]?.value ?? + dwc.propertiesMap[typeSource]?.value ?? DataWidgetProperties(); return Positioned( left: properties.position.item1, top: properties.position.item2, child: InkWell( onTap: () { - endDrawerController.selectedDataType.value = - dwp.value.dataType; + endDrawerController.selectedDataTypeSource.value = + Tuple2(dwp.value.dataType, dwp.value.dataSource); }, child: Provider.value( - value: dwp.value.dataType, + value: typeSource, builder: (context, _) { if (dwp.value.dataType == DataType.heartRate) { return HeartRateWidget(); @@ -74,13 +79,24 @@ class DataView extends StatelessWidget { }, ); - return Obx( - () => Container( - width: settingsController.settings.value.overlayWidth / Get.pixelRatio, - height: - settingsController.settings.value.overlayHeight / Get.pixelRatio, - color: Color(settingsController.settings.value.overlayBackgroundColor), - child: dataWidgets, + return LifecycleWrapper( + onLifecycleEvent: (LifecycleEvent event) { + if (event == LifecycleEvent.push) { + socketServerController.startServer(); + } else if (event == LifecycleEvent.invisible) { + socketServerController.stopServer(); + } + }, + child: Obx( + () => Container( + width: + settingsController.settings.value.overlayWidth / Get.pixelRatio, + height: + settingsController.settings.value.overlayHeight / Get.pixelRatio, + color: + Color(settingsController.settings.value.overlayBackgroundColor), + child: dataWidgets, + ), ), ); } diff --git a/lib/view/screens/settings_view.dart b/lib/view/screens/settings_view.dart index cebb5e2..2852bb9 100644 --- a/lib/view/screens/settings_view.dart +++ b/lib/view/screens/settings_view.dart @@ -96,7 +96,9 @@ class SettingsView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Overlay size'), - Text('Minimum 800x600', style: Theme.of(context).textTheme.caption), + SizedBox(height: 3), + Text('Minimum 800x600', + style: Theme.of(context).textTheme.caption), ], ), Spacer(), @@ -144,6 +146,131 @@ class SettingsView extends StatelessWidget { ], ); + final clientNameEditor = Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Client name'), + SizedBox(height: 3), + Text( + 'Used to identify with other HDS overlays', + style: Theme.of(context).textTheme.caption, + ), + ], + ), + Spacer(), + Container( + width: 200, + child: TextField( + controller: TextEditingController( + text: settingsController.settings.value.clientName, + ), + decoration: InputDecoration( + border: OutlineInputBorder(), + ), + onChanged: (value) { + settingsController.settings.value.clientName = value; + refreshAndSave(); + }, + ), + ), + ], + ); + + final serverIpsEditor = Column( + children: [ + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Send data to these IPs'), + SizedBox(height: 3), + Text( + 'Allow other HDS overlays to show your data', + style: Theme.of(context).textTheme.caption, + ), + ], + ), + Spacer(), + IconButton( + icon: Icon(Icons.add), + onPressed: () { + settingsController.settings.value.serverIps = + settingsController.settings.value.serverIps + + ['0.0.0.0']; + refreshAndSave(); + }, + ), + ], + ), + // Wow this is really something + // Correction this is horrifying + Obx( + () { + final widgets = List.generate( + settingsController.settings.value.serverIps.length, + (i) => i, + ) + .map((index) => [ + SizedBox(height: 5), + Row( + children: [ + Container( + width: 300, + child: TextField( + controller: TextEditingController( + text: settingsController + .settings.value.serverIps[index], + ), + decoration: InputDecoration( + border: OutlineInputBorder(), + ), + onChanged: (value) { + final serverIps = settingsController + .settings.value.serverIps; + serverIps[index] = value; + settingsController + .settings.value.serverIps = serverIps; + settingsController.settings.value.save(); + }, + ), + ), + Spacer(), + TextButton( + onPressed: () { + final serverIps = settingsController + .settings.value.serverIps; + serverIps.removeAt(index); + settingsController + .settings.value.serverIps = serverIps; + refreshAndSave(); + }, + child: Text('DELETE'), + ) + ], + ), + SizedBox(height: 5), + ]) + .toList(); + + final List widgetList; + if (widgets.isEmpty) { + widgetList = []; + } else { + widgetList = + widgets.reduce((value, element) => value + element); + } + + return Column( + children: widgetList, + ); + }, + ), + ], + ); + return Card( elevation: 8, margin: EdgeInsets.only(left: 100, right: 100, top: 20, bottom: 20), @@ -159,6 +286,10 @@ class SettingsView extends StatelessWidget { backgroundColorPicker, Divider(), overlaySizeEditor, + Divider(), + clientNameEditor, + Divider(), + serverIpsEditor, ], ), ); diff --git a/lib/view/widgets/data/data_widget.dart b/lib/view/widgets/data/data_widget.dart index 30da9b0..43e6db5 100644 --- a/lib/view/widgets/data/data_widget.dart +++ b/lib/view/widgets/data/data_widget.dart @@ -1,6 +1,5 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hds_overlay/controllers/data_widget_controller.dart'; @@ -9,8 +8,9 @@ import 'package:hds_overlay/hive/data_type.dart'; import 'package:hds_overlay/hive/data_widget_properties.dart'; import 'package:hds_overlay/model/default_image.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; -class DataWidgetBase extends HookWidget { +class DataWidgetBase extends StatelessWidget { final DataWidgetController dwc = Get.find(); late final Widget image; late final Widget text; @@ -19,23 +19,29 @@ class DataWidgetBase extends HookWidget { @override Widget build(BuildContext context) { - final dataType = Provider.of(context); + final typeSource = Provider.of>(context); final properties = - dwc.propertiesMap[dataType] ?? DataWidgetProperties().obs; + dwc.propertiesMap[typeSource] ?? DataWidgetProperties().obs; return Obx(() { if (properties.value.textInsideImage) { return Stack( alignment: Alignment.center, children: [ - properties.value.showImage ? image : SizedBox.shrink(), + Visibility( + visible: properties.value.showImage, + child: image, + ), text, ], ); } else { return Row( children: [ - properties.value.showImage ? image : SizedBox.shrink(), + Visibility( + visible: properties.value.showImage, + child: image, + ), text ], ); @@ -57,11 +63,11 @@ class DataWidgetImage extends StatelessWidget { @override Widget build(BuildContext context) { - final dataType = Provider.of(context); + final typeSource = Provider.of>(context); return Obx(() { final properties = - dwc.propertiesMap[dataType] ?? DataWidgetProperties().obs; + dwc.propertiesMap[typeSource] ?? DataWidgetProperties().obs; final image = properties.value.image; final imageSize = properties.value.imageSize; @@ -70,7 +76,7 @@ class DataWidgetImage extends StatelessWidget { width: square ? imageSize : null, child: Builder(builder: (context) { if (image == null) { - return Image.asset(getDefaultImage(dataType)); + return Image.asset(getDefaultImage(typeSource.item1)); } else { return Image.memory(image); } @@ -86,18 +92,18 @@ class DataWidgetText extends StatelessWidget { @override Widget build(BuildContext context) { - final dataType = Provider.of(context); + final typeSource = Provider.of>(context); return Obx( () { final properties = - dwc.propertiesMap[dataType] ?? DataWidgetProperties().obs; + dwc.propertiesMap[typeSource] ?? DataWidgetProperties().obs; final preProcessedValue = - socketServerController.messages[dataType]?.value; + socketServerController.messages[typeSource]?.value; final String valueText; if (preProcessedValue == null) { valueText = '-'; - } else if (dataType.isRounded()) { + } else if (typeSource.item1.isRounded()) { valueText = double.parse(preProcessedValue) .toStringAsFixed(properties.value.decimals); } else { @@ -118,7 +124,7 @@ class DataWidgetText extends StatelessWidget { fontSize: properties.value.fontSize, ); final baseTextStyle = textStyle.copyWith( - color: getTextColor(properties), + color: getTextColor(properties, context), shadows: () { if (properties.value.textShadow) { return [ @@ -156,18 +162,20 @@ class DataWidgetText extends StatelessWidget { Stack( children: [ Text(valueText, style: baseTextStyle), - properties.value.textStroke - ? Text(valueText, style: strokeTextStyle) - : SizedBox.shrink(), + Visibility( + visible: properties.value.textStroke, + child: Text(valueText, style: strokeTextStyle), + ), ], ), SizedBox(width: 3), Stack( children: [ Text(unitText, style: unitBaseTextStyle), - properties.value.textStroke - ? Text(unitText, style: unitStrokeTextStyle) - : SizedBox.shrink(), + Visibility( + visible: properties.value.textStroke, + child: Text(unitText, style: unitStrokeTextStyle), + ), ], ), ], @@ -177,7 +185,8 @@ class DataWidgetText extends StatelessWidget { ); } - Color getTextColor(Rx properties) { + Color getTextColor( + Rx properties, BuildContext context) { return Color(properties.value.textColor); } } diff --git a/lib/view/widgets/data/heart_rate_widget.dart b/lib/view/widgets/data/heart_rate_widget.dart index a74cbe1..c0cf180 100644 --- a/lib/view/widgets/data/heart_rate_widget.dart +++ b/lib/view/widgets/data/heart_rate_widget.dart @@ -9,80 +9,99 @@ import 'package:hds_overlay/controllers/heart_rate_widget_controller.dart'; import 'package:hds_overlay/controllers/socket_server_controller.dart'; import 'package:hds_overlay/hive/data_type.dart'; import 'package:hds_overlay/hive/data_widget_properties.dart'; +import 'package:hds_overlay/model/message.dart'; import 'package:hds_overlay/utils/audio_source_macos.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:lifecycle/lifecycle.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; import 'data_widget.dart'; class HeartRateWidget extends DataWidgetBase { HeartRateWidget() : super.withWidgets(HeartRateImage(), HeartRateText()); + + @override + Widget build(BuildContext context) { + return Provider( + create: (_) => HeartRateWidgetController(), + child: super.build(context), + ); + } } class HeartRateImage extends HookWidget { final DataWidgetController dwc = Get.find(); - final HeartRateWidgetController hrwc = Get.put(HeartRateWidgetController()) - ..animating = false; final SocketServerController socketServerController = Get.find(); @override Widget build(BuildContext context) { + final hrwc = Provider.of(context); + final typeSource = Provider.of>(context); + final controller = useAnimationController(initialValue: 1.0); useAnimation(controller); final properties = - dwc.propertiesMap[DataType.heartRate] ?? DataWidgetProperties().obs; + dwc.propertiesMap[typeSource] ?? DataWidgetProperties().obs; - ever( - socketServerController.messages, - (_) => hrwc.currentHeartRate = int.tryParse( - socketServerController.messages[DataType.heartRate]?.value ?? - '') ?? - 0); + ever(socketServerController.messages, + (Map, DataMessage> messages) { + hrwc.currentHeartRate = + int.tryParse(messages[typeSource]?.value ?? '') ?? 0; + }); ever(properties, (_) { if (properties.value.animated && !hrwc.animating) { - animateImage(controller); + animateImage(controller, hrwc); } hrwc.animating = properties.value.animated; if (properties.value.heartBeatSound != null && !hrwc.sounding) { - playBeatSound(properties); + playBeatSound(properties, hrwc); } hrwc.sounding = properties.value.heartBeatSound != null; }); if (properties.value.animated && !hrwc.animating) { - animateImage(controller); + animateImage(controller, hrwc); } if (properties.value.heartBeatSound != null && !hrwc.sounding) { - playBeatSound(properties); + playBeatSound(properties, hrwc); } - return Obx(() { - if (properties.value.showImage) { - return SizedBox( - height: properties.value.imageSize, - width: properties.value.imageSize, - child: Center( - child: SizedBox( - height: properties.value.imageSize * controller.value, - width: properties.value.imageSize * controller.value, - child: DataWidgetImage(square: true), + return LifecycleWrapper( + onLifecycleEvent: (LifecycleEvent event) { + if (event == LifecycleEvent.invisible) { + hrwc.visible = false; + } + }, + child: Obx( + () => Visibility( + visible: properties.value.showImage, + child: SizedBox( + height: properties.value.imageSize, + width: properties.value.imageSize, + child: Center( + child: SizedBox( + height: properties.value.imageSize * controller.value, + width: properties.value.imageSize * controller.value, + child: DataWidgetImage(square: true), + ), ), ), - ); - } else { - return SizedBox.shrink(); - } - }); + ), + ), + ); } - void animateImage(AnimationController controller) async { + void animateImage( + AnimationController controller, HeartRateWidgetController hrwc) async { hrwc.animating = true; - while (hrwc.animating) { + while (hrwc.animating && hrwc.visible) { if (hrwc.currentHeartRate == 0) { await Future.delayed(Duration(milliseconds: 100)); continue; @@ -92,11 +111,11 @@ class HeartRateImage extends HookWidget { try { await controller.animateTo(0.85, - duration: - Duration(milliseconds: (millisecondsPerBeat * (3 / 4)).toInt())); + duration: Duration( + milliseconds: (millisecondsPerBeat * (3 / 4)).toInt())); await controller.animateTo(1.0, - duration: - Duration(milliseconds: (millisecondsPerBeat * (1 / 4)).toInt())); + duration: Duration( + milliseconds: (millisecondsPerBeat * (1 / 4)).toInt())); } catch (error) { // The controller is disposed return; @@ -104,7 +123,8 @@ class HeartRateImage extends HookWidget { } } - void playBeatSound(Rx properties) async { + void playBeatSound(Rx properties, + HeartRateWidgetController hrwc) async { if (properties.value.heartBeatSound == null) return; hrwc.sounding = true; @@ -121,7 +141,7 @@ class HeartRateImage extends HookWidget { '\\Health Data Server\\beatSound.mp3') ..writeAsBytesSync(soundBytes))); } - while (hrwc.sounding) { + while (hrwc.sounding && hrwc.visible) { final startTime = DateTime.now().millisecondsSinceEpoch; if (hrwc.currentHeartRate == 0) { await Future.delayed(Duration(milliseconds: 100)); @@ -147,16 +167,17 @@ class HeartRateImage extends HookWidget { } class HeartRateText extends DataWidgetText { - final HeartRateWidgetController hrwc = Get.find(); - @override - Color getTextColor(Rx properties) { + Color getTextColor( + Rx properties, BuildContext context) { + final hrwc = Provider.of(context); + final ranges = properties.value.heartRateRanges.entries.toList(); ranges.sort((a, b) => a.key.compareTo(b.key)); return Color( ranges.reversed .firstWhere((e) => hrwc.currentHeartRate >= e.key, - orElse: () => MapEntry(0, Colors.white.value)) + orElse: () => MapEntry(0, properties.value.textColor)) .value, ); } diff --git a/lib/view/widgets/drawers/end_drawer.dart b/lib/view/widgets/drawers/end_drawer.dart index 10788f5..7a2b59e 100644 --- a/lib/view/widgets/drawers/end_drawer.dart +++ b/lib/view/widgets/drawers/end_drawer.dart @@ -13,7 +13,8 @@ class EndDrawer extends StatelessWidget { return Drawer( child: Obx( () { - if (controller.selectedDataType.value == DataType.unknown) { + if (controller.selectedDataTypeSource.value.item1 == + DataType.unknown) { return WidgetSelector(); } else { return WidgetEditor(); diff --git a/lib/view/widgets/heart_rate_range_editor.dart b/lib/view/widgets/heart_rate_range_editor.dart index 0cf47b5..8901869 100644 --- a/lib/view/widgets/heart_rate_range_editor.dart +++ b/lib/view/widgets/heart_rate_range_editor.dart @@ -2,21 +2,22 @@ import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hds_overlay/controllers/data_widget_controller.dart'; +import 'package:hds_overlay/controllers/end_drawer_controller.dart'; import 'package:hds_overlay/controllers/heart_rate_range_editor_controller.dart'; -import 'package:hds_overlay/hive/data_type.dart'; import 'package:hds_overlay/hive/data_widget_properties.dart'; import 'package:tuple/tuple.dart'; class HeartRateRangeEditor extends StatelessWidget { final DataWidgetController dataWidgetController = Get.find(); + final EndDrawerController endDrawerController = Get.find(); @override Widget build(BuildContext context) { final HeartRateRangeEditorController hrrec = HeartRateRangeEditorController(); - final Rx properties = - dataWidgetController.propertiesMap[DataType.heartRate] ?? - DataWidgetProperties().obs; + final Rx properties = dataWidgetController + .propertiesMap[endDrawerController.selectedDataTypeSource.value] ?? + DataWidgetProperties().obs; return Column( children: [ Row( diff --git a/lib/view/widgets/overlay.dart b/lib/view/widgets/overlay.dart index 5c433c7..c790a39 100644 --- a/lib/view/widgets/overlay.dart +++ b/lib/view/widgets/overlay.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hds_overlay/controllers/end_drawer_controller.dart'; import 'package:hds_overlay/hive/data_type.dart'; +import 'package:hds_overlay/model/data_source.dart'; +import 'package:tuple/tuple.dart'; import '../screens/data_view.dart'; import 'drawers/end_drawer.dart'; -import 'log_view.dart'; import 'drawers/navigation_drawer.dart'; +import 'log_view.dart'; class HDSOverlay extends StatelessWidget { final endDrawerController = Get.put(EndDrawerController()); @@ -33,7 +35,8 @@ class HDSOverlay extends StatelessWidget { onEndDrawerChanged: (open) { if (!open) { // Reset the drawer when it is closed - endDrawerController.selectedDataType.value = DataType.unknown; + endDrawerController.selectedDataTypeSource.value = + Tuple2(DataType.unknown, DataSource.watch); } }, body: Container( diff --git a/lib/view/widgets/widget_editor.dart b/lib/view/widgets/widget_editor.dart index b988118..0db51e8 100644 --- a/lib/view/widgets/widget_editor.dart +++ b/lib/view/widgets/widget_editor.dart @@ -21,16 +21,24 @@ class WidgetEditor extends StatelessWidget { @override Widget build(BuildContext context) { final properties = - dwc.propertiesMap[endDrawerController.selectedDataType.value] ?? + dwc.propertiesMap[endDrawerController.selectedDataTypeSource.value] ?? DataWidgetProperties().obs; final header = Center( - child: Text( - EnumToString.convertToString( - endDrawerController.selectedDataType.value, - camelCase: true, - ), - style: Theme.of(context).textTheme.headline6, + child: Column( + children: [ + Text( + EnumToString.convertToString( + endDrawerController.selectedDataTypeSource.value.item1, + camelCase: true, + ), + style: Theme.of(context).textTheme.headline6, + ), + Text( + 'Data source: ${properties.value.dataSource}', + style: Theme.of(context).textTheme.caption, + ), + ], ), ); @@ -57,10 +65,11 @@ class WidgetEditor extends StatelessWidget { }, ), ), - Obx(() { - if (properties.value.showImage && - properties.value.image != null) { - return Padding( + Obx( + () => Visibility( + visible: properties.value.showImage && + properties.value.image != null, + child: Padding( padding: EdgeInsets.only(left: 5, right: 5), child: TextButton( onPressed: () { @@ -76,50 +85,47 @@ class WidgetEditor extends StatelessWidget { child: Text( wec.removeImageTapped.value ? 'Really?' : 'Remove'), ), - ); - } else { - return SizedBox.shrink(); - } - }), - Obx(() { - if (properties.value.showImage) { - return InkWell( - onTap: () => selectImageFile(properties), - child: Card( - margin: - EdgeInsets.only(left: 10, right: 10, top: 4, bottom: 4), - elevation: 8, - child: Padding( - padding: EdgeInsets.all(5), - child: Container( - width: 30, - height: 30, - child: Builder(builder: (context) { - final image = properties.value.image; - if (image == null) { - return Image.asset( - getDefaultImage(properties.value.dataType)); - } else { - return Image.memory(image); - } - }), + ), + ), + ), + Obx( + () => Visibility( + visible: properties.value.showImage, + replacement: SizedBox(width: 0, height: 48), + child: InkWell( + onTap: () => selectImageFile(properties), + child: Card( + margin: EdgeInsets.only( + left: 10, right: 10, top: 4, bottom: 4), + elevation: 8, + child: Padding( + padding: EdgeInsets.all(5), + child: Container( + width: 30, + height: 30, + child: Builder(builder: (context) { + final image = properties.value.image; + if (image == null) { + return Image.asset( + getDefaultImage(properties.value.dataType)); + } else { + return Image.memory(image); + } + }), + ), ), ), - ), - ); - } else { - // Prevent view shifting - return SizedBox(width: 0, height: 48); - } - }), + )), + ), ], ), SizedBox(height: 5), WidgetEditorTextField(EditorType.imageSize, properties), SizedBox(height: 5), - Obx(() { - if (properties.value.dataType.isAnimated()) { - return Row( + Obx( + () => Visibility( + visible: properties.value.dataType.isAnimated(), + child: Row( children: [ Text('Animate'), Spacer(), @@ -131,11 +137,9 @@ class WidgetEditor extends StatelessWidget { }, ), ], - ); - } else { - return SizedBox.shrink(); - } - }), + ), + ), + ), ], ); @@ -161,18 +165,15 @@ class WidgetEditor extends StatelessWidget { children: [ colorSelector, SizedBox(height: 5), - () { - if (properties.value.dataType.isRounded()) { - return Column( - children: [ - WidgetEditorTextField(EditorType.decimals, properties), - SizedBox(height: 5), - ], - ); - } else { - return SizedBox.shrink(); - } - }(), + Visibility( + visible: properties.value.dataType.isRounded(), + child: Column( + children: [ + WidgetEditorTextField(EditorType.decimals, properties), + SizedBox(height: 5), + ], + ), + ), WidgetEditorTextField(EditorType.font, properties), SizedBox(height: 10), Text( @@ -184,18 +185,15 @@ class WidgetEditor extends StatelessWidget { SizedBox(height: 5), WidgetEditorTextField(EditorType.unit, properties), Obx( - () { - if (properties.value.unit.isEmpty) { - return SizedBox.shrink(); - } else { - return Column( - children: [ - SizedBox(height: 5), - WidgetEditorTextField(EditorType.unitFontSize, properties), - ], - ); - } - }, + () => Visibility( + visible: !properties.value.unit.isEmpty, + child: Column( + children: [ + SizedBox(height: 5), + WidgetEditorTextField(EditorType.unitFontSize, properties), + ], + ), + ), ), SizedBox(height: 5), Obx(() => Row( @@ -232,19 +230,15 @@ class WidgetEditor extends StatelessWidget { ), ), Obx( - () { - if (properties.value.textShadow) { - return Column( - children: [ - SizedBox(height: 5), - WidgetEditorTextField( - EditorType.textShadowRadius, properties), - ], - ); - } else { - return SizedBox.shrink(); - } - }, + () => Visibility( + visible: properties.value.textShadow, + child: Column( + children: [ + SizedBox(height: 5), + WidgetEditorTextField(EditorType.textShadowRadius, properties), + ], + ), + ), ), SizedBox(height: 5), Obx( @@ -263,18 +257,15 @@ class WidgetEditor extends StatelessWidget { ), ), Obx( - () { - if (properties.value.textStroke) { - return Column( - children: [ - SizedBox(height: 5), - WidgetEditorTextField(EditorType.textStrokeWidth, properties), - ], - ); - } else { - return SizedBox.shrink(); - } - }, + () => Visibility( + visible: properties.value.textStroke, + child: Column( + children: [ + SizedBox(height: 5), + WidgetEditorTextField(EditorType.textStrokeWidth, properties), + ], + ), + ), ), ], ); @@ -296,45 +287,42 @@ class WidgetEditor extends StatelessWidget { ), ); - final heartRateEditor = Builder(builder: (context) { - if (properties.value.dataType == DataType.heartRate) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - HeartRateRangeEditor(), - Divider(), - SizedBox(height: 10), - Text('Heart beat sound', - style: Theme.of(context).textTheme.subtitle1), - SizedBox(height: 10), - TextButton( - onPressed: () { - if (properties.value.heartBeatSound == null) { - selectAudioFile(properties); - } else if (!wec.removeSoundTapped.value) { - wec.removeSoundTapped.value = true; - Future.delayed(Duration(seconds: 1)) - .then((_) => wec.removeSoundTapped.value = false); - } else { - properties.value.heartBeatSound = null; - saveAndRefresh(properties); - } - }, - child: Obx( - () => Text(properties.value.heartBeatSound == null - ? 'Select audio file' - : wec.removeSoundTapped.value - ? 'Really?' - : 'Remove audio file'), - ), + final heartRateEditor = Visibility( + visible: properties.value.dataType == DataType.heartRate, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HeartRateRangeEditor(), + Divider(), + SizedBox(height: 10), + Text('Heart beat sound', + style: Theme.of(context).textTheme.subtitle1), + SizedBox(height: 10), + TextButton( + onPressed: () { + if (properties.value.heartBeatSound == null) { + selectAudioFile(properties); + } else if (!wec.removeSoundTapped.value) { + wec.removeSoundTapped.value = true; + Future.delayed(Duration(seconds: 1)) + .then((_) => wec.removeSoundTapped.value = false); + } else { + properties.value.heartBeatSound = null; + saveAndRefresh(properties); + } + }, + child: Obx( + () => Text(properties.value.heartBeatSound == null + ? 'Select audio file' + : wec.removeSoundTapped.value + ? 'Really?' + : 'Remove audio file'), ), - SizedBox(height: 10), - ], - ); - } else { - return SizedBox.shrink(); - } - }); + ), + SizedBox(height: 10), + ], + ), + ); return ListView( padding: EdgeInsets.all(10), @@ -368,6 +356,7 @@ class WidgetEditor extends StatelessWidget { SizedBox(height: 10), Divider(), heartRateEditor, + Divider(), SizedBox(height: 10), deleteButton, SizedBox(height: 10), diff --git a/lib/view/widgets/widget_editor_text_field.dart b/lib/view/widgets/widget_editor_text_field.dart index 76008a0..3ff2d96 100644 --- a/lib/view/widgets/widget_editor_text_field.dart +++ b/lib/view/widgets/widget_editor_text_field.dart @@ -24,7 +24,10 @@ class WidgetEditorTextField extends StatelessWidget { return Row( children: [ Text(label), - spacer ? Spacer() : SizedBox.shrink(), + Visibility( + visible: spacer, + child: Spacer(), + ), Container( width: 100, child: TextField( diff --git a/lib/view/widgets/widget_selector.dart b/lib/view/widgets/widget_selector.dart index 5c0aa16..3944fdb 100644 --- a/lib/view/widgets/widget_selector.dart +++ b/lib/view/widgets/widget_selector.dart @@ -2,65 +2,104 @@ import 'package:enum_to_string/enum_to_string.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hds_overlay/controllers/data_widget_controller.dart'; +import 'package:hds_overlay/controllers/widget_selector_controller.dart'; import 'package:hds_overlay/hive/data_type.dart'; import 'package:hds_overlay/hive/data_widget_properties.dart'; import 'package:hds_overlay/hive/hive_utils.dart'; +import 'package:hds_overlay/model/data_source.dart'; import 'package:hive/hive.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; import 'data/data_widget.dart'; import 'data/heart_rate_widget.dart'; class WidgetSelector extends StatelessWidget { final DataWidgetController dwc = Get.find(); + final WidgetSelectorController wsc = WidgetSelectorController(); @override Widget build(BuildContext context) { return Obx(() { - final usedDataTypes = - dwc.propertiesMap.values.map((e) => e.value.dataType); + final usedDataTypeSources = dwc.propertiesMap.values + .map((e) => Tuple2(e.value.dataType, e.value.dataSource)); final dataTypes = DataType.values.toList(); - dataTypes.removeWhere((e) => usedDataTypes.contains(e)); - dataTypes.remove(DataType.unknown); + if (wsc.dataSource.isEmpty) { + dataTypes.clear(); + } else { + dataTypes.removeWhere( + (e) => usedDataTypeSources.contains(Tuple2(e, wsc.dataSource))); + dataTypes.remove(DataType.unknown); + } + + final tec = TextEditingController(text: wsc.dataSource.value); + tec.selection = TextSelection.collapsed(offset: tec.text.length); return Container( decoration: BoxDecoration(color: Colors.black), child: ListView( padding: EdgeInsets.all(10), - children: dataTypes.map((DataType dataType) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - EnumToString.convertToString(dataType, camelCase: true), - style: Theme.of(context) - .textTheme - .subtitle1 - ?.copyWith(color: Colors.white), - ), - SizedBox(height: 5), - InkWell( - onTap: () => addWidget(dataType), - child: Provider.value( - value: dataType, - builder: (context, _) { - if (dataType == DataType.heartRate) { - return HeartRateWidget(); - } - return DataWidget(); - }), + children: [ + Row( + children: [ + Text( + 'Data source', + style: TextStyle(color: Colors.white), + ), + Spacer(), + Container( + width: 100, + child: TextField( + controller: tec, + onChanged: (value) => wsc.dataSource.value = value, + style: TextStyle(color: Colors.white), + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + ), + ), + ), + ), + ], ), SizedBox(height: 20), - ], - ); - }).toList()), + ] + + dataTypes.map((DataType dataType) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + EnumToString.convertToString(dataType, camelCase: true), + style: Theme.of(context) + .textTheme + .subtitle1 + ?.copyWith(color: Colors.white), + ), + SizedBox(height: 5), + InkWell( + onTap: () => addWidget(dataType, wsc.dataSource.value), + child: Provider.value( + value: Tuple2(dataType, wsc.dataSource.value), + builder: (context, _) { + if (dataType == DataType.heartRate) { + return HeartRateWidget(); + } + return DataWidget(); + }), + ), + SizedBox(height: 20), + ], + ); + }).toList()), ); }); } - void addWidget(DataType dataType) { + void addWidget(DataType dataType, String dataSource) { Hive.box(HiveUtils.boxDataWidgetProperties) - .add(DataWidgetProperties()..dataType = dataType); + .add(DataWidgetProperties() + ..dataType = dataType + ..dataSource = dataSource); } } diff --git a/pubspec.lock b/pubspec.lock index 322b665..70bef88 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -436,6 +436,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.1" + lifecycle: + dependency: "direct main" + description: + name: lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" logging: dependency: transitive description: @@ -701,7 +708,7 @@ packages: source: hosted version: "1.3.0" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 69bae78..dd5edb0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,8 @@ dependencies: google_fonts: ^2.0.0 flex_color_picker: ^2.0.0 path_provider: ^2.0.1 + uuid: ^3.0.4 + lifecycle: ^0.2.0 # Shelf shelf: ^1.1.0