diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png index 7fa40868..33e1f166 100644 Binary files a/android/app/src/main/ic_launcher-playstore.png and b/android/app/src/main/ic_launcher-playstore.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_notification.png b/android/app/src/main/res/drawable-hdpi/ic_notification.png index 240f9d6a..0df0327f 100644 Binary files a/android/app/src/main/res/drawable-hdpi/ic_notification.png and b/android/app/src/main/res/drawable-hdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_notification.png b/android/app/src/main/res/drawable-mdpi/ic_notification.png index a9004702..440c81ea 100644 Binary files a/android/app/src/main/res/drawable-mdpi/ic_notification.png and b/android/app/src/main/res/drawable-mdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_notification.png b/android/app/src/main/res/drawable-xhdpi/ic_notification.png index a1144990..0d5cc3c8 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/ic_notification.png and b/android/app/src/main/res/drawable-xhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_notification.png b/android/app/src/main/res/drawable-xxhdpi/ic_notification.png index 6e1a6477..0a0f66bd 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/ic_notification.png and b/android/app/src/main/res/drawable-xxhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png b/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png index f81858fa..0050b0fd 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png and b/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index d52f4b94..5f088c9a 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index fdf309d0..5b1d1a09 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png index df559469..e647e99d 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 6d3b7c5c..f08cdfc6 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 65d9407e..5fa061ae 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 24fca47c..822e084a 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png index 9ce7c13e..92dc1c1a 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 39bdd583..9ff188ed 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 6cd12cb8..3ab5167d 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index 4c2390eb..fb259206 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png index ed982690..edeee826 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index c86855a5..10c5bf6b 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 923892fd..6aae00d2 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index b1f5ca5b..fc408102 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png index 552c4ffa..343ec92c 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index d36ac55a..fe371a41 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b42e2a69..5096454b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index 4b281bdd..94ae429f 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png index fb783119..3eab7c8b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 51da4b23..4691a7a9 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/build.yaml b/build.yaml new file mode 100644 index 00000000..7e57e2bf --- /dev/null +++ b/build.yaml @@ -0,0 +1,23 @@ +targets: + $default: + builders: + json_serializable: + options: + # Options configure how source code is generated for every + # `@JsonSerializable`-annotated class in the package. + # + # The default value for each is listed. + any_map: false + checked: false + constructor: "" + create_factory: true + create_field_map: false + create_json_keys: false + create_per_field_to_json: false + create_to_json: true + disallow_unrecognized_keys: false + explicit_to_json: true + field_rename: none + generic_argument_factories: false + ignore_unannotated: false + include_if_null: true \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 22ced1fe..a2977848 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index c86cf99d..b448eb99 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index e687d2ab..f7cb0542 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 2c3a85c6..362c3b03 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 4502d305..913ba027 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@2x.png index 60d18233..0bf61e57 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@3x.png index fde1c944..94a35439 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 328599de..15979746 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index ca518f05..bd946ac3 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index ca518f05..bd946ac3 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index ebc01b5b..d971cc0f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@2x.png index 582550ae..1141c397 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@3x.png index b42e2a69..5096454b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-68x68@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-68x68@2x.png index 41588c96..bdcff2b7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-68x68@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-68x68@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 04c7b62e..7402043e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index dad8bdd3..8ccbfa06 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-1024x1024@1x.png index 0e277b3e..702a5642 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@2x.png index 69e5dcd2..405e95c0 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@3x.png index 1b4510da..1b972cc2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@2x.png index 6af7e9ef..495b7103 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@3x.png index 5c152716..a57473d1 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@2x.png index 5d2606b9..0d03a332 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@3x.png index 3b6bc275..fbf91188 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@2x.png index c6019cd3..dc0cac98 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@3x.png index aebb7fb4..fe2edc5a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@2x.png index aebb7fb4..fe2edc5a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@3x.png index e2309f22..9fe4a1f6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@2x.png index 4bf36823..bd8a03cd 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@3x.png index b9163452..515a797e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-68x68@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-68x68@2x.png index 7d51431e..575a4632 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-68x68@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-68x68@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-76x76@2x.png index f7e40591..8062a912 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-83.5x83.5@2x.png index fe9e45d4..3c454728 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-1024x1024@1x.png index c0ef72f2..2add639d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@2x.png index a14ce2d5..f91c0eb8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@3x.png index 18a239a0..7afcaff0 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@2x.png index b0c566e9..c3f8b5d6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@3x.png index ba531ce6..69f61cf8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@2x.png index aa4f5ab5..12e82bc0 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@3x.png index 972cd5de..49fcee08 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@2x.png index 021cbf66..0e4c77ac 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@3x.png index d5c8c951..d36c090c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@2x.png index d5c8c951..d36c090c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@3x.png index 1e2748b0..b8f9d71c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@2x.png index 98c81821..fa8af245 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@3x.png index 4ea7d9fd..454f56e6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-68x68@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-68x68@2x.png index 962306bb..7f1f12a7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-68x68@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-68x68@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-76x76@2x.png index 7bfb59ce..92bc204c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-83.5x83.5@2x.png index 9ca0ac3d..740e4f0c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-83.5x83.5@2x.png differ diff --git a/lib/Backend/Bluetooth/bluetooth_manager_plus.dart b/lib/Backend/Bluetooth/bluetooth_manager_plus.dart index 13190270..d76f70ee 100644 --- a/lib/Backend/Bluetooth/bluetooth_manager_plus.dart +++ b/lib/Backend/Bluetooth/bluetooth_manager_plus.dart @@ -28,16 +28,6 @@ import 'bluetooth_utils.dart'; part 'bluetooth_manager_plus.g.dart'; -StreamSubscription? _onConnectionStateChangedStreamSubscription; -StreamSubscription? _onReadRssiStreamSubscription; -StreamSubscription? _onDiscoveredServicesStreamSubscription; -StreamSubscription? _onCharacteristicReceivedStreamSubscription; -StreamSubscription? _onServicesResetStreamSubscription; -StreamSubscription? _adapterStateStreamSubscription; -StreamSubscription>? _onScanResultsStreamSubscription; -StreamSubscription? _onMtuChanged; -StreamSubscription? _keepAliveStreamSubscription; - final _bluetoothPlusLogger = log.Logger('BluetoothPlus'); ValueNotifier isBluetoothEnabled = ValueNotifier(false); @@ -46,327 +36,338 @@ bool _didInitFlutterBluePlus = false; FlutterBluePlusMockable flutterBluePlus = FlutterBluePlusMockable(); @Riverpod(keepAlive: true) -Future initFlutterBluePlus(InitFlutterBluePlusRef ref) async { - if (_didInitFlutterBluePlus) { - return; - } - if (!await getBluetoothPermission(bluetoothLog)) { - ref.invalidateSelf(); - _bluetoothPlusLogger.info("Bluetooth permission not granted"); - return; - } - _didInitFlutterBluePlus = true; +class initFlutterBluePlus extends _$initFlutterBluePlus { + StreamSubscription? _onConnectionStateChangedStreamSubscription; + StreamSubscription? _onReadRssiStreamSubscription; + StreamSubscription? _onDiscoveredServicesStreamSubscription; + StreamSubscription? _onCharacteristicReceivedStreamSubscription; + StreamSubscription? _onServicesResetStreamSubscription; + StreamSubscription? _adapterStateStreamSubscription; + StreamSubscription>? _onScanResultsStreamSubscription; + StreamSubscription? _onMtuChanged; + StreamSubscription? _keepAliveStreamSubscription; - await flutterBluePlus.setLogLevel(LogLevel.warning, color: true); - // first, check if bluetooth is supported by your hardware - // Note: The platform is initialized on the first call to any FlutterBluePlus method. - if (await flutterBluePlus.isSupported == false) { - _bluetoothPlusLogger.info("Bluetooth not supported by this device"); - return; - } + @override + Future build() async { + if (!await getBluetoothPermission(bluetoothLog)) { + ref.invalidateSelf(); + _bluetoothPlusLogger.info("Bluetooth permission not granted"); + return; + } - // listen to *any device* connection state changes - _onConnectionStateChangedStreamSubscription = flutterBluePlus.events.onConnectionStateChanged.listen((event) async { - _bluetoothPlusLogger.info('${event.device.advName} ${event.connectionState}'); - BuiltMap knownDevices = ref.read(knownDevicesProvider); - BluetoothDevice bluetoothDevice = event.device; - BluetoothConnectionState bluetoothConnectionState = event.connectionState; - String deviceID = bluetoothDevice.remoteId.str; + _didInitFlutterBluePlus = true; - //final ISentrySpan transaction = Sentry.startTransaction('connectToDevice()', 'task'); - BaseDeviceDefinition? deviceDefinition = DeviceRegistry.getByName(bluetoothDevice.advName); - if (deviceDefinition == null) { - bluetoothLog.warning("Unknown device found: ${bluetoothDevice.advName}"); - //transaction.status = const SpanStatus.notFound(); - //transaction.finish(); + await flutterBluePlus.setLogLevel(LogLevel.warning, color: true); + // first, check if bluetooth is supported by your hardware + // Note: The platform is initialized on the first call to any FlutterBluePlus method. + if (await flutterBluePlus.isSupported == false) { + _bluetoothPlusLogger.info("Bluetooth not supported by this device"); return; } - BaseStoredDevice baseStoredDevice; - BaseStatefulDevice statefulDevice; - //get existing entry - if (knownDevices.containsKey(deviceID)) { - statefulDevice = knownDevices[deviceID]!; - baseStoredDevice = statefulDevice.baseStoredDevice; - if (statefulDevice.baseStoredDevice.conModePin.isEmpty) { + // listen to *any device* connection state changes + _onConnectionStateChangedStreamSubscription = flutterBluePlus.events.onConnectionStateChanged.listen((event) async { + _bluetoothPlusLogger.info('${event.device.advName} ${event.connectionState}'); + BuiltMap knownDevices = ref.read(knownDevicesProvider); + BluetoothDevice bluetoothDevice = event.device; + BluetoothConnectionState bluetoothConnectionState = event.connectionState; + String deviceID = bluetoothDevice.remoteId.str; + + //final ISentrySpan transaction = Sentry.startTransaction('connectToDevice()', 'task'); + BaseDeviceDefinition? deviceDefinition = DeviceRegistry.getByName(bluetoothDevice.advName); + if (deviceDefinition == null) { + bluetoothLog.warning("Unknown device found: ${bluetoothDevice.advName}"); + //transaction.status = const SpanStatus.notFound(); + //transaction.finish(); + return; + } + + BaseStoredDevice baseStoredDevice; + BaseStatefulDevice statefulDevice; + //get existing entry + if (knownDevices.containsKey(deviceID)) { + statefulDevice = knownDevices[deviceID]!; + baseStoredDevice = statefulDevice.baseStoredDevice; + if (statefulDevice.baseStoredDevice.conModePin.isEmpty) { + int code = Random().nextInt(899999) + 100000; + baseStoredDevice.conModePin = code.toString(); + Future(() => ref.read(knownDevicesProvider.notifier).add(statefulDevice)); + } + //transaction.setTag('Known Device', 'Yes'); + } else { + baseStoredDevice = BaseStoredDevice(deviceDefinition.uuid, deviceID, deviceDefinition.deviceType.color(ref: ref).value)..name = getNameFromBTName(deviceDefinition.btName); int code = Random().nextInt(899999) + 100000; baseStoredDevice.conModePin = code.toString(); + statefulDevice = BaseStatefulDevice(deviceDefinition, baseStoredDevice); + //transaction.setTag('Known Device', 'No'); Future(() => ref.read(knownDevicesProvider.notifier).add(statefulDevice)); } - //transaction.setTag('Known Device', 'Yes'); - } else { - baseStoredDevice = BaseStoredDevice(deviceDefinition.uuid, deviceID, deviceDefinition.deviceType.color(ref: ref).value)..name = getNameFromBTName(deviceDefinition.btName); - int code = Random().nextInt(899999) + 100000; - baseStoredDevice.conModePin = code.toString(); - statefulDevice = BaseStatefulDevice(deviceDefinition, baseStoredDevice); - //transaction.setTag('Known Device', 'No'); - Future(() => ref.read(knownDevicesProvider.notifier).add(statefulDevice)); - } - //transaction.setTag('Device Name', device.name); - statefulDevice.deviceConnectionState.value = event.connectionState == BluetoothConnectionState.connected ? ConnectivityState.connected : ConnectivityState.disconnected; - if (bluetoothConnectionState == BluetoothConnectionState.connected) { - bluetoothDevice.readRssi().catchError((e) => -1).onError( - (error, stackTrace) => -1, - ); - BaseDeviceDefinition? baseDeviceDefinition = DeviceRegistry.getByName(event.device.advName); - if (baseDeviceDefinition == null) { - return; - } - // The timer used for the time value on the battery level graph - statefulDevice.stopWatch.start(); - if (HiveProxy.getOrDefault(settings, keepAwake, defaultValue: keepAwakeDefault)) { - _bluetoothPlusLogger.fine('Enabling wakelock'); - WakelockPlus.enable(); - } - if (Platform.isAndroid) { - //start foreground service on device connected. Library handles duplicate start calls - _bluetoothPlusLogger - ..fine('Requesting notification permission') - ..finer('Requesting notification permission result${await Permission.notification.request()}'); // Used only for Foreground service - FlutterForegroundTask.init( - androidNotificationOptions: AndroidNotificationOptions( - channelId: 'foreground_service', - channelName: 'Gear Connected', - channelDescription: 'This notification appears when any gear is running.', - channelImportance: NotificationChannelImportance.LOW, - priority: NotificationPriority.LOW, + //transaction.setTag('Device Name', device.name); + statefulDevice.deviceConnectionState.value = event.connectionState == BluetoothConnectionState.connected ? ConnectivityState.connected : ConnectivityState.disconnected; + if (bluetoothConnectionState == BluetoothConnectionState.connected) { + bluetoothDevice.readRssi().catchError((e) => -1).onError( + (error, stackTrace) => -1, + ); + BaseDeviceDefinition? baseDeviceDefinition = DeviceRegistry.getByName(event.device.advName); + if (baseDeviceDefinition == null) { + return; + } + // The timer used for the time value on the battery level graph + statefulDevice.stopWatch.start(); + if (HiveProxy.getOrDefault(settings, keepAwake, defaultValue: keepAwakeDefault)) { + _bluetoothPlusLogger.fine('Enabling wakelock'); + WakelockPlus.enable(); + } + if (Platform.isAndroid) { + //start foreground service on device connected. Library handles duplicate start calls + _bluetoothPlusLogger + ..fine('Requesting notification permission') + ..finer('Requesting notification permission result${await Permission.notification.request()}'); // Used only for Foreground service + FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + channelId: 'foreground_service', + channelName: 'Gear Connected', + channelDescription: 'This notification appears when any gear is running.', + channelImportance: NotificationChannelImportance.LOW, + priority: NotificationPriority.LOW, + ), + iosNotificationOptions: const IOSNotificationOptions(), + foregroundTaskOptions: ForegroundTaskOptions( + eventAction: ForegroundTaskEventAction.nothing(), + )); + FlutterForegroundTask.startService( + notificationTitle: "Gear Connected", + notificationText: "Gear is connected to The Tail Company app", + notificationIcon: const NotificationIcon( + metaDataName: 'com.codel1417.tailApp.notificationIcon', ), - iosNotificationOptions: const IOSNotificationOptions(), - foregroundTaskOptions: ForegroundTaskOptions( - eventAction: ForegroundTaskEventAction.nothing(), - )); - FlutterForegroundTask.startService( - notificationTitle: "Gear Connected", - notificationText: "Gear is connected to The Tail Company app", - notificationIcon: const NotificationIcon( - metaDataName: 'com.codel1417.tailApp.notificationIcon', - ), - ); - FlutterForegroundTask.setOnLockScreenVisibility(true); - } - await event.device.discoverServices(); - } - if (bluetoothConnectionState == BluetoothConnectionState.disconnected) { - _bluetoothPlusLogger.info("Disconnected from device: ${bluetoothDevice.remoteId.str}"); - // We don't want to display the app review screen right away. We keep track of gear disconnects and after 5 we try to display the review dialog. - int count = HiveProxy.getOrDefault(settings, gearDisconnectCount, defaultValue: gearDisconnectCountDefault) + 1; - if (count > 5 && HiveProxy.getOrDefault(settings, hasDisplayedReview, defaultValue: hasDisplayedReviewDefault)!) { - HiveProxy.put(settings, shouldDisplayReview, true); - _bluetoothPlusLogger.finer('Setting shouldDisplayReview to true'); - } else if (count <= 5) { - HiveProxy.put(settings, gearDisconnectCount, count); - _bluetoothPlusLogger.finer('Setting gearDisconnectCount to $count'); + ); + FlutterForegroundTask.setOnLockScreenVisibility(true); + } + await event.device.discoverServices(); } - //ref.read(snackbarStreamProvider.notifier).add(SnackBar(content: Text("Disconnected from ${baseStatefulDevice.baseStoredDevice.name}"))); + if (bluetoothConnectionState == BluetoothConnectionState.disconnected) { + _bluetoothPlusLogger.info("Disconnected from device: ${bluetoothDevice.remoteId.str}"); + // We don't want to display the app review screen right away. We keep track of gear disconnects and after 5 we try to display the review dialog. + int count = HiveProxy.getOrDefault(settings, gearDisconnectCount, defaultValue: gearDisconnectCountDefault) + 1; + if (count > 5 && HiveProxy.getOrDefault(settings, hasDisplayedReview, defaultValue: hasDisplayedReviewDefault)!) { + HiveProxy.put(settings, shouldDisplayReview, true); + _bluetoothPlusLogger.finer('Setting shouldDisplayReview to true'); + } else if (count <= 5) { + HiveProxy.put(settings, gearDisconnectCount, count); + _bluetoothPlusLogger.finer('Setting gearDisconnectCount to $count'); + } + //ref.read(snackbarStreamProvider.notifier).add(SnackBar(content: Text("Disconnected from ${baseStatefulDevice.baseStoredDevice.name}"))); - // remove foreground service if no devices connected - int deviceCount = knownDevices.values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected).length; - bool lastDevice = deviceCount == 0; - if (lastDevice) { - _bluetoothPlusLogger.fine('Last gear detected'); - // Disable all triggers on last device - ref.read(triggerListProvider).where((element) => element.enabled).forEach( - (element) { - element.enabled = false; - }, - ); - _bluetoothPlusLogger.finer('Disabling wakelock'); - // stop wakelock if its started - WakelockPlus.disable(); - // Close foreground service - if (Platform.isAndroid) { - _bluetoothPlusLogger.finer('Stopping foreground service'); - FlutterForegroundTask.stopService(); + // remove foreground service if no devices connected + int deviceCount = knownDevices.values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected).length; + bool lastDevice = deviceCount == 0; + if (lastDevice) { + _bluetoothPlusLogger.fine('Last gear detected'); + // Disable all triggers on last device + ref.read(triggerListProvider).where((element) => element.enabled).forEach( + (element) { + element.enabled = false; + }, + ); + _bluetoothPlusLogger.finer('Disabling wakelock'); + // stop wakelock if its started + WakelockPlus.disable(); + // Close foreground service + if (Platform.isAndroid) { + _bluetoothPlusLogger.finer('Stopping foreground service'); + FlutterForegroundTask.stopService(); + } + } + // if the forget button was used, remove the device + if (knownDevices[bluetoothDevice.remoteId.str] != null && knownDevices[bluetoothDevice.remoteId.str]!.forgetOnDisconnect) { + _bluetoothPlusLogger.finer('forgetting about gear'); + ref.read(knownDevicesProvider.notifier).remove(bluetoothDevice.remoteId.str); } } - // if the forget button was used, remove the device - if (knownDevices[bluetoothDevice.remoteId.str] != null && knownDevices[bluetoothDevice.remoteId.str]!.forgetOnDisconnect) { - _bluetoothPlusLogger.finer('forgetting about gear'); - ref.read(knownDevicesProvider.notifier).remove(bluetoothDevice.remoteId.str); - } - } - }); - _onReadRssiStreamSubscription = flutterBluePlus.events.onReadRssi.listen((event) { - _bluetoothPlusLogger.info('${event.device.advName} RSSI:${event.rssi}'); - BaseStatefulDevice? statefulDevice = ref.read(knownDevicesProvider)[event.device.remoteId.str]; - statefulDevice?.rssi.value = event.rssi; - }, onError: (e, s) => _bluetoothPlusLogger.warning("Unable to read rssi: $e", e, s)); - _onMtuChanged = flutterBluePlus.events.onMtuChanged.listen((event) { - _bluetoothPlusLogger.info('${event.device.advName} MTU:${event.mtu}'); - BaseStatefulDevice? statefulDevice = ref.read(knownDevicesProvider)[event.device.remoteId.str]; - statefulDevice?.mtu.value = event.mtu; - }, onError: (e, s) => _bluetoothPlusLogger.warning("Unable to read mtu: $e", e, s)); - _onDiscoveredServicesStreamSubscription = flutterBluePlus.events.onDiscoveredServices.listen((event) async { - //_bluetoothPlusLogger.info('${event.device} ${event.services}'); - //Subscribes to all characteristics - for (BluetoothService service in event.services) { - BluetoothUartService? bluetoothUartService = uartServices.firstWhereOrNull( - (element) => element.bleDeviceService == service.serviceUuid.str, - ); - if (bluetoothUartService != null) { - BaseStatefulDevice? statefulDevice = ref.read(knownDevicesProvider)[event.device.remoteId.str]; - statefulDevice?.bluetoothUartService.value = bluetoothUartService; - } - for (BluetoothCharacteristic characteristic in service.characteristics) { - await characteristic.setNotifyValue(true); + }); + _onReadRssiStreamSubscription = flutterBluePlus.events.onReadRssi.listen((event) { + _bluetoothPlusLogger.info('${event.device.advName} RSSI:${event.rssi}'); + BaseStatefulDevice? statefulDevice = ref.read(knownDevicesProvider)[event.device.remoteId.str]; + statefulDevice?.rssi.value = event.rssi; + }, onError: (e, s) => _bluetoothPlusLogger.warning("Unable to read rssi: $e", e, s)); + _onMtuChanged = flutterBluePlus.events.onMtuChanged.listen((event) { + _bluetoothPlusLogger.info('${event.device.advName} MTU:${event.mtu}'); + BaseStatefulDevice? statefulDevice = ref.read(knownDevicesProvider)[event.device.remoteId.str]; + statefulDevice?.mtu.value = event.mtu; + }, onError: (e, s) => _bluetoothPlusLogger.warning("Unable to read mtu: $e", e, s)); + _onDiscoveredServicesStreamSubscription = flutterBluePlus.events.onDiscoveredServices.listen((event) async { + //_bluetoothPlusLogger.info('${event.device} ${event.services}'); + //Subscribes to all characteristics + for (BluetoothService service in event.services) { + BluetoothUartService? bluetoothUartService = uartServices.firstWhereOrNull( + (element) => element.bleDeviceService == service.serviceUuid.str, + ); + if (bluetoothUartService != null) { + BaseStatefulDevice? statefulDevice = ref.read(knownDevicesProvider)[event.device.remoteId.str]; + statefulDevice?.bluetoothUartService.value = bluetoothUartService; + } + for (BluetoothCharacteristic characteristic in service.characteristics) { + await characteristic.setNotifyValue(true); + } } - } - }, onError: (e, s) => _bluetoothPlusLogger.warning("Unable to discover services: $e", e, s)); - _onCharacteristicReceivedStreamSubscription = flutterBluePlus.events.onCharacteristicReceived.listen((event) async { - _bluetoothPlusLogger.info('onCharacteristicReceived ${event.device.advName} ${event.characteristic.uuid.str} ${event.value}'); + }, onError: (e, s) => _bluetoothPlusLogger.warning("Unable to discover services: $e", e, s)); + _onCharacteristicReceivedStreamSubscription = flutterBluePlus.events.onCharacteristicReceived.listen((event) async { + _bluetoothPlusLogger.info('onCharacteristicReceived ${event.device.advName} ${event.characteristic.uuid.str} ${event.value}'); - BluetoothDevice bluetoothDevice = event.device; - BluetoothCharacteristic bluetoothCharacteristic = event.characteristic; - List values = event.value; - BaseStatefulDevice? statefulDevice = ref.read(knownDevicesProvider)[bluetoothDevice.remoteId.str]; - // get Device object - // set value - if (statefulDevice == null) { - return; - } - if (bluetoothCharacteristic.characteristicUuid == Guid("2a19")) { - statefulDevice.batteryLevel.value = values.first.toDouble(); - } else if (bluetoothCharacteristic.characteristicUuid == Guid("5073792e-4fc0-45a0-b0a5-78b6c1756c91")) { - try { - String value = const Utf8Decoder().convert(values); - statefulDevice.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.receive, message: value)); - statefulDevice.batteryCharging.value = value == "CHARGE ON"; - } catch (e, s) { - _bluetoothPlusLogger.warning("Unable to read values: $values", e, s); - statefulDevice.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.receive, message: "Unknown: ${values.toString()}")); + BluetoothDevice bluetoothDevice = event.device; + BluetoothCharacteristic bluetoothCharacteristic = event.characteristic; + List values = event.value; + BaseStatefulDevice? statefulDevice = ref.read(knownDevicesProvider)[bluetoothDevice.remoteId.str]; + // get Device object + // set value + if (statefulDevice == null) { return; } - } else if (statefulDevice.bluetoothUartService.value != null || bluetoothCharacteristic.characteristicUuid == Guid(statefulDevice.bluetoothUartService.value!.bleRxCharacteristic)) { - String value = ""; - try { - value = const Utf8Decoder().convert(values); - } catch (e, s) { - _bluetoothPlusLogger.warning("Unable to read values: $values $e", e, s); - statefulDevice.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.receive, message: "Unknown: ${values.toString()}")); - return; - } - statefulDevice.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.receive, message: value)); - // Firmware Version - if (value.startsWith("VER")) { - statefulDevice.fwVersion.value = getVersionSemVer(value.substring(value.indexOf(" "))); - if ((statefulDevice.fwVersion.value.major >= 5 && statefulDevice.fwVersion.value.minor >= 9) || statefulDevice.fwVersion.value.major > 5) { - //TODO: read response - statefulDevice.commandQueue.addCommand( - BluetoothMessage( - message: "READNVS", - device: statefulDevice, - timestamp: DateTime.timestamp(), - ), - ); + if (bluetoothCharacteristic.characteristicUuid == Guid("2a19")) { + statefulDevice.batteryLevel.value = values.first.toDouble(); + } else if (bluetoothCharacteristic.characteristicUuid == Guid("5073792e-4fc0-45a0-b0a5-78b6c1756c91")) { + try { + String value = const Utf8Decoder().convert(values); + statefulDevice.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.receive, message: value)); + statefulDevice.batteryCharging.value = value == "CHARGE ON"; + } catch (e, s) { + _bluetoothPlusLogger.warning("Unable to read values: $values", e, s); + statefulDevice.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.receive, message: "Unknown: ${values.toString()}")); + return; } - await ref.read(hasOtaUpdateProvider(statefulDevice).future); - // Sent after VER message - } else if (value.startsWith("GLOWTIP")) { - String substring = value.substring(value.indexOf(" ")).trim(); - if (substring == 'TRUE') { - statefulDevice.hasGlowtip.value = GlowtipStatus.glowtip; - } else if (substring == 'FALSE') { - statefulDevice.hasGlowtip.value = GlowtipStatus.noGlowtip; + } else if (statefulDevice.bluetoothUartService.value != null || bluetoothCharacteristic.characteristicUuid == Guid(statefulDevice.bluetoothUartService.value!.bleRxCharacteristic)) { + String value = ""; + try { + value = const Utf8Decoder().convert(values); + } catch (e, s) { + _bluetoothPlusLogger.warning("Unable to read values: $values $e", e, s); + statefulDevice.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.receive, message: "Unknown: ${values.toString()}")); + return; } - } else if (value.contains("BUSY")) { - //statefulDevice.deviceState.value = DeviceState.busy; - } else if (value.contains("LOWBATT")) { - statefulDevice.batteryLow.value = true; - } else if (value.contains("ERR")) { - statefulDevice.gearReturnedError.value = true; - } else if (value.contains("SHUTDOWN BEGIN")) { - statefulDevice.deviceConnectionState.value = ConnectivityState.disconnected; - } else if (value.contains("HWVER") || value.contains("MITAIL") || value.contains("MINITAIL") || value.contains("FLUTTERWINGS")) { - // Hardware Version - statefulDevice.hwVersion.value = value.substring(value.indexOf(" ")); - await ref.read(hasOtaUpdateProvider(statefulDevice).future); - } else if (int.tryParse(value) != null) { - // Battery Level - statefulDevice.batteryLevel.value = int.parse(value).toDouble(); - } - } - }); - _onServicesResetStreamSubscription = flutterBluePlus.events.onServicesReset.listen((event) async { - _bluetoothPlusLogger.info("${event.device.advName} onServicesReset"); - await event.device.discoverServices(); - }); - // handle bluetooth on & off - // note: for iOS the initial state is typically BluetoothAdapterState.unknown - // note: if you have permissions issues you will get stuck at BluetoothAdapterState.unauthorized - _adapterStateStreamSubscription = flutterBluePlus.adapterState.listen((BluetoothAdapterState state) { - _bluetoothPlusLogger.info(state); - isBluetoothEnabled.value = state == BluetoothAdapterState.on; - }); - _onScanResultsStreamSubscription = flutterBluePlus.onScanResults.listen( - (results) async { - if (results.isNotEmpty) { - ScanResult r = results.last; // the most recently found device - _bluetoothPlusLogger.info('${r.device.remoteId}: "${r.advertisementData.advName}" found!'); - BuiltMap knownDevices = ref.read(knownDevicesProvider); - if (knownDevices.containsKey(r.device.remoteId.str) && knownDevices[r.device.remoteId.str]?.deviceConnectionState.value == ConnectivityState.disconnected && !knownDevices[r.device.remoteId.str]!.disableAutoConnect) { - knownDevices[r.device.remoteId.str]?.deviceConnectionState.value = ConnectivityState.connecting; - await connect(r.device.remoteId.str); + statefulDevice.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.receive, message: value)); + // Firmware Version + if (value.startsWith("VER")) { + statefulDevice.fwVersion.value = getVersionSemVer(value.substring(value.indexOf(" "))); + if ((statefulDevice.fwVersion.value.major >= 5 && statefulDevice.fwVersion.value.minor >= 9) || statefulDevice.fwVersion.value.major > 5) { + //TODO: read response + statefulDevice.commandQueue.addCommand( + BluetoothMessage( + message: "READNVS", + device: statefulDevice, + timestamp: DateTime.timestamp(), + ), + ); + } + await ref.read(hasOtaUpdateProvider(statefulDevice).future); + // Sent after VER message + } else if (value.startsWith("GLOWTIP")) { + String substring = value.substring(value.indexOf(" ")).trim(); + if (substring == 'TRUE') { + statefulDevice.hasGlowtip.value = GlowtipStatus.glowtip; + } else if (substring == 'FALSE') { + statefulDevice.hasGlowtip.value = GlowtipStatus.noGlowtip; + } + } else if (value.contains("BUSY")) { + //statefulDevice.deviceState.value = DeviceState.busy; + } else if (value.contains("LOWBATT")) { + statefulDevice.batteryLow.value = true; + } else if (value.contains("ERR")) { + statefulDevice.gearReturnedError.value = true; + } else if (value.contains("SHUTDOWN BEGIN")) { + statefulDevice.deviceConnectionState.value = ConnectivityState.disconnected; + } else if (value.contains("HWVER") || value.contains("MITAIL") || value.contains("MINITAIL") || value.contains("FLUTTERWINGS")) { + // Hardware Version + statefulDevice.hwVersion.value = value.substring(value.indexOf(" ")); + await ref.read(hasOtaUpdateProvider(statefulDevice).future); + } else if (int.tryParse(value) != null) { + // Battery Level + statefulDevice.batteryLevel.value = int.parse(value).toDouble(); } } - }, - onError: (e, s) => _bluetoothPlusLogger.severe("", e, s), - ); + }); + _onServicesResetStreamSubscription = flutterBluePlus.events.onServicesReset.listen((event) async { + _bluetoothPlusLogger.info("${event.device.advName} onServicesReset"); + await event.device.discoverServices(); + }); + // handle bluetooth on & off + // note: for iOS the initial state is typically BluetoothAdapterState.unknown + // note: if you have permissions issues you will get stuck at BluetoothAdapterState.unauthorized + _adapterStateStreamSubscription = flutterBluePlus.adapterState.listen((BluetoothAdapterState state) { + _bluetoothPlusLogger.info(state); + isBluetoothEnabled.value = state == BluetoothAdapterState.on; + }); + _onScanResultsStreamSubscription = flutterBluePlus.onScanResults.listen( + (results) async { + if (results.isNotEmpty) { + ScanResult r = results.last; // the most recently found device + _bluetoothPlusLogger.info('${r.device.remoteId}: "${r.advertisementData.advName}" found!'); + BuiltMap knownDevices = ref.read(knownDevicesProvider); + if (knownDevices.containsKey(r.device.remoteId.str) && knownDevices[r.device.remoteId.str]?.deviceConnectionState.value == ConnectivityState.disconnected && !knownDevices[r.device.remoteId.str]!.disableAutoConnect) { + knownDevices[r.device.remoteId.str]?.deviceConnectionState.value = ConnectivityState.connecting; + await connect(r.device.remoteId.str); + } + } + }, + onError: (e, s) => _bluetoothPlusLogger.severe("", e, s), + ); - _keepAliveStreamSubscription = Stream.periodic(const Duration(seconds: 15)).listen( - (event) async { - BuiltMap knownDevices = ref.read(knownDevicesProvider); - for (var element in flutterBluePlus.connectedDevices) { - BaseStatefulDevice? device = knownDevices[element.remoteId.str]; - if (device != null) { - device.commandQueue.addCommand(BluetoothMessage(message: "PING", device: device, priority: Priority.low, type: CommandType.system, timestamp: DateTime.now())); - device.commandQueue.addCommand(BluetoothMessage(message: "BATT", device: device, priority: Priority.low, type: CommandType.system, timestamp: DateTime.now())); - element.readRssi().catchError((e) => -1).onError( - (error, stackTrace) => -1, - ); + _keepAliveStreamSubscription = Stream.periodic(const Duration(seconds: 15)).listen( + (event) async { + BuiltMap knownDevices = ref.watch(knownDevicesProvider); + for (var element in flutterBluePlus.connectedDevices) { + BaseStatefulDevice? device = knownDevices[element.remoteId.str]; + if (device != null) { + device.commandQueue.addCommand(BluetoothMessage(message: "PING", device: device, priority: Priority.low, type: CommandType.system, timestamp: DateTime.now())); + device.commandQueue.addCommand(BluetoothMessage(message: "BATT", device: device, priority: Priority.low, type: CommandType.system, timestamp: DateTime.now())); + element.readRssi().catchError((e) => -1).onError( + (error, stackTrace) => -1, + ); - if (device.baseDeviceDefinition.deviceType != DeviceType.ears && device.hasGlowtip.value == GlowtipStatus.unknown) { - device.commandQueue.addCommand(BluetoothMessage(message: "VER", device: device, priority: Priority.low, type: CommandType.system, timestamp: DateTime.now())); + if (device.baseDeviceDefinition.deviceType != DeviceType.ears && device.hasGlowtip.value == GlowtipStatus.unknown) { + device.commandQueue.addCommand(BluetoothMessage(message: "VER", device: device, priority: Priority.low, type: CommandType.system, timestamp: DateTime.now())); + } } } - } - }, - cancelOnError: true, - ); + }, + cancelOnError: true, + ); - // Shut down bluetooth related things - ref.onDispose(() async { - stopScan(); - //Disconnect any gear - for (var element in flutterBluePlus.connectedDevices) { - await disconnect(element.remoteId.str); - } - //cancel streams - await _keepAliveStreamSubscription?.cancel(); - _keepAliveStreamSubscription = null; - await _onCharacteristicReceivedStreamSubscription?.cancel(); - _onCharacteristicReceivedStreamSubscription = null; - await _onConnectionStateChangedStreamSubscription?.cancel(); - _onConnectionStateChangedStreamSubscription = null; - await _onDiscoveredServicesStreamSubscription?.cancel(); - _onDiscoveredServicesStreamSubscription = null; - await _onMtuChanged?.cancel(); - _onMtuChanged = null; - await _adapterStateStreamSubscription?.cancel(); - _adapterStateStreamSubscription = null; - await _onScanResultsStreamSubscription?.cancel(); - _onScanResultsStreamSubscription = null; - await _onServicesResetStreamSubscription?.cancel(); - _onServicesResetStreamSubscription = null; - await _onReadRssiStreamSubscription?.cancel(); - _onReadRssiStreamSubscription = null; - // Mark all gear disconnected; - ref.read(knownDevicesProvider).forEach( - (key, value) => value.deviceConnectionState.value = ConnectivityState.disconnected, - ); - isBluetoothEnabled.value = false; - _didInitFlutterBluePlus = false; // Allow restarting ble stack - }); - ref.read(scanMonitorProvider); + // Shut down bluetooth related things + ref.onDispose(() async { + stopScan(); + //Disconnect any gear + for (var element in flutterBluePlus.connectedDevices) { + await disconnect(element.remoteId.str); + } + //cancel streams + await _keepAliveStreamSubscription?.cancel(); + _keepAliveStreamSubscription = null; + await _onCharacteristicReceivedStreamSubscription?.cancel(); + _onCharacteristicReceivedStreamSubscription = null; + await _onConnectionStateChangedStreamSubscription?.cancel(); + _onConnectionStateChangedStreamSubscription = null; + await _onDiscoveredServicesStreamSubscription?.cancel(); + _onDiscoveredServicesStreamSubscription = null; + await _onMtuChanged?.cancel(); + _onMtuChanged = null; + await _adapterStateStreamSubscription?.cancel(); + _adapterStateStreamSubscription = null; + await _onScanResultsStreamSubscription?.cancel(); + _onScanResultsStreamSubscription = null; + await _onServicesResetStreamSubscription?.cancel(); + _onServicesResetStreamSubscription = null; + await _onReadRssiStreamSubscription?.cancel(); + _onReadRssiStreamSubscription = null; + // Mark all gear disconnected; + ref.read(knownDevicesProvider).forEach( + (key, value) => value.deviceConnectionState.value = ConnectivityState.disconnected, + ); + isBluetoothEnabled.value = false; + _didInitFlutterBluePlus = false; // Allow restarting ble stack + }); + ref.read(scanMonitorProvider); + } } Future disconnect(String id) async { diff --git a/lib/Backend/Definitions/Device/device_definition.dart b/lib/Backend/Definitions/Device/device_definition.dart index 57c08631..86b13867 100644 --- a/lib/Backend/Definitions/Device/device_definition.dart +++ b/lib/Backend/Definitions/Device/device_definition.dart @@ -123,6 +123,8 @@ enum DeviceState { standby, runAction, busy } enum GlowtipStatus { glowtip, noGlowtip, unknown } +enum tailControlStatus { tailControl, legacy, unknown } + @freezed class BluetoothUartService with _$BluetoothUartService { const factory BluetoothUartService({ @@ -143,6 +145,7 @@ final List uartServices = const [ bleRxCharacteristic: "0b646a19-371e-4327-b169-9632d56c0e84", bleTxCharacteristic: "05e026d8-b395-4416-9f8a-c00d6c3781b9", ), + // TailCoNTROL uuids BluetoothUartService( bleDeviceService: "19F8ADE2-D0C6-4C0A-912A-30601D9B3060", bleRxCharacteristic: "5E4D86AC-EF2F-466F-A857-8776D45FFBC2", @@ -181,6 +184,8 @@ class BaseStatefulDevice { final ValueNotifier gearConfigInfo = ValueNotifier(GearConfigInfo()); final ValueNotifier fwInfo = ValueNotifier(null); final ValueNotifier hasUpdate = ValueNotifier(false); + final ValueNotifier isTailCoNTROL = ValueNotifier(tailControlStatus.unknown); + late final Stream rxCharacteristicStream; late final CommandQueue commandQueue; List batlevels = []; @@ -219,6 +224,22 @@ class BaseStatefulDevice { batlevels.add(FlSpot(stopWatch.elapsed.inSeconds.toDouble(), batteryLevel.value)); batteryLow.value = batteryLevel.value < 20; }); + + bluetoothUartService.addListener( + () { + if (bluetoothUartService.value == null) { + isTailCoNTROL.value = tailControlStatus.unknown; + return; + } + + isTailCoNTROL.value = bluetoothUartService.value == + uartServices.firstWhere( + (element) => element.bleDeviceService == "19F8ADE2-D0C6-4C0A-912A-30601D9B3060", + ) + ? tailControlStatus.tailControl + : tailControlStatus.legacy; + }, + ); } @override diff --git a/lib/Backend/action_registry.dart b/lib/Backend/action_registry.dart index c2b8d5a7..03373d3c 100644 --- a/lib/Backend/action_registry.dart +++ b/lib/Backend/action_registry.dart @@ -22,7 +22,7 @@ class ActionRegistry { CommandAction( name: "Slow 1", command: "TAILS1", - deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.calm, response: "TAILS1 END", uuid: "c53e980e-899e-4148-a13e-f57a8f9707f4", @@ -33,7 +33,7 @@ class ActionRegistry { CommandAction( name: "Slow 2", command: "TAILS2", - deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.calm, response: "TAILS2 END", uuid: "eb1bdfe7-d374-4e97-943a-13e89f27ddcd", @@ -44,7 +44,7 @@ class ActionRegistry { CommandAction( name: "Slow 3", command: "TAILS3", - deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.calm, response: "TAILS3 END", uuid: "6937b9af-3ff7-43fb-ae62-a403e5dfaf95", @@ -55,7 +55,7 @@ class ActionRegistry { CommandAction( name: "Fast", command: "TAILFA", - deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.fast, response: "TAILFA END", uuid: "a04b558f-0ad5-410f-8e39-8f5c594791d2", @@ -66,7 +66,7 @@ class ActionRegistry { CommandAction( name: "Short", command: "TAILSH", - deviceCategory: [DeviceType.tail, DeviceType.wings], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.ears], actionCategory: ActionCategory.fast, response: "TAILSH END", uuid: "05a4c47b-45ee-4da8-bec2-4a46f4e04a7f", @@ -77,7 +77,7 @@ class ActionRegistry { CommandAction( name: "Happy", command: "TAILHA", - deviceCategory: [DeviceType.tail, DeviceType.wings], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.ears], actionCategory: ActionCategory.fast, response: "TAILHA END", uuid: "86b13d13-b09c-46ba-a887-b40d8118b00a", @@ -88,7 +88,7 @@ class ActionRegistry { CommandAction( name: "Stand Up", command: "TAILER", - deviceCategory: [DeviceType.tail, DeviceType.wings], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.ears], actionCategory: ActionCategory.fast, response: "TAILER END", uuid: "5b04ca3d-a22a-4aff-8f40-99363248fcaa", @@ -99,7 +99,7 @@ class ActionRegistry { CommandAction( name: "Pulse", command: "TAILEP", - deviceCategory: [DeviceType.tail, DeviceType.wings], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.ears], actionCategory: ActionCategory.tense, response: "TAILEP END", uuid: "39bbe39d-aa92-4189-ac90-4bb821a59f5e", @@ -110,7 +110,7 @@ class ActionRegistry { CommandAction( name: "Tremble 1", command: "TAILT1", - deviceCategory: [DeviceType.tail, DeviceType.wings], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.ears], actionCategory: ActionCategory.tense, response: "TAILT1 END", uuid: "8cc3fc60-b8d2-4f22-810a-1e042d3984f7", @@ -121,7 +121,7 @@ class ActionRegistry { CommandAction( name: "Tremble 2", command: "TAILT2", - deviceCategory: [DeviceType.tail, DeviceType.wings], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.ears], actionCategory: ActionCategory.tense, response: "TAILT2 END", uuid: "123557a2-5489-43da-99e2-da37a36f055a", @@ -132,7 +132,7 @@ class ActionRegistry { CommandAction( name: "Tremble 3", command: "TAILET", - deviceCategory: [DeviceType.tail, DeviceType.wings], + deviceCategory: [DeviceType.tail, DeviceType.wings, DeviceType.ears], actionCategory: ActionCategory.tense, response: "TAILET END", uuid: "4909d4c2-0054-4f16-9589-6273ef6bf6c9", @@ -143,49 +143,49 @@ class ActionRegistry { CommandAction( name: "LEDs off", command: "LEDOFF", - deviceCategory: [DeviceType.tail, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.glowtip, uuid: "6b2a7fae-b58c-43f3-81bf-070aa21c2242", ), CommandAction( name: "Rectangle wave", command: "LEDREC", - deviceCategory: [DeviceType.tail, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.glowtip, uuid: "34269c91-90bd-4a34-851d-d49daa6ac863", ), CommandAction( name: "Triangle wave", command: "LEDTRI", - deviceCategory: [DeviceType.tail, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.glowtip, uuid: "64142e0b-4cc0-4b1e-845f-9c560875f993", ), CommandAction( name: "Sawtooth wave", command: "LEDSAW", - deviceCategory: [DeviceType.tail, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.glowtip, uuid: "047b84ad-3eb8-4d9c-b59b-13186cf965ca", ), CommandAction( name: "SOS", command: "LEDSOS", - deviceCategory: [DeviceType.tail, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.glowtip, uuid: "66164945-840f-4302-b27c-e7a7623bf475", ), CommandAction( name: "Beacon", command: "LEDBEA", - deviceCategory: [DeviceType.tail, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.glowtip, uuid: "4955a936-7703-4ce6-8d4a-b18857c0ea0a", ), CommandAction( name: "Flame", command: "LEDFLA", - deviceCategory: [DeviceType.tail, DeviceType.miniTail], + deviceCategory: [DeviceType.tail, DeviceType.miniTail, DeviceType.ears], actionCategory: ActionCategory.glowtip, uuid: "e46566b4-1071-4866-815b-1aefbf06b573", ), @@ -282,6 +282,9 @@ class GetAvailableActions extends _$GetAvailableActions { baseStatefulDevice.hasGlowtip ..removeListener(_listener) ..addListener(_listener); + baseStatefulDevice.isTailCoNTROL + ..removeListener(_listener) + ..addListener(_listener); } return getState(); } @@ -295,6 +298,21 @@ class GetAvailableActions extends _$GetAvailableActions { for (BaseStatefulDevice baseStatefulDevice in availableGear) { // check if command matches device type if (baseAction.deviceCategory.contains(baseStatefulDevice.baseDeviceDefinition.deviceType) && ((baseAction.actionCategory == ActionCategory.glowtip && baseStatefulDevice.hasGlowtip.value == GlowtipStatus.glowtip) || baseAction.actionCategory != ActionCategory.glowtip)) { + if (baseAction.deviceCategory.contains(DeviceType.ears)) { + // Handle migrating ears to unified firmware + if (baseStatefulDevice.isTailCoNTROL.value == tailControlStatus.tailControl) { + // skip legacy moves + if (baseAction is EarsMoveList) { + continue; + } + // skip unified moves for legacy firmware ears + } else if (baseStatefulDevice.isTailCoNTROL.value == tailControlStatus.legacy) { + if (baseAction is CommandAction) { + continue; + } + } + } + // get category if it exists if (sortedActions.containsKey(baseAction.actionCategory)) { baseActions = sortedActions[baseAction.actionCategory]; @@ -347,49 +365,6 @@ BuiltMap> getAllActions(GetAllActionsRef re ); } -@Riverpod(keepAlive: true) -BuiltMap> getAllActionsFiltered(GetAllActionsFilteredRef ref, BuiltSet deviceType) { - Map> sortedActions = {}; - final BuiltMap> read = ref.watch(getAllActionsProvider); - for (BaseAction baseAction in read.values.flattened) { - Set? baseActions = {}; - // check if command matches device type - if (baseAction.deviceCategory.toBuiltSet().intersection(deviceType).isNotEmpty) { - // get category if it exists - if (sortedActions.containsKey(baseAction.actionCategory)) { - baseActions = sortedActions[baseAction.actionCategory]; - } - // add action to category - baseActions?.add(baseAction); - } - // store result - if (baseActions != null && baseActions.isNotEmpty) { - sortedActions[baseAction.actionCategory] = baseActions; - } - } - return BuiltMap( - sortedActions.map( - (key, value) => MapEntry(key, value.build()), - ), - ); -} - -@Riverpod(keepAlive: true) -BuiltList getAllActionsForCategory(GetAllActionsForCategoryRef ref, ActionCategory actionCategory) { - final BuiltMap> allActions = ref.watch(getAllActionsProvider); - if (allActions.containsKey(actionCategory)) { - return allActions[actionCategory]!.toBuiltList(); - } - return BuiltList(); -} - -BuiltList splitBaseAction(BaseAction baseAction) { - if (baseAction.deviceCategory.length <= 1) { - return [baseAction].build(); - } - return BuiltList(); -} - @Riverpod(keepAlive: true) BaseAction? getActionFromUUID(GetActionFromUUIDRef ref, String? uuid) { if (uuid == null) { diff --git a/lib/Backend/move_lists.dart b/lib/Backend/move_lists.dart index 5a8f5ee4..7d29c63b 100644 --- a/lib/Backend/move_lists.dart +++ b/lib/Backend/move_lists.dart @@ -182,6 +182,7 @@ class MoveLists extends _$MoveLists { } } +//TODO: move the core running moves OUT of move_lists Future runAction(BaseAction action, BaseStatefulDevice device) async { //cursed handling of ears specifically //TODO: Remove with TAILCoNTROL update @@ -213,7 +214,7 @@ Future runAction(BaseAction action, BaseStatefulDevice device) async { } else if (action is MoveList) { sequencesLogger.info("Starting MoveList ${action.name}."); //plausible.event(name: "Run Sequence", props: {"Sequence Repeat": action.repeat.toInt().toString(), "Sequence Device Type": device.baseDeviceDefinition.deviceType.name, "Sequence Moves": action.moves.length.toString()}); - if (action.moves.isNotEmpty && action.moves.length <= 5 && device.baseDeviceDefinition.deviceType != DeviceType.ears) { + if (action.moves.isNotEmpty && action.moves.length <= 5 && (device.baseDeviceDefinition.deviceType != DeviceType.ears || device.isTailCoNTROL.value == tailControlStatus.tailControl)) { int preset = 1; //TODO: store String cmd = "USERMOVE U${preset}P${action.moves.length}N${action.repeat.toInt()}"; String a = ''; // servo 1 position @@ -282,7 +283,7 @@ List generateMoveCommand(Move move, BaseStatefulDevice device, List commands = []; if (move.moveType == MoveType.home) { //TODO: Remove for TAILCoNTROL update - if (device.baseDeviceDefinition.deviceType == DeviceType.ears) { + if (device.baseDeviceDefinition.deviceType == DeviceType.ears && device.isTailCoNTROL.value != tailControlStatus.tailControl) { commands.add(BluetoothMessage(message: "EARHOME", device: device, priority: priority, responseMSG: noResponseMsg ? null : "EARHOME END", type: type, timestamp: DateTime.now())); } else { commands.add(BluetoothMessage(message: "TAILHM", device: device, priority: priority, responseMSG: noResponseMsg ? null : "END TAILHM", type: type, timestamp: DateTime.now())); diff --git a/lib/Backend/sensors.dart b/lib/Backend/sensors.dart index 163ff11a..eb8f8f8a 100644 --- a/lib/Backend/sensors.dart +++ b/lib/Backend/sensors.dart @@ -205,7 +205,7 @@ abstract class TriggerDefinition extends ChangeNotifier implements Comparable allActionsMapped = triggerAction.actions.map((element) => ref.read(getActionFromUUIDProvider(element))).whereNotNull().toList(); + final List allActionsMapped = triggerAction.actions.map((element) => ref.read(getActionFromUUIDProvider(element))).nonNulls.toList(); // no moves exist if (allActionsMapped.isEmpty) { @@ -217,7 +217,13 @@ abstract class TriggerDefinition extends ChangeNotifier implements Comparable actionsToRun = []; - + // we need to handle legacy ears for now + bool hasLegacyEars = ref + .read(getAvailableIdleGearForTypeProvider([DeviceType.ears].toBuiltSet())) + .where( + (p0) => p0.isTailCoNTROL.value == tailControlStatus.legacy, + ) + .isNotEmpty; // add a glowtip action if it exists if (glowActions.isNotEmpty) { final BaseAction glowAction = glowActions[_random.nextInt(glowActions.length)]; @@ -234,16 +240,36 @@ abstract class TriggerDefinition extends ChangeNotifier implements Comparable missingGearAction = baseAction.deviceCategory.toSet().difference(baseAction.deviceCategory.toSet()); - final List remainingActions = moveActions - .where( - // Check if any actions contain the device type of the gear the first action is missing - (element) => element.deviceCategory.toSet().intersection(missingGearAction).isNotEmpty, + final Set missingGearAction = baseAction.deviceCategory + .whereNot( + // filtering out the first actions ears entry if its a unified move but legacy gear is connected + (element) => DeviceType.ears == element && baseAction is CommandAction && hasLegacyEars, ) - .toList(); + .toSet() + .difference(flattenedDeviceTypes); + final List remainingActions = moveActions.where( + // Check if any actions contain the device type of the gear the first action is missing + (element) { + if (baseAction is CommandAction && missingGearAction.contains(DeviceType.ears)) { + if (element is EarsMoveList) { + return true; + } else if (element is CommandAction) { + return false; + } + } else if (baseAction is EarsMoveList && missingGearAction.contains(DeviceType.ears)) { + if (element is CommandAction) { + return true; + } else if (element is EarsMoveList) { + return false; + } + } + return element.deviceCategory.toSet().intersection(missingGearAction).isNotEmpty; + }, + ).toList(); if (remainingActions.isNotEmpty) { final BaseAction otherAction = remainingActions[_random.nextInt(remainingActions.length)]; actionsToRun.add(otherAction); @@ -265,6 +291,12 @@ abstract class TriggerDefinition extends ChangeNotifier implements Comparable> applicationContext() { @Riverpod() Future updateWearActions(UpdateWearActionsRef ref) async { + if (await isWear()) { + return; + } try { Iterable allActions = ref .read(favoriteActionsProvider) @@ -79,7 +83,7 @@ Future updateWearActions(UpdateWearActionsRef ref) async { final WearData wearData = WearData(favoriteActions: favoriteMap, configuredTriggers: triggersMap, uiColor: HiveProxy.getOrDefault(settings, appColor, defaultValue: appColorDefault)); if (await isReachable()) { - //await _watch.updateApplicationContext(wearData.toJson()); + await _watch.updateApplicationContext(wearData.toJson()); } } catch (e, s) { _wearLogger.severe("Unable to send favorite actions to watch", e, s); diff --git a/lib/Frontend/Widgets/manage_gear.dart b/lib/Frontend/Widgets/manage_gear.dart index ef500db4..5577fbef 100644 --- a/lib/Frontend/Widgets/manage_gear.dart +++ b/lib/Frontend/Widgets/manage_gear.dart @@ -582,6 +582,26 @@ class _ManageGearDebugState extends State { .toList(), ), ), + ListTile( + title: const Text("isTailCoNTROL"), + trailing: DropdownMenu( + initialSelection: widget.device.isTailCoNTROL.value, + onSelected: (value) { + if (value != null) { + setState( + () { + widget.device.isTailCoNTROL.value = value; + }, + ); + } + }, + dropdownMenuEntries: tailControlStatus.values + .map( + (e) => DropdownMenuEntry(value: e, label: e.name), + ) + .toList(), + ), + ), ListTile( title: const Text("RSSI Level"), subtitle: Slider( diff --git a/lib/Frontend/pages/actions.dart b/lib/Frontend/pages/actions.dart index d5a0b89f..49dd2f56 100644 --- a/lib/Frontend/pages/actions.dart +++ b/lib/Frontend/pages/actions.dart @@ -56,7 +56,14 @@ class _ActionPageBuilderState extends ConsumerState { //TODO: Remove for TAILCoNTROL update AnimatedSwitcher( duration: animationTransitionDuration, - child: ref.watch(getAvailableGearForTypeProvider(BuiltSet([DeviceType.ears]))).isNotEmpty ? const EarSpeedWidget() : null, + child: ref + .watch(getAvailableGearForTypeProvider(BuiltSet([DeviceType.ears]))) + .where( + (p0) => p0.isTailCoNTROL.value == tailControlStatus.legacy, + ) + .isNotEmpty + ? const EarSpeedWidget() + : null, ), AnimatedCrossFade( firstChild: PageInfoCard( diff --git a/lib/main.dart b/lib/main.dart index bfbd8ec2..2bf01f8c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -24,6 +24,7 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_hive/sentry_hive.dart'; import 'package:sentry_logging/sentry_logging.dart'; +import 'Backend/Bluetooth/bluetooth_manager.dart'; import 'Backend/Definitions/Action/base_action.dart'; import 'Backend/Definitions/Device/device_definition.dart'; import 'Backend/app_shortcuts.dart'; @@ -436,10 +437,10 @@ class _EagerInitialization extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { // Eagerly initialize providers by watching them. // By using "watch", the provider will stay alive and not be disposed. - //ref.watch(knownDevicesProvider); - //ref.watch(triggerListProvider); - //ref.watch(moveListsProvider); - //ref.watch(favoriteActionsProvider); + ref.watch(knownDevicesProvider); + ref.watch(triggerListProvider); + ref.watch(moveListsProvider); + ref.watch(favoriteActionsProvider); ref.watch(appShortcutsProvider); if (kDebugMode) { ref.watch(initWearProvider); diff --git a/test/Backend/action_registry_test.dart b/test/Backend/action_registry_test.dart index d045f970..16e12241 100644 --- a/test/Backend/action_registry_test.dart +++ b/test/Backend/action_registry_test.dart @@ -78,40 +78,5 @@ void main() { var actions = container.read(getAllActionsProvider); expect(actions.length, 5); }); - test('Tail Actions', () { - final container = ProviderContainer( - overrides: [], - ); - var actions = container.read(getAllActionsFilteredProvider(BuiltSet({DeviceType.tail}))); - expect(actions.length, 4); - }); - test('Ear Actions', () { - final container = ProviderContainer( - overrides: [], - ); - var actions = container.read(getAllActionsFilteredProvider(BuiltSet({DeviceType.ears}))); - expect(actions.length, 1); - }); - test('Wings Actions', () { - final container = ProviderContainer( - overrides: [], - ); - var actions = container.read(getAllActionsFilteredProvider(BuiltSet({DeviceType.wings}))); - expect(actions.length, 3); - }); - test('Mini Tail Actions', () { - final container = ProviderContainer( - overrides: [], - ); - var actions = container.read(getAllActionsFilteredProvider(BuiltSet({DeviceType.miniTail}))); - expect(actions.length, 3); - }); - test('No Actions', () { - final container = ProviderContainer( - overrides: [], - ); - var actions = container.read(getAllActionsFilteredProvider(BuiltSet())); - expect(actions.length, 0); - }); }); }