aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--animations/LoadingAnimation.json1
-rw-r--r--assets/VoiceAssistantActive.svg56
-rw-r--r--assets/VoiceAssistantBottomSheet.svg9
-rw-r--r--assets/VoiceAssistantBottomSheetBg.pngbin0 -> 46359 bytes
-rw-r--r--assets/VoiceAssistantEnabled.svg57
-rw-r--r--assets/VoiceAssistantPressed.svg33
-rw-r--r--assets/VoiceControlButton.svg62
-rw-r--r--lib/core/constants/constants.dart3
-rw-r--r--lib/core/constants/users_path.dart6
-rw-r--r--lib/data/data_providers/app.dart6
-rw-r--r--lib/data/data_providers/app_config_provider.dart63
-rw-r--r--lib/data/data_providers/app_provider.dart17
-rw-r--r--lib/data/data_providers/initialize_settings.dart12
-rw-r--r--lib/data/data_providers/mpd_client.dart11
-rw-r--r--lib/data/data_providers/units_notifier.dart104
-rw-r--r--lib/data/data_providers/users_notifier.dart157
-rw-r--r--lib/data/data_providers/val_client.dart28
-rw-r--r--lib/data/data_providers/voice_agent_client.dart312
-rw-r--r--lib/data/data_providers/voice_assistant_notifier.dart148
-rw-r--r--lib/data/models/users.dart2
-rw-r--r--lib/data/models/voice_assistant_state.dart104
-rw-r--r--lib/export.dart1
-rw-r--r--lib/main.dart27
-rw-r--r--lib/presentation/common_widget/voice_assistant_button.dart214
-rw-r--r--lib/presentation/router/routes/routes.dart6
-rw-r--r--lib/presentation/screens/home/home.dart11
-rw-r--r--lib/presentation/screens/settings/settings_screens/date_time/date_time_screen.dart2
-rw-r--r--lib/presentation/screens/settings/settings_screens/units/units_screen.dart3
-rw-r--r--lib/presentation/screens/settings/settings_screens/version_info/version_info_screend.dart30
-rw-r--r--lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart28
-rw-r--r--lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart120
-rw-r--r--lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart251
-rw-r--r--lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart111
-rw-r--r--lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart102
-rw-r--r--lib/presentation/screens/settings/widgets/settings_content.dart10
-rw-r--r--protos/lib/src/generated/voice_agent/voice_agent.pb.dart880
-rw-r--r--protos/lib/src/generated/voice_agent/voice_agent.pbenum.dart138
-rw-r--r--protos/lib/src/generated/voice_agent/voice_agent.pbgrpc.dart167
-rw-r--r--protos/lib/src/generated/voice_agent/voice_agent.pbjson.dart326
-rw-r--r--protos/lib/val_api.dart5
-rw-r--r--protos/protos/voice_agent/voice_agent.proto120
-rw-r--r--pubspec.lock76
-rw-r--r--test/storageAPI_UnitsForUsers_test.dart286
43 files changed, 4006 insertions, 99 deletions
diff --git a/animations/LoadingAnimation.json b/animations/LoadingAnimation.json
new file mode 100644
index 0000000..b8a7d49
--- /dev/null
+++ b/animations/LoadingAnimation.json
@@ -0,0 +1 @@
+{"nm":"LoadingAnimation","ddd":0,"h":40,"w":277,"meta":{"g":"LottieFiles Figma v67"},"layers":[{"ty":0,"nm":"voiceAnimation","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[138.5,20]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[138.5,20]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"w":277,"h":40,"refId":"1","ind":1}],"v":"5.7.0","fr":60,"op":78.18,"ip":0,"assets":[{"nm":"[Asset] voiceAnimation","id":"1","layers":[{"ty":0,"nm":"Frame 3","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[203.5,20],"t":78},{"s":[203.5,20],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"w":277,"h":40,"refId":"2","ind":1},{"ty":0,"nm":"Frame 2","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[143.5,20],"t":78},{"s":[143.5,20],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"w":277,"h":40,"refId":"3","ind":2},{"ty":0,"nm":"Frame 1","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[83.5,20],"t":78},{"s":[83.5,20],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"w":277,"h":40,"refId":"4","ind":3},{"ty":4,"nm":"voiceAnimation Bg","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[138.5,20],"t":78},{"s":[138.5,20],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[138.5,20],"t":78},{"s":[138.5,20],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":78},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":84}]}}],"ind":4}]},{"nm":"[Asset] Frame 3","id":"2","layers":[{"ty":4,"nm":"circle3","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[10,10],"t":78},{"s":[10,10],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15.5,15],"t":78},{"s":[15.5,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"ef":[{"ty":25,"nm":"","en":1,"ef":[{"ty":2,"nm":"color","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.7687,0.1138,1],"t":78},{"s":[0.7687,0.1138,1],"t":84}]}},{"ty":0,"nm":"opacity","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[256],"t":78},{"s":[256],"t":84}]}},{"ty":1,"nm":"angle","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[90],"t":78},{"s":[90],"t":84}]}},{"ty":0,"nm":"distance","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]}},{"ty":0,"nm":"blur","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[8],"t":78},{"s":[8],"t":84}]}}]}],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-8.28],[0,0],[8.28,0],[0,0],[0,8.28],[0,0],[-8.28,0],[0,0]],"o":[[0,0],[0,8.28],[0,0],[-8.28,0],[0,0],[0,-8.28],[0,0],[8.28,0]],"v":[[30,15],[30,15],[15,30],[15,30],[0,15],[0,15],[15,0],[15,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":78},{"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":84}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.7693,0.1125,1],"t":78},{"s":[0.7693,0.1125,1],"t":84}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}}],"ind":1},{"ty":4,"nm":"Frame 3 Bg","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":78},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":84}]}}],"ind":2}]},{"nm":"[Asset] Frame 2","id":"3","layers":[{"ty":4,"nm":"circle2","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[10,10],"t":78},{"s":[10,10],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15.5,15],"t":78},{"s":[15.5,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"ef":[{"ty":25,"nm":"","en":1,"ef":[{"ty":2,"nm":"color","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.2314,0.3569,1],"t":78},{"s":[0.2314,0.3569,1],"t":84}]}},{"ty":0,"nm":"opacity","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[256],"t":78},{"s":[256],"t":84}]}},{"ty":1,"nm":"angle","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[90],"t":78},{"s":[90],"t":84}]}},{"ty":0,"nm":"distance","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]}},{"ty":0,"nm":"blur","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[8],"t":78},{"s":[8],"t":84}]}}]}],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-8.28],[0,0],[8.28,0],[0,0],[0,8.28],[0,0],[-8.28,0],[0,0]],"o":[[0,0],[0,8.28],[0,0],[-8.28,0],[0,0],[0,-8.28],[0,0],[8.28,0]],"v":[[30,15],[30,15],[15,30],[15,30],[0,15],[0,15],[15,0],[15,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":78},{"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":84}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.2334,0.356,1],"t":78},{"s":[0.2334,0.356,1],"t":84}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}}],"ind":1},{"ty":4,"nm":"Frame 2 Bg","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":78},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":84}]}}],"ind":2}]},{"nm":"[Asset] Frame 1","id":"4","layers":[{"ty":4,"nm":"circle1","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[9,9],"t":78},{"s":[9,9],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15.5,15],"t":78},{"s":[15.5,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"ef":[{"ty":25,"nm":"","en":1,"ef":[{"ty":2,"nm":"color","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.0236,0.9412,1],"t":78},{"s":[0.0236,0.9412,1],"t":84}]}},{"ty":0,"nm":"opacity","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[256],"t":78},{"s":[256],"t":84}]}},{"ty":1,"nm":"angle","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[90],"t":78},{"s":[90],"t":84}]}},{"ty":0,"nm":"distance","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]}},{"ty":0,"nm":"blur","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[8],"t":78},{"s":[8],"t":84}]}}]}],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-8.28],[0,0],[8.28,0],[0,0],[0,8.28],[0,0],[-8.28,0],[0,0]],"o":[[0,0],[0,8.28],[0,0],[-8.28,0],[0,0],[0,-8.28],[0,0],[8.28,0]],"v":[[30,15],[30,15],[15,30],[15,30],[0,15],[0,15],[15,0],[15,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":78},{"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":84}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.0251,0.9415,1],"t":78},{"s":[0.0251,0.9415,1],"t":84}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}}],"ind":1},{"ty":4,"nm":"Frame 1 Bg","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":78},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":84}]}}],"ind":2}]}]}
diff --git a/assets/VoiceAssistantActive.svg b/assets/VoiceAssistantActive.svg
new file mode 100644
index 0000000..3589294
--- /dev/null
+++ b/assets/VoiceAssistantActive.svg
@@ -0,0 +1,56 @@
+<svg width="86" height="86" viewBox="0 0 86 86" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_1_1652)">
+<rect x="2" y="1" width="80" height="80" rx="40" fill="url(#paint0_linear_1_1652)" shape-rendering="crispEdges"/>
+<rect x="2" y="1" width="80" height="80" rx="40" fill="url(#paint1_linear_1_1652)" shape-rendering="crispEdges"/>
+<rect x="2.5" y="1.5" width="79" height="79" rx="39.5" stroke="url(#paint2_linear_1_1652)" shape-rendering="crispEdges"/>
+<g filter="url(#filter1_i_1_1652)">
+<rect x="10" y="9" width="64" height="64" rx="32" fill="url(#paint3_radial_1_1652)" fill-opacity="0.5"/>
+<rect x="10.5" y="9.5" width="63" height="63" rx="31.5" stroke="url(#paint4_linear_1_1652)"/>
+<path d="M34 30.3333C34 25.915 37.5817 22.3333 42 22.3333C46.4183 22.3333 50 25.915 50 30.3333V35.6666C50 40.0849 46.4183 43.6666 42 43.6666C37.5817 43.6666 34 40.0849 34 35.6666L34 30.3333Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M29.3703 34.5072C30.4953 34.5072 31.4073 35.423 31.4073 36.5527C31.4073 42.4271 36.1498 47.6666 41.9999 47.6666C47.85 47.6666 52.5925 42.4271 52.5925 36.5527C52.5925 35.423 53.5045 34.5072 54.6295 34.5072C55.7546 34.5072 56.6666 35.423 56.6666 36.5527C56.6666 43.9923 51.8026 50.671 44.6666 51.6666V57C44.6666 58.3333 43.3333 59.6666 41.9999 59.6666C40.6666 59.6666 39.3333 58.3333 39.3333 57V51.6666C32.1972 50.671 27.3333 43.9923 27.3333 36.5527C27.3333 35.423 28.2453 34.5072 29.3703 34.5072Z" fill="white"/>
+</g>
+</g>
+<defs>
+<filter id="filter0_d_1_1652" x="0" y="0" width="86" height="86" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="1" dy="2"/>
+<feGaussianBlur stdDeviation="1.5"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_1652"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_1652" result="shape"/>
+</filter>
+<filter id="filter1_i_1_1652" x="10" y="9" width="65" height="66" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="1" dy="2"/>
+<feGaussianBlur stdDeviation="8"/>
+<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
+<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1_1652"/>
+</filter>
+<linearGradient id="paint0_linear_1_1652" x1="2" y1="41" x2="82" y2="41" gradientUnits="userSpaceOnUse">
+<stop offset="0.0001" stop-color="#1C7692"/>
+<stop offset="1" stop-color="#1A727E"/>
+</linearGradient>
+<linearGradient id="paint1_linear_1_1652" x1="2" y1="54.3334" x2="82.0274" y2="54.1669" gradientUnits="userSpaceOnUse">
+<stop stop-color="#2998FF"/>
+<stop offset="1" stop-color="#29CCFF" stop-opacity="0.7"/>
+</linearGradient>
+<linearGradient id="paint2_linear_1_1652" x1="2" y1="1" x2="96.7074" y2="26.6089" gradientUnits="userSpaceOnUse">
+<stop stop-color="#C1D8FF"/>
+<stop offset="1" stop-color="#C1D8FF" stop-opacity="0.25"/>
+</linearGradient>
+<radialGradient id="paint3_radial_1_1652" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(42 41) rotate(90) scale(32)">
+<stop stop-color="#0C1039"/>
+<stop offset="1"/>
+<stop offset="1"/>
+</radialGradient>
+<linearGradient id="paint4_linear_1_1652" x1="10" y1="9" x2="85.7659" y2="29.4871" gradientUnits="userSpaceOnUse">
+<stop stop-color="#C1D8FF"/>
+<stop offset="1" stop-color="#C1D8FF" stop-opacity="0.25"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/assets/VoiceAssistantBottomSheet.svg b/assets/VoiceAssistantBottomSheet.svg
new file mode 100644
index 0000000..cde9fbc
--- /dev/null
+++ b/assets/VoiceAssistantBottomSheet.svg
@@ -0,0 +1,9 @@
+<svg width="774" height="224" viewBox="0 0 774 224" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="774" height="224" fill="url(#paint0_linear_7_673)"/>
+<defs>
+<linearGradient id="paint0_linear_7_673" x1="387" y1="0" x2="387" y2="224" gradientUnits="userSpaceOnUse">
+<stop stop-color="#0C1039" stop-opacity="0"/>
+<stop offset="0.5" stop-opacity="0.95"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/assets/VoiceAssistantBottomSheetBg.png b/assets/VoiceAssistantBottomSheetBg.png
new file mode 100644
index 0000000..059864a
--- /dev/null
+++ b/assets/VoiceAssistantBottomSheetBg.png
Binary files differ
diff --git a/assets/VoiceAssistantEnabled.svg b/assets/VoiceAssistantEnabled.svg
new file mode 100644
index 0000000..6b5a83d
--- /dev/null
+++ b/assets/VoiceAssistantEnabled.svg
@@ -0,0 +1,57 @@
+<svg width="86" height="86" viewBox="0 0 86 86" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_1_1648)">
+<rect x="2" y="1" width="80" height="80" rx="40" fill="url(#paint0_linear_1_1648)" shape-rendering="crispEdges"/>
+<rect x="2" y="1" width="80" height="80" rx="40" fill="url(#paint1_linear_1_1648)" fill-opacity="0.15" shape-rendering="crispEdges"/>
+<rect x="2.5" y="1.5" width="79" height="79" rx="39.5" stroke="url(#paint2_linear_1_1648)" shape-rendering="crispEdges"/>
+<g filter="url(#filter1_d_1_1648)">
+<path d="M34 30.3333C34 25.915 37.5817 22.3333 42 22.3333C46.4183 22.3333 50 25.915 50 30.3333V35.6666C50 40.0849 46.4183 43.6666 42 43.6666C37.5817 43.6666 34 40.0849 34 35.6666L34 30.3333Z" fill="white"/>
+</g>
+<g filter="url(#filter2_d_1_1648)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M29.3703 34.5072C30.4953 34.5072 31.4073 35.423 31.4073 36.5527C31.4073 42.4271 36.1498 47.6666 41.9999 47.6666C47.85 47.6666 52.5925 42.4271 52.5925 36.5527C52.5925 35.423 53.5045 34.5072 54.6295 34.5072C55.7546 34.5072 56.6666 35.423 56.6666 36.5527C56.6666 43.9923 51.8026 50.671 44.6666 51.6666V57C44.6666 58.3333 43.3333 59.6666 41.9999 59.6666C40.6666 59.6666 39.3333 58.3333 39.3333 57V51.6666C32.1972 50.671 27.3333 43.9923 27.3333 36.5527C27.3333 35.423 28.2453 34.5072 29.3703 34.5072Z" fill="white"/>
+</g>
+</g>
+<defs>
+<filter id="filter0_d_1_1648" x="0" y="0" width="86" height="86" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="1" dy="2"/>
+<feGaussianBlur stdDeviation="1.5"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_1648"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_1648" result="shape"/>
+</filter>
+<filter id="filter1_d_1_1648" x="32" y="21.3333" width="22" height="27.3333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="1" dy="2"/>
+<feGaussianBlur stdDeviation="1.5"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_1648"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_1648" result="shape"/>
+</filter>
+<filter id="filter2_d_1_1648" x="25.3333" y="33.5072" width="35.3333" height="31.1594" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="1" dy="2"/>
+<feGaussianBlur stdDeviation="1.5"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_1648"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_1648" result="shape"/>
+</filter>
+<linearGradient id="paint0_linear_1_1648" x1="2" y1="41" x2="82" y2="41" gradientUnits="userSpaceOnUse">
+<stop stop-color="#009DCE"/>
+<stop offset="1" stop-color="#005D7A"/>
+</linearGradient>
+<linearGradient id="paint1_linear_1_1648" x1="2" y1="54.3334" x2="82.0274" y2="54.1669" gradientUnits="userSpaceOnUse">
+<stop stop-color="#2962FF"/>
+<stop offset="1" stop-color="#2962FF" stop-opacity="0.5"/>
+</linearGradient>
+<linearGradient id="paint2_linear_1_1648" x1="2" y1="1" x2="96.7074" y2="26.6089" gradientUnits="userSpaceOnUse">
+<stop stop-color="#29CCFF"/>
+<stop offset="1" stop-color="#29F2FF" stop-opacity="0.2"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/assets/VoiceAssistantPressed.svg b/assets/VoiceAssistantPressed.svg
new file mode 100644
index 0000000..7ac4e33
--- /dev/null
+++ b/assets/VoiceAssistantPressed.svg
@@ -0,0 +1,33 @@
+<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_i_1_1650)">
+<rect width="80" height="80" rx="40" fill="url(#paint0_linear_1_1650)"/>
+<rect width="80" height="80" rx="40" fill="url(#paint1_linear_1_1650)" fill-opacity="0.5"/>
+<rect x="0.5" y="0.5" width="79" height="79" rx="39.5" stroke="url(#paint2_linear_1_1650)"/>
+<path d="M32 29.3333C32 24.915 35.5817 21.3333 40 21.3333C44.4183 21.3333 48 24.915 48 29.3333V34.6666C48 39.0849 44.4183 42.6666 40 42.6666C35.5817 42.6666 32 39.0849 32 34.6666L32 29.3333Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M27.3703 33.5072C28.4953 33.5072 29.4073 34.423 29.4073 35.5527C29.4073 41.4271 34.1498 46.6666 39.9999 46.6666C45.85 46.6666 50.5925 41.4271 50.5925 35.5527C50.5925 34.423 51.5045 33.5072 52.6295 33.5072C53.7546 33.5072 54.6666 34.423 54.6666 35.5527C54.6666 42.9923 49.8026 49.671 42.6666 50.6666V56C42.6666 57.3333 41.3333 58.6666 39.9999 58.6666C38.6666 58.6666 37.3333 57.3333 37.3333 56V50.6666C30.1972 49.671 25.3333 42.9923 25.3333 35.5527C25.3333 34.423 26.2453 33.5072 27.3703 33.5072Z" fill="white"/>
+</g>
+<defs>
+<filter id="filter0_i_1_1650" x="0" y="0" width="81" height="82" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="1" dy="2"/>
+<feGaussianBlur stdDeviation="8"/>
+<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
+<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1_1650"/>
+</filter>
+<linearGradient id="paint0_linear_1_1650" x1="40" y1="0" x2="40" y2="80" gradientUnits="userSpaceOnUse">
+<stop stop-color="#1A5A7E"/>
+<stop offset="1" stop-color="#1C7692"/>
+</linearGradient>
+<linearGradient id="paint1_linear_1_1650" x1="0" y1="0" x2="95.9119" y2="31.4055" gradientUnits="userSpaceOnUse">
+<stop stop-color="#2962FF" stop-opacity="0.5"/>
+<stop offset="1" stop-color="#2962FF"/>
+</linearGradient>
+<linearGradient id="paint2_linear_1_1650" x1="0" y1="0" x2="94.7074" y2="25.6089" gradientUnits="userSpaceOnUse">
+<stop stop-color="#C1D8FF" stop-opacity="0.2"/>
+<stop offset="1" stop-color="#C1D8FF"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/assets/VoiceControlButton.svg b/assets/VoiceControlButton.svg
new file mode 100644
index 0000000..bbd2757
--- /dev/null
+++ b/assets/VoiceControlButton.svg
@@ -0,0 +1,62 @@
+<svg width="183" height="184" viewBox="0 0 183 184" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_dd_7_3273)">
+<rect x="14" y="13" width="160" height="160" rx="80" fill="url(#paint0_linear_7_3273)" shape-rendering="crispEdges"/>
+<rect x="14" y="13" width="160" height="160" rx="80" fill="url(#paint1_linear_7_3273)" shape-rendering="crispEdges"/>
+<rect x="14.5" y="13.5" width="159" height="159" rx="79.5" stroke="url(#paint2_linear_7_3273)" shape-rendering="crispEdges"/>
+<g filter="url(#filter1_i_7_3273)">
+<rect x="30" y="29" width="128" height="128" rx="64" fill="url(#paint3_radial_7_3273)" fill-opacity="0.5"/>
+<rect x="30.5" y="29.5" width="127" height="127" rx="63.5" stroke="url(#paint4_linear_7_3273)"/>
+<path d="M78 71.6667C78 62.8301 85.1634 55.6667 94 55.6667C102.837 55.6667 110 62.8301 110 71.6667V82.3334C110 91.1699 102.837 98.3334 94 98.3334C85.1634 98.3334 78 91.1699 78 82.3334L78 71.6667Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M68.7408 80.0145C70.9909 80.0145 72.8149 81.8461 72.8149 84.1055C72.8149 95.8542 82.2998 106.333 94.0001 106.333C105.7 106.333 115.185 95.8542 115.185 84.1055C115.185 81.8461 117.009 80.0145 119.259 80.0145C121.509 80.0145 123.333 81.8461 123.333 84.1055C123.333 98.9848 113.605 112.342 99.3334 114.333V125C99.3334 127.667 96.6667 130.333 94.0001 130.333C91.3334 130.333 88.6667 127.667 88.6667 125V114.333C74.3947 112.342 64.6667 98.9848 64.6667 84.1055C64.6667 81.8461 66.4908 80.0145 68.7408 80.0145Z" fill="white"/>
+</g>
+</g>
+<defs>
+<filter id="filter0_dd_7_3273" x="0" y="0" width="182.3" height="183.3" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="3" dy="5"/>
+<feGaussianBlur stdDeviation="2.65"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.05 0 0 0 0 0.772 0 0 0 0 1 0 0 0 0.5 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_7_3273"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="-8" dy="-7"/>
+<feGaussianBlur stdDeviation="3"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.56 0 0 0 0 0 0 0 0 0 1 0 0 0 0.7 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow_7_3273" result="effect2_dropShadow_7_3273"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_7_3273" result="shape"/>
+</filter>
+<filter id="filter1_i_7_3273" x="30" y="29" width="129" height="130" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dx="1" dy="2"/>
+<feGaussianBlur stdDeviation="8"/>
+<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
+<feBlend mode="normal" in2="shape" result="effect1_innerShadow_7_3273"/>
+</filter>
+<linearGradient id="paint0_linear_7_3273" x1="14" y1="93.0001" x2="174" y2="93.0001" gradientUnits="userSpaceOnUse">
+<stop offset="0.0001" stop-color="#1C7692"/>
+<stop offset="1" stop-color="#1A727E"/>
+</linearGradient>
+<linearGradient id="paint1_linear_7_3273" x1="14" y1="119.667" x2="174.055" y2="119.334" gradientUnits="userSpaceOnUse">
+<stop stop-color="#2998FF"/>
+<stop offset="1" stop-color="#29CCFF" stop-opacity="0.7"/>
+</linearGradient>
+<linearGradient id="paint2_linear_7_3273" x1="14" y1="13" x2="203.415" y2="64.2177" gradientUnits="userSpaceOnUse">
+<stop stop-color="#C1D8FF"/>
+<stop offset="1" stop-color="#C1D8FF" stop-opacity="0.25"/>
+</linearGradient>
+<radialGradient id="paint3_radial_7_3273" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(94 93) rotate(90) scale(64)">
+<stop stop-color="#0C1039"/>
+<stop offset="1"/>
+<stop offset="1"/>
+</radialGradient>
+<linearGradient id="paint4_linear_7_3273" x1="30" y1="29" x2="181.532" y2="69.9742" gradientUnits="userSpaceOnUse">
+<stop stop-color="#C1D8FF"/>
+<stop offset="1" stop-color="#C1D8FF" stop-opacity="0.25"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/lib/core/constants/constants.dart b/lib/core/constants/constants.dart
index 65aa680..7bb8e75 100644
--- a/lib/core/constants/constants.dart
+++ b/lib/core/constants/constants.dart
@@ -2,8 +2,6 @@ import '../../export.dart';
const splashWarning =
'Please use the IVI system responsibly while driving. Keep your attention on the road, and use voice commands or hands-free controls when interacting with the system. Distracted driving can lead to accidents and serious injury. Follow all traffic laws and drive safely.';
-const aglVeriosn = 'AGL 16.0.2 (pike)';
-const kernelVeriosn = 'Kernel: 5.10.41.-yocto-standard';
const maxFuelLevel = 100.0;
const maxSpeed = 240.0;
const maxRpm = 8000;
@@ -11,6 +9,7 @@ final GlobalKey<ScaffoldState> homeScaffoldKey = GlobalKey();
const debugDisplay = bool.fromEnvironment('DEBUG_DISPLAY');
const disableBkgAnimationDefault = bool.fromEnvironment('DISABLE_BKG_ANIMATION');
const randomHybridAnimationDefault = bool.fromEnvironment('RANDOM_HYBRID_ANIMATION');
+const enableVoiceAssistantDefault = bool.fromEnvironment('ENABLE_VOICE_ASSISTANT');
diff --git a/lib/core/constants/users_path.dart b/lib/core/constants/users_path.dart
new file mode 100644
index 0000000..47c2486
--- /dev/null
+++ b/lib/core/constants/users_path.dart
@@ -0,0 +1,6 @@
+class UsersPath {
+ static const String InfotainmentCurrentUser =
+ 'Infotainment.Users.selectedUser';
+ static const String InfotainmentUsers =
+ 'Infotainment.Users';
+}
diff --git a/lib/data/data_providers/app.dart b/lib/data/data_providers/app.dart
index 3368a83..05e56f0 100644
--- a/lib/data/data_providers/app.dart
+++ b/lib/data/data_providers/app.dart
@@ -12,13 +12,11 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return ProviderScope(
- child: MaterialApp(
+ return MaterialApp(
debugShowCheckedModeBanner: false,
theme: theme,
home: const AppView(),
- ),
- );
+ );
}
}
diff --git a/lib/data/data_providers/app_config_provider.dart b/lib/data/data_providers/app_config_provider.dart
index b82eb54..4052164 100644
--- a/lib/data/data_providers/app_config_provider.dart
+++ b/lib/data/data_providers/app_config_provider.dart
@@ -88,6 +88,21 @@ class MpdConfig {
}
}
+class VoiceAgentConfig {
+ final String hostname;
+ final int port;
+
+ static String defaultHostname = 'localhost';
+ static int defaultPort = 51053;
+
+ VoiceAgentConfig({required this.hostname,required this.port});
+
+ static VoiceAgentConfig defaultConfig() {
+ return VoiceAgentConfig(
+ hostname: VoiceAgentConfig.defaultHostname, port: VoiceAgentConfig.defaultPort);
+ }
+}
+
class AppConfig {
final bool disableBkgAnimation;
final bool plainBackground;
@@ -96,6 +111,8 @@ class AppConfig {
final RadioConfig radioConfig;
final StorageConfig storageConfig;
final MpdConfig mpdConfig;
+ final VoiceAgentConfig voiceAgentConfig;
+ final bool enableVoiceAssistant;
static String configFilePath = '/etc/xdg/AGL/ics-homescreen.yaml';
@@ -106,7 +123,9 @@ class AppConfig {
required this.kuksaConfig,
required this.radioConfig,
required this.storageConfig,
- required this.mpdConfig});
+ required this.mpdConfig,
+ required this.voiceAgentConfig,
+ required this.enableVoiceAssistant});
static KuksaConfig parseKuksaConfig(YamlMap kuksaMap) {
try {
@@ -238,6 +257,25 @@ class AppConfig {
return MpdConfig.defaultConfig();
}
}
+
+ static VoiceAgentConfig parseVoiceAgentConfig(YamlMap voiceAgentMap) {
+ try {
+ String hostname = VoiceAgentConfig.defaultHostname;
+ if (voiceAgentMap.containsKey('hostname')) {
+ hostname = voiceAgentMap['hostname'];
+ }
+
+ int port = VoiceAgentConfig.defaultPort;
+ if (voiceAgentMap.containsKey('port')) {
+ port = voiceAgentMap['port'];
+ }
+
+ return VoiceAgentConfig(hostname: hostname, port: port);
+ } catch (_) {
+ debugPrint("Invalid VoiceAgent configuration, using defaults");
+ return VoiceAgentConfig.defaultConfig();
+ }
+ }
}
final appConfigProvider = Provider((ref) {
@@ -281,6 +319,21 @@ final appConfigProvider = Provider((ref) {
mpdConfig = MpdConfig.defaultConfig();
}
+ VoiceAgentConfig voiceAgentConfig;
+ if(yamlMap.containsKey('voiceAgent')){
+ voiceAgentConfig = AppConfig.parseVoiceAgentConfig(yamlMap['voiceAgent']);
+ } else {
+ voiceAgentConfig = VoiceAgentConfig.defaultConfig();
+ }
+
+ bool enableVoiceAssistant = enableVoiceAssistantDefault;
+ if (yamlMap.containsKey('enable-voice-assistant')) {
+ var value = yamlMap['enable-voice-assistant'];
+ if (value is bool) {
+ enableVoiceAssistant = value;
+ }
+ }
+
bool disableBkgAnimation = disableBkgAnimationDefault;
if (yamlMap.containsKey('disable-bg-animation')) {
var value = yamlMap['disable-bg-animation'];
@@ -312,7 +365,9 @@ final appConfigProvider = Provider((ref) {
kuksaConfig: kuksaConfig,
radioConfig: radioConfig,
storageConfig: storageConfig,
- mpdConfig: mpdConfig);
+ mpdConfig: mpdConfig,
+ voiceAgentConfig: voiceAgentConfig,
+ enableVoiceAssistant: enableVoiceAssistant);
} catch (_) {
return AppConfig(
disableBkgAnimation: false,
@@ -321,6 +376,8 @@ final appConfigProvider = Provider((ref) {
kuksaConfig: KuksaConfig.defaultConfig(),
radioConfig: RadioConfig.defaultConfig(),
storageConfig: StorageConfig.defaultConfig(),
- mpdConfig: MpdConfig.defaultConfig());
+ mpdConfig: MpdConfig.defaultConfig(),
+ voiceAgentConfig: VoiceAgentConfig.defaultConfig(),
+ enableVoiceAssistant: false);
}
});
diff --git a/lib/data/data_providers/app_provider.dart b/lib/data/data_providers/app_provider.dart
index 0f7ed0c..64c0e47 100644
--- a/lib/data/data_providers/app_provider.dart
+++ b/lib/data/data_providers/app_provider.dart
@@ -16,10 +16,14 @@ import 'package:flutter_ics_homescreen/data/data_providers/radio_client.dart';
import 'package:flutter_ics_homescreen/data/data_providers/storage_client.dart';
import 'package:flutter_ics_homescreen/data/data_providers/mpd_client.dart';
import 'package:flutter_ics_homescreen/data/data_providers/play_controller.dart';
+import 'package:flutter_ics_homescreen/data/data_providers/voice_agent_client.dart';
+import 'package:flutter_ics_homescreen/data/data_providers/voice_assistant_notifier.dart';
import 'package:flutter_ics_homescreen/export.dart';
import 'package:flutter_ics_homescreen/data/models/users.dart';
+import '../models/voice_assistant_state.dart';
+
enum AppState {
home,
dashboard,
@@ -44,7 +48,9 @@ enum AppState {
clock,
date,
time,
- year
+ year,
+ voiceAssistant,
+ sttModel,
}
class AppStateNotifier extends Notifier<AppState> {
@@ -73,6 +79,11 @@ final valClientProvider = Provider((ref) {
return ValClient(config: config, ref: ref);
});
+final voiceAgentClientProvider = Provider((ref){
+ VoiceAgentConfig config = ref.watch(appConfigProvider).voiceAgentConfig;
+ return VoiceAgentClient(config: config, ref: ref);
+});
+
final appLauncherProvider = Provider((ref) {
return AppLauncher(ref: ref);
});
@@ -154,3 +165,7 @@ final currentTimeProvider =
StateNotifierProvider<CurrentTimeNotifier, DateTime>((ref) {
return CurrentTimeNotifier();
});
+
+
+final voiceAssistantStateProvider =
+ NotifierProvider<VoiceAssistantStateNotifier, VoiceAssistantState>(VoiceAssistantStateNotifier.new);
diff --git a/lib/data/data_providers/initialize_settings.dart b/lib/data/data_providers/initialize_settings.dart
new file mode 100644
index 0000000..b5a5e80
--- /dev/null
+++ b/lib/data/data_providers/initialize_settings.dart
@@ -0,0 +1,12 @@
+import 'package:flutter_ics_homescreen/export.dart';
+
+Future<void> initializeSettings(ProviderContainer container) async {
+ await container.read(usersProvider.notifier).loadSettingsUsers();
+ await container.read(unitStateProvider.notifier).loadSettingsUnits();
+ // Initialize other settings or providers if needed.
+}
+
+Future<void> initializeSettingsUser(Ref ref) async {
+ await ref.read(unitStateProvider.notifier).loadSettingsUnits();
+ // Initialize other settings or providers if needed.
+}
diff --git a/lib/data/data_providers/mpd_client.dart b/lib/data/data_providers/mpd_client.dart
index 98d8918..daafab6 100644
--- a/lib/data/data_providers/mpd_client.dart
+++ b/lib/data/data_providers/mpd_client.dart
@@ -266,14 +266,17 @@ class MpdClient {
artState[position] = ArtStateEntry(reading: true, read: false);
}
debugPrint("Reading art for \"$file\"");
- // Work around dart_mpd not escaping spaced strings itself
- String escapedFile = file.replaceAll(RegExp(r" "), "\\ ");
- escapedFile = "\"$file\"";
bool first = true;
do {
//debugPrint("Reading, offset = $offset, size = $size");
- var chunk = await client.readpicture(escapedFile, offset);
+ api.MpdImage? chunk = null;
+ try {
+ chunk = await client.readpicture(file, offset);
+ } catch (e) {
+ debugPrint(e.toString());
+ chunk = null;
+ }
if (chunk != null) {
if (chunk.size != null) {
if (chunk.size == 0) {
diff --git a/lib/data/data_providers/units_notifier.dart b/lib/data/data_providers/units_notifier.dart
index 68c9e65..9f4a1ac 100644
--- a/lib/data/data_providers/units_notifier.dart
+++ b/lib/data/data_providers/units_notifier.dart
@@ -1,12 +1,53 @@
import 'package:flutter_ics_homescreen/export.dart';
import 'package:protos/val_api.dart';
+import 'package:protos/storage-api.dart' as storage_api;
+import 'initialize_settings.dart';
+
class UnitsNotifier extends Notifier<Units> {
@override
Units build() {
return const Units.initial();
}
+ // Load Units state of the selected user from the storage API.
+ Future<void> loadSettingsUnits() async {
+ final storageClient = ref.read(storageClientProvider);
+ final userClient = ref.read(usersProvider);
+
+ try {
+ // Read unit values from the selected user namespace.
+ final distanceResponse = await storageClient.read(storage_api.Key(
+ key: VSSPath.vehicleHmiDistanceUnit,
+ namespace: userClient.selectedUser.id));
+ final temperatureResponse = await storageClient.read(storage_api.Key(
+ key: VSSPath.vehicleHmiTemperatureUnit,
+ namespace: userClient.selectedUser.id));
+ final pressureResponse = await storageClient.read(storage_api.Key(
+ key: VSSPath.vehicleHmiPressureUnit,
+ namespace: userClient.selectedUser.id));
+
+ // Prepare state declaration and fall back to default values if the key is not present in the storage API.
+ final distanceUnit = distanceResponse.result == 'MILES'
+ ? DistanceUnit.miles
+ : DistanceUnit.kilometers;
+
+ final temperatureUnit = temperatureResponse.result == 'F'
+ ? TemperatureUnit.fahrenheit
+ : TemperatureUnit.celsius;
+
+ final pressureUnit = pressureResponse.result == 'PSI'
+ ? PressureUnit.psi
+ : PressureUnit.kilopascals;
+
+ state = Units(distanceUnit, temperatureUnit, pressureUnit);
+ } catch (e) {
+ // Fallback to initial defaults if error occurs.
+ debugPrint('Error loading settings for units: $e');
+ state = const Units.initial();
+ }
+ }
+
bool handleSignalUpdate(DataEntry entry) {
bool handled = true;
switch (entry.path) {
@@ -40,33 +81,60 @@ class UnitsNotifier extends Notifier<Units> {
return handled;
}
- void setDistanceUnit(DistanceUnit unit) {
+ Future<void> setDistanceUnit(DistanceUnit unit) async {
state = state.copyWith(distanceUnit: unit);
+
var valClient = ref.read(valClientProvider);
- valClient.setString(
- VSSPath.vehicleHmiDistanceUnit,
- unit == DistanceUnit.kilometers ? "KILOMETERS" : "MILES",
- true,
- );
+ valClient.setDistanceUnit(unit);
+
+ // Write to storage API (to selected user namespace).
+ var storageClient = ref.read(storageClientProvider);
+ final userClient = ref.read(usersProvider);
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: VSSPath.vehicleHmiDistanceUnit,
+ value: unit == DistanceUnit.kilometers ? 'KILOMETERS' : 'MILES',
+ namespace: userClient.selectedUser.id));
+ } catch (e) {
+ debugPrint('Error saving distance unit: $e');
+ }
}
- void setTemperatureUnit(TemperatureUnit unit) {
+ Future<void> setTemperatureUnit(TemperatureUnit unit) async {
state = state.copyWith(temperatureUnit: unit);
+
var valClient = ref.read(valClientProvider);
- valClient.setString(
- VSSPath.vehicleHmiTemperatureUnit,
- unit == TemperatureUnit.celsius ? "C" : "F",
- true,
- );
+ valClient.setTemperatureUnit(unit);
+
+ // Write to storage API (to selected user namespace).
+ var storageClient = ref.read(storageClientProvider);
+ final userClient = ref.read(usersProvider);
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: VSSPath.vehicleHmiTemperatureUnit,
+ value: unit == TemperatureUnit.celsius ? "C" : "F",
+ namespace: userClient.selectedUser.id));
+ } catch (e) {
+ debugPrint('Error saving distance unit: $e');
+ }
}
- void setPressureUnit(PressureUnit unit) {
+ Future<void> setPressureUnit(PressureUnit unit) async {
state = state.copyWith(pressureUnit: unit);
+
var valClient = ref.read(valClientProvider);
- valClient.setString(
- VSSPath.vehicleHmiPressureUnit,
- unit == PressureUnit.kilopascals ? "KPA" : "PSI",
- true,
- );
+ valClient.setPressureUnit(unit);
+
+ // Write to storage API (to selected user namespace).
+ var storageClient = ref.read(storageClientProvider);
+ final userClient = ref.read(usersProvider);
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: VSSPath.vehicleHmiPressureUnit,
+ value: unit == PressureUnit.kilopascals ? "KPA" : "PSI",
+ namespace: userClient.selectedUser.id));
+ } catch (e) {
+ debugPrint('Error saving pressure unit: $e');
+ }
}
}
diff --git a/lib/data/data_providers/users_notifier.dart b/lib/data/data_providers/users_notifier.dart
index 8b48382..a4e056c 100644
--- a/lib/data/data_providers/users_notifier.dart
+++ b/lib/data/data_providers/users_notifier.dart
@@ -4,41 +4,166 @@ import 'package:uuid/uuid.dart';
import '../models/user.dart';
-class UsersNotifier extends StateNotifier<Users> {
- UsersNotifier(super.state) {
+import 'package:protos/storage-api.dart' as storage_api;
+import 'initialize_settings.dart';
+
+class UsersNotifier extends Notifier<Users> {
+ @override
+ final List<User> _users = [
+ const User(id: '1', name: 'Heather'),
+ const User(id: '2', name: 'George'),
+ const User(id: '3', name: 'Riley'),
+ ];
+
+ Users build() {
+ // Initialize default state.
+ state = Users.initial();
loadUsers();
+ return state;
+ }
+
+ Future<void> loadSettingsUsers() async {
+ final storageClient = ref.read(storageClientProvider);
+ try {
+ // Access users branch.
+ final searchResponseUsers = await storageClient
+ .search(storage_api.Key(key: UsersPath.InfotainmentUsers));
+ if (searchResponseUsers.result.isEmpty) {
+ // Add default users if no users are inside the storage API.
+ debugPrint("Adding default demo user profiles");
+ loadUsers();
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.${_users[0].id}.id',
+ value: _users[0].id));
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.${_users[0].id}.name',
+ value: _users[0].name));
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.${_users[1].id}.id',
+ value: _users[1].id));
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.${_users[1].id}.name',
+ value: _users[1].name));
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.${_users[2].id}.id',
+ value: _users[2].id));
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.${_users[2].id}.name',
+ value: _users[2].name));
+ await selectUser(_users[0].id);
+ } else {
+ List<User> users = [];
+ List<String> idList = [];
+ // Get list of all ids.
+ for (var key in searchResponseUsers.result) {
+ var readResponse =
+ await storageClient.read(storage_api.Key(key: key));
+ if (key.contains('.id')) {
+ idList.insert(0, readResponse.result);
+ }
+ }
+ // Extract names corresponding to ids.
+ for (var id in idList) {
+ var readResponse = await storageClient.read(
+ storage_api.Key(key: '${UsersPath.InfotainmentUsers}.$id.name'));
+ users.insert(0, User(id: id, name: readResponse.result));
+ }
+ // Extract id of selected user.
+ final readResponseSelectedUser = await storageClient
+ .read(storage_api.Key(key: UsersPath.InfotainmentCurrentUser));
+ User selectedUser;
+ final userCurrentId = readResponseSelectedUser.result;
+ // Extract name of selected user.
+ final readResponseCurrentUserName = await storageClient.read(
+ storage_api.Key(
+ key: '${UsersPath.InfotainmentUsers}.$userCurrentId.name'));
+ final userCurrentName = readResponseCurrentUserName.result;
+ selectedUser = User(id: userCurrentId, name: userCurrentName);
+ state = Users(users: users, selectedUser: selectedUser);
+ }
+ } catch (e) {
+ // Fallback to initial defaults if error.
+ debugPrint('Error loading users: $e');
+ loadUsers();
+ state = state.copyWith(selectedUser: _users[0]);
+ }
}
void loadUsers() {
state = state.copyWith(users: _users);
}
- final List<User> _users = [
- const User(id: '1', name: 'Heather'),
- const User(id: '2', name: 'George'),
- const User(id: '3', name: 'Riley'),
- ];
- void selectUser(String userId) {
- var seletedUser = state.users.firstWhere((user) => user.id == userId);
- state = state.copyWith(selectedUser: seletedUser);
+ Future<void> selectUser(String userId) async {
+ final storageClient = ref.read(storageClientProvider);
+ var selectedUser = state.users.firstWhere((user) => user.id == userId);
+ state = state.copyWith(selectedUser: selectedUser);
+ // Write to storage API.
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: UsersPath.InfotainmentCurrentUser,
+ value: userId,
+ ));
+ } catch (e) {
+ debugPrint('Error selecting user: $e');
+ }
+
+ try {
+ await initializeSettingsUser(ref);
+ } catch (e) {
+ debugPrint('Error loading settings of user: $e');
+ }
}
- void removeUser(String userId) {
+ Future<void> removeUser(String userId) async {
+ final storageClient = ref.read(storageClientProvider);
+ var currentUserId = state.selectedUser.id;
state.users.removeWhere((user) => user.id == userId);
- if (state.users.isNotEmpty) {
+
+ if (state.users.isNotEmpty && currentUserId == userId) {
state = state.copyWith(selectedUser: state.users.first);
+ // Write to API to change selected user.
+ await storageClient.write(storage_api.KeyValue(
+ key: UsersPath.InfotainmentCurrentUser, value: state.users.first.id));
}
if (state.users.isEmpty) {
- state = state.copyWith(selectedUser: const User(id: '', name: ''));
+ state = state.copyWith(selectedUser: const User(id: '0', name: ''));
+ // Write to API to change selected user.
+ await storageClient.write(storage_api.KeyValue(
+ key: UsersPath.InfotainmentCurrentUser, value: '0'));
+ }
+ // Delete from storage API.
+ try {
+ final searchResponse =
+ await storageClient.search(storage_api.Key(key: userId));
+ final keyList = searchResponse.result;
+ // Delete id, name entries of the user from the default namespace.
+ for (final key in keyList) {
+ await storageClient.delete(storage_api.Key(key: key));
+ }
+ // Delete all VSS keys from the user namespace.
+ await storageClient
+ .deleteNodes(storage_api.Key(key: "Vehicle", namespace: userId));
+ } catch (e) {
+ debugPrint('Error removing user with id $userId: $e');
}
}
- void addUser(String userName) {
+ Future<void> addUser(String userName) async {
+ final storageClient = ref.read(storageClientProvider);
final id = const Uuid().v1();
final user = User(id: id, name: userName);
-
state.users.insert(0, user);
- state = state.copyWith(selectedUser: state.users.first);
+ // New user is automatically selected.
+ await selectUser(user.id);
+ // Write to storage API.
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.$id.name', value: userName));
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.$id.id', value: id));
+ } catch (e) {
+ debugPrint('Error adding user with id $id: $e');
+ }
}
void editUser(User user) {
diff --git a/lib/data/data_providers/val_client.dart b/lib/data/data_providers/val_client.dart
index 173dbfb..8436012 100644
--- a/lib/data/data_providers/val_client.dart
+++ b/lib/data/data_providers/val_client.dart
@@ -35,6 +35,13 @@ class ValClient {
metadata = {'authorization': "Bearer ${config.authorization}"};
}
+ // Push out persisted user preferences so databroker defaults
+ // will not overwrite them.
+ var units = ref.read(unitStateProvider);
+ setDistanceUnit(units.distanceUnit);
+ setTemperatureUnit(units.temperatureUnit);
+ setPressureUnit(units.pressureUnit);
+
// Initialize signal states
for (int i = 0; i < signals.length; i++) {
get(signals[i]);
@@ -145,4 +152,25 @@ class ValClient {
handleSignalUpdate(entry);
}
}
+
+ void setDistanceUnit(DistanceUnit unit) async {
+ setString(VSSPath.vehicleHmiDistanceUnit,
+ unit == DistanceUnit.kilometers ? "KILOMETERS" : "MILES",
+ true,
+ );
+ }
+
+ void setTemperatureUnit(TemperatureUnit unit) async {
+ setString(VSSPath.vehicleHmiTemperatureUnit,
+ unit == TemperatureUnit.celsius ? "C" : "F",
+ true,
+ );
+ }
+
+ void setPressureUnit(PressureUnit unit) async {
+ setString(VSSPath.vehicleHmiPressureUnit,
+ unit == PressureUnit.kilopascals ? "KPA" : "PSI",
+ true,
+ );
+ }
}
diff --git a/lib/data/data_providers/voice_agent_client.dart b/lib/data/data_providers/voice_agent_client.dart
new file mode 100644
index 0000000..903a5d0
--- /dev/null
+++ b/lib/data/data_providers/voice_agent_client.dart
@@ -0,0 +1,312 @@
+import 'dart:async';
+import 'package:flutter_ics_homescreen/data/models/voice_assistant_state.dart';
+import 'package:protos/val_api.dart';
+
+import '../../export.dart';
+
+class VoiceAgentClient {
+ final VoiceAgentConfig config;
+ late ClientChannel _channel;
+ late VoiceAgentServiceClient _client;
+ final Ref ref;
+ StreamSubscription<WakeWordStatus>? _wakeWordStatusSubscription;
+
+ VoiceAgentClient({required this.config,required this.ref}) {
+ // Initialize the client channel without connecting immediately
+ debugPrint("Connecting to Voice Assistant at ${config.hostname}:${config.port}");
+ String host = config.hostname;
+ int port = config.port;
+ _channel = ClientChannel(
+ host,
+ port: port,
+ options: const ChannelOptions(
+ credentials: ChannelCredentials.insecure(),
+ ),
+ );
+ _client = VoiceAgentServiceClient(_channel);
+
+ }
+
+ Future<ServiceStatus> checkServiceStatus() async {
+ final empty = Empty();
+ try {
+ final response = await _client.checkServiceStatus(empty);
+ return response;
+ } catch (e) {
+ // Handle the error gracefully, such as returning an error status
+ return ServiceStatus()..status = false;
+ }
+ }
+
+ Stream<WakeWordStatus> detectWakeWord() {
+ final empty = Empty();
+ try {
+ return _client.detectWakeWord(empty);
+ } catch (e) {
+ // Handle the error gracefully, such as returning a default status
+ return const Stream.empty(); // An empty stream as a placeholder
+ }
+ }
+
+ Future<RecognizeResult> recognizeVoiceCommand(
+ Stream<RecognizeVoiceControl> controlStream) async {
+ try {
+ final response = await _client.recognizeVoiceCommand(controlStream);
+ return response;
+ } catch (e) {
+ // Handle the error gracefully, such as returning a default RecognizeResult
+ return RecognizeResult()..status = RecognizeStatusType.REC_ERROR;
+ }
+ }
+
+ Future<RecognizeResult> recognizeTextCommandGrpc(
+ RecognizeTextControl controlInput) async {
+ try {
+ final response = await _client.recognizeTextCommand(controlInput);
+ return response;
+ } catch (e) {
+ // Handle the error gracefully, such as returning a default RecognizeResult
+ return RecognizeResult()..status = RecognizeStatusType.REC_ERROR;
+ }
+ }
+
+ Future<ExecuteResult> executeCommandGrpc(ExecuteInput input) async {
+ try {
+ final response = await _client.executeCommand(input);
+ return response;
+ } catch (e) {
+ // Handle the error gracefully, such as returning an error status
+ return ExecuteResult()..status = ExecuteStatusType.EXEC_ERROR;
+ }
+ }
+
+ Future<void> shutdown() async {
+ // await _channel.shutdown();
+ }
+
+ // Grpc helper methods
+ Future<void> startWakeWordDetection() async {
+ // Capture the state before any async operations
+ _wakeWordStatusSubscription?.cancel();
+ final isWakeWordModeActive = ref.read(voiceAssistantStateProvider.select((value) => value.isWakeWordMode));
+
+ if (isWakeWordModeActive) {
+ debugPrint("Wake Word Detection Started");
+ } else {
+ debugPrint("Wake Word Detection Stopped");
+ return;
+ }
+ _wakeWordStatusSubscription = detectWakeWord().listen(
+ (response) async {
+ if (response.status) {
+ await startVoiceAssistant();
+ // Wait for 2-3 seconds and then restart wake word detection
+ await Future.delayed(const Duration(seconds: 2));
+ startWakeWordDetection();
+ }
+ if(!ref.read(voiceAssistantStateProvider.select((value) => value.isWakeWordMode))){
+ _wakeWordStatusSubscription?.cancel();
+ return;
+ }
+ },
+ onError: (error) {
+ },
+ cancelOnError: true,
+ );
+ }
+
+ Future<String> startRecording() async {
+ String streamId = "";
+ try {
+ // Create a RecognizeControl message to start recording
+ final controlMessage = RecognizeVoiceControl()
+ ..action = RecordAction.START
+ ..recordMode = RecordMode
+ .MANUAL; // You can change this to your desired record mode
+
+ // Create a Stream with the control message
+ final controlStream = Stream.fromIterable([controlMessage]);
+
+ // Call the gRPC method to start recording
+ final response =
+ await recognizeVoiceCommand(controlStream);
+
+ streamId = response.streamId;
+ } catch (e) {
+ }
+ return streamId;
+ }
+
+ Future<RecognizeResult> stopRecording(
+ String streamId, String nluModel, String stt,bool isOnlineMode) async {
+
+ try {
+ NLUModel model = NLUModel.RASA;
+ if (nluModel == "snips") {
+ model = NLUModel.SNIPS;
+ }
+ STTFramework sttFramework = STTFramework.VOSK;
+ if (stt == "whisper") {
+ sttFramework = STTFramework.WHISPER;
+ }
+ OnlineMode onlineMode = OnlineMode.OFFLINE;
+ if (isOnlineMode) {
+ onlineMode = OnlineMode.ONLINE;
+ }
+ // Create a RecognizeControl message to stop recording
+ final controlMessage = RecognizeVoiceControl()
+ ..action = RecordAction.STOP
+ ..nluModel = model
+ ..streamId =
+ streamId // Use the same stream ID as when starting recording
+ ..recordMode = RecordMode.MANUAL
+ ..sttFramework = sttFramework
+ ..onlineMode = onlineMode;
+
+
+ // Create a Stream with the control message
+ final controlStream = Stream.fromIterable([controlMessage]);
+
+ // Call the gRPC method to stop recording
+ final response =
+ await recognizeVoiceCommand(controlStream);
+
+ // Process and store the result
+ if (response.status == RecognizeStatusType.REC_SUCCESS) {
+ } else if (response.status == RecognizeStatusType.INTENT_NOT_RECOGNIZED) {
+ final command = response.command;
+ debugPrint("Command is : $command");
+ }
+ else {
+ debugPrint('Failed to process your voice command. Please try again.');
+ }
+ await shutdown();
+ return response;
+ } catch (e) {
+ // addChatMessage(/**/'Failed to process your voice command. Please try again.');
+ await shutdown();
+ return RecognizeResult()..status = RecognizeStatusType.REC_ERROR;
+ }
+ // await voiceAgentClient.shutdown();
+ }
+
+ Future<RecognizeResult> recognizeTextCommand(String command, String nluModel) async {
+ debugPrint("Recognizing Text Command: $command");
+ try {
+ NLUModel model = NLUModel.RASA;
+ if (nluModel == "snips") {
+ model = NLUModel.SNIPS;
+ }
+ // Create a RecognizeControl message to stop recording
+ final controlMessage = RecognizeTextControl()
+ ..textCommand = command
+ ..nluModel = model;
+
+ // Call the gRPC method to stop recording
+ final response =
+ await recognizeTextCommandGrpc(controlMessage);
+ debugPrint("Response is : $response");
+
+ // Process and store the result
+ if (response.status == RecognizeStatusType.REC_SUCCESS) {
+ // Do nothing
+ } else if (response.status == RecognizeStatusType.INTENT_NOT_RECOGNIZED) {
+ final command = response.command;
+ debugPrint("Command is : $command");
+ } else {
+ debugPrint('Failed to process your voice command. Please try again.');
+ }
+ return response;
+ } catch (e) {
+ return RecognizeResult()..status = RecognizeStatusType.REC_ERROR;
+ }
+ }
+
+ Future<void> executeCommand(RecognizeResult response) async {
+ try {
+ // Create an ExecuteInput message using the response from stopRecording
+ final executeInput = ExecuteInput()
+ ..intent = response.intent
+ ..intentSlots.addAll(response.intentSlots);
+
+ // Call the gRPC method to execute the voice command
+ final execResponse = await executeCommandGrpc(executeInput);
+
+ // Handle the response as needed
+ if (execResponse.status == ExecuteStatusType.EXEC_SUCCESS) {
+ final commandResponse = execResponse.response;
+ ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(commandResponse);
+ debugPrint("Command Response is : $commandResponse");
+ } else if (execResponse.status == ExecuteStatusType.KUKSA_CONN_ERROR) {
+ final commandResponse = execResponse.response;
+ ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(commandResponse);
+ } else {
+ ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse("Sorry, I couldn't execute your command. Please try again.");
+ }
+ } catch (e) {
+ }
+ await shutdown();
+ }
+
+
+ Future<void> disableOverlay() async{
+ await Future.delayed(Duration(seconds: 3));
+ ref.read(voiceAssistantStateProvider.notifier).toggleShowOverlay(false);
+ }
+
+ Future<void> startVoiceAssistant()async {
+ ref.read(voiceAssistantStateProvider.notifier).updateCommand(null);
+ ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(null);
+
+ SttModel stt = ref.read(voiceAssistantStateProvider.select((value)=>value.sttModel));
+ bool isOnlineMode = ref.read(voiceAssistantStateProvider.select((value)=>value.isOnlineMode));
+ String nluModel = "snips";
+ String sttModel = "whisper";
+ if(stt == SttModel.vosk){
+ sttModel = "vosk";
+ }
+ bool isOverlayEnabled = ref.read(voiceAssistantStateProvider.select((value)=>value.voiceAssistantOverlay));
+ bool overlayState = ref.read(voiceAssistantStateProvider.select((value)=>value.showOverLay));
+
+ String streamId = await startRecording();
+ if (streamId.isNotEmpty) {
+ debugPrint('Recording started. Please speak your command.');
+ if(isOverlayEnabled){
+ if(!overlayState){
+ ref.read(voiceAssistantStateProvider.notifier).toggleShowOverlay(true);
+ }
+ }
+
+ ref.read(voiceAssistantStateProvider.notifier).updateButtonPressed(true);
+ ref.read(voiceAssistantStateProvider.notifier).updateIsRecording();
+ ref.read(voiceAssistantStateProvider.notifier).updateIsCommandProcessing(false);
+
+ // wait for the recording time
+ await Future.delayed(Duration(seconds: ref.watch(voiceAssistantStateProvider.select((value)=>value.recordingTime))));
+
+ ref.read(voiceAssistantStateProvider.notifier).updateIsRecording();
+ ref.read(voiceAssistantStateProvider.notifier).updateIsCommandProcessing(true);
+
+ // stop the recording and process the command
+ RecognizeResult recognizeResult = await stopRecording(streamId, nluModel, sttModel,isOnlineMode);
+
+ ref.read(voiceAssistantStateProvider.notifier).updateCommand(recognizeResult.command);
+ debugPrint('Recording stopped. Processing the command...');
+
+ // Execute the command
+ await executeCommand(recognizeResult);
+
+ ref.read(voiceAssistantStateProvider.notifier).updateIsCommandProcessing(false);
+ ref.read(voiceAssistantStateProvider.notifier).updateButtonPressed(false);
+ ref.read(voiceAssistantStateProvider.notifier).updateCommand(null);
+ ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(null);
+ disableOverlay();
+
+ } else {
+ debugPrint('Failed to start recording. Please try again.');
+ }
+
+ }
+
+
+}
diff --git a/lib/data/data_providers/voice_assistant_notifier.dart b/lib/data/data_providers/voice_assistant_notifier.dart
new file mode 100644
index 0000000..0bc681a
--- /dev/null
+++ b/lib/data/data_providers/voice_assistant_notifier.dart
@@ -0,0 +1,148 @@
+import 'package:protos/val_api.dart';
+
+import '../../export.dart';
+import '../models/voice_assistant_state.dart';
+
+
+class VoiceAssistantStateNotifier extends Notifier<VoiceAssistantState>{
+ @override
+ VoiceAssistantState build() {
+ return const VoiceAssistantState.initial();
+ }
+
+ void updateVoiceAssistantState(VoiceAssistantState newState){
+ state = newState;
+ }
+
+ void updateVoiceAssistantStateWith({
+ bool? isWakeWordMode,
+ bool? isVoiceAssistantEnable,
+ bool? voiceAssistantOverlay,
+ bool? isOnlineMode,
+ bool? isOnlineModeAvailable,
+ String? wakeWord,
+ SttModel? sttModel,
+ String? streamId,
+ bool? isCommandProcessing,
+ String? commandProcessingText,
+ int? recordingTime,
+ bool? buttonPressed,
+ bool? isRecording,
+ String? command,
+ String? commandResponse,
+ bool? isWakeWordDetected,
+ bool? showOverLay,
+ }){
+ state = state.copyWith(
+ isWakeWordMode: isWakeWordMode,
+ isVoiceAssistantEnable: isVoiceAssistantEnable,
+ voiceAssistantOverlay: voiceAssistantOverlay,
+ isOnlineMode: isOnlineMode,
+ isOnlineModeAvailable: isOnlineModeAvailable,
+ wakeWord: wakeWord,
+ sttModel: sttModel,
+ streamId: streamId,
+ isCommandProcessing: isCommandProcessing,
+ commandProcessingText: commandProcessingText,
+ recordingTime: recordingTime,
+ buttonPressed: buttonPressed,
+ isRecording: isRecording,
+ command: command,
+ commandResponse: commandResponse,
+ isWakeWordDetected: isWakeWordDetected,
+ showOverLay: showOverLay,
+ );
+ }
+
+ void resetToDefaults(){
+ state = const VoiceAssistantState.initial();
+ }
+
+ void updateWakeWordDetected(bool isWakeWordDetected){
+ state = state.copyWith(isWakeWordDetected: isWakeWordDetected);
+ }
+
+ void toggleShowOverlay(bool value){
+ state = state.copyWith(showOverLay: value);
+ }
+
+ bool toggleWakeWordMode(){
+ state = state.copyWith(isWakeWordMode: !state.isWakeWordMode);
+ return state.isWakeWordMode;
+ }
+
+ Future<void> toggleVoiceAssistant(ServiceStatus status) async {
+ bool prevState = state.isVoiceAssistantEnable;
+ if(!prevState){
+ if(status.status){
+ state = state.copyWith(isVoiceAssistantEnable: !state.isVoiceAssistantEnable);
+ state = state.copyWith(wakeWord: status.wakeWord);
+ state = state.copyWith(isOnlineModeAvailable: status.onlineMode);
+ }
+ else{
+ debugPrint("Failed to start the Voice Assistant");
+ }
+ }
+ else{
+ state = state.copyWith(isVoiceAssistantEnable: !state.isVoiceAssistantEnable);
+ if(state.isWakeWordMode){
+ state = state.copyWith(isWakeWordMode: false);
+ }
+ }
+ }
+
+ void toggleVoiceAssistantOverlay(){
+ state = state.copyWith(voiceAssistantOverlay: !state.voiceAssistantOverlay);
+ }
+
+ void toggleOnlineMode(){
+ state = state.copyWith(isOnlineMode: !state.isOnlineMode);
+ }
+
+ void updateWakeWord(String wakeWord){
+ state = state.copyWith(wakeWord: wakeWord);
+ }
+
+ void updateSttModel(SttModel sttModel){
+ state = state.copyWith(sttModel: sttModel);
+ }
+
+ void updateStreamId(String streamId){
+ state = state.copyWith(streamId: streamId);
+ }
+
+ void updateIsCommandProcessing(bool isCommandProcessing){
+ state = state.copyWith(isCommandProcessing: isCommandProcessing);
+ }
+
+ void updateCommandProcessingText(String commandProcessingText){
+ state = state.copyWith(commandProcessingText: commandProcessingText);
+ }
+
+ void updateRecordingTime(int recordingTime){
+ state = state.copyWith(recordingTime: recordingTime);
+ }
+
+ void updateIsRecording(){
+ state = state.copyWith(isRecording: !state.isRecording);
+ }
+
+ void updateCommand(String? command){
+ state = state.copyWith(command: command);
+ }
+
+ void updateCommandResponse(String? commandResponse){
+ state = state.copyWith(commandResponse: commandResponse);
+ }
+
+
+ bool toggleButtonPressed(){
+ bool prevState = state.buttonPressed;
+ state = state.copyWith(buttonPressed: !state.buttonPressed);
+ return !prevState;
+ }
+
+ void updateButtonPressed(bool buttonPressed){
+ state = state.copyWith(buttonPressed: buttonPressed);
+ }
+} \ No newline at end of file
diff --git a/lib/data/models/users.dart b/lib/data/models/users.dart
index 9b4d027..0bb4070 100644
--- a/lib/data/models/users.dart
+++ b/lib/data/models/users.dart
@@ -16,7 +16,7 @@ class Users {
Users.initial()
//: users = <User>[],
: users = [],
- selectedUser = const User(id: '', name: '');
+ selectedUser = const User(id: '0', name: '');
Users copyWith({
List<User>? users,
diff --git a/lib/data/models/voice_assistant_state.dart b/lib/data/models/voice_assistant_state.dart
new file mode 100644
index 0000000..f898dd5
--- /dev/null
+++ b/lib/data/models/voice_assistant_state.dart
@@ -0,0 +1,104 @@
+enum SttModel {
+ whisper,
+ vosk
+}
+
+class VoiceAssistantState{
+ final bool isWakeWordMode;
+ final bool isVoiceAssistantEnable;
+ final bool voiceAssistantOverlay;
+ final bool isOnlineMode;
+ final bool isOnlineModeAvailable;
+ final String wakeWord;
+ final SttModel sttModel;
+ final String streamId;
+ final bool isCommandProcessing;
+ final String commandProcessingText;
+ final int recordingTime;
+ final bool buttonPressed;
+ final bool isRecording;
+ final String command;
+ final String commandResponse;
+ final bool isWakeWordDetected;
+ final bool showOverLay;
+
+
+ const VoiceAssistantState({
+ required this.isWakeWordMode,
+ required this.isVoiceAssistantEnable,
+ required this.voiceAssistantOverlay,
+ required this.isOnlineMode,
+ required this.isOnlineModeAvailable,
+ required this.wakeWord,
+ required this.sttModel,
+ required this.streamId,
+ required this.isCommandProcessing,
+ required this.commandProcessingText,
+ required this.recordingTime,
+ required this.buttonPressed,
+ required this.isRecording,
+ required this.command,
+ required this.commandResponse,
+ required this.isWakeWordDetected,
+ required this.showOverLay,
+ });
+
+ const VoiceAssistantState.initial()
+ : wakeWord = "hello auto",
+ sttModel = SttModel.whisper,
+ streamId = "",
+ isWakeWordMode = false,
+ isVoiceAssistantEnable = false,
+ voiceAssistantOverlay = false,
+ isOnlineMode = false,
+ isOnlineModeAvailable = false,
+ isCommandProcessing = false,
+ commandProcessingText = "Processing...",
+ recordingTime = 4,
+ buttonPressed = false,
+ isRecording = false,
+ command = "",
+ commandResponse = "",
+ isWakeWordDetected = false,
+ showOverLay = false;
+
+ VoiceAssistantState copyWith({
+ bool? isWakeWordMode,
+ bool? isVoiceAssistantEnable,
+ bool? voiceAssistantOverlay,
+ bool? isOnlineMode,
+ bool? isOnlineModeAvailable,
+ String? wakeWord,
+ SttModel? sttModel,
+ String? streamId,
+ bool? isCommandProcessing,
+ String? commandProcessingText,
+ int? recordingTime,
+ bool? buttonPressed,
+ bool? isRecording,
+ String? command,
+ String? commandResponse,
+ bool? isWakeWordDetected,
+ bool? showOverLay,
+ }) {
+ return VoiceAssistantState(
+ isVoiceAssistantEnable : isVoiceAssistantEnable ?? this.isVoiceAssistantEnable,
+ isWakeWordMode : isWakeWordMode ?? this.isWakeWordMode,
+ voiceAssistantOverlay : voiceAssistantOverlay ?? this.voiceAssistantOverlay,
+ isOnlineMode : isOnlineMode ?? this.isOnlineMode,
+ isOnlineModeAvailable : isOnlineModeAvailable ?? this.isOnlineModeAvailable,
+ wakeWord : wakeWord ?? this.wakeWord,
+ sttModel : sttModel ?? this.sttModel,
+ streamId : streamId ?? this.streamId,
+ isCommandProcessing : isCommandProcessing ?? this.isCommandProcessing,
+ commandProcessingText : commandProcessingText ?? this.commandProcessingText,
+ recordingTime : recordingTime ?? this.recordingTime,
+ buttonPressed : buttonPressed ?? this.buttonPressed,
+ isRecording : isRecording ?? this.isRecording,
+ command : command ?? this.command,
+ commandResponse : commandResponse ?? this.commandResponse,
+ isWakeWordDetected: isWakeWordDetected ?? this.isWakeWordDetected,
+ showOverLay: showOverLay ?? this.showOverLay,
+ );
+ }
+} \ No newline at end of file
diff --git a/lib/export.dart b/lib/export.dart
index 90ed196..be4026f 100644
--- a/lib/export.dart
+++ b/lib/export.dart
@@ -60,6 +60,7 @@ export 'presentation/screens/clock/clock.dart';
export 'core/utils/widgets/back_button.dart';
export 'core/constants/vss_path.dart';
+export 'core/constants/users_path.dart';
export 'core/constants/constants.dart';
//Common widgets
export 'presentation/common_widget/settings_top_bar.dart';
diff --git a/lib/main.dart b/lib/main.dart
index c7d84ee..0699f06 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,14 +1,27 @@
import 'package:device_preview/device_preview.dart';
import 'export.dart';
+import 'data/data_providers/initialize_settings.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
- runApp(DevicePreview(
- enabled: debugDisplay,
- tools: const [
- ...DevicePreview.defaultTools,
- ],
- builder: (context) => const App(),
- ));
+
+ // Initialize settings from storage API.
+ final container = ProviderContainer();
+ await initializeSettings(container);
+
+ // Pass the container to ProviderScope and then run the app.
+ runApp(
+ ProviderScope(
+ parent: container,
+ child: DevicePreview(
+ enabled: debugDisplay,
+ tools: const [
+ ...DevicePreview.defaultTools,
+ ],
+ builder: (context) => const App(),
+ ),
+ ),
+ );
}
+
diff --git a/lib/presentation/common_widget/voice_assistant_button.dart b/lib/presentation/common_widget/voice_assistant_button.dart
new file mode 100644
index 0000000..2a82a0a
--- /dev/null
+++ b/lib/presentation/common_widget/voice_assistant_button.dart
@@ -0,0 +1,214 @@
+import 'package:flutter_ics_homescreen/export.dart';
+
+class VoiceAssistantButton extends ConsumerStatefulWidget {
+ const VoiceAssistantButton({super.key});
+
+ @override
+ ConsumerState<VoiceAssistantButton> createState() => _VoiceAssistantButtonState();
+}
+
+class _VoiceAssistantButtonState extends ConsumerState<VoiceAssistantButton> with SingleTickerProviderStateMixin {
+ bool _showOverlay = false;
+ late AnimationController _animationController;
+ late Animation<double> _pulseAnimation;
+ int overlayLock = 0;
+
+ @override
+ void initState() {
+ super.initState();
+ _animationController = AnimationController(
+ vsync: this,
+ duration: const Duration(milliseconds: 700),
+ )..stop(); // Stop the animation initially
+
+ _pulseAnimation = Tween<double>(begin: 1.0, end: 1.05).animate(
+ CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
+ );
+ }
+
+ @override
+ void dispose() {
+ _animationController.dispose();
+ super.dispose();
+ }
+
+ void _onTap() {
+ ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse("");
+ ref.read(voiceAssistantStateProvider.notifier).updateCommand("");
+ bool state = ref.read(voiceAssistantStateProvider.notifier).toggleButtonPressed();
+ if(state){
+ var voiceAgentClient = ref.read(voiceAgentClientProvider);
+ voiceAgentClient.startVoiceAssistant();
+ }
+ }
+
+ void _showAssistantPopup(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ backgroundColor: Colors.transparent,
+ builder: (context) {
+ return Consumer(
+ builder: (context, ref, child) {
+ final String? command = ref.watch(voiceAssistantStateProvider.select((value) => value.command));
+ final String? commandResponse = ref.watch(voiceAssistantStateProvider.select((value) => value.commandResponse));
+ final bool isRecording = ref.watch(voiceAssistantStateProvider.select((value)=>value.isRecording));
+ final bool isProcessing = ref.watch(voiceAssistantStateProvider.select((value)=>value.isCommandProcessing));
+
+ if (isRecording) {
+ _animationController.repeat(reverse: true);
+ } else {
+ _animationController.stop();
+ }
+
+ return Container(
+ height: MediaQuery.of(context).size.height * 0.35,
+ decoration: const BoxDecoration(
+ image: DecorationImage(
+ image: AssetImage('assets/VoiceAssistantBottomSheetBg.png'),
+ fit: BoxFit.cover,
+ ),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(30, 0, 40, 0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ if(!isRecording && !isProcessing)
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Text(
+ command ?? "No Command Detected",
+ textAlign: TextAlign.center,
+ style: const TextStyle(
+ color: Colors.white70,
+ fontSize: 43,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ),
+ SizedBox(
+ height: MediaQuery.of(context).size.height * 0.03
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Text(
+ commandResponse ?? "No Response",
+ textAlign: TextAlign.center,
+ style: const TextStyle(
+ color: Color.fromRGBO(41, 95, 248, 1),
+ fontSize: 43,
+ fontWeight: FontWeight.w800,
+ ),
+ ),
+ ),
+
+ SizedBox(
+ height: MediaQuery.of(context).size.height * 0.02,
+ ),
+ ],
+ ),
+
+ if(isRecording)
+ Column(
+ children: [
+ const Text("Listening...",
+ style: TextStyle(
+ color: Colors.white70,
+ fontSize: 43,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ SizedBox(
+ height: MediaQuery.of(context).size.height*0.02,
+ ),
+ ScaleTransition(
+ scale: _pulseAnimation, // Apply the pulse animation here
+ child: SvgPicture.asset(
+ 'assets/VoiceControlButton.svg',
+ fit: BoxFit.cover,
+ semanticsLabel: 'Voice Assistant',
+ ),
+ ),
+ ],
+ ),
+
+ if(!isRecording && isProcessing)
+ Column(
+ children: [
+ const Text("Processing...",
+ style: TextStyle(
+ color: Colors.white70,
+ fontSize: 43,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ SizedBox(
+ height: MediaQuery.of(context).size.height*0.05,
+ ),
+ Lottie.asset(
+ 'animations/LoadingAnimation.json',
+ fit: BoxFit.cover,
+ repeat: true,
+ ),
+ ],
+ ),
+ SizedBox(
+ height: MediaQuery.of(context).size.height * 0.035,
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ },
+ ).whenComplete(() {
+ ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(null);
+ ref.read(voiceAssistantStateProvider.notifier).updateCommand(null);
+ ref.read(voiceAssistantStateProvider.notifier).toggleShowOverlay(false);
+ overlayLock = 0;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ _showOverlay = ref.watch(voiceAssistantStateProvider.select((value) => value.showOverLay));
+
+ if(_showOverlay){
+ WidgetsBinding.instance!.addPostFrameCallback((_) {
+ if(overlayLock == 0){
+ overlayLock = 1;
+ _showAssistantPopup(context);
+ }
+ });
+ }
+ else if(overlayLock == 1){
+ overlayLock = 0;
+ Navigator.of(context).pop();
+ }
+
+ String svgPath = ref.watch(voiceAssistantStateProvider.select((value) => value.buttonPressed))
+ ? 'assets/VoiceAssistantActive.svg'
+ : 'assets/VoiceAssistantEnabled.svg';
+
+ return Padding(
+ padding: const EdgeInsets.only(left: 8),
+ child: GestureDetector(
+ onTap: _onTap,
+ child: Container(
+ padding: EdgeInsets.zero,
+ child: SvgPicture.asset(
+ svgPath,
+ fit: BoxFit.cover,
+ semanticsLabel: 'Voice Assistant',
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/router/routes/routes.dart b/lib/presentation/router/routes/routes.dart
index 328d495..24eab3a 100644
--- a/lib/presentation/router/routes/routes.dart
+++ b/lib/presentation/router/routes/routes.dart
@@ -3,6 +3,8 @@ import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_sc
import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/date_time/time/time_screen.dart';
import '../../../../export.dart';
+import '../../screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart';
+import '../../screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart';
List<Page<dynamic>> onGenerateAppViewPages(
AppState state,
@@ -57,5 +59,9 @@ List<Page<dynamic>> onGenerateAppViewPages(
return [TimePage.page()];
case AppState.year:
return [SelectYearPage.page()];
+ case AppState.voiceAssistant:
+ return [VoiceAssistantPage.page()];
+ case AppState.sttModel:
+ return [STTModelPage.page()];
}
}
diff --git a/lib/presentation/screens/home/home.dart b/lib/presentation/screens/home/home.dart
index 0ee52ac..6e3e119 100644
--- a/lib/presentation/screens/home/home.dart
+++ b/lib/presentation/screens/home/home.dart
@@ -1,4 +1,6 @@
import 'package:flutter_ics_homescreen/export.dart';
+
+import '../../common_widget/voice_assistant_button.dart';
// import 'package:media_kit_video/media_kit_video.dart';
final bkgImageProvider = Provider((ref) {
@@ -76,6 +78,15 @@ class HomeScreenState extends ConsumerState<HomeScreen> {
height: 500,
child: const VolumeFanControl()),
),
+ // Voice Assistant Button
+ if (appState != AppState.splash && ref.watch(voiceAssistantStateProvider.select((value)=>value.isVoiceAssistantEnable)))
+ Positioned(
+ top: MediaQuery.of(context).size.height * 0.82,
+ child: Container(
+ padding: const EdgeInsets.only(left: 8),
+ child: const VoiceAssistantButton()
+ ),
+ ),
],
),
bottomNavigationBar:
diff --git a/lib/presentation/screens/settings/settings_screens/date_time/date_time_screen.dart b/lib/presentation/screens/settings/settings_screens/date_time/date_time_screen.dart
index caf56a1..acc1541 100644
--- a/lib/presentation/screens/settings/settings_screens/date_time/date_time_screen.dart
+++ b/lib/presentation/screens/settings/settings_screens/date_time/date_time_screen.dart
@@ -16,7 +16,7 @@ class DateTimePage extends ConsumerWidget {
body: Column(
children: [
CommonTitle(
- title: 'Date & Time',
+ title: 'Date & Time',
hasBackButton: true,
onPressed: () {
context.flow<AppState>().update((state) => AppState.settings);
diff --git a/lib/presentation/screens/settings/settings_screens/units/units_screen.dart b/lib/presentation/screens/settings/settings_screens/units/units_screen.dart
index a49546f..ba84471 100644
--- a/lib/presentation/screens/settings/settings_screens/units/units_screen.dart
+++ b/lib/presentation/screens/settings/settings_screens/units/units_screen.dart
@@ -100,7 +100,6 @@ class UnitsTileState extends ConsumerState<UnitsTile> {
children: [
Container(
margin: const EdgeInsets.symmetric(vertical: 8),
- padding: const EdgeInsets.symmetric(vertical: 15),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
@@ -111,7 +110,7 @@ class UnitsTileState extends ConsumerState<UnitsTile> {
//color: Color(0xFF0D113F),
child: ListTile(
contentPadding:
- const EdgeInsets.symmetric(vertical: 17, horizontal: 24),
+ const EdgeInsets.symmetric(vertical: 32, horizontal: 24),
leading: widget.icon != null
? Icon(
widget.icon,
diff --git a/lib/presentation/screens/settings/settings_screens/version_info/version_info_screend.dart b/lib/presentation/screens/settings/settings_screens/version_info/version_info_screend.dart
index fce1837..c5e571d 100644
--- a/lib/presentation/screens/settings/settings_screens/version_info/version_info_screend.dart
+++ b/lib/presentation/screens/settings/settings_screens/version_info/version_info_screend.dart
@@ -1,6 +1,32 @@
import 'package:flutter_ics_homescreen/export.dart';
class VersionInfoPage extends ConsumerWidget {
+ static String aglVersionFilePath = '/etc/os-release';
+ static String kernelVersionFilePath = '/proc/version';
+
+ static String aglVersion() {
+ try {
+ final file = File(aglVersionFilePath);
+ final data = file.readAsStringSync().split("\n");
+ if (!data[1].contains('Automotive Grade Linux')) {
+ throw 'Non-AGL distribution';
+ }
+ return "AGL: " + data[2].split('"')[1];
+ } catch (_) {
+ return '<AGL version not found>';
+ }
+ }
+
+ static String kernelVersion() {
+ try {
+ final file = File(kernelVersionFilePath);
+ final data = file.readAsStringSync().split(" ");
+ return "Kernel: " + data[2];
+ } catch (_) {
+ return '<Kernel version not found>';
+ }
+ }
+
const VersionInfoPage({super.key});
static Page<void> page() =>
@@ -43,7 +69,7 @@ class VersionInfoPage extends ConsumerWidget {
child: ListTile(
contentPadding: const EdgeInsets.only(top: 50, left: 25),
leading: Text(
- aglVeriosn,
+ aglVersion(),
style: Theme.of(context).textTheme.titleMedium,
),
),
@@ -64,7 +90,7 @@ class VersionInfoPage extends ConsumerWidget {
contentPadding: const EdgeInsets.only(top: 50, left: 25),
leading: Text(
- kernelVeriosn,
+ kernelVersion(),
style: Theme.of(context).textTheme.titleMedium,
),
),
diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart
new file mode 100644
index 0000000..e1f38ae
--- /dev/null
+++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart
@@ -0,0 +1,28 @@
+
+import 'package:flutter_ics_homescreen/export.dart';
+import 'widgets/voice_assistant_content.dart';
+
+class VoiceAssistantPage extends ConsumerWidget{
+ const VoiceAssistantPage({super.key});
+
+ static Page<void> page() => const MaterialPage<void>(child: VoiceAssistantPage());
+ @override
+ Widget build(BuildContext context,WidgetRef ref) {
+
+ return Scaffold(
+ body: Column(
+ children: [
+ CommonTitle(
+ title: 'Voice Assistant',
+ hasBackButton: true,
+ onPressed: () {
+ ref.read(appProvider.notifier).back();
+ },
+ ),
+ Expanded(child: VoiceAssistantContent()),
+ ],
+ ),
+ );
+ }
+}
+
diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart
new file mode 100644
index 0000000..614763d
--- /dev/null
+++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart
@@ -0,0 +1,120 @@
+import 'package:flutter_ics_homescreen/export.dart';
+
+import '../../../../../../../data/models/voice_assistant_state.dart';
+
+class STTModelPage extends ConsumerWidget {
+ const STTModelPage({super.key});
+
+ static Page<void> page() =>
+ const MaterialPage<void>(child: STTModelPage());
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final SttModel sttModel = ref.watch(voiceAssistantStateProvider.select((value) => value.sttModel));
+
+ return Scaffold(
+ body: Column(
+ children: [
+ CommonTitle(
+ title: 'Speech to Text Model',
+ hasBackButton: true,
+ onPressed: () {
+ context.flow<AppState>().update((state) => AppState.voiceAssistant);
+ },
+ ),
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 144),
+ child: ListView(
+ children: [
+ Container(
+ height: 130,
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ stops: sttModel == SttModel.whisper
+ ? [0, 0.01, 0.8]
+ : [0.1, 1],
+ colors: sttModel == SttModel.whisper
+ ? <Color>[
+ Colors.white,
+ Colors.blue,
+ const Color.fromARGB(16, 41, 98, 255)
+ ]
+ : <Color>[Colors.black, Colors.black12]),
+ ),
+ child: ListTile(
+ minVerticalPadding: 0.0,
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 16.0, vertical: 40.0),
+ leading: Text(
+ 'Whisper AI',
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
+ trailing: sttModel == SttModel.whisper
+ ? const Icon(
+ Icons.done,
+ color: AGLDemoColors.periwinkleColor,
+ size: 48,
+ )
+ : null,
+ onTap: () {
+ ref
+ .read(voiceAssistantStateProvider.notifier)
+ .updateSttModel(SttModel.whisper);
+ }),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ Container(
+ height: 130,
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ stops: sttModel == SttModel.vosk
+ ? [0, 0.01, 0.8]
+ : [0.1, 1],
+ colors: sttModel == SttModel.vosk
+ ? <Color>[
+ Colors.white,
+ Colors.blue,
+ const Color.fromARGB(16, 41, 98, 255)
+ ]
+ : <Color>[Colors.black, Colors.black12]),
+ ),
+ child: ListTile(
+ minVerticalPadding: 0.0,
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 16.0, vertical: 40.0),
+ leading: Text(
+ 'Vosk',
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
+ //title: Text(widget.title),
+ //enabled: isSwitchOn,
+ trailing: sttModel == SttModel.vosk
+ ? const Icon(
+ Icons.done,
+ color: AGLDemoColors.periwinkleColor,
+ size: 48,
+ )
+ : null,
+
+ onTap: () {
+ ref
+ .read(voiceAssistantStateProvider.notifier)
+ .updateSttModel(SttModel.vosk);
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart
new file mode 100644
index 0000000..924a219
--- /dev/null
+++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart
@@ -0,0 +1,251 @@
+import 'package:flutter_ics_homescreen/export.dart';
+
+import 'package:flutter_ics_homescreen/export.dart';
+import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart';
+
+import '../../../../../../core/utils/helpers.dart';
+import '../../../../../../data/models/voice_assistant_state.dart';
+
+@immutable
+class VoiceAssistantContent extends ConsumerWidget {
+ VoiceAssistantContent({Key? key}) : super(key: key);
+ bool isWakeWordMode = false;
+ bool isVoiceAssistantOverlay = false;
+ bool isOnlineMode = false;
+ SttModel sttModel = SttModel.whisper;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ isWakeWordMode =
+ ref.watch(voiceAssistantStateProvider.select((value) => value.isWakeWordMode));
+ isVoiceAssistantOverlay =
+ ref.watch(voiceAssistantStateProvider.select((value) => value.voiceAssistantOverlay));
+ isOnlineMode =
+ ref.watch(voiceAssistantStateProvider.select((value) => value.isOnlineMode));
+ sttModel =
+ ref.watch(voiceAssistantStateProvider.select((value) => value.sttModel));
+
+ final wakeWordCallback = () {
+ bool status = ref.read(voiceAssistantStateProvider.notifier).toggleWakeWordMode();
+ if(status){
+ var voiceAgentClient = ref.read(voiceAgentClientProvider);
+ voiceAgentClient.startWakeWordDetection();
+ }
+ };
+
+ final voiceAssistantOverlayCallback = () {
+ ref.read(voiceAssistantStateProvider.notifier).toggleVoiceAssistantOverlay();
+ };
+
+ final onlineModeCallback = () {
+ ref.read(voiceAssistantStateProvider.notifier).toggleOnlineMode();
+ };
+
+
+ return Column(
+ children: [
+ Expanded(
+ child: ListView(
+ padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144),
+ children: [
+ VoiceAssistantTile(
+ icon: Icons.insert_comment_outlined,
+ title: "Voice Assistant Overlay",
+ hasSwitch: true,
+ voidCallback: voiceAssistantOverlayCallback,
+ isSwitchOn: isVoiceAssistantOverlay
+ ),
+ if(ref.watch(voiceAssistantStateProvider.select((value) => value.isOnlineModeAvailable)))
+ VoiceAssistantTile(
+ icon: Icons.cloud_circle,
+ title: "Online Mode",
+ hasSwitch: true,
+ voidCallback: onlineModeCallback,
+ isSwitchOn: isOnlineMode
+ ),
+ VoiceAssistantTile(
+ icon: Icons.mic_none_outlined,
+ title: "Wake Word Mode",
+ hasSwitch: true,
+ voidCallback: wakeWordCallback,
+ isSwitchOn: isWakeWordMode
+ ),
+ if(ref.watch(voiceAssistantStateProvider.select((value) => value.isWakeWordMode)))
+ WakeWordTile(),
+ SttTile(
+ title: " Speech To Text",
+ sttName: sttModel==SttModel.whisper ? "Whisper AI" : "Vosk",
+ hasSwich: true,
+ voidCallback: () async {
+ context
+ .flow<AppState>()
+ .update((next) => AppState.sttModel);
+ }),
+ ],
+ )
+ ),
+ ],
+ );
+ }
+}
+
+class SttTile extends ConsumerStatefulWidget {
+ final IconData? icon;
+ final String title;
+ final String sttName;
+ final bool hasSwich;
+ final VoidCallback voidCallback;
+ final String? image;
+ const SttTile({
+ Key? key,
+ this.icon,
+ required this.title,
+ required this.sttName,
+ required this.hasSwich,
+ required this.voidCallback,
+ this.image,
+ }) : super(key: key);
+
+ @override
+ SttTileState createState() => SttTileState();
+}
+
+class SttTileState extends ConsumerState<SttTile> {
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ Container(
+ margin: const EdgeInsets.symmetric(vertical: 8),
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ decoration: const BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ stops: [0.3, 1],
+ colors: <Color>[Colors.black, Colors.black12]),
+ ),
+ //color: Color(0xFF0D113F),
+ child: ListTile(
+ contentPadding:
+ const EdgeInsets.symmetric(vertical: 17, horizontal: 24),
+ leading: Icon(
+ Icons.transcribe_outlined,
+ color: AGLDemoColors.periwinkleColor,
+ size: 48,
+ ),
+ title: Text(
+ widget.title,
+ style: TextStyle(
+ color: AGLDemoColors.periwinkleColor,
+ shadows: [
+ Helpers.dropShadowRegular,
+ ],
+ fontSize: 40),
+ ),
+ trailing: Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Text(
+ widget.sttName,
+ style: TextStyle(
+ color: AGLDemoColors.periwinkleColor,
+ shadows: [
+ Helpers.dropShadowRegular,
+ ],
+ fontSize: 40,
+ ),
+ ),
+ const SizedBox(
+ width: 24,
+ ),
+ const Icon(
+ Icons.arrow_forward_ios,
+ color: AGLDemoColors.periwinkleColor,
+ size: 48,
+ ),
+ ],
+ ),
+ onTap: widget.voidCallback,
+ ),
+ ),
+ const SizedBox(
+ height: 8,
+ )
+ ],
+ );
+ }
+}
+
+
+
+class WakeWordTile extends ConsumerStatefulWidget {
+ const WakeWordTile({Key? key}) : super(key: key);
+
+ @override
+ WakeWordTileState createState() => WakeWordTileState();
+}
+
+class WakeWordTileState extends ConsumerState<WakeWordTile> {
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ Container(
+ margin: const EdgeInsets.symmetric(vertical: 8),
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ decoration: const BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ stops: [0.3, 1],
+ colors: <Color>[Colors.black, Colors.black12]),
+ ),
+ //color: Color(0xFF0D113F),
+ child: ListTile(
+ contentPadding:
+ const EdgeInsets.symmetric(vertical: 17, horizontal: 24),
+ leading: Icon(
+ Icons.mic_none_outlined,
+ color: AGLDemoColors.periwinkleColor,
+ size: 48,
+ ),
+ title: Text(
+ "Wake Word",
+ style: TextStyle(
+ color: AGLDemoColors.periwinkleColor,
+ shadows: [
+ Helpers.dropShadowRegular,
+ ],
+ fontSize: 40),
+ ),
+ trailing: Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Text(
+ ref.watch(voiceAssistantStateProvider.select((value) => value.wakeWord)) ?? "Not Set",
+ style: TextStyle(
+ color: AGLDemoColors.periwinkleColor,
+ shadows: [
+ Helpers.dropShadowRegular,
+ ],
+ fontSize: 40,
+ ),
+ ),
+ const SizedBox(
+ width: 50,
+ ),
+
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 8,
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart
new file mode 100644
index 0000000..ee0365a
--- /dev/null
+++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart
@@ -0,0 +1,111 @@
+import 'package:flutter_ics_homescreen/export.dart';
+import 'package:protos/val_api.dart';
+
+class VoiceAssistantSettingsTile extends ConsumerStatefulWidget {
+ final IconData icon;
+ final String title;
+ final bool hasSwich;
+ final VoidCallback voidCallback;
+ const VoiceAssistantSettingsTile({
+ Key? key,
+ required this.icon,
+ required this.title,
+ required this.hasSwich,
+ required this.voidCallback,
+ }) : super(key: key);
+
+ @override
+ VoiceAssistantSettingsTileState createState() => VoiceAssistantSettingsTileState();
+}
+
+class VoiceAssistantSettingsTileState extends ConsumerState<VoiceAssistantSettingsTile> {
+ bool isSwitchOn = true;
+ @override
+ Widget build(BuildContext context) {
+ isSwitchOn = ref.watch(voiceAssistantStateProvider.select((voiceAssistant) => voiceAssistant.isVoiceAssistantEnable));
+ return Column(
+ children: [
+ GestureDetector(
+ onTap: isSwitchOn ? widget.voidCallback : () {},
+ child: Container(
+ height: 130,
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ stops: isSwitchOn ? [0.3, 1] : [0.8, 1],
+ colors: isSwitchOn
+ ? <Color>[Colors.black, Colors.black12]
+ : <Color>[
+ const Color.fromARGB(50, 0, 0, 0),
+ Colors.transparent
+ ],
+ ),
+ ),
+ child: Card(
+ color: Colors.transparent,
+ elevation: 5,
+ child: Padding(
+ padding:
+ const EdgeInsets.symmetric(vertical: 0, horizontal: 24),
+ child: Row(
+ children: [
+ Icon(
+ widget.icon,
+ color: AGLDemoColors.periwinkleColor,
+ size: 48,
+ ),
+ const SizedBox(width: 24),
+ Expanded(
+ child: Text(
+ widget.title,
+ style: const TextStyle(fontSize: 40),
+ ),
+ ),
+ widget.hasSwich
+ ? Container(
+ width: 126,
+ height: 80,
+ decoration: const ShapeDecoration(
+ color:
+ AGLDemoColors.gradientBackgroundDarkColor,
+ shape: StadiumBorder(
+ side: BorderSide(
+ color: Color(0xFF5477D4),
+ width: 4,
+ )),
+ ),
+ child: FittedBox(
+ fit: BoxFit.fill,
+ child: Switch(
+ value: isSwitchOn,
+ onChanged: (bool value) async {
+ var voiceAgentClient = ref.read(voiceAgentClientProvider);
+ ServiceStatus status = await voiceAgentClient.checkServiceStatus();
+ ref.read(voiceAssistantStateProvider.notifier).toggleVoiceAssistant(status);
+ setState(() {
+ isSwitchOn = value;
+ });
+ // This is called when the user toggles the switch.
+ },
+ inactiveTrackColor: Colors.transparent,
+ activeTrackColor: Colors.transparent,
+ thumbColor:
+ MaterialStateProperty.all<Color>(
+ AGLDemoColors.periwinkleColor)),
+ ),
+ )
+ : const SizedBox(),
+ ],
+ ),
+ ),
+ )
+ ),
+ ),
+ const SizedBox(
+ height: 8,
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart
new file mode 100644
index 0000000..d4bdd48
--- /dev/null
+++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart
@@ -0,0 +1,102 @@
+import 'package:flutter_ics_homescreen/export.dart';
+
+class VoiceAssistantTile extends ConsumerStatefulWidget {
+ final IconData icon;
+ final String title;
+ final bool hasSwitch;
+ final VoidCallback voidCallback;
+ final bool isSwitchOn;
+ const VoiceAssistantTile({super.key, required this.icon, required this.title, required this.hasSwitch, required this.voidCallback,required this.isSwitchOn});
+
+ @override
+ ConsumerState<VoiceAssistantTile> createState() => _VoiceAssistantTileState();
+}
+
+class _VoiceAssistantTileState extends ConsumerState<VoiceAssistantTile> {
+ bool isSwitchOn = true;
+ @override
+ Widget build(BuildContext context) {
+ isSwitchOn = widget.isSwitchOn;
+ return Column(
+ children: [
+ Container(
+ height: 130,
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ stops: isSwitchOn ? [0.3, 1] : [0.8, 1],
+ colors: isSwitchOn
+ ? <Color>[Colors.black, Colors.black12]
+ : <Color>[
+ const Color.fromARGB(50, 0, 0, 0),
+ Colors.transparent
+ ],
+ ),
+ ),
+ child: Card(
+
+ color: Colors.transparent,
+ elevation: 5,
+ child: Padding(
+ padding:
+ const EdgeInsets.symmetric(vertical: 0, horizontal: 24),
+ child: Row(
+ children: [
+ Icon(
+ widget.icon,
+ color: AGLDemoColors.periwinkleColor,
+ size: 48,
+ ),
+ const SizedBox(width: 24),
+ Expanded(
+ child: Text(
+ widget.title,
+ style: const TextStyle(fontSize: 40),
+ ),
+ ),
+ widget.hasSwitch
+ ? Container(
+ width: 126,
+ height: 80,
+ decoration: const ShapeDecoration(
+ color:
+ AGLDemoColors.gradientBackgroundDarkColor,
+ shape: StadiumBorder(
+ side: BorderSide(
+ color: Color(0xFF5477D4),
+ width: 4,
+ )),
+ ),
+ child: FittedBox(
+ fit: BoxFit.fill,
+ child: Switch(
+ value: isSwitchOn,
+ onChanged: (bool value) {
+ setState(() {
+ isSwitchOn = value;
+ });
+ widget.voidCallback();
+ },
+ inactiveTrackColor: Colors.transparent,
+ activeTrackColor: Colors.transparent,
+ thumbColor:
+ MaterialStateProperty.all<Color>(
+ AGLDemoColors.periwinkleColor)),
+ ),
+ )
+ : const SizedBox(),
+ ],
+ ),
+ ),
+ )
+ ),
+ const SizedBox(
+ height: 14,
+ )
+ ],
+ );
+ }
+}
+
+
diff --git a/lib/presentation/screens/settings/widgets/settings_content.dart b/lib/presentation/screens/settings/widgets/settings_content.dart
index 6d0df50..b43c07b 100644
--- a/lib/presentation/screens/settings/widgets/settings_content.dart
+++ b/lib/presentation/screens/settings/widgets/settings_content.dart
@@ -1,6 +1,7 @@
import 'package:flutter_ics_homescreen/export.dart';
import '../../../custom_icons/custom_icons.dart';
+import '../settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart';
class Settings extends ConsumerWidget {
const Settings({
@@ -55,6 +56,15 @@ class Settings extends ConsumerWidget {
voidCallback: () {
ref.read(appProvider.notifier).update(AppState.audioSettings);
}),
+ if(ref.watch(appConfigProvider).enableVoiceAssistant)
+ VoiceAssistantSettingsTile(
+ icon: Icons.keyboard_voice_outlined,
+ title: "Voice Assistant",
+ hasSwich: true,
+ voidCallback: (){
+ ref.read(appProvider.notifier).update(AppState.voiceAssistant);
+ }
+ ),
SettingsTile(
icon: Icons.person_2_outlined,
title: 'Profiles',
diff --git a/protos/lib/src/generated/voice_agent/voice_agent.pb.dart b/protos/lib/src/generated/voice_agent/voice_agent.pb.dart
new file mode 100644
index 0000000..eb6f360
--- /dev/null
+++ b/protos/lib/src/generated/voice_agent/voice_agent.pb.dart
@@ -0,0 +1,880 @@
+//
+// Generated code. Do not modify.
+// source: voice_agent.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:async' as $async;
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'voice_agent.pbenum.dart';
+
+export 'voice_agent.pbenum.dart';
+
+class Empty extends $pb.GeneratedMessage {
+ factory Empty() => create();
+ Empty._() : super();
+ factory Empty.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory Empty.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Empty', createEmptyInstance: create)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ Empty clone() => Empty()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ Empty copyWith(void Function(Empty) updates) => super.copyWith((message) => updates(message as Empty)) as Empty;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static Empty create() => Empty._();
+ Empty createEmptyInstance() => create();
+ static $pb.PbList<Empty> createRepeated() => $pb.PbList<Empty>();
+ @$core.pragma('dart2js:noInline')
+ static Empty getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Empty>(create);
+ static Empty? _defaultInstance;
+}
+
+class ServiceStatus extends $pb.GeneratedMessage {
+ factory ServiceStatus({
+ $core.String? version,
+ $core.bool? status,
+ $core.String? wakeWord,
+ $core.bool? onlineMode,
+ }) {
+ final $result = create();
+ if (version != null) {
+ $result.version = version;
+ }
+ if (status != null) {
+ $result.status = status;
+ }
+ if (wakeWord != null) {
+ $result.wakeWord = wakeWord;
+ }
+ if (onlineMode != null) {
+ $result.onlineMode = onlineMode;
+ }
+ return $result;
+ }
+ ServiceStatus._() : super();
+ factory ServiceStatus.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory ServiceStatus.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ServiceStatus', createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'version')
+ ..aOB(2, _omitFieldNames ? '' : 'status')
+ ..aOS(3, _omitFieldNames ? '' : 'wakeWord')
+ ..aOB(4, _omitFieldNames ? '' : 'onlineMode')
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ ServiceStatus clone() => ServiceStatus()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ ServiceStatus copyWith(void Function(ServiceStatus) updates) => super.copyWith((message) => updates(message as ServiceStatus)) as ServiceStatus;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static ServiceStatus create() => ServiceStatus._();
+ ServiceStatus createEmptyInstance() => create();
+ static $pb.PbList<ServiceStatus> createRepeated() => $pb.PbList<ServiceStatus>();
+ @$core.pragma('dart2js:noInline')
+ static ServiceStatus getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ServiceStatus>(create);
+ static ServiceStatus? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get version => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set version($core.String v) { $_setString(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasVersion() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearVersion() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.bool get status => $_getBF(1);
+ @$pb.TagNumber(2)
+ set status($core.bool v) { $_setBool(1, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasStatus() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearStatus() => clearField(2);
+
+ @$pb.TagNumber(3)
+ $core.String get wakeWord => $_getSZ(2);
+ @$pb.TagNumber(3)
+ set wakeWord($core.String v) { $_setString(2, v); }
+ @$pb.TagNumber(3)
+ $core.bool hasWakeWord() => $_has(2);
+ @$pb.TagNumber(3)
+ void clearWakeWord() => clearField(3);
+
+ @$pb.TagNumber(4)
+ $core.bool get onlineMode => $_getBF(3);
+ @$pb.TagNumber(4)
+ set onlineMode($core.bool v) { $_setBool(3, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasOnlineMode() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearOnlineMode() => clearField(4);
+}
+
+class VoiceAudio extends $pb.GeneratedMessage {
+ factory VoiceAudio({
+ $core.List<$core.int>? audioChunk,
+ $core.String? audioFormat,
+ $core.int? sampleRate,
+ $core.String? language,
+ }) {
+ final $result = create();
+ if (audioChunk != null) {
+ $result.audioChunk = audioChunk;
+ }
+ if (audioFormat != null) {
+ $result.audioFormat = audioFormat;
+ }
+ if (sampleRate != null) {
+ $result.sampleRate = sampleRate;
+ }
+ if (language != null) {
+ $result.language = language;
+ }
+ return $result;
+ }
+ VoiceAudio._() : super();
+ factory VoiceAudio.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory VoiceAudio.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'VoiceAudio', createEmptyInstance: create)
+ ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'audioChunk', $pb.PbFieldType.OY)
+ ..aOS(2, _omitFieldNames ? '' : 'audioFormat')
+ ..a<$core.int>(3, _omitFieldNames ? '' : 'sampleRate', $pb.PbFieldType.O3)
+ ..aOS(4, _omitFieldNames ? '' : 'language')
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ VoiceAudio clone() => VoiceAudio()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ VoiceAudio copyWith(void Function(VoiceAudio) updates) => super.copyWith((message) => updates(message as VoiceAudio)) as VoiceAudio;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static VoiceAudio create() => VoiceAudio._();
+ VoiceAudio createEmptyInstance() => create();
+ static $pb.PbList<VoiceAudio> createRepeated() => $pb.PbList<VoiceAudio>();
+ @$core.pragma('dart2js:noInline')
+ static VoiceAudio getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<VoiceAudio>(create);
+ static VoiceAudio? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.List<$core.int> get audioChunk => $_getN(0);
+ @$pb.TagNumber(1)
+ set audioChunk($core.List<$core.int> v) { $_setBytes(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasAudioChunk() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearAudioChunk() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.String get audioFormat => $_getSZ(1);
+ @$pb.TagNumber(2)
+ set audioFormat($core.String v) { $_setString(1, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasAudioFormat() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearAudioFormat() => clearField(2);
+
+ @$pb.TagNumber(3)
+ $core.int get sampleRate => $_getIZ(2);
+ @$pb.TagNumber(3)
+ set sampleRate($core.int v) { $_setSignedInt32(2, v); }
+ @$pb.TagNumber(3)
+ $core.bool hasSampleRate() => $_has(2);
+ @$pb.TagNumber(3)
+ void clearSampleRate() => clearField(3);
+
+ @$pb.TagNumber(4)
+ $core.String get language => $_getSZ(3);
+ @$pb.TagNumber(4)
+ set language($core.String v) { $_setString(3, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasLanguage() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearLanguage() => clearField(4);
+}
+
+class WakeWordStatus extends $pb.GeneratedMessage {
+ factory WakeWordStatus({
+ $core.bool? status,
+ }) {
+ final $result = create();
+ if (status != null) {
+ $result.status = status;
+ }
+ return $result;
+ }
+ WakeWordStatus._() : super();
+ factory WakeWordStatus.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory WakeWordStatus.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'WakeWordStatus', createEmptyInstance: create)
+ ..aOB(1, _omitFieldNames ? '' : 'status')
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ WakeWordStatus clone() => WakeWordStatus()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ WakeWordStatus copyWith(void Function(WakeWordStatus) updates) => super.copyWith((message) => updates(message as WakeWordStatus)) as WakeWordStatus;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static WakeWordStatus create() => WakeWordStatus._();
+ WakeWordStatus createEmptyInstance() => create();
+ static $pb.PbList<WakeWordStatus> createRepeated() => $pb.PbList<WakeWordStatus>();
+ @$core.pragma('dart2js:noInline')
+ static WakeWordStatus getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<WakeWordStatus>(create);
+ static WakeWordStatus? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.bool get status => $_getBF(0);
+ @$pb.TagNumber(1)
+ set status($core.bool v) { $_setBool(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasStatus() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearStatus() => clearField(1);
+}
+
+class S_RecognizeVoiceControl extends $pb.GeneratedMessage {
+ factory S_RecognizeVoiceControl({
+ VoiceAudio? audioStream,
+ NLUModel? nluModel,
+ $core.String? streamId,
+ STTFramework? sttFramework,
+ }) {
+ final $result = create();
+ if (audioStream != null) {
+ $result.audioStream = audioStream;
+ }
+ if (nluModel != null) {
+ $result.nluModel = nluModel;
+ }
+ if (streamId != null) {
+ $result.streamId = streamId;
+ }
+ if (sttFramework != null) {
+ $result.sttFramework = sttFramework;
+ }
+ return $result;
+ }
+ S_RecognizeVoiceControl._() : super();
+ factory S_RecognizeVoiceControl.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory S_RecognizeVoiceControl.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'S_RecognizeVoiceControl', createEmptyInstance: create)
+ ..aOM<VoiceAudio>(1, _omitFieldNames ? '' : 'audioStream', subBuilder: VoiceAudio.create)
+ ..e<NLUModel>(2, _omitFieldNames ? '' : 'nluModel', $pb.PbFieldType.OE, defaultOrMaker: NLUModel.SNIPS, valueOf: NLUModel.valueOf, enumValues: NLUModel.values)
+ ..aOS(3, _omitFieldNames ? '' : 'streamId')
+ ..e<STTFramework>(4, _omitFieldNames ? '' : 'sttFramework', $pb.PbFieldType.OE, defaultOrMaker: STTFramework.VOSK, valueOf: STTFramework.valueOf, enumValues: STTFramework.values)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ S_RecognizeVoiceControl clone() => S_RecognizeVoiceControl()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ S_RecognizeVoiceControl copyWith(void Function(S_RecognizeVoiceControl) updates) => super.copyWith((message) => updates(message as S_RecognizeVoiceControl)) as S_RecognizeVoiceControl;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static S_RecognizeVoiceControl create() => S_RecognizeVoiceControl._();
+ S_RecognizeVoiceControl createEmptyInstance() => create();
+ static $pb.PbList<S_RecognizeVoiceControl> createRepeated() => $pb.PbList<S_RecognizeVoiceControl>();
+ @$core.pragma('dart2js:noInline')
+ static S_RecognizeVoiceControl getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<S_RecognizeVoiceControl>(create);
+ static S_RecognizeVoiceControl? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ VoiceAudio get audioStream => $_getN(0);
+ @$pb.TagNumber(1)
+ set audioStream(VoiceAudio v) { setField(1, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasAudioStream() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearAudioStream() => clearField(1);
+ @$pb.TagNumber(1)
+ VoiceAudio ensureAudioStream() => $_ensure(0);
+
+ @$pb.TagNumber(2)
+ NLUModel get nluModel => $_getN(1);
+ @$pb.TagNumber(2)
+ set nluModel(NLUModel v) { setField(2, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasNluModel() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearNluModel() => clearField(2);
+
+ @$pb.TagNumber(3)
+ $core.String get streamId => $_getSZ(2);
+ @$pb.TagNumber(3)
+ set streamId($core.String v) { $_setString(2, v); }
+ @$pb.TagNumber(3)
+ $core.bool hasStreamId() => $_has(2);
+ @$pb.TagNumber(3)
+ void clearStreamId() => clearField(3);
+
+ @$pb.TagNumber(4)
+ STTFramework get sttFramework => $_getN(3);
+ @$pb.TagNumber(4)
+ set sttFramework(STTFramework v) { setField(4, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasSttFramework() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearSttFramework() => clearField(4);
+}
+
+class RecognizeVoiceControl extends $pb.GeneratedMessage {
+ factory RecognizeVoiceControl({
+ RecordAction? action,
+ NLUModel? nluModel,
+ RecordMode? recordMode,
+ $core.String? streamId,
+ STTFramework? sttFramework,
+ OnlineMode? onlineMode,
+ }) {
+ final $result = create();
+ if (action != null) {
+ $result.action = action;
+ }
+ if (nluModel != null) {
+ $result.nluModel = nluModel;
+ }
+ if (recordMode != null) {
+ $result.recordMode = recordMode;
+ }
+ if (streamId != null) {
+ $result.streamId = streamId;
+ }
+ if (sttFramework != null) {
+ $result.sttFramework = sttFramework;
+ }
+ if (onlineMode != null) {
+ $result.onlineMode = onlineMode;
+ }
+ return $result;
+ }
+ RecognizeVoiceControl._() : super();
+ factory RecognizeVoiceControl.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory RecognizeVoiceControl.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RecognizeVoiceControl', createEmptyInstance: create)
+ ..e<RecordAction>(1, _omitFieldNames ? '' : 'action', $pb.PbFieldType.OE, defaultOrMaker: RecordAction.START, valueOf: RecordAction.valueOf, enumValues: RecordAction.values)
+ ..e<NLUModel>(2, _omitFieldNames ? '' : 'nluModel', $pb.PbFieldType.OE, defaultOrMaker: NLUModel.SNIPS, valueOf: NLUModel.valueOf, enumValues: NLUModel.values)
+ ..e<RecordMode>(3, _omitFieldNames ? '' : 'recordMode', $pb.PbFieldType.OE, defaultOrMaker: RecordMode.MANUAL, valueOf: RecordMode.valueOf, enumValues: RecordMode.values)
+ ..aOS(4, _omitFieldNames ? '' : 'streamId')
+ ..e<STTFramework>(5, _omitFieldNames ? '' : 'sttFramework', $pb.PbFieldType.OE, defaultOrMaker: STTFramework.VOSK, valueOf: STTFramework.valueOf, enumValues: STTFramework.values)
+ ..e<OnlineMode>(6, _omitFieldNames ? '' : 'onlineMode', $pb.PbFieldType.OE, defaultOrMaker: OnlineMode.ONLINE, valueOf: OnlineMode.valueOf, enumValues: OnlineMode.values)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ RecognizeVoiceControl clone() => RecognizeVoiceControl()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ RecognizeVoiceControl copyWith(void Function(RecognizeVoiceControl) updates) => super.copyWith((message) => updates(message as RecognizeVoiceControl)) as RecognizeVoiceControl;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static RecognizeVoiceControl create() => RecognizeVoiceControl._();
+ RecognizeVoiceControl createEmptyInstance() => create();
+ static $pb.PbList<RecognizeVoiceControl> createRepeated() => $pb.PbList<RecognizeVoiceControl>();
+ @$core.pragma('dart2js:noInline')
+ static RecognizeVoiceControl getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RecognizeVoiceControl>(create);
+ static RecognizeVoiceControl? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ RecordAction get action => $_getN(0);
+ @$pb.TagNumber(1)
+ set action(RecordAction v) { setField(1, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasAction() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearAction() => clearField(1);
+
+ @$pb.TagNumber(2)
+ NLUModel get nluModel => $_getN(1);
+ @$pb.TagNumber(2)
+ set nluModel(NLUModel v) { setField(2, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasNluModel() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearNluModel() => clearField(2);
+
+ @$pb.TagNumber(3)
+ RecordMode get recordMode => $_getN(2);
+ @$pb.TagNumber(3)
+ set recordMode(RecordMode v) { setField(3, v); }
+ @$pb.TagNumber(3)
+ $core.bool hasRecordMode() => $_has(2);
+ @$pb.TagNumber(3)
+ void clearRecordMode() => clearField(3);
+
+ @$pb.TagNumber(4)
+ $core.String get streamId => $_getSZ(3);
+ @$pb.TagNumber(4)
+ set streamId($core.String v) { $_setString(3, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasStreamId() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearStreamId() => clearField(4);
+
+ @$pb.TagNumber(5)
+ STTFramework get sttFramework => $_getN(4);
+ @$pb.TagNumber(5)
+ set sttFramework(STTFramework v) { setField(5, v); }
+ @$pb.TagNumber(5)
+ $core.bool hasSttFramework() => $_has(4);
+ @$pb.TagNumber(5)
+ void clearSttFramework() => clearField(5);
+
+ @$pb.TagNumber(6)
+ OnlineMode get onlineMode => $_getN(5);
+ @$pb.TagNumber(6)
+ set onlineMode(OnlineMode v) { setField(6, v); }
+ @$pb.TagNumber(6)
+ $core.bool hasOnlineMode() => $_has(5);
+ @$pb.TagNumber(6)
+ void clearOnlineMode() => clearField(6);
+}
+
+class RecognizeTextControl extends $pb.GeneratedMessage {
+ factory RecognizeTextControl({
+ $core.String? textCommand,
+ NLUModel? nluModel,
+ }) {
+ final $result = create();
+ if (textCommand != null) {
+ $result.textCommand = textCommand;
+ }
+ if (nluModel != null) {
+ $result.nluModel = nluModel;
+ }
+ return $result;
+ }
+ RecognizeTextControl._() : super();
+ factory RecognizeTextControl.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory RecognizeTextControl.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RecognizeTextControl', createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'textCommand')
+ ..e<NLUModel>(2, _omitFieldNames ? '' : 'nluModel', $pb.PbFieldType.OE, defaultOrMaker: NLUModel.SNIPS, valueOf: NLUModel.valueOf, enumValues: NLUModel.values)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ RecognizeTextControl clone() => RecognizeTextControl()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ RecognizeTextControl copyWith(void Function(RecognizeTextControl) updates) => super.copyWith((message) => updates(message as RecognizeTextControl)) as RecognizeTextControl;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static RecognizeTextControl create() => RecognizeTextControl._();
+ RecognizeTextControl createEmptyInstance() => create();
+ static $pb.PbList<RecognizeTextControl> createRepeated() => $pb.PbList<RecognizeTextControl>();
+ @$core.pragma('dart2js:noInline')
+ static RecognizeTextControl getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RecognizeTextControl>(create);
+ static RecognizeTextControl? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get textCommand => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set textCommand($core.String v) { $_setString(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasTextCommand() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearTextCommand() => clearField(1);
+
+ @$pb.TagNumber(2)
+ NLUModel get nluModel => $_getN(1);
+ @$pb.TagNumber(2)
+ set nluModel(NLUModel v) { setField(2, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasNluModel() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearNluModel() => clearField(2);
+}
+
+class IntentSlot extends $pb.GeneratedMessage {
+ factory IntentSlot({
+ $core.String? name,
+ $core.String? value,
+ }) {
+ final $result = create();
+ if (name != null) {
+ $result.name = name;
+ }
+ if (value != null) {
+ $result.value = value;
+ }
+ return $result;
+ }
+ IntentSlot._() : super();
+ factory IntentSlot.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory IntentSlot.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'IntentSlot', createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'name')
+ ..aOS(2, _omitFieldNames ? '' : 'value')
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ IntentSlot clone() => IntentSlot()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ IntentSlot copyWith(void Function(IntentSlot) updates) => super.copyWith((message) => updates(message as IntentSlot)) as IntentSlot;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static IntentSlot create() => IntentSlot._();
+ IntentSlot createEmptyInstance() => create();
+ static $pb.PbList<IntentSlot> createRepeated() => $pb.PbList<IntentSlot>();
+ @$core.pragma('dart2js:noInline')
+ static IntentSlot getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<IntentSlot>(create);
+ static IntentSlot? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get name => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set name($core.String v) { $_setString(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasName() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearName() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.String get value => $_getSZ(1);
+ @$pb.TagNumber(2)
+ set value($core.String v) { $_setString(1, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasValue() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearValue() => clearField(2);
+}
+
+class RecognizeResult extends $pb.GeneratedMessage {
+ factory RecognizeResult({
+ $core.String? command,
+ $core.String? intent,
+ $core.Iterable<IntentSlot>? intentSlots,
+ $core.String? streamId,
+ RecognizeStatusType? status,
+ }) {
+ final $result = create();
+ if (command != null) {
+ $result.command = command;
+ }
+ if (intent != null) {
+ $result.intent = intent;
+ }
+ if (intentSlots != null) {
+ $result.intentSlots.addAll(intentSlots);
+ }
+ if (streamId != null) {
+ $result.streamId = streamId;
+ }
+ if (status != null) {
+ $result.status = status;
+ }
+ return $result;
+ }
+ RecognizeResult._() : super();
+ factory RecognizeResult.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory RecognizeResult.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RecognizeResult', createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'command')
+ ..aOS(2, _omitFieldNames ? '' : 'intent')
+ ..pc<IntentSlot>(3, _omitFieldNames ? '' : 'intentSlots', $pb.PbFieldType.PM, subBuilder: IntentSlot.create)
+ ..aOS(4, _omitFieldNames ? '' : 'streamId')
+ ..e<RecognizeStatusType>(5, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE, defaultOrMaker: RecognizeStatusType.REC_ERROR, valueOf: RecognizeStatusType.valueOf, enumValues: RecognizeStatusType.values)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ RecognizeResult clone() => RecognizeResult()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ RecognizeResult copyWith(void Function(RecognizeResult) updates) => super.copyWith((message) => updates(message as RecognizeResult)) as RecognizeResult;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static RecognizeResult create() => RecognizeResult._();
+ RecognizeResult createEmptyInstance() => create();
+ static $pb.PbList<RecognizeResult> createRepeated() => $pb.PbList<RecognizeResult>();
+ @$core.pragma('dart2js:noInline')
+ static RecognizeResult getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RecognizeResult>(create);
+ static RecognizeResult? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get command => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set command($core.String v) { $_setString(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasCommand() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearCommand() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.String get intent => $_getSZ(1);
+ @$pb.TagNumber(2)
+ set intent($core.String v) { $_setString(1, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasIntent() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearIntent() => clearField(2);
+
+ @$pb.TagNumber(3)
+ $core.List<IntentSlot> get intentSlots => $_getList(2);
+
+ @$pb.TagNumber(4)
+ $core.String get streamId => $_getSZ(3);
+ @$pb.TagNumber(4)
+ set streamId($core.String v) { $_setString(3, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasStreamId() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearStreamId() => clearField(4);
+
+ @$pb.TagNumber(5)
+ RecognizeStatusType get status => $_getN(4);
+ @$pb.TagNumber(5)
+ set status(RecognizeStatusType v) { setField(5, v); }
+ @$pb.TagNumber(5)
+ $core.bool hasStatus() => $_has(4);
+ @$pb.TagNumber(5)
+ void clearStatus() => clearField(5);
+}
+
+class ExecuteInput extends $pb.GeneratedMessage {
+ factory ExecuteInput({
+ $core.String? intent,
+ $core.Iterable<IntentSlot>? intentSlots,
+ }) {
+ final $result = create();
+ if (intent != null) {
+ $result.intent = intent;
+ }
+ if (intentSlots != null) {
+ $result.intentSlots.addAll(intentSlots);
+ }
+ return $result;
+ }
+ ExecuteInput._() : super();
+ factory ExecuteInput.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory ExecuteInput.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ExecuteInput', createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'intent')
+ ..pc<IntentSlot>(2, _omitFieldNames ? '' : 'intentSlots', $pb.PbFieldType.PM, subBuilder: IntentSlot.create)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ ExecuteInput clone() => ExecuteInput()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ ExecuteInput copyWith(void Function(ExecuteInput) updates) => super.copyWith((message) => updates(message as ExecuteInput)) as ExecuteInput;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static ExecuteInput create() => ExecuteInput._();
+ ExecuteInput createEmptyInstance() => create();
+ static $pb.PbList<ExecuteInput> createRepeated() => $pb.PbList<ExecuteInput>();
+ @$core.pragma('dart2js:noInline')
+ static ExecuteInput getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ExecuteInput>(create);
+ static ExecuteInput? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get intent => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set intent($core.String v) { $_setString(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasIntent() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearIntent() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.List<IntentSlot> get intentSlots => $_getList(1);
+}
+
+class ExecuteResult extends $pb.GeneratedMessage {
+ factory ExecuteResult({
+ $core.String? response,
+ ExecuteStatusType? status,
+ }) {
+ final $result = create();
+ if (response != null) {
+ $result.response = response;
+ }
+ if (status != null) {
+ $result.status = status;
+ }
+ return $result;
+ }
+ ExecuteResult._() : super();
+ factory ExecuteResult.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory ExecuteResult.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ExecuteResult', createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'response')
+ ..e<ExecuteStatusType>(2, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE, defaultOrMaker: ExecuteStatusType.EXEC_ERROR, valueOf: ExecuteStatusType.valueOf, enumValues: ExecuteStatusType.values)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ ExecuteResult clone() => ExecuteResult()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ ExecuteResult copyWith(void Function(ExecuteResult) updates) => super.copyWith((message) => updates(message as ExecuteResult)) as ExecuteResult;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static ExecuteResult create() => ExecuteResult._();
+ ExecuteResult createEmptyInstance() => create();
+ static $pb.PbList<ExecuteResult> createRepeated() => $pb.PbList<ExecuteResult>();
+ @$core.pragma('dart2js:noInline')
+ static ExecuteResult getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ExecuteResult>(create);
+ static ExecuteResult? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get response => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set response($core.String v) { $_setString(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasResponse() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearResponse() => clearField(1);
+
+ @$pb.TagNumber(2)
+ ExecuteStatusType get status => $_getN(1);
+ @$pb.TagNumber(2)
+ set status(ExecuteStatusType v) { setField(2, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasStatus() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearStatus() => clearField(2);
+}
+
+class VoiceAgentServiceApi {
+ $pb.RpcClient _client;
+ VoiceAgentServiceApi(this._client);
+
+ $async.Future<ServiceStatus> checkServiceStatus($pb.ClientContext? ctx, Empty request) =>
+ _client.invoke<ServiceStatus>(ctx, 'VoiceAgentService', 'CheckServiceStatus', request, ServiceStatus())
+ ;
+ $async.Future<WakeWordStatus> s_DetectWakeWord($pb.ClientContext? ctx, VoiceAudio request) =>
+ _client.invoke<WakeWordStatus>(ctx, 'VoiceAgentService', 'S_DetectWakeWord', request, WakeWordStatus())
+ ;
+ $async.Future<WakeWordStatus> detectWakeWord($pb.ClientContext? ctx, Empty request) =>
+ _client.invoke<WakeWordStatus>(ctx, 'VoiceAgentService', 'DetectWakeWord', request, WakeWordStatus())
+ ;
+ $async.Future<RecognizeResult> s_RecognizeVoiceCommand($pb.ClientContext? ctx, S_RecognizeVoiceControl request) =>
+ _client.invoke<RecognizeResult>(ctx, 'VoiceAgentService', 'S_RecognizeVoiceCommand', request, RecognizeResult())
+ ;
+ $async.Future<RecognizeResult> recognizeVoiceCommand($pb.ClientContext? ctx, RecognizeVoiceControl request) =>
+ _client.invoke<RecognizeResult>(ctx, 'VoiceAgentService', 'RecognizeVoiceCommand', request, RecognizeResult())
+ ;
+ $async.Future<RecognizeResult> recognizeTextCommand($pb.ClientContext? ctx, RecognizeTextControl request) =>
+ _client.invoke<RecognizeResult>(ctx, 'VoiceAgentService', 'RecognizeTextCommand', request, RecognizeResult())
+ ;
+ $async.Future<ExecuteResult> executeCommand($pb.ClientContext? ctx, ExecuteInput request) =>
+ _client.invoke<ExecuteResult>(ctx, 'VoiceAgentService', 'ExecuteCommand', request, ExecuteResult())
+ ;
+}
+
+
+const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
+const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
diff --git a/protos/lib/src/generated/voice_agent/voice_agent.pbenum.dart b/protos/lib/src/generated/voice_agent/voice_agent.pbenum.dart
new file mode 100644
index 0000000..51e7427
--- /dev/null
+++ b/protos/lib/src/generated/voice_agent/voice_agent.pbenum.dart
@@ -0,0 +1,138 @@
+//
+// Generated code. Do not modify.
+// source: voice_agent.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class STTFramework extends $pb.ProtobufEnum {
+ static const STTFramework VOSK = STTFramework._(0, _omitEnumNames ? '' : 'VOSK');
+ static const STTFramework WHISPER = STTFramework._(1, _omitEnumNames ? '' : 'WHISPER');
+
+ static const $core.List<STTFramework> values = <STTFramework> [
+ VOSK,
+ WHISPER,
+ ];
+
+ static final $core.Map<$core.int, STTFramework> _byValue = $pb.ProtobufEnum.initByValue(values);
+ static STTFramework? valueOf($core.int value) => _byValue[value];
+
+ const STTFramework._($core.int v, $core.String n) : super(v, n);
+}
+
+class OnlineMode extends $pb.ProtobufEnum {
+ static const OnlineMode ONLINE = OnlineMode._(0, _omitEnumNames ? '' : 'ONLINE');
+ static const OnlineMode OFFLINE = OnlineMode._(1, _omitEnumNames ? '' : 'OFFLINE');
+
+ static const $core.List<OnlineMode> values = <OnlineMode> [
+ ONLINE,
+ OFFLINE,
+ ];
+
+ static final $core.Map<$core.int, OnlineMode> _byValue = $pb.ProtobufEnum.initByValue(values);
+ static OnlineMode? valueOf($core.int value) => _byValue[value];
+
+ const OnlineMode._($core.int v, $core.String n) : super(v, n);
+}
+
+class RecordAction extends $pb.ProtobufEnum {
+ static const RecordAction START = RecordAction._(0, _omitEnumNames ? '' : 'START');
+ static const RecordAction STOP = RecordAction._(1, _omitEnumNames ? '' : 'STOP');
+
+ static const $core.List<RecordAction> values = <RecordAction> [
+ START,
+ STOP,
+ ];
+
+ static final $core.Map<$core.int, RecordAction> _byValue = $pb.ProtobufEnum.initByValue(values);
+ static RecordAction? valueOf($core.int value) => _byValue[value];
+
+ const RecordAction._($core.int v, $core.String n) : super(v, n);
+}
+
+class NLUModel extends $pb.ProtobufEnum {
+ static const NLUModel SNIPS = NLUModel._(0, _omitEnumNames ? '' : 'SNIPS');
+ static const NLUModel RASA = NLUModel._(1, _omitEnumNames ? '' : 'RASA');
+
+ static const $core.List<NLUModel> values = <NLUModel> [
+ SNIPS,
+ RASA,
+ ];
+
+ static final $core.Map<$core.int, NLUModel> _byValue = $pb.ProtobufEnum.initByValue(values);
+ static NLUModel? valueOf($core.int value) => _byValue[value];
+
+ const NLUModel._($core.int v, $core.String n) : super(v, n);
+}
+
+class RecordMode extends $pb.ProtobufEnum {
+ static const RecordMode MANUAL = RecordMode._(0, _omitEnumNames ? '' : 'MANUAL');
+ static const RecordMode AUTO = RecordMode._(1, _omitEnumNames ? '' : 'AUTO');
+
+ static const $core.List<RecordMode> values = <RecordMode> [
+ MANUAL,
+ AUTO,
+ ];
+
+ static final $core.Map<$core.int, RecordMode> _byValue = $pb.ProtobufEnum.initByValue(values);
+ static RecordMode? valueOf($core.int value) => _byValue[value];
+
+ const RecordMode._($core.int v, $core.String n) : super(v, n);
+}
+
+class RecognizeStatusType extends $pb.ProtobufEnum {
+ static const RecognizeStatusType REC_ERROR = RecognizeStatusType._(0, _omitEnumNames ? '' : 'REC_ERROR');
+ static const RecognizeStatusType REC_SUCCESS = RecognizeStatusType._(1, _omitEnumNames ? '' : 'REC_SUCCESS');
+ static const RecognizeStatusType REC_PROCESSING = RecognizeStatusType._(2, _omitEnumNames ? '' : 'REC_PROCESSING');
+ static const RecognizeStatusType VOICE_NOT_RECOGNIZED = RecognizeStatusType._(3, _omitEnumNames ? '' : 'VOICE_NOT_RECOGNIZED');
+ static const RecognizeStatusType INTENT_NOT_RECOGNIZED = RecognizeStatusType._(4, _omitEnumNames ? '' : 'INTENT_NOT_RECOGNIZED');
+ static const RecognizeStatusType TEXT_NOT_RECOGNIZED = RecognizeStatusType._(5, _omitEnumNames ? '' : 'TEXT_NOT_RECOGNIZED');
+ static const RecognizeStatusType NLU_MODEL_NOT_SUPPORTED = RecognizeStatusType._(6, _omitEnumNames ? '' : 'NLU_MODEL_NOT_SUPPORTED');
+
+ static const $core.List<RecognizeStatusType> values = <RecognizeStatusType> [
+ REC_ERROR,
+ REC_SUCCESS,
+ REC_PROCESSING,
+ VOICE_NOT_RECOGNIZED,
+ INTENT_NOT_RECOGNIZED,
+ TEXT_NOT_RECOGNIZED,
+ NLU_MODEL_NOT_SUPPORTED,
+ ];
+
+ static final $core.Map<$core.int, RecognizeStatusType> _byValue = $pb.ProtobufEnum.initByValue(values);
+ static RecognizeStatusType? valueOf($core.int value) => _byValue[value];
+
+ const RecognizeStatusType._($core.int v, $core.String n) : super(v, n);
+}
+
+class ExecuteStatusType extends $pb.ProtobufEnum {
+ static const ExecuteStatusType EXEC_ERROR = ExecuteStatusType._(0, _omitEnumNames ? '' : 'EXEC_ERROR');
+ static const ExecuteStatusType EXEC_SUCCESS = ExecuteStatusType._(1, _omitEnumNames ? '' : 'EXEC_SUCCESS');
+ static const ExecuteStatusType KUKSA_CONN_ERROR = ExecuteStatusType._(2, _omitEnumNames ? '' : 'KUKSA_CONN_ERROR');
+ static const ExecuteStatusType INTENT_NOT_SUPPORTED = ExecuteStatusType._(3, _omitEnumNames ? '' : 'INTENT_NOT_SUPPORTED');
+ static const ExecuteStatusType INTENT_SLOTS_INCOMPLETE = ExecuteStatusType._(4, _omitEnumNames ? '' : 'INTENT_SLOTS_INCOMPLETE');
+
+ static const $core.List<ExecuteStatusType> values = <ExecuteStatusType> [
+ EXEC_ERROR,
+ EXEC_SUCCESS,
+ KUKSA_CONN_ERROR,
+ INTENT_NOT_SUPPORTED,
+ INTENT_SLOTS_INCOMPLETE,
+ ];
+
+ static final $core.Map<$core.int, ExecuteStatusType> _byValue = $pb.ProtobufEnum.initByValue(values);
+ static ExecuteStatusType? valueOf($core.int value) => _byValue[value];
+
+ const ExecuteStatusType._($core.int v, $core.String n) : super(v, n);
+}
+
+
+const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
diff --git a/protos/lib/src/generated/voice_agent/voice_agent.pbgrpc.dart b/protos/lib/src/generated/voice_agent/voice_agent.pbgrpc.dart
new file mode 100644
index 0000000..e972432
--- /dev/null
+++ b/protos/lib/src/generated/voice_agent/voice_agent.pbgrpc.dart
@@ -0,0 +1,167 @@
+//
+// Generated code. Do not modify.
+// source: voice_agent.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:async' as $async;
+import 'dart:core' as $core;
+
+import 'package:grpc/service_api.dart' as $grpc;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'voice_agent.pb.dart' as $0;
+
+export 'voice_agent.pb.dart';
+
+@$pb.GrpcServiceName('VoiceAgentService')
+class VoiceAgentServiceClient extends $grpc.Client {
+ static final _$checkServiceStatus = $grpc.ClientMethod<$0.Empty, $0.ServiceStatus>(
+ '/VoiceAgentService/CheckServiceStatus',
+ ($0.Empty value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.ServiceStatus.fromBuffer(value));
+ static final _$s_DetectWakeWord = $grpc.ClientMethod<$0.VoiceAudio, $0.WakeWordStatus>(
+ '/VoiceAgentService/S_DetectWakeWord',
+ ($0.VoiceAudio value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.WakeWordStatus.fromBuffer(value));
+ static final _$detectWakeWord = $grpc.ClientMethod<$0.Empty, $0.WakeWordStatus>(
+ '/VoiceAgentService/DetectWakeWord',
+ ($0.Empty value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.WakeWordStatus.fromBuffer(value));
+ static final _$s_RecognizeVoiceCommand = $grpc.ClientMethod<$0.S_RecognizeVoiceControl, $0.RecognizeResult>(
+ '/VoiceAgentService/S_RecognizeVoiceCommand',
+ ($0.S_RecognizeVoiceControl value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.RecognizeResult.fromBuffer(value));
+ static final _$recognizeVoiceCommand = $grpc.ClientMethod<$0.RecognizeVoiceControl, $0.RecognizeResult>(
+ '/VoiceAgentService/RecognizeVoiceCommand',
+ ($0.RecognizeVoiceControl value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.RecognizeResult.fromBuffer(value));
+ static final _$recognizeTextCommand = $grpc.ClientMethod<$0.RecognizeTextControl, $0.RecognizeResult>(
+ '/VoiceAgentService/RecognizeTextCommand',
+ ($0.RecognizeTextControl value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.RecognizeResult.fromBuffer(value));
+ static final _$executeCommand = $grpc.ClientMethod<$0.ExecuteInput, $0.ExecuteResult>(
+ '/VoiceAgentService/ExecuteCommand',
+ ($0.ExecuteInput value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.ExecuteResult.fromBuffer(value));
+
+ VoiceAgentServiceClient($grpc.ClientChannel channel,
+ {$grpc.CallOptions? options,
+ $core.Iterable<$grpc.ClientInterceptor>? interceptors})
+ : super(channel, options: options,
+ interceptors: interceptors);
+
+ $grpc.ResponseFuture<$0.ServiceStatus> checkServiceStatus($0.Empty request, {$grpc.CallOptions? options}) {
+ return $createUnaryCall(_$checkServiceStatus, request, options: options);
+ }
+
+ $grpc.ResponseStream<$0.WakeWordStatus> s_DetectWakeWord($async.Stream<$0.VoiceAudio> request, {$grpc.CallOptions? options}) {
+ return $createStreamingCall(_$s_DetectWakeWord, request, options: options);
+ }
+
+ $grpc.ResponseStream<$0.WakeWordStatus> detectWakeWord($0.Empty request, {$grpc.CallOptions? options}) {
+ return $createStreamingCall(_$detectWakeWord, $async.Stream.fromIterable([request]), options: options);
+ }
+
+ $grpc.ResponseFuture<$0.RecognizeResult> s_RecognizeVoiceCommand($async.Stream<$0.S_RecognizeVoiceControl> request, {$grpc.CallOptions? options}) {
+ return $createStreamingCall(_$s_RecognizeVoiceCommand, request, options: options).single;
+ }
+
+ $grpc.ResponseFuture<$0.RecognizeResult> recognizeVoiceCommand($async.Stream<$0.RecognizeVoiceControl> request, {$grpc.CallOptions? options}) {
+ return $createStreamingCall(_$recognizeVoiceCommand, request, options: options).single;
+ }
+
+ $grpc.ResponseFuture<$0.RecognizeResult> recognizeTextCommand($0.RecognizeTextControl request, {$grpc.CallOptions? options}) {
+ return $createUnaryCall(_$recognizeTextCommand, request, options: options);
+ }
+
+ $grpc.ResponseFuture<$0.ExecuteResult> executeCommand($0.ExecuteInput request, {$grpc.CallOptions? options}) {
+ return $createUnaryCall(_$executeCommand, request, options: options);
+ }
+}
+
+@$pb.GrpcServiceName('VoiceAgentService')
+abstract class VoiceAgentServiceBase extends $grpc.Service {
+ $core.String get $name => 'VoiceAgentService';
+
+ VoiceAgentServiceBase() {
+ $addMethod($grpc.ServiceMethod<$0.Empty, $0.ServiceStatus>(
+ 'CheckServiceStatus',
+ checkServiceStatus_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.Empty.fromBuffer(value),
+ ($0.ServiceStatus value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.VoiceAudio, $0.WakeWordStatus>(
+ 'S_DetectWakeWord',
+ s_DetectWakeWord,
+ true,
+ true,
+ ($core.List<$core.int> value) => $0.VoiceAudio.fromBuffer(value),
+ ($0.WakeWordStatus value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.Empty, $0.WakeWordStatus>(
+ 'DetectWakeWord',
+ detectWakeWord_Pre,
+ false,
+ true,
+ ($core.List<$core.int> value) => $0.Empty.fromBuffer(value),
+ ($0.WakeWordStatus value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.S_RecognizeVoiceControl, $0.RecognizeResult>(
+ 'S_RecognizeVoiceCommand',
+ s_RecognizeVoiceCommand,
+ true,
+ false,
+ ($core.List<$core.int> value) => $0.S_RecognizeVoiceControl.fromBuffer(value),
+ ($0.RecognizeResult value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.RecognizeVoiceControl, $0.RecognizeResult>(
+ 'RecognizeVoiceCommand',
+ recognizeVoiceCommand,
+ true,
+ false,
+ ($core.List<$core.int> value) => $0.RecognizeVoiceControl.fromBuffer(value),
+ ($0.RecognizeResult value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.RecognizeTextControl, $0.RecognizeResult>(
+ 'RecognizeTextCommand',
+ recognizeTextCommand_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.RecognizeTextControl.fromBuffer(value),
+ ($0.RecognizeResult value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.ExecuteInput, $0.ExecuteResult>(
+ 'ExecuteCommand',
+ executeCommand_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.ExecuteInput.fromBuffer(value),
+ ($0.ExecuteResult value) => value.writeToBuffer()));
+ }
+
+ $async.Future<$0.ServiceStatus> checkServiceStatus_Pre($grpc.ServiceCall call, $async.Future<$0.Empty> request) async {
+ return checkServiceStatus(call, await request);
+ }
+
+ $async.Stream<$0.WakeWordStatus> detectWakeWord_Pre($grpc.ServiceCall call, $async.Future<$0.Empty> request) async* {
+ yield* detectWakeWord(call, await request);
+ }
+
+ $async.Future<$0.RecognizeResult> recognizeTextCommand_Pre($grpc.ServiceCall call, $async.Future<$0.RecognizeTextControl> request) async {
+ return recognizeTextCommand(call, await request);
+ }
+
+ $async.Future<$0.ExecuteResult> executeCommand_Pre($grpc.ServiceCall call, $async.Future<$0.ExecuteInput> request) async {
+ return executeCommand(call, await request);
+ }
+
+ $async.Future<$0.ServiceStatus> checkServiceStatus($grpc.ServiceCall call, $0.Empty request);
+ $async.Stream<$0.WakeWordStatus> s_DetectWakeWord($grpc.ServiceCall call, $async.Stream<$0.VoiceAudio> request);
+ $async.Stream<$0.WakeWordStatus> detectWakeWord($grpc.ServiceCall call, $0.Empty request);
+ $async.Future<$0.RecognizeResult> s_RecognizeVoiceCommand($grpc.ServiceCall call, $async.Stream<$0.S_RecognizeVoiceControl> request);
+ $async.Future<$0.RecognizeResult> recognizeVoiceCommand($grpc.ServiceCall call, $async.Stream<$0.RecognizeVoiceControl> request);
+ $async.Future<$0.RecognizeResult> recognizeTextCommand($grpc.ServiceCall call, $0.RecognizeTextControl request);
+ $async.Future<$0.ExecuteResult> executeCommand($grpc.ServiceCall call, $0.ExecuteInput request);
+}
diff --git a/protos/lib/src/generated/voice_agent/voice_agent.pbjson.dart b/protos/lib/src/generated/voice_agent/voice_agent.pbjson.dart
new file mode 100644
index 0000000..2a824c1
--- /dev/null
+++ b/protos/lib/src/generated/voice_agent/voice_agent.pbjson.dart
@@ -0,0 +1,326 @@
+//
+// Generated code. Do not modify.
+// source: voice_agent.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:convert' as $convert;
+import 'dart:core' as $core;
+import 'dart:typed_data' as $typed_data;
+
+@$core.Deprecated('Use sTTFrameworkDescriptor instead')
+const STTFramework$json = {
+ '1': 'STTFramework',
+ '2': [
+ {'1': 'VOSK', '2': 0},
+ {'1': 'WHISPER', '2': 1},
+ ],
+};
+
+/// Descriptor for `STTFramework`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List sTTFrameworkDescriptor = $convert.base64Decode(
+ 'CgxTVFRGcmFtZXdvcmsSCAoEVk9TSxAAEgsKB1dISVNQRVIQAQ==');
+
+@$core.Deprecated('Use onlineModeDescriptor instead')
+const OnlineMode$json = {
+ '1': 'OnlineMode',
+ '2': [
+ {'1': 'ONLINE', '2': 0},
+ {'1': 'OFFLINE', '2': 1},
+ ],
+};
+
+/// Descriptor for `OnlineMode`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List onlineModeDescriptor = $convert.base64Decode(
+ 'CgpPbmxpbmVNb2RlEgoKBk9OTElORRAAEgsKB09GRkxJTkUQAQ==');
+
+@$core.Deprecated('Use recordActionDescriptor instead')
+const RecordAction$json = {
+ '1': 'RecordAction',
+ '2': [
+ {'1': 'START', '2': 0},
+ {'1': 'STOP', '2': 1},
+ ],
+};
+
+/// Descriptor for `RecordAction`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List recordActionDescriptor = $convert.base64Decode(
+ 'CgxSZWNvcmRBY3Rpb24SCQoFU1RBUlQQABIICgRTVE9QEAE=');
+
+@$core.Deprecated('Use nLUModelDescriptor instead')
+const NLUModel$json = {
+ '1': 'NLUModel',
+ '2': [
+ {'1': 'SNIPS', '2': 0},
+ {'1': 'RASA', '2': 1},
+ ],
+};
+
+/// Descriptor for `NLUModel`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List nLUModelDescriptor = $convert.base64Decode(
+ 'CghOTFVNb2RlbBIJCgVTTklQUxAAEggKBFJBU0EQAQ==');
+
+@$core.Deprecated('Use recordModeDescriptor instead')
+const RecordMode$json = {
+ '1': 'RecordMode',
+ '2': [
+ {'1': 'MANUAL', '2': 0},
+ {'1': 'AUTO', '2': 1},
+ ],
+};
+
+/// Descriptor for `RecordMode`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List recordModeDescriptor = $convert.base64Decode(
+ 'CgpSZWNvcmRNb2RlEgoKBk1BTlVBTBAAEggKBEFVVE8QAQ==');
+
+@$core.Deprecated('Use recognizeStatusTypeDescriptor instead')
+const RecognizeStatusType$json = {
+ '1': 'RecognizeStatusType',
+ '2': [
+ {'1': 'REC_ERROR', '2': 0},
+ {'1': 'REC_SUCCESS', '2': 1},
+ {'1': 'REC_PROCESSING', '2': 2},
+ {'1': 'VOICE_NOT_RECOGNIZED', '2': 3},
+ {'1': 'INTENT_NOT_RECOGNIZED', '2': 4},
+ {'1': 'TEXT_NOT_RECOGNIZED', '2': 5},
+ {'1': 'NLU_MODEL_NOT_SUPPORTED', '2': 6},
+ ],
+};
+
+/// Descriptor for `RecognizeStatusType`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List recognizeStatusTypeDescriptor = $convert.base64Decode(
+ 'ChNSZWNvZ25pemVTdGF0dXNUeXBlEg0KCVJFQ19FUlJPUhAAEg8KC1JFQ19TVUNDRVNTEAESEg'
+ 'oOUkVDX1BST0NFU1NJTkcQAhIYChRWT0lDRV9OT1RfUkVDT0dOSVpFRBADEhkKFUlOVEVOVF9O'
+ 'T1RfUkVDT0dOSVpFRBAEEhcKE1RFWFRfTk9UX1JFQ09HTklaRUQQBRIbChdOTFVfTU9ERUxfTk'
+ '9UX1NVUFBPUlRFRBAG');
+
+@$core.Deprecated('Use executeStatusTypeDescriptor instead')
+const ExecuteStatusType$json = {
+ '1': 'ExecuteStatusType',
+ '2': [
+ {'1': 'EXEC_ERROR', '2': 0},
+ {'1': 'EXEC_SUCCESS', '2': 1},
+ {'1': 'KUKSA_CONN_ERROR', '2': 2},
+ {'1': 'INTENT_NOT_SUPPORTED', '2': 3},
+ {'1': 'INTENT_SLOTS_INCOMPLETE', '2': 4},
+ ],
+};
+
+/// Descriptor for `ExecuteStatusType`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List executeStatusTypeDescriptor = $convert.base64Decode(
+ 'ChFFeGVjdXRlU3RhdHVzVHlwZRIOCgpFWEVDX0VSUk9SEAASEAoMRVhFQ19TVUNDRVNTEAESFA'
+ 'oQS1VLU0FfQ09OTl9FUlJPUhACEhgKFElOVEVOVF9OT1RfU1VQUE9SVEVEEAMSGwoXSU5URU5U'
+ 'X1NMT1RTX0lOQ09NUExFVEUQBA==');
+
+@$core.Deprecated('Use emptyDescriptor instead')
+const Empty$json = {
+ '1': 'Empty',
+};
+
+/// Descriptor for `Empty`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List emptyDescriptor = $convert.base64Decode(
+ 'CgVFbXB0eQ==');
+
+@$core.Deprecated('Use serviceStatusDescriptor instead')
+const ServiceStatus$json = {
+ '1': 'ServiceStatus',
+ '2': [
+ {'1': 'version', '3': 1, '4': 1, '5': 9, '10': 'version'},
+ {'1': 'status', '3': 2, '4': 1, '5': 8, '10': 'status'},
+ {'1': 'wake_word', '3': 3, '4': 1, '5': 9, '10': 'wakeWord'},
+ {'1': 'online_mode', '3': 4, '4': 1, '5': 8, '10': 'onlineMode'},
+ ],
+};
+
+/// Descriptor for `ServiceStatus`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List serviceStatusDescriptor = $convert.base64Decode(
+ 'Cg1TZXJ2aWNlU3RhdHVzEhgKB3ZlcnNpb24YASABKAlSB3ZlcnNpb24SFgoGc3RhdHVzGAIgAS'
+ 'gIUgZzdGF0dXMSGwoJd2FrZV93b3JkGAMgASgJUgh3YWtlV29yZBIfCgtvbmxpbmVfbW9kZRgE'
+ 'IAEoCFIKb25saW5lTW9kZQ==');
+
+@$core.Deprecated('Use voiceAudioDescriptor instead')
+const VoiceAudio$json = {
+ '1': 'VoiceAudio',
+ '2': [
+ {'1': 'audio_chunk', '3': 1, '4': 1, '5': 12, '10': 'audioChunk'},
+ {'1': 'audio_format', '3': 2, '4': 1, '5': 9, '10': 'audioFormat'},
+ {'1': 'sample_rate', '3': 3, '4': 1, '5': 5, '10': 'sampleRate'},
+ {'1': 'language', '3': 4, '4': 1, '5': 9, '10': 'language'},
+ ],
+};
+
+/// Descriptor for `VoiceAudio`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List voiceAudioDescriptor = $convert.base64Decode(
+ 'CgpWb2ljZUF1ZGlvEh8KC2F1ZGlvX2NodW5rGAEgASgMUgphdWRpb0NodW5rEiEKDGF1ZGlvX2'
+ 'Zvcm1hdBgCIAEoCVILYXVkaW9Gb3JtYXQSHwoLc2FtcGxlX3JhdGUYAyABKAVSCnNhbXBsZVJh'
+ 'dGUSGgoIbGFuZ3VhZ2UYBCABKAlSCGxhbmd1YWdl');
+
+@$core.Deprecated('Use wakeWordStatusDescriptor instead')
+const WakeWordStatus$json = {
+ '1': 'WakeWordStatus',
+ '2': [
+ {'1': 'status', '3': 1, '4': 1, '5': 8, '10': 'status'},
+ ],
+};
+
+/// Descriptor for `WakeWordStatus`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List wakeWordStatusDescriptor = $convert.base64Decode(
+ 'Cg5XYWtlV29yZFN0YXR1cxIWCgZzdGF0dXMYASABKAhSBnN0YXR1cw==');
+
+@$core.Deprecated('Use s_RecognizeVoiceControlDescriptor instead')
+const S_RecognizeVoiceControl$json = {
+ '1': 'S_RecognizeVoiceControl',
+ '2': [
+ {'1': 'audio_stream', '3': 1, '4': 1, '5': 11, '6': '.VoiceAudio', '10': 'audioStream'},
+ {'1': 'nlu_model', '3': 2, '4': 1, '5': 14, '6': '.NLUModel', '10': 'nluModel'},
+ {'1': 'stream_id', '3': 3, '4': 1, '5': 9, '10': 'streamId'},
+ {'1': 'stt_framework', '3': 4, '4': 1, '5': 14, '6': '.STTFramework', '10': 'sttFramework'},
+ ],
+};
+
+/// Descriptor for `S_RecognizeVoiceControl`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List s_RecognizeVoiceControlDescriptor = $convert.base64Decode(
+ 'ChdTX1JlY29nbml6ZVZvaWNlQ29udHJvbBIuCgxhdWRpb19zdHJlYW0YASABKAsyCy5Wb2ljZU'
+ 'F1ZGlvUgthdWRpb1N0cmVhbRImCglubHVfbW9kZWwYAiABKA4yCS5OTFVNb2RlbFIIbmx1TW9k'
+ 'ZWwSGwoJc3RyZWFtX2lkGAMgASgJUghzdHJlYW1JZBIyCg1zdHRfZnJhbWV3b3JrGAQgASgOMg'
+ '0uU1RURnJhbWV3b3JrUgxzdHRGcmFtZXdvcms=');
+
+@$core.Deprecated('Use recognizeVoiceControlDescriptor instead')
+const RecognizeVoiceControl$json = {
+ '1': 'RecognizeVoiceControl',
+ '2': [
+ {'1': 'action', '3': 1, '4': 1, '5': 14, '6': '.RecordAction', '10': 'action'},
+ {'1': 'nlu_model', '3': 2, '4': 1, '5': 14, '6': '.NLUModel', '10': 'nluModel'},
+ {'1': 'record_mode', '3': 3, '4': 1, '5': 14, '6': '.RecordMode', '10': 'recordMode'},
+ {'1': 'stream_id', '3': 4, '4': 1, '5': 9, '10': 'streamId'},
+ {'1': 'stt_framework', '3': 5, '4': 1, '5': 14, '6': '.STTFramework', '10': 'sttFramework'},
+ {'1': 'online_mode', '3': 6, '4': 1, '5': 14, '6': '.OnlineMode', '10': 'onlineMode'},
+ ],
+};
+
+/// Descriptor for `RecognizeVoiceControl`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List recognizeVoiceControlDescriptor = $convert.base64Decode(
+ 'ChVSZWNvZ25pemVWb2ljZUNvbnRyb2wSJQoGYWN0aW9uGAEgASgOMg0uUmVjb3JkQWN0aW9uUg'
+ 'ZhY3Rpb24SJgoJbmx1X21vZGVsGAIgASgOMgkuTkxVTW9kZWxSCG5sdU1vZGVsEiwKC3JlY29y'
+ 'ZF9tb2RlGAMgASgOMgsuUmVjb3JkTW9kZVIKcmVjb3JkTW9kZRIbCglzdHJlYW1faWQYBCABKA'
+ 'lSCHN0cmVhbUlkEjIKDXN0dF9mcmFtZXdvcmsYBSABKA4yDS5TVFRGcmFtZXdvcmtSDHN0dEZy'
+ 'YW1ld29yaxIsCgtvbmxpbmVfbW9kZRgGIAEoDjILLk9ubGluZU1vZGVSCm9ubGluZU1vZGU=');
+
+@$core.Deprecated('Use recognizeTextControlDescriptor instead')
+const RecognizeTextControl$json = {
+ '1': 'RecognizeTextControl',
+ '2': [
+ {'1': 'text_command', '3': 1, '4': 1, '5': 9, '10': 'textCommand'},
+ {'1': 'nlu_model', '3': 2, '4': 1, '5': 14, '6': '.NLUModel', '10': 'nluModel'},
+ ],
+};
+
+/// Descriptor for `RecognizeTextControl`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List recognizeTextControlDescriptor = $convert.base64Decode(
+ 'ChRSZWNvZ25pemVUZXh0Q29udHJvbBIhCgx0ZXh0X2NvbW1hbmQYASABKAlSC3RleHRDb21tYW'
+ '5kEiYKCW5sdV9tb2RlbBgCIAEoDjIJLk5MVU1vZGVsUghubHVNb2RlbA==');
+
+@$core.Deprecated('Use intentSlotDescriptor instead')
+const IntentSlot$json = {
+ '1': 'IntentSlot',
+ '2': [
+ {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
+ {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'},
+ ],
+};
+
+/// Descriptor for `IntentSlot`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List intentSlotDescriptor = $convert.base64Decode(
+ 'CgpJbnRlbnRTbG90EhIKBG5hbWUYASABKAlSBG5hbWUSFAoFdmFsdWUYAiABKAlSBXZhbHVl');
+
+@$core.Deprecated('Use recognizeResultDescriptor instead')
+const RecognizeResult$json = {
+ '1': 'RecognizeResult',
+ '2': [
+ {'1': 'command', '3': 1, '4': 1, '5': 9, '10': 'command'},
+ {'1': 'intent', '3': 2, '4': 1, '5': 9, '10': 'intent'},
+ {'1': 'intent_slots', '3': 3, '4': 3, '5': 11, '6': '.IntentSlot', '10': 'intentSlots'},
+ {'1': 'stream_id', '3': 4, '4': 1, '5': 9, '10': 'streamId'},
+ {'1': 'status', '3': 5, '4': 1, '5': 14, '6': '.RecognizeStatusType', '10': 'status'},
+ ],
+};
+
+/// Descriptor for `RecognizeResult`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List recognizeResultDescriptor = $convert.base64Decode(
+ 'Cg9SZWNvZ25pemVSZXN1bHQSGAoHY29tbWFuZBgBIAEoCVIHY29tbWFuZBIWCgZpbnRlbnQYAi'
+ 'ABKAlSBmludGVudBIuCgxpbnRlbnRfc2xvdHMYAyADKAsyCy5JbnRlbnRTbG90UgtpbnRlbnRT'
+ 'bG90cxIbCglzdHJlYW1faWQYBCABKAlSCHN0cmVhbUlkEiwKBnN0YXR1cxgFIAEoDjIULlJlY2'
+ '9nbml6ZVN0YXR1c1R5cGVSBnN0YXR1cw==');
+
+@$core.Deprecated('Use executeInputDescriptor instead')
+const ExecuteInput$json = {
+ '1': 'ExecuteInput',
+ '2': [
+ {'1': 'intent', '3': 1, '4': 1, '5': 9, '10': 'intent'},
+ {'1': 'intent_slots', '3': 2, '4': 3, '5': 11, '6': '.IntentSlot', '10': 'intentSlots'},
+ ],
+};
+
+/// Descriptor for `ExecuteInput`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List executeInputDescriptor = $convert.base64Decode(
+ 'CgxFeGVjdXRlSW5wdXQSFgoGaW50ZW50GAEgASgJUgZpbnRlbnQSLgoMaW50ZW50X3Nsb3RzGA'
+ 'IgAygLMgsuSW50ZW50U2xvdFILaW50ZW50U2xvdHM=');
+
+@$core.Deprecated('Use executeResultDescriptor instead')
+const ExecuteResult$json = {
+ '1': 'ExecuteResult',
+ '2': [
+ {'1': 'response', '3': 1, '4': 1, '5': 9, '10': 'response'},
+ {'1': 'status', '3': 2, '4': 1, '5': 14, '6': '.ExecuteStatusType', '10': 'status'},
+ ],
+};
+
+/// Descriptor for `ExecuteResult`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List executeResultDescriptor = $convert.base64Decode(
+ 'Cg1FeGVjdXRlUmVzdWx0EhoKCHJlc3BvbnNlGAEgASgJUghyZXNwb25zZRIqCgZzdGF0dXMYAi'
+ 'ABKA4yEi5FeGVjdXRlU3RhdHVzVHlwZVIGc3RhdHVz');
+
+const $core.Map<$core.String, $core.dynamic> VoiceAgentServiceBase$json = {
+ '1': 'VoiceAgentService',
+ '2': [
+ {'1': 'CheckServiceStatus', '2': '.Empty', '3': '.ServiceStatus'},
+ {'1': 'S_DetectWakeWord', '2': '.VoiceAudio', '3': '.WakeWordStatus', '5': true, '6': true},
+ {'1': 'DetectWakeWord', '2': '.Empty', '3': '.WakeWordStatus', '6': true},
+ {'1': 'S_RecognizeVoiceCommand', '2': '.S_RecognizeVoiceControl', '3': '.RecognizeResult', '5': true},
+ {'1': 'RecognizeVoiceCommand', '2': '.RecognizeVoiceControl', '3': '.RecognizeResult', '5': true},
+ {'1': 'RecognizeTextCommand', '2': '.RecognizeTextControl', '3': '.RecognizeResult'},
+ {'1': 'ExecuteCommand', '2': '.ExecuteInput', '3': '.ExecuteResult'},
+ ],
+};
+
+@$core.Deprecated('Use voiceAgentServiceDescriptor instead')
+const $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> VoiceAgentServiceBase$messageJson = {
+ '.Empty': Empty$json,
+ '.ServiceStatus': ServiceStatus$json,
+ '.VoiceAudio': VoiceAudio$json,
+ '.WakeWordStatus': WakeWordStatus$json,
+ '.S_RecognizeVoiceControl': S_RecognizeVoiceControl$json,
+ '.RecognizeResult': RecognizeResult$json,
+ '.IntentSlot': IntentSlot$json,
+ '.RecognizeVoiceControl': RecognizeVoiceControl$json,
+ '.RecognizeTextControl': RecognizeTextControl$json,
+ '.ExecuteInput': ExecuteInput$json,
+ '.ExecuteResult': ExecuteResult$json,
+};
+
+/// Descriptor for `VoiceAgentService`. Decode as a `google.protobuf.ServiceDescriptorProto`.
+final $typed_data.Uint8List voiceAgentServiceDescriptor = $convert.base64Decode(
+ 'ChFWb2ljZUFnZW50U2VydmljZRIsChJDaGVja1NlcnZpY2VTdGF0dXMSBi5FbXB0eRoOLlNlcn'
+ 'ZpY2VTdGF0dXMSNAoQU19EZXRlY3RXYWtlV29yZBILLlZvaWNlQXVkaW8aDy5XYWtlV29yZFN0'
+ 'YXR1cygBMAESKwoORGV0ZWN0V2FrZVdvcmQSBi5FbXB0eRoPLldha2VXb3JkU3RhdHVzMAESRw'
+ 'oXU19SZWNvZ25pemVWb2ljZUNvbW1hbmQSGC5TX1JlY29nbml6ZVZvaWNlQ29udHJvbBoQLlJl'
+ 'Y29nbml6ZVJlc3VsdCgBEkMKFVJlY29nbml6ZVZvaWNlQ29tbWFuZBIWLlJlY29nbml6ZVZvaW'
+ 'NlQ29udHJvbBoQLlJlY29nbml6ZVJlc3VsdCgBEj8KFFJlY29nbml6ZVRleHRDb21tYW5kEhUu'
+ 'UmVjb2duaXplVGV4dENvbnRyb2waEC5SZWNvZ25pemVSZXN1bHQSLwoORXhlY3V0ZUNvbW1hbm'
+ 'QSDS5FeGVjdXRlSW5wdXQaDi5FeGVjdXRlUmVzdWx0');
+
diff --git a/protos/lib/val_api.dart b/protos/lib/val_api.dart
index de6dfac..713a393 100644
--- a/protos/lib/val_api.dart
+++ b/protos/lib/val_api.dart
@@ -13,4 +13,9 @@ export 'src/generated/kuksa/val/v1/val.pbenum.dart';
export 'src/generated/kuksa/val/v1/val.pbjson.dart';
export 'src/generated/kuksa/val/v1/val.pbgrpc.dart';
+export 'src/generated/voice_agent/voice_agent.pb.dart';
+export 'src/generated/voice_agent/voice_agent.pbenum.dart';
+export 'src/generated/voice_agent/voice_agent.pbjson.dart';
+export 'src/generated/voice_agent/voice_agent.pbgrpc.dart';
+
export 'package:grpc/grpc.dart';
diff --git a/protos/protos/voice_agent/voice_agent.proto b/protos/protos/voice_agent/voice_agent.proto
new file mode 100644
index 0000000..f1164ff
--- /dev/null
+++ b/protos/protos/voice_agent/voice_agent.proto
@@ -0,0 +1,120 @@
+syntax = "proto3";
+
+
+service VoiceAgentService {
+ rpc CheckServiceStatus(Empty) returns (ServiceStatus);
+ rpc S_DetectWakeWord(stream VoiceAudio) returns (stream WakeWordStatus); // Stream version of DetectWakeWord, assumes audio is coming from client
+ rpc DetectWakeWord(Empty) returns (stream WakeWordStatus);
+ rpc S_RecognizeVoiceCommand(stream S_RecognizeVoiceControl) returns (RecognizeResult); // Stream version of RecognizeVoiceCommand, assumes audio is coming from client
+ rpc RecognizeVoiceCommand(stream RecognizeVoiceControl) returns (RecognizeResult);
+ rpc RecognizeTextCommand(RecognizeTextControl) returns (RecognizeResult);
+ rpc ExecuteCommand(ExecuteInput) returns (ExecuteResult);
+}
+
+enum STTFramework {
+ VOSK = 0;
+ WHISPER = 1;
+}
+
+enum OnlineMode {
+ ONLINE = 0;
+ OFFLINE = 1;
+}
+
+enum RecordAction {
+ START = 0;
+ STOP = 1;
+}
+
+enum NLUModel {
+ SNIPS = 0;
+ RASA = 1;
+}
+
+enum RecordMode {
+ MANUAL = 0;
+ AUTO = 1;
+}
+
+enum RecognizeStatusType {
+ REC_ERROR = 0;
+ REC_SUCCESS = 1;
+ REC_PROCESSING = 2;
+ VOICE_NOT_RECOGNIZED = 3;
+ INTENT_NOT_RECOGNIZED = 4;
+ TEXT_NOT_RECOGNIZED = 5;
+ NLU_MODEL_NOT_SUPPORTED = 6;
+}
+
+enum ExecuteStatusType {
+ EXEC_ERROR = 0;
+ EXEC_SUCCESS = 1;
+ KUKSA_CONN_ERROR = 2;
+ INTENT_NOT_SUPPORTED = 3;
+ INTENT_SLOTS_INCOMPLETE = 4;
+}
+
+
+message Empty {}
+
+message ServiceStatus {
+ string version = 1;
+ bool status = 2;
+ string wake_word = 3;
+ bool online_mode = 4;
+}
+
+message VoiceAudio {
+ bytes audio_chunk = 1;
+ string audio_format = 2;
+ int32 sample_rate = 3;
+ string language = 4;
+}
+
+message WakeWordStatus {
+ bool status = 1;
+}
+
+message S_RecognizeVoiceControl {
+ VoiceAudio audio_stream = 1;
+ NLUModel nlu_model = 2;
+ string stream_id = 3;
+ STTFramework stt_framework = 4;
+}
+
+message RecognizeVoiceControl {
+ RecordAction action = 1;
+ NLUModel nlu_model = 2;
+ RecordMode record_mode = 3;
+ string stream_id = 4;
+ STTFramework stt_framework = 5;
+ OnlineMode online_mode = 6;
+}
+
+message RecognizeTextControl {
+ string text_command = 1;
+ NLUModel nlu_model = 2;
+}
+
+message IntentSlot {
+ string name = 1;
+ string value = 2;
+}
+
+message RecognizeResult {
+ string command = 1;
+ string intent = 2;
+ repeated IntentSlot intent_slots = 3;
+ string stream_id = 4;
+ RecognizeStatusType status = 5;
+}
+
+message ExecuteInput {
+ string intent = 1;
+ repeated IntentSlot intent_slots = 2;
+}
+
+message ExecuteResult {
+ string response = 1;
+ ExecuteStatusType status = 2;
+}
diff --git a/pubspec.lock b/pubspec.lock
index d82dd3f..f256dbb 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: args
- sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
+ sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
- version: "2.5.0"
+ version: "2.6.0"
async:
dependency: transitive
description:
@@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: crypto
- sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
+ sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
- version: "3.0.5"
+ version: "3.0.6"
cupertino_icons:
dependency: "direct main"
description:
@@ -141,10 +141,10 @@ packages:
dependency: transitive
description:
name: fixnum
- sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
+ sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.1.1"
flow_builder:
dependency: "direct main"
description:
@@ -190,10 +190,10 @@ packages:
dependency: "direct main"
description:
name: flutter_riverpod
- sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d"
+ sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
url: "https://pub.dev"
source: hosted
- version: "2.5.1"
+ version: "2.6.1"
flutter_svg:
dependency: "direct main"
description:
@@ -352,10 +352,10 @@ packages:
dependency: "direct main"
description:
name: lottie
- sha256: "6a24ade5d3d918c306bb1c21a6b9a04aab0489d51a2582522eea820b4093b62b"
+ sha256: "7afc60865a2429d994144f7d66ced2ae4305fe35d82890b8766e3359872d872c"
url: "https://pub.dev"
source: hosted
- version: "3.1.2"
+ version: "3.1.3"
matcher:
dependency: transitive
description:
@@ -392,10 +392,10 @@ packages:
dependency: "direct main"
description:
name: network_info_plus
- sha256: "6a31fa47c1f6e240f1b60de0a57d65a092ac1af7515247660f03643576984eb8"
+ sha256: "89bad7bf9614e78716f0f86c905fe2a850dbdcc00c377968d5260c49c2c6f2eb"
url: "https://pub.dev"
source: hosted
- version: "6.0.1"
+ version: "6.1.0"
network_info_plus_platform_interface:
dependency: transitive
description:
@@ -448,10 +448,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
- sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
+ sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
url: "https://pub.dev"
source: hosted
- version: "2.2.10"
+ version: "2.2.12"
path_provider_foundation:
dependency: transitive
description:
@@ -496,10 +496,10 @@ packages:
dependency: transitive
description:
name: platform
- sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
+ sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
- version: "3.1.5"
+ version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@@ -535,26 +535,26 @@ packages:
dependency: "direct main"
description:
name: rive
- sha256: daa5394a7d064b4997b39e9afa02f6882c479c38b19fa0dd60f052b99c105400
+ sha256: "468f0880d49c513e09fdfba26e4abd9d50433c2cf398210b62948d8de3837dd5"
url: "https://pub.dev"
source: hosted
- version: "0.13.13"
+ version: "0.13.15"
rive_common:
dependency: transitive
description:
name: rive_common
- sha256: c7bf0781b1621629361579c300ac2f8aa1a238227a242202a596e82becc244d7
+ sha256: a3e5786f8d85c89977062b9ceeb3b72a7c28f81e32fb68497744042ce20bee2f
url: "https://pub.dev"
source: hosted
- version: "0.4.11"
+ version: "0.4.12"
riverpod:
dependency: "direct main"
description:
name: riverpod
- sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d
+ sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
url: "https://pub.dev"
source: hosted
- version: "2.5.1"
+ version: "2.6.1"
shared_preferences:
dependency: transitive
description:
@@ -567,18 +567,18 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
- sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
+ sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
url: "https://pub.dev"
source: hosted
- version: "2.3.2"
+ version: "2.3.3"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
- sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
+ sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
url: "https://pub.dev"
source: hosted
- version: "2.5.2"
+ version: "2.5.3"
shared_preferences_linux:
dependency: transitive
description:
@@ -684,18 +684,18 @@ packages:
dependency: transitive
description:
name: typed_data
- sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
- version: "1.3.2"
+ version: "1.4.0"
uuid:
dependency: "direct main"
description:
name: uuid
- sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77
+ sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
- version: "4.5.0"
+ version: "4.5.1"
vector_graphics:
dependency: transitive
description:
@@ -740,26 +740,26 @@ packages:
dependency: transitive
description:
name: web
- sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
+ sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
- version: "1.0.0"
+ version: "1.1.0"
win32:
dependency: transitive
description:
name: win32
- sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
+ sha256: e1d0cc62e65dc2561f5071fcbccecf58ff20c344f8f3dc7d4922df372a11df1f
url: "https://pub.dev"
source: hosted
- version: "5.5.4"
+ version: "5.7.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
- sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
+ sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
- version: "1.0.4"
+ version: "1.1.0"
xml:
dependency: transitive
description:
@@ -777,5 +777,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
- dart: ">=3.5.0 <3.13.2"
- flutter: ">=3.22.0"
+ dart: ">=3.5.0 <4.0.0"
+ flutter: ">=3.24.0"
diff --git a/test/storageAPI_UnitsForUsers_test.dart b/test/storageAPI_UnitsForUsers_test.dart
new file mode 100644
index 0000000..c4bdb14
--- /dev/null
+++ b/test/storageAPI_UnitsForUsers_test.dart
@@ -0,0 +1,286 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:protos/storage-api.dart' as storage_api;
+import 'package:flutter_ics_homescreen/export.dart';
+
+import 'package:flutter_ics_homescreen/data/data_providers/storage_client.dart';
+import 'package:flutter_ics_homescreen/data/data_providers/initialize_settings.dart';
+
+// Mock implementation of Ref if necessary.
+class MockRef extends Ref {
+ @override
+ dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
+
+void main() {
+ late StorageClient storageClient;
+ late ProviderContainer container;
+
+ setUp(() {
+ storageClient = StorageClient(
+ config: StorageConfig.defaultConfig(),
+ ref: MockRef(),
+ );
+ container = ProviderContainer();
+ // Dispose container after each test.
+ addTearDown(container.dispose);
+ });
+
+ test('add User', () async {
+ await storageClient.destroyDB();
+ final userClient = container.read(usersProvider.notifier);
+ await userClient.addUser('Mark');
+ // Access state.
+ var userState = container.read(usersProvider);
+ final userId = userState.users[0].id;
+ final searchResponse = await storageClient.read(storage_api.Key(key: '${UsersPath.InfotainmentUsers}.$userId.name'));
+ expect(searchResponse.success, isTrue);
+ expect(searchResponse.result, 'Mark');
+ await storageClient.destroyDB();
+ });
+
+ test('remove User', () async {
+ await storageClient.destroyDB();
+ // Add User.
+ final userClient = container.read(usersProvider.notifier);
+ await userClient.addUser('Mark');
+ // Access state.
+ var userState = container.read(usersProvider);
+ final markId = userState.users[0].id;
+
+ // Remove User.
+ await userClient.removeUser(markId);
+ final searchResponse = await storageClient.search(storage_api.Key(key: markId));
+ expect(searchResponse.success, isTrue);
+ expect(searchResponse.result, []);
+ await storageClient.destroyDB();
+ });
+
+ test('add users, select user', () async {
+ await storageClient.destroyDB();
+ // Add Users.
+ final userClient = container.read(usersProvider.notifier);
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+
+ var userState = container.read(usersProvider);
+ final markId = userState.users[1].id;
+ await userClient.selectUser(markId);
+
+ final readResponseAfterSelectUser = await storageClient.read(storage_api.Key(key: UsersPath.InfotainmentCurrentUser));
+ expect(readResponseAfterSelectUser.success, isTrue);
+ expect(readResponseAfterSelectUser.result, markId);
+ await storageClient.destroyDB();
+ });
+
+ test('selected user default', () async {
+ await storageClient.destroyDB();
+
+ // Access state.
+ var userState = container.read(usersProvider);
+ final selectedId = userState.selectedUser.id;
+
+ final readResponse = await storageClient.read(storage_api.Key(key: UsersPath.InfotainmentCurrentUser));
+ expect(readResponse.success, isFalse);
+ expect(selectedId, '0');
+ await storageClient.destroyDB();
+ });
+
+ test('save Unit preference for a User', () async {
+ await storageClient.destroyDB();
+ // Add Users.
+ final userClient = container.read(usersProvider.notifier);
+ final unitsClient = container.read(unitStateProvider.notifier);
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+
+ var userState = container.read(usersProvider);
+ final markId = userState.users[1].id;
+ await userClient.selectUser(markId);
+
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+
+ final readResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiDistanceUnit, namespace: markId));
+ expect(readResponse.success, isTrue);
+ expect(readResponse.result, 'MILES');
+ await storageClient.destroyDB();
+ });
+
+ test('load settings: add users, selcect user, setDistanceUnit, kill state, initialize', () async {
+ await storageClient.destroyDB();
+
+ final userClient = container.read(usersProvider.notifier);
+ final unitsClient = container.read(unitStateProvider.notifier);
+
+ // Prepare state.
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+ var userState = container.read(usersProvider);
+ final markId = userState.users[1].id;
+ await userClient.selectUser(markId);
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+
+ // Check success.
+ var unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.users[0].name, 'Clara');
+ expect(userState.users[1].name, 'Mark');
+ expect(userState.users.length, 5);
+ expect(userState.selectedUser.id, markId);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+
+ // Killing state.
+ container.dispose();
+ container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ // Check that state is killed.
+ userState = container.read(usersProvider);
+ unitState = container.read(unitStateProvider);
+ expect(userState.selectedUser.id, '0');
+ expect(unitState.distanceUnit, DistanceUnit.kilometers);
+
+ // Load state.
+ await initializeSettings(container);
+
+ // Check success.
+ unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.selectedUser.id, markId);
+ List<String> userNames = userState.users.map((user) => user.name).toList();
+ expect(userNames.contains('Mark'), isTrue);
+ expect(userNames.contains('Clara'), isTrue);
+ expect(userState.users.length, 2);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+
+ await storageClient.destroyDB();
+ });
+
+ test('loadsettings: add users, setDistanceUnit, kill state, initialize', () async {
+ await storageClient.destroyDB();
+
+ final userClient = container.read(usersProvider.notifier);
+ final unitsClient = container.read(unitStateProvider.notifier);
+
+ // Prepare state.
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+ var userState = container.read(usersProvider);
+ final claraId = userState.users[0].id;
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+
+ // Check success.
+ var unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.users[0].name, 'Clara');
+ expect(userState.users[1].name, 'Mark');
+ expect(userState.selectedUser.id, claraId);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+
+ // Killing state.
+ container.dispose();
+ container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ // Check that state is killed.
+ userState = container.read(usersProvider);
+ unitState = container.read(unitStateProvider);
+ expect(userState.selectedUser.id, '0');
+ expect(unitState.distanceUnit, DistanceUnit.kilometers);
+
+ // Load state.
+ await initializeSettings(container);
+
+ // Check success.
+ unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.selectedUser.id, claraId);
+ List<String> userNames = userState.users.map((user) => user.name).toList();
+ expect(userNames.contains('Mark'), isTrue);
+ expect(userNames.contains('Clara'), isTrue);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+
+ await storageClient.destroyDB();
+ });
+
+ test('loadsettings: initialize, add no user, setDistanceUnit, kill state, inizialize', () async {
+ await storageClient.destroyDB();
+ await initializeSettings(container);
+
+ var userState = container.read(usersProvider);
+ var readResponse = await storageClient.read(storage_api.Key(key: '${UsersPath.InfotainmentUsers}.1.name'));
+ expect(readResponse.result, 'Heather');
+
+ final unitsClient = container.read(unitStateProvider.notifier);
+
+ // Prepare state.
+ userState = container.read(usersProvider);
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+
+ // Check success.
+ var unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.users[0].name, 'Heather');
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+ expect(userState.selectedUser.id, '1');
+
+ // Killing state.
+ container.dispose();
+ container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ // Check that state is killed.
+ userState = container.read(usersProvider);
+ unitState = container.read(unitStateProvider);
+ expect(userState.selectedUser.id, '0');
+ expect(unitState.distanceUnit, DistanceUnit.kilometers);
+
+ // Load state.
+ await initializeSettings(container);
+
+ // Check success.
+ unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.users[0].name, 'Heather');
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+ expect(userState.selectedUser.name, 'Heather');
+
+ await storageClient.destroyDB();
+ });
+
+ test('select user: load settings', () async {
+ await storageClient.destroyDB();
+
+ final userClient = container.read(usersProvider.notifier);
+ final unitsClient = container.read(unitStateProvider.notifier);
+ // Prepare state.
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+ var userState = container.read(usersProvider);
+ final claraId = userState.users[0].id;
+ final markId = userState.users[1].id;
+ // Note: current user is Clara.
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+ await unitsClient.setTemperatureUnit(TemperatureUnit.fahrenheit);
+ await unitsClient.setPressureUnit(PressureUnit.psi);
+
+ await userClient.selectUser(markId);
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+ await unitsClient.setTemperatureUnit(TemperatureUnit.celsius);
+ await unitsClient.setPressureUnit(PressureUnit.kilopascals);
+
+ await userClient.selectUser(claraId);
+ var unitState = container.read(unitStateProvider);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+ expect(unitState.temperatureUnit, TemperatureUnit.fahrenheit);
+ expect(unitState.pressureUnit, PressureUnit.psi);
+
+ var readResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiDistanceUnit, namespace: claraId));
+ expect(readResponse.result, 'MILES');
+ readResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiTemperatureUnit, namespace: claraId));
+ expect(readResponse.result, 'F');
+ readResponse = await storageClient.read(storage_api.Key(key: VSSPath. vehicleHmiPressureUnit, namespace: claraId));
+ expect(readResponse.result, 'PSI');
+
+ await storageClient.destroyDB();
+ });
+}