From f25c9e9d198386ba926f1fcd2590bb19f23d66f3 Mon Sep 17 00:00:00 2001 From: Adrian Edwards <17362949+MoralCode@users.noreply.github.com> Date: Mon, 9 May 2022 21:01:03 -0400 Subject: [PATCH 01/21] begin de-future-ifying the values --- lib/data/workoutsummary.dart | 53 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/data/workoutsummary.dart b/lib/data/workoutsummary.dart index 3a9c0d9..c869347 100644 --- a/lib/data/workoutsummary.dart +++ b/lib/data/workoutsummary.dart @@ -64,27 +64,27 @@ class WorkoutSummary { /// Construct a WorkoutSummary from the bytes returned from the erg void _setBasicBytes(Uint8List data) { - _timestamp.completeIfNotAlready(Concept2DateExtension.fromBytes(data.sublist(0, 4))); - _workTime.completeIfNotAlready( + _timestamp = Concept2DateExtension.fromBytes(data.sublist(0, 4))); + _workTime = CsafeIntExtension.fromBytes(data.sublist(4, 7), endian: Endian.little) / 100); //divide by 100 to convert to seconds - _workDistance.completeIfNotAlready(CsafeIntExtension.fromBytes(data.sublist(7, 10), + _workDistance = CsafeIntExtension.fromBytes(data.sublist(7, 10), endian: Endian.little) / 10); //divide by 10 to convert to meters - _avgSPM.completeIfNotAlready(data.elementAt(10)); - _endHeartRate.completeIfNotAlready(data.elementAt(11)); - _avgHeartRate.completeIfNotAlready(data.elementAt(12)); - _minHeartRate.completeIfNotAlready(data.elementAt(13)); - _maxHeartRate.completeIfNotAlready(data.elementAt(14)); - _avgDragFactor.completeIfNotAlready(data.elementAt(15)); + _avgSPM = data.elementAt(10)); + _endHeartRate = data.elementAt(11)); + _avgHeartRate = data.elementAt(12)); + _minHeartRate = data.elementAt(13)); + _maxHeartRate = data.elementAt(14)); + _avgDragFactor = data.elementAt(15)); //recovery heart rate here int recHRVal = data.elementAt(16); // 0 is not a valid value here according to the spec if (recHRVal > 0) { - _recoveryHeartRate.completeIfNotAlready(recHRVal); + _recoveryHeartRate = recHRVal); } - _workoutType.completeIfNotAlready(WorkoutTypeExtension.fromInt(data.elementAt(17))); - _avgPace.completeIfNotAlready(CsafeIntExtension.fromBytes(data.sublist(18, 20), + _workoutType = WorkoutTypeExtension.fromInt(data.elementAt(17))); + _avgPace = CsafeIntExtension.fromBytes(data.sublist(18, 20), endian: Endian.little) / 10); //{ } @@ -96,21 +96,22 @@ class WorkoutSummary { // throw ArgumentError( // "Bytes passed to WorkoutSummary from multiple characteristics must have the same timestamp"); // } - _intervalType.completeIfNotAlready(IntervalTypeExtension.fromInt(data.elementAt(4))); - _intervalSize.completeIfNotAlready( - CsafeIntExtension.fromBytes(data.sublist(5, 7), endian: Endian.little)); - _intervalCount.completeIfNotAlready(data.elementAt(7)); - _totalCalories.completeIfNotAlready(CsafeIntExtension.fromBytes(data.sublist(8, 10), - endian: Endian.little)); - _watts.completeIfNotAlready(CsafeIntExtension.fromBytes(data.sublist(10, 12), - endian: Endian.little)); - _totalRestDistance.completeIfNotAlready(CsafeIntExtension.fromBytes( + + intervalType = IntervalTypeExtension.fromInt(data.elementAt(4)); + intervalSize = + CsafeIntExtension.fromBytes(data.sublist(5, 7), endian: Endian.little); + intervalCount = data.elementAt(7); + totalCalories = CsafeIntExtension.fromBytes(data.sublist(8, 10), + endian: Endian.little); + watts = CsafeIntExtension.fromBytes(data.sublist(10, 12), + endian: Endian.little); + totalRestDistance = CsafeIntExtension.fromBytes( data.sublist(12, 15), - endian: Endian.little)); - _intervalRestTime.completeIfNotAlready(CsafeIntExtension.fromBytes(data.sublist(15, 17), - endian: Endian.little)); - _avgCalories.completeIfNotAlready(CsafeIntExtension.fromBytes(data.sublist(17, 19), - endian: Endian.little)); + endian: Endian.little); + intervalRestTime = CsafeIntExtension.fromBytes(data.sublist(15, 17), + endian: Endian.little); + avgCalories = CsafeIntExtension.fromBytes(data.sublist(17, 19), + endian: Endian.little); } @override From 8ebba12b33eb22671bee4fa738a94982b2f8f0ea Mon Sep 17 00:00:00 2001 From: Adrian Edwards <17362949+MoralCode@users.noreply.github.com> Date: Mon, 9 May 2022 21:05:55 -0400 Subject: [PATCH 02/21] add and use classes for Time and Duration-stamped packets --- lib/data/workoutsummary.dart | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/data/workoutsummary.dart b/lib/data/workoutsummary.dart index c869347..1fbf525 100644 --- a/lib/data/workoutsummary.dart +++ b/lib/data/workoutsummary.dart @@ -7,11 +7,28 @@ import 'package:csafe_fitness/csafe_fitness.dart'; import '../helpers.dart'; import 'package:c2bluetooth/enums.dart'; + +///Represents a data packet from Concept2 that is stamped with a date. +class TimestampedData { + DateTime timestamp; + + TimestampedData.fromBytes(Uint8List bytes): timestamp = Concept2DateExtension.fromBytes(bytes.sublist(0, 4)); + +} + +///Represents a data packet from Concept2 that is stamped with a duration. + +class DurationstampedData { + Duration elapsedTime; + + DurationstampedData.fromBytes(Uint8List data) : elapsedTime = Concept2DurationExtension.fromBytes(data.sublist(0, 3)); +} + + /// Represents a summary of a completed workout /// /// This takes care of processesing the raw byte data from workout summary characteristics into easily accessible fields. This class also takes care of things like byte endianness, combining multiple high and low bytes .etc, allowing applications to access things in terms of flutter native types. -class WorkoutSummary { - Completer _timestamp = new Completer(); +class WorkoutSummary extends TimestampedData { Completer _workTime = new Completer(); Completer _workDistance = new Completer(); Completer _avgSPM = new Completer(); @@ -33,7 +50,6 @@ class WorkoutSummary { Completer _avgCalories = new Completer(); // external getters for clients to get futures for the data they want - Future get timestamp => _timestamp.future; Future get workTime => _workTime.future; Future get workDistance => _workDistance.future; Future get avgSPM => _avgSPM.future; @@ -63,8 +79,7 @@ class WorkoutSummary { } /// Construct a WorkoutSummary from the bytes returned from the erg - void _setBasicBytes(Uint8List data) { - _timestamp = Concept2DateExtension.fromBytes(data.sublist(0, 4))); + WorkoutSummary.fromBytes(Uint8List data) : super(data) { _workTime = CsafeIntExtension.fromBytes(data.sublist(4, 7), endian: Endian.little) / 100); //divide by 100 to convert to seconds From 44870e54d755e750d1fc46438fa01c7275a5bedc Mon Sep 17 00:00:00 2001 From: Adrian Edwards <17362949+MoralCode@users.noreply.github.com> Date: Mon, 9 May 2022 21:06:09 -0400 Subject: [PATCH 03/21] split workout summary into a second class for the part 2 data --- lib/data/workoutsummary.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/data/workoutsummary.dart b/lib/data/workoutsummary.dart index 1fbf525..ae1a0fe 100644 --- a/lib/data/workoutsummary.dart +++ b/lib/data/workoutsummary.dart @@ -103,8 +103,10 @@ class WorkoutSummary extends TimestampedData { endian: Endian.little) / 10); //{ } +} - void _setExtendedBytes(Uint8List data) { +class WorkoutSummary2 { + WorkoutSummary2.fromBytes(Uint8List data) { // if (data.length > 20) { // var timestamp2 = Concept2DateExtension.fromBytes(data.sublist(20, 24)); // if (timestamp != timestamp2) { From e19cc7b38a806aec267d874e0162f1c2d5e78523 Mon Sep 17 00:00:00 2001 From: Adrian Edwards <17362949+MoralCode@users.noreply.github.com> Date: Mon, 9 May 2022 21:09:12 -0400 Subject: [PATCH 04/21] remove completer fields and split into appropriate classes --- lib/data/workoutsummary.dart | 68 +++++++++++------------------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/lib/data/workoutsummary.dart b/lib/data/workoutsummary.dart index ae1a0fe..c32e62a 100644 --- a/lib/data/workoutsummary.dart +++ b/lib/data/workoutsummary.dart @@ -29,54 +29,17 @@ class DurationstampedData { /// /// This takes care of processesing the raw byte data from workout summary characteristics into easily accessible fields. This class also takes care of things like byte endianness, combining multiple high and low bytes .etc, allowing applications to access things in terms of flutter native types. class WorkoutSummary extends TimestampedData { - Completer _workTime = new Completer(); - Completer _workDistance = new Completer(); - Completer _avgSPM = new Completer(); - Completer _endHeartRate = new Completer(); - Completer _avgHeartRate = new Completer(); - Completer _minHeartRate = new Completer(); - Completer _maxHeartRate = new Completer(); - Completer _avgDragFactor = new Completer(); - Completer _recoveryHeartRate = new Completer(); - Completer _workoutType = new Completer(); - Completer _avgPace = new Completer(); - Completer _intervalType = new Completer(); - Completer _intervalSize = new Completer(); - Completer _intervalCount = new Completer(); - Completer _totalCalories = new Completer(); - Completer _watts = new Completer(); - Completer _totalRestDistance = new Completer(); - Completer _intervalRestTime = new Completer(); - Completer _avgCalories = new Completer(); - - // external getters for clients to get futures for the data they want - Future get workTime => _workTime.future; - Future get workDistance => _workDistance.future; - Future get avgSPM => _avgSPM.future; - Future get endHeartRate => _endHeartRate.future; - Future get avgHeartRate => _avgHeartRate.future; - Future get minHeartRate => _minHeartRate.future; - Future get maxHeartRate => _maxHeartRate.future; - Future get avgDragFactor => _avgDragFactor.future; - //recoveryHeartRate is sent as an amended packet later. zero is not valid - Future get recoveryHeartRate => _recoveryHeartRate.future; - Future get workoutType => _workoutType.future; - Future get avgPace => _avgPace.future; - Future get intervalType => _intervalType.future; - Future get intervalSize => _intervalSize.future; - Future get intervalCount => _intervalCount.future; - Future get totalCalories => _totalCalories.future; - Future get watts => _watts.future; - Future get totalRestDistance => _totalRestDistance.future; - Future get intervalRestTime => _intervalRestTime.future; - Future get avgCalories => _avgCalories.future; - - WorkoutSummary.fromBytes(Uint8List data) { - _setBasicBytes(data.sublist(0, 20)); - if (data.length > 20) { - _setExtendedBytes(data.sublist(20)); - } - } + double workTime; + double workDistance; + int avgSPM; + int endHeartRate; + int avgHeartRate; + int minHeartRate; + int maxHeartRate; + int avgDragFactor; + int recoveryHeartRate; + WorkoutType workoutType; + double avgPace; /// Construct a WorkoutSummary from the bytes returned from the erg WorkoutSummary.fromBytes(Uint8List data) : super(data) { @@ -106,6 +69,15 @@ class WorkoutSummary extends TimestampedData { } class WorkoutSummary2 { + IntervalType intervalType; + int intervalSize; + int intervalCount; + int totalCalories; + int watts; + int totalRestDistance; + int intervalRestTime; + int avgCalories; + WorkoutSummary2.fromBytes(Uint8List data) { // if (data.length > 20) { // var timestamp2 = Concept2DateExtension.fromBytes(data.sublist(20, 24)); From 20f45e7c9087fc6e4fcc9e7debcb31f98d41ab1c Mon Sep 17 00:00:00 2001 From: Adrian Edwards <17362949+MoralCode@users.noreply.github.com> Date: Mon, 9 May 2022 21:10:30 -0400 Subject: [PATCH 05/21] syntax and stuff --- lib/data/workoutsummary.dart | 44 +++++++++++++++++------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/data/workoutsummary.dart b/lib/data/workoutsummary.dart index c32e62a..7b59790 100644 --- a/lib/data/workoutsummary.dart +++ b/lib/data/workoutsummary.dart @@ -7,13 +7,12 @@ import 'package:csafe_fitness/csafe_fitness.dart'; import '../helpers.dart'; import 'package:c2bluetooth/enums.dart'; - ///Represents a data packet from Concept2 that is stamped with a date. class TimestampedData { DateTime timestamp; - TimestampedData.fromBytes(Uint8List bytes): timestamp = Concept2DateExtension.fromBytes(bytes.sublist(0, 4)); - + TimestampedData.fromBytes(Uint8List bytes) + : timestamp = Concept2DateExtension.fromBytes(bytes.sublist(0, 4)); } ///Represents a data packet from Concept2 that is stamped with a duration. @@ -21,10 +20,10 @@ class TimestampedData { class DurationstampedData { Duration elapsedTime; - DurationstampedData.fromBytes(Uint8List data) : elapsedTime = Concept2DurationExtension.fromBytes(data.sublist(0, 3)); + DurationstampedData.fromBytes(Uint8List data) + : elapsedTime = Concept2DurationExtension.fromBytes(data.sublist(0, 3)); } - /// Represents a summary of a completed workout /// /// This takes care of processesing the raw byte data from workout summary characteristics into easily accessible fields. This class also takes care of things like byte endianness, combining multiple high and low bytes .etc, allowing applications to access things in terms of flutter native types. @@ -43,28 +42,28 @@ class WorkoutSummary extends TimestampedData { /// Construct a WorkoutSummary from the bytes returned from the erg WorkoutSummary.fromBytes(Uint8List data) : super(data) { - _workTime = + _workTime = CsafeIntExtension.fromBytes(data.sublist(4, 7), endian: Endian.little) / - 100); //divide by 100 to convert to seconds + 100; //divide by 100 to convert to seconds _workDistance = CsafeIntExtension.fromBytes(data.sublist(7, 10), endian: Endian.little) / - 10); //divide by 10 to convert to meters - _avgSPM = data.elementAt(10)); - _endHeartRate = data.elementAt(11)); - _avgHeartRate = data.elementAt(12)); - _minHeartRate = data.elementAt(13)); - _maxHeartRate = data.elementAt(14)); - _avgDragFactor = data.elementAt(15)); + 10; //divide by 10 to convert to meters + _avgSPM = data.elementAt(10); + _endHeartRate = data.elementAt(11); + _avgHeartRate = data.elementAt(12); + _minHeartRate = data.elementAt(13); + _maxHeartRate = data.elementAt(14); + _avgDragFactor = data.elementAt(15); //recovery heart rate here int recHRVal = data.elementAt(16); // 0 is not a valid value here according to the spec if (recHRVal > 0) { - _recoveryHeartRate = recHRVal); + _recoveryHeartRate = recHRVal; } - _workoutType = WorkoutTypeExtension.fromInt(data.elementAt(17))); + _workoutType = WorkoutTypeExtension.fromInt(data.elementAt(17)); _avgPace = CsafeIntExtension.fromBytes(data.sublist(18, 20), endian: Endian.little) / - 10); //{ + 10; } } @@ -85,17 +84,16 @@ class WorkoutSummary2 { // throw ArgumentError( // "Bytes passed to WorkoutSummary from multiple characteristics must have the same timestamp"); // } - + intervalType = IntervalTypeExtension.fromInt(data.elementAt(4)); - intervalSize = + intervalSize = CsafeIntExtension.fromBytes(data.sublist(5, 7), endian: Endian.little); intervalCount = data.elementAt(7); - totalCalories = CsafeIntExtension.fromBytes(data.sublist(8, 10), - endian: Endian.little); + totalCalories = + CsafeIntExtension.fromBytes(data.sublist(8, 10), endian: Endian.little); watts = CsafeIntExtension.fromBytes(data.sublist(10, 12), endian: Endian.little); - totalRestDistance = CsafeIntExtension.fromBytes( - data.sublist(12, 15), + totalRestDistance = CsafeIntExtension.fromBytes(data.sublist(12, 15), endian: Endian.little); intervalRestTime = CsafeIntExtension.fromBytes(data.sublist(15, 17), endian: Endian.little); From aaf75d7c65f0312ae64b71df5b8093c9bc59d162 Mon Sep 17 00:00:00 2001 From: Adrian Edwards <17362949+MoralCode@users.noreply.github.com> Date: Mon, 9 May 2022 21:10:46 -0400 Subject: [PATCH 06/21] use fromBytes in super call --- lib/data/workoutsummary.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/workoutsummary.dart b/lib/data/workoutsummary.dart index 7b59790..2c4a02f 100644 --- a/lib/data/workoutsummary.dart +++ b/lib/data/workoutsummary.dart @@ -41,7 +41,7 @@ class WorkoutSummary extends TimestampedData { double avgPace; /// Construct a WorkoutSummary from the bytes returned from the erg - WorkoutSummary.fromBytes(Uint8List data) : super(data) { + WorkoutSummary.fromBytes(Uint8List data) : super.fromBytes(data) { _workTime = CsafeIntExtension.fromBytes(data.sublist(4, 7), endian: Endian.little) / 100; //divide by 100 to convert to seconds From 8518ee8edf2eb9af7d23bcf7754ca69219ca4594 Mon Sep 17 00:00:00 2001 From: Adrian Edwards <17362949+MoralCode@users.noreply.github.com> Date: Wed, 18 May 2022 21:23:19 -0400 Subject: [PATCH 07/21] remove IdempotentCompleter helper --- lib/helpers.dart | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/helpers.dart b/lib/helpers.dart index 1ed7d9c..86109f9 100644 --- a/lib/helpers.dart +++ b/lib/helpers.dart @@ -91,17 +91,3 @@ String wattsToSplit(double watts) { var split = durationToSplit(Duration(milliseconds: millis)); return split; } - -extension IdempotentCompleter on Completer { - void completeIfNotAlready([FutureOr? value]) { - if (!this.isCompleted) { - this.complete(value); - } - } - - void completeErrorIfNotAlready(Object error, [StackTrace? stackTrace]) { - if (!this.isCompleted) { - this.completeError(error, stackTrace); - } - } -} From e8f8cb7c2fbabcb33a611ee9f65883437eb749c5 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sun, 20 Apr 2025 11:42:59 -0400 Subject: [PATCH 08/21] remove Futures Docs --- README.md | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index a8c4ed0..0ea9197 100644 --- a/README.md +++ b/README.md @@ -109,32 +109,28 @@ myErg.monitorForWorkoutSummary().listen((workoutSummary) { ``` Each workout summary is a flutter object that contains the following fields: -- `Future timestamp` -- `Future workTime` -- `Future workDistance` -- `Future avgSPM` -- `Future endHeartRate` -- `Future avgHeartRate` -- `Future minHeartRate` -- `Future maxHeartRate` -- `Future avgDragFactor` -- `Future recoveryHeartRate` -- `Future workoutType` -- `Future avgPace` -- `Future intervalType` -- `Future intervalSize` -- `Future intervalCount` -- `Future totalCalories` -- `Future watts` -- `Future totalRestDistance` -- `Future intervalRestTime` -- `Future avgCalories` - -Futures are handy here since the erg can send back different data at different times. The primary reason for this is the `recoveryHeartRate` field which the erg sends after the user has been resting for 1 minute. If the workout is cancelled or the erg is turned off before the end of this minute, the data may never arrive. See #10. - -Overall this method of accessing workout summary data is not the most ideal, and is likely to change later if a better solution is found. See #11. - - +- `timestamp` +- `workTime` +- `workDistance` +- `avgSPM` +- `endHeartRate` +- `avgHeartRate` +- `minHeartRate` +- `maxHeartRate` +- `avgDragFactor` +- `recoveryHeartRate` +- `workoutType` +- `avgPace` +- `intervalType` +- `intervalSize` +- `intervalCount` +- `totalCalories` +- `watts` +- `totalRestDistance` +- `intervalRestTime` +- `avgCalories` + +The `recoveryHeartRate` field is one that the erg sends after the rower has been resting for 1 minute. If the workout is cancelled or the erg is turned off before the end of this minute, the data may never arrive. See #10. This is likely to change later if a better solution is found. See #11. From 4fa22e633d610315bea752f79491b0779669f59f Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Fri, 18 Apr 2025 23:44:29 -0400 Subject: [PATCH 09/21] automatic android update from flutter --- example/android/build.gradle | 4 ++-- example/android/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/android/build.gradle b/example/android/build.gradle index 9b6ed06..01de4e2 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -24,6 +24,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { - delete rootProject.buildDir +tasks.register("clean", Delete) { + delete rootProject.layout.buildDirectory } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58a..cfe88f6 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip From 0fe350c6ab6e0595a58f46aef402aa8f19e6d3e6 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sun, 20 Apr 2025 12:26:06 -0400 Subject: [PATCH 10/21] finish reverting WorkoutSummary away from futures --- lib/data/workoutsummary.dart | 97 ++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/lib/data/workoutsummary.dart b/lib/data/workoutsummary.dart index 2c4a02f..b22039e 100644 --- a/lib/data/workoutsummary.dart +++ b/lib/data/workoutsummary.dart @@ -36,35 +36,43 @@ class WorkoutSummary extends TimestampedData { int minHeartRate; int maxHeartRate; int avgDragFactor; - int recoveryHeartRate; + late int recoveryHeartRate; WorkoutType workoutType; double avgPace; /// Construct a WorkoutSummary from the bytes returned from the erg - WorkoutSummary.fromBytes(Uint8List data) : super.fromBytes(data) { - _workTime = - CsafeIntExtension.fromBytes(data.sublist(4, 7), endian: Endian.little) / - 100; //divide by 100 to convert to seconds - _workDistance = CsafeIntExtension.fromBytes(data.sublist(7, 10), - endian: Endian.little) / - 10; //divide by 10 to convert to meters - _avgSPM = data.elementAt(10); - _endHeartRate = data.elementAt(11); - _avgHeartRate = data.elementAt(12); - _minHeartRate = data.elementAt(13); - _maxHeartRate = data.elementAt(14); - _avgDragFactor = data.elementAt(15); + WorkoutSummary.fromBytes(Uint8List data) + : workTime = CsafeIntExtension.fromBytes(data.sublist(4, 7), + endian: Endian.little) / + 100, //divide by 100 to convert to seconds + workDistance = CsafeIntExtension.fromBytes(data.sublist(7, 10), + endian: Endian.little) / + 10, //divide by 10 to convert to meters + avgSPM = data.elementAt(10), + endHeartRate = data.elementAt(11), + avgHeartRate = data.elementAt(12), + minHeartRate = data.elementAt(13), + maxHeartRate = data.elementAt(14), + avgDragFactor = data.elementAt(15), + workoutType = WorkoutTypeExtension.fromInt(data.elementAt(17)), + avgPace = CsafeIntExtension.fromBytes(data.sublist(18, 20), + endian: Endian.little) / + 10, + super.fromBytes(data) { //recovery heart rate here int recHRVal = data.elementAt(16); // 0 is not a valid value here according to the spec if (recHRVal > 0) { - _recoveryHeartRate = recHRVal; + recoveryHeartRate = recHRVal; } - _workoutType = WorkoutTypeExtension.fromInt(data.elementAt(17)); - _avgPace = CsafeIntExtension.fromBytes(data.sublist(18, 20), - endian: Endian.little) / - 10; } + + @override + String toString() => "WorkoutSummary (" + "Timestamp: $timestamp, " + "elapsedTime: $workTime, " + "distance: $workDistance, " + "avgSPM: $avgSPM)"; } class WorkoutSummary2 { @@ -77,34 +85,27 @@ class WorkoutSummary2 { int intervalRestTime; int avgCalories; - WorkoutSummary2.fromBytes(Uint8List data) { - // if (data.length > 20) { - // var timestamp2 = Concept2DateExtension.fromBytes(data.sublist(20, 24)); - // if (timestamp != timestamp2) { - // throw ArgumentError( - // "Bytes passed to WorkoutSummary from multiple characteristics must have the same timestamp"); - // } + WorkoutSummary2.fromBytes(Uint8List data) + : + // if (data.length > 20) { + // var timestamp2 = Concept2DateExtension.fromBytes(data.sublist(20, 24)); + // if (timestamp != timestamp2) { + // throw ArgumentError( + // "Bytes passed to WorkoutSummary from multiple characteristics must have the same timestamp"); + // } - intervalType = IntervalTypeExtension.fromInt(data.elementAt(4)); - intervalSize = - CsafeIntExtension.fromBytes(data.sublist(5, 7), endian: Endian.little); - intervalCount = data.elementAt(7); - totalCalories = - CsafeIntExtension.fromBytes(data.sublist(8, 10), endian: Endian.little); - watts = CsafeIntExtension.fromBytes(data.sublist(10, 12), - endian: Endian.little); - totalRestDistance = CsafeIntExtension.fromBytes(data.sublist(12, 15), - endian: Endian.little); - intervalRestTime = CsafeIntExtension.fromBytes(data.sublist(15, 17), - endian: Endian.little); - avgCalories = CsafeIntExtension.fromBytes(data.sublist(17, 19), - endian: Endian.little); - } - - @override - String toString() => "WorkoutSummary (" - "Timestamp: $timestamp, " - "elapsedTime: $workTime, " - "distance: $workDistance, " - "avgSPM: $avgSPM)"; + intervalType = IntervalTypeExtension.fromInt(data.elementAt(4)), + intervalSize = CsafeIntExtension.fromBytes(data.sublist(5, 7), + endian: Endian.little), + intervalCount = data.elementAt(7), + totalCalories = CsafeIntExtension.fromBytes(data.sublist(8, 10), + endian: Endian.little), + watts = CsafeIntExtension.fromBytes(data.sublist(10, 12), + endian: Endian.little), + totalRestDistance = CsafeIntExtension.fromBytes(data.sublist(12, 15), + endian: Endian.little), + intervalRestTime = CsafeIntExtension.fromBytes(data.sublist(15, 17), + endian: Endian.little), + avgCalories = CsafeIntExtension.fromBytes(data.sublist(17, 19), + endian: Endian.little) {} } From 3e2a4a498cca05db4d5dc476e22bc150b0856ea8 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sun, 20 Apr 2025 13:09:03 -0400 Subject: [PATCH 11/21] start using some dynamic values from the flutter SDK for build targets --- example/android/app/build.gradle | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 5a1c3ea..8c92756 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,7 +26,9 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 30 + // Any value starting with "flutter." get its value from + // the Flutter Gradle plugin. + compileSdk flutter.compileSdkVersion sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -35,8 +37,10 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.fresh_example" - minSdkVersion 18 - targetSdkVersion 30 + // You can update the following value to match your application needs. + minSdk flutter.minSdkVersion + targetSdk flutter.targetSdkVersion + // You can set these values in the property declaration or use a variable versionCode flutterVersionCode.toInteger() versionName flutterVersionName } From 4ddc3eb5c47d660788045fbbd3d7880536e7c08d Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sun, 20 Apr 2025 13:09:15 -0400 Subject: [PATCH 12/21] bump some versions to get the build to work --- example/android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/android/build.gradle b/example/android/build.gradle index 01de4e2..fc94ae1 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.20' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } From 575c18a20aaaf05849d14faff661eb4388a02618 Mon Sep 17 00:00:00 2001 From: Bertrand Esperou Date: Sun, 6 Apr 2025 15:43:27 +0000 Subject: [PATCH 13/21] Updating Manifest according to current state of flutter_reactive_ble --- example/android/app/src/main/AndroidManifest.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 99c6fb1..dab5e37 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,11 @@ + + + + + + @@ -9,7 +15,8 @@ android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:exported="true"> - From c1ec5ea9561e0b26e36a5204460c3dff11476b71 Mon Sep 17 00:00:00 2001 From: Aidan Lee Date: Mon, 7 Nov 2022 11:52:43 -0500 Subject: [PATCH 15/21] Fixed all iOS errors in example --- .idea/libraries/Dart_SDK.xml | 30 +++--- .idea/workspace.xml | 85 ++++++++++++----- c2bluetooth.iml | 9 ++ example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Flutter/Debug.xcconfig | 1 + example/ios/Flutter/Release.xcconfig | 1 + example/ios/Runner.xcodeproj/project.pbxproj | 93 +++++++++++++++++-- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../contents.xcworkspacedata | 3 + example/ios/Runner/Info.plist | 14 +++ example/lib/main.dart | 8 +- lib/models/ergblemanager.dart | 4 +- 12 files changed, 204 insertions(+), 48 deletions(-) diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml index e8943a4..91dae00 100644 --- a/.idea/libraries/Dart_SDK.xml +++ b/.idea/libraries/Dart_SDK.xml @@ -1,17 +1,25 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 5b3388c..50c2250 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,36 +1,79 @@ - - - - - - - - - - - - + + - - - - - + + + + + + + + + + + + + + + + + + + - - + + + - + + + + + + + - + + + + + + + + + 1667839562502 + + + + + + + + + + + + \ No newline at end of file diff --git a/c2bluetooth.iml b/c2bluetooth.iml index 6048a33..80f26f4 100644 --- a/c2bluetooth.iml +++ b/c2bluetooth.iml @@ -9,6 +9,15 @@ + + + + + + + + + diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9367d48..9625e10 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 11.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 4877c65..fb03af2 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,12 +3,13 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 71E0CED985FD1762BCF30DDE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 534834C7320293612BB51C2B /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -29,9 +30,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 05A57BCE264DB95F63255FBC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 534834C7320293612BB51C2B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -42,6 +45,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B8D80978C7D3224A89929ED7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + B8D95075A9661C60A4AD023F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +54,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 71E0CED985FD1762BCF30DDE /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1E4F1236A3F1929EB4383FE3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 534834C7320293612BB51C2B /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +86,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + F0E4278F0E8B08CD7FD47A28 /* Pods */, + 1E4F1236A3F1929EB4383FE3 /* Frameworks */, ); sourceTree = ""; }; @@ -98,6 +114,17 @@ path = Runner; sourceTree = ""; }; + F0E4278F0E8B08CD7FD47A28 /* Pods */ = { + isa = PBXGroup; + children = ( + B8D95075A9661C60A4AD023F /* Pods-Runner.debug.xcconfig */, + B8D80978C7D3224A89929ED7 /* Pods-Runner.release.xcconfig */, + 05A57BCE264DB95F63255FBC /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -105,12 +132,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + EBEC1DE42650B27D75C6CB9F /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + AECB0AC27820DCCC1649CDF1 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -127,7 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -197,6 +226,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + AECB0AC27820DCCC1649CDF1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + EBEC1DE42650B27D75C6CB9F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -272,7 +340,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -290,7 +358,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.example.freshExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -346,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -395,7 +466,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -414,7 +485,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.example.freshExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -433,7 +507,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.example.freshExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..3db53b6 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index f12a5c4..7c3eefe 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -41,5 +43,17 @@ UIViewControllerBasedStatusBarAppearance + NSBonjourServices + + _dartobservatory._tcp + + NSBluetoothAlwaysUsageDescription + The app uses bluetooth to find, connect and transfer data between different devices + NSBluetoothPeripheralUsageDescription + The app uses bluetooth to find, connect and transfer data between different devices + UIBackgroundModes + + bluetooth-central + diff --git a/example/lib/main.dart b/example/lib/main.dart index 97d8f46..d5fafb3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -49,12 +49,12 @@ class _SimpleErgViewState extends State { @override void initState() { super.initState(); - bleManager.init(); //ready to go! - - startScan(); + unawaited( + bleManager.init().then((_) => startScan()), + ); } - startScan() { + Future startScan() async { setState(() { displayText = "Start Scanning"; }); diff --git a/lib/models/ergblemanager.dart b/lib/models/ergblemanager.dart index ccc0a1b..919e131 100644 --- a/lib/models/ergblemanager.dart +++ b/lib/models/ergblemanager.dart @@ -6,8 +6,8 @@ class ErgBleManager { BleManager _manager = BleManager(); /// perform set up to get the Bluetooth client ready to scan for devices - void init() { - _manager.createClient(); + Future init() async { + await _manager.createClient(restoreStateIdentifier: "example-restore-state-identifier"); } /// Begin scanning for Ergs. From 312a98a02da2ec6e38168140d7ea4d61aa1eed66 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 21 Apr 2025 13:48:30 -0400 Subject: [PATCH 16/21] adjust unit tests for new WorkoutSummary Structure --- lib/data/workoutsummary.dart | 5 +-- test/workoutsummary_test.dart | 66 +++++++++++++++++------------------ 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/lib/data/workoutsummary.dart b/lib/data/workoutsummary.dart index b22039e..0ecb5f4 100644 --- a/lib/data/workoutsummary.dart +++ b/lib/data/workoutsummary.dart @@ -75,7 +75,7 @@ class WorkoutSummary extends TimestampedData { "avgSPM: $avgSPM)"; } -class WorkoutSummary2 { +class WorkoutSummary2 extends TimestampedData { IntervalType intervalType; int intervalSize; int intervalCount; @@ -107,5 +107,6 @@ class WorkoutSummary2 { intervalRestTime = CsafeIntExtension.fromBytes(data.sublist(15, 17), endian: Endian.little), avgCalories = CsafeIntExtension.fromBytes(data.sublist(17, 19), - endian: Endian.little) {} + endian: Endian.little), + super.fromBytes(data); } diff --git a/test/workoutsummary_test.dart b/test/workoutsummary_test.dart index 9832c59..6fdf89e 100644 --- a/test/workoutsummary_test.dart +++ b/test/workoutsummary_test.dart @@ -50,7 +50,7 @@ void main() { 0 ]; - List bothSets = basicBytes + extendedBytes; + // List bothSets = basicBytes + extendedBytes; test('can extract basic values from a workout summary byte list', () { final summary = WorkoutSummary.fromBytes(Uint8List.fromList(basicBytes)); @@ -65,14 +65,12 @@ void main() { expect(summary.avgDragFactor, 120); expect(summary.workoutType, WorkoutType.JUSTROW_SPLITS); expect(summary.avgPace, 10); - expect(summary.watts, null); }); - test( - 'can extract basic and extended values from a workout summary byte list', - () { - final summary = WorkoutSummary.fromBytes(Uint8List.fromList(bothSets)); - // expect(summary.timestamp, DateTime(2000, 0, 0, 0, 0)); + test('can extract extended values from a workout summary byte list', () { + final summary = + WorkoutSummary2.fromBytes(Uint8List.fromList(extendedBytes)); + expect(summary.timestamp, DateTime(2000, 0, 0, 0, 0)); expect(summary.intervalType, IntervalType.TIME); expect(summary.intervalSize, 255); expect(summary.intervalCount, 2); @@ -83,34 +81,34 @@ void main() { expect(summary.avgCalories, 100); }); - test('fails if it receives two different datetime values', () { - List modifiedDateBytes = [ - 42, - 0, - 0, - 0, - 0, - 255, - 0, - 2, - 34, - 0, - 196, - 0, - 72, - 0, - 0, - 55, - 0, - 100, - 0 - ]; + // test('fails if it receives two different datetime values', () { + // List modifiedDateBytes = [ + // 42, + // 0, + // 0, + // 0, + // 0, + // 255, + // 0, + // 2, + // 34, + // 0, + // 196, + // 0, + // 72, + // 0, + // 0, + // 55, + // 0, + // 100, + // 0 + // ]; - Uint8List differentSets = - Uint8List.fromList(basicBytes + modifiedDateBytes); + // Uint8List differentSets = + // Uint8List.fromList(basicBytes + modifiedDateBytes); - expect( - () => WorkoutSummary.fromBytes(differentSets), throwsArgumentError); - }); + // expect( + // () => WorkoutSummary.fromBytes(differentSets), throwsArgumentError); + // }); }); } From 7235101753bd14fbc6b6b43abba811194f4cd610 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 21 Apr 2025 13:50:35 -0400 Subject: [PATCH 17/21] ensure that warnings and infos don't cause the analysis to fail --- .github/workflows/dart.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 852d1d3..0f6dc50 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -37,7 +37,7 @@ jobs: # Consider passing '--fatal-infos' for slightly stricter analysis. - name: Analyze project source - run: flutter analyze + run: flutter analyze --no-fatal-infos --no-fatal-warnings # Your project will need to have tests in test/ and a dependency on # package:test for this step to succeed. Note that Flutter projects will From 152645636eda567233c6bd571304d834bbcc1a81 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 21 Apr 2025 13:56:00 -0400 Subject: [PATCH 18/21] fix analysis warning by constraining lower end of compatibility --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 5326386..f945a96 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.1.6 repository: https://github.com/CrewLAB/c2bluetooth environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.15.0 <3.0.0" flutter: ">=1.17.0" dependencies: From 06a884db1cfc0543a72ee56b8d80b10e3f1ab0a6 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 21 Apr 2025 19:55:28 -0400 Subject: [PATCH 19/21] is it checking out something wrong? --- .github/workflows/dart.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 0f6dc50..4f5f8b1 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 # Note: This workflow uses the latest stable version of the Dart SDK. # You can specify other versions if desired, see documentation here: From 69ded72e84cc7a2d7e444b62fedbddef146e05c1 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Tue, 22 Apr 2025 08:49:28 -0400 Subject: [PATCH 20/21] try specific versions --- .github/workflows/dart.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 4f5f8b1..e2b8fc2 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -21,12 +21,14 @@ jobs: # Note: This workflow uses the latest stable version of the Dart SDK. # You can specify other versions if desired, see documentation here: # https://github.com/dart-lang/setup-dart/blob/main/README.md - # - uses: dart-lang/setup-dart@v1 - - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 + - uses: dart-lang/setup-dart@v1 + with: + sdk: 3.1.3 - uses: subosito/flutter-action@v1 with: channel: 'stable' + flutter-version: 3.13.0 - name: Install dependencies run: flutter pub get From dca6ab0a2e93989a2702a8d918240391576e31a2 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Tue, 22 Apr 2025 10:06:10 -0400 Subject: [PATCH 21/21] Revert "update example for new futures api" This reverts commit 2cfa52bd7b4ecb7e1d8704cdf2de7309e2845180. --- example/lib/main.dart | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d5fafb3..ca643be 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -151,22 +151,10 @@ class _SimpleErgViewState extends State { }); targetDevice!.monitorForWorkoutSummary().listen((summary) { - print(summary); - //TODO: update this for futures - summary.workDistance.then((dist) { - setState(() { - displayText = "distance: $dist"; - }); - }); - summary.timestamp.then((time) { - setState(() { - displayText2 = "datetime: $time"; - }); - }); - summary.avgSPM.then((spm) { - setState(() { - displayText3 = "sr: $spm"; - }); + setState(() { + displayText = "distance: ${summary.workDistance}"; + displayText2 = "datetime: ${summary.timestamp}"; + displayText3 = "sr: ${summary.avgSPM}"; }); }); }