neoagent 2.3.1-beta.2 → 2.3.1-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/.env.example +39 -0
  2. package/README.md +2 -0
  3. package/docs/capabilities.md +2 -2
  4. package/docs/configuration.md +13 -5
  5. package/docs/integrations.md +4 -1
  6. package/flutter_app/.metadata +42 -0
  7. package/flutter_app/README.md +21 -0
  8. package/flutter_app/analysis_options.yaml +32 -0
  9. package/flutter_app/android/app/build.gradle.kts +109 -0
  10. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  11. package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
  12. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
  13. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  14. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  15. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  16. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  17. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  19. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  20. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  21. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  22. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  30. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  31. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  32. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  33. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  34. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  35. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  36. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  37. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  38. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  39. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  40. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  41. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  42. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  43. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  44. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  45. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  46. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  47. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  48. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  49. package/flutter_app/android/build.gradle.kts +24 -0
  50. package/flutter_app/android/ci-release.keystore +0 -0
  51. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  52. package/flutter_app/android/gradle.properties +3 -0
  53. package/flutter_app/android/key.properties +4 -0
  54. package/flutter_app/android/settings.gradle.kts +26 -0
  55. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  56. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  57. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  58. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  59. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  60. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  61. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  62. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  63. package/flutter_app/lib/features/location/location_service.dart +119 -0
  64. package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
  65. package/flutter_app/lib/main.dart +23057 -0
  66. package/flutter_app/lib/main_app_shell.dart +1682 -0
  67. package/flutter_app/lib/main_integrations.dart +931 -0
  68. package/flutter_app/lib/main_launcher.dart +959 -0
  69. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  70. package/flutter_app/lib/main_models.dart +3473 -0
  71. package/flutter_app/lib/main_shared.dart +2861 -0
  72. package/flutter_app/lib/main_theme.dart +204 -0
  73. package/flutter_app/lib/main_voice_assistant.dart +831 -0
  74. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  75. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  76. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  77. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  78. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  79. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  80. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  81. package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
  82. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  83. package/flutter_app/lib/src/backend_client.dart +1833 -0
  84. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  85. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  86. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  87. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  88. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  89. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  90. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  91. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  92. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  93. package/flutter_app/lib/src/health_bridge.dart +136 -0
  94. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  95. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  96. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  97. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  98. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  99. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  100. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  101. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  102. package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
  103. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  104. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  105. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  106. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  107. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  108. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  109. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  110. package/flutter_app/lib/src/theme/palette.dart +81 -0
  111. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  112. package/flutter_app/linux/CMakeLists.txt +128 -0
  113. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  114. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
  115. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  116. package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
  117. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  118. package/flutter_app/linux/runner/main.cc +6 -0
  119. package/flutter_app/linux/runner/my_application.cc +144 -0
  120. package/flutter_app/linux/runner/my_application.h +18 -0
  121. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  122. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  123. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  124. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
  125. package/flutter_app/macos/Podfile +42 -0
  126. package/flutter_app/macos/Podfile.lock +87 -0
  127. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  128. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  129. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  130. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  131. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  132. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  133. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  134. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  135. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  136. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  137. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  138. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  139. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  140. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  141. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  142. package/flutter_app/macos/Runner/Info.plist +36 -0
  143. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  144. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  145. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  146. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  147. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  148. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  149. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  150. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  151. package/flutter_app/patch_strings.py +12 -0
  152. package/flutter_app/pubspec.lock +1088 -0
  153. package/flutter_app/pubspec.yaml +53 -0
  154. package/flutter_app/test/messaging_access_summary_test.dart +22 -0
  155. package/flutter_app/test/recording_payloads_test.dart +53 -0
  156. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  157. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  158. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  159. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  160. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  161. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  162. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  163. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  164. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  165. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  166. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  167. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  168. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  169. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  170. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  171. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  172. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  173. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  174. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  175. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  176. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  177. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  178. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  179. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  180. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  181. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  182. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  183. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  184. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  185. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  186. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  187. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  188. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  189. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  190. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  191. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  192. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  193. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  194. package/flutter_app/web/favicon.png +0 -0
  195. package/flutter_app/web/favicon.svg +12 -0
  196. package/flutter_app/web/icons/Icon-192.png +0 -0
  197. package/flutter_app/web/icons/Icon-512.png +0 -0
  198. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  199. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  200. package/flutter_app/web/index.html +39 -0
  201. package/flutter_app/web/manifest.json +35 -0
  202. package/flutter_app/windows/CMakeLists.txt +108 -0
  203. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  204. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
  205. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  206. package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
  207. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  208. package/flutter_app/windows/runner/Runner.rc +121 -0
  209. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  210. package/flutter_app/windows/runner/flutter_window.h +37 -0
  211. package/flutter_app/windows/runner/main.cpp +53 -0
  212. package/flutter_app/windows/runner/resource.h +16 -0
  213. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  214. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  215. package/flutter_app/windows/runner/utils.cpp +65 -0
  216. package/flutter_app/windows/runner/utils.h +19 -0
  217. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  218. package/flutter_app/windows/runner/win32_window.h +102 -0
  219. package/lib/manager.js +231 -7
  220. package/package.json +3 -1
  221. package/server/db/database.js +68 -0
  222. package/server/http/middleware.js +50 -0
  223. package/server/http/routes.js +3 -1
  224. package/server/index.js +1 -0
  225. package/server/public/.last_build_id +1 -1
  226. package/server/public/assets/NOTICES +61 -0
  227. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  228. package/server/public/flutter_bootstrap.js +1 -1
  229. package/server/public/main.dart.js +65262 -64422
  230. package/server/routes/integrations.js +86 -0
  231. package/server/routes/memory.js +11 -2
  232. package/server/routes/screenHistory.js +46 -0
  233. package/server/routes/triggers.js +81 -0
  234. package/server/services/ai/models.js +30 -0
  235. package/server/services/ai/providers/githubCopilot.js +97 -0
  236. package/server/services/ai/providers/openai.js +2 -1
  237. package/server/services/ai/providers/openaiCodex.js +31 -0
  238. package/server/services/ai/settings.js +20 -0
  239. package/server/services/ai/systemPrompt.js +1 -1
  240. package/server/services/ai/tools.js +35 -6
  241. package/server/services/browser/controller.js +47 -3
  242. package/server/services/desktop/screenRecorder.js +172 -0
  243. package/server/services/integrations/env.js +5 -0
  244. package/server/services/integrations/github/common.js +106 -0
  245. package/server/services/integrations/github/provider.js +499 -0
  246. package/server/services/integrations/github/repos.js +1124 -0
  247. package/server/services/integrations/home_assistant/provider.js +306 -26
  248. package/server/services/integrations/manager.js +63 -7
  249. package/server/services/integrations/oauth_provider.js +13 -6
  250. package/server/services/integrations/provider_config_store.js +76 -0
  251. package/server/services/integrations/registry.js +4 -0
  252. package/server/services/integrations/trello/provider.js +744 -0
  253. package/server/services/integrations/whatsapp/provider.js +6 -2
  254. package/server/services/manager.js +22 -0
  255. package/server/services/memory/manager.js +39 -2
  256. package/server/services/skills/base_catalog.js +33 -0
  257. package/server/services/tasks/adapters/index.js +1 -0
  258. package/server/services/tasks/adapters/manual.js +12 -0
  259. package/server/services/tasks/runtime.js +1 -1
  260. package/server/services/voice/openaiClient.js +4 -1
  261. package/server/services/voice/providers.js +2 -1
  262. package/server/services/widgets/service.js +49 -4
  263. package/server/utils/local_secrets.js +56 -0
  264. package/server/utils/logger.js +37 -9
@@ -0,0 +1,194 @@
1
+ package com.neoagent.flutter_app.widgets
2
+
3
+ import android.content.Context
4
+ import org.json.JSONArray
5
+ import org.json.JSONObject
6
+
7
+ data class CachedAiWidgetTask(
8
+ val id: String,
9
+ val name: String,
10
+ val triggerSummary: String,
11
+ )
12
+
13
+ data class CachedAiWidget(
14
+ val id: String,
15
+ val name: String,
16
+ val template: String,
17
+ val layoutVariant: String,
18
+ val refreshCron: String,
19
+ val enabled: Boolean,
20
+ val lastError: String?,
21
+ val latestSnapshot: JSONObject?,
22
+ val tasks: List<CachedAiWidgetTask>,
23
+ )
24
+
25
+ internal object AiWidgetPrefs {
26
+ const val PREFS_NAME = "neoagent_ai_widgets"
27
+ const val KEY_ENABLED = "enabled"
28
+ const val KEY_BACKEND_URL = "backend_url"
29
+ const val KEY_SESSION_COOKIE = "session_cookie"
30
+ const val KEY_CACHED_WIDGETS = "cached_widgets"
31
+ const val KEY_LAST_SYNC_AT = "last_sync_at"
32
+ const val KEY_LAST_ERROR = "last_error"
33
+ const val KEY_PENDING_OPEN_WIDGET_ID = "pending_open_widget_id"
34
+ const val KEY_BINDING_PREFIX = "binding_"
35
+ const val KEY_EXPANDED_PREFIX = "expanded_"
36
+
37
+ fun read(context: Context) =
38
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
39
+ }
40
+
41
+ class AiWidgetStore(private val context: Context) {
42
+
43
+ private val pendingOpenLock = Any()
44
+
45
+ fun saveConfig(
46
+ enabled: Boolean,
47
+ backendUrl: String,
48
+ sessionCookie: String,
49
+ ) {
50
+ AiWidgetPrefs.read(context)
51
+ .edit()
52
+ .putBoolean(AiWidgetPrefs.KEY_ENABLED, enabled)
53
+ .putString(AiWidgetPrefs.KEY_BACKEND_URL, backendUrl.trim())
54
+ .putString(AiWidgetPrefs.KEY_SESSION_COOKIE, sessionCookie.trim())
55
+ .apply()
56
+ }
57
+
58
+ fun isEnabled(): Boolean =
59
+ AiWidgetPrefs.read(context).getBoolean(AiWidgetPrefs.KEY_ENABLED, false)
60
+
61
+ fun backendUrl(): String =
62
+ AiWidgetPrefs.read(context)
63
+ .getString(AiWidgetPrefs.KEY_BACKEND_URL, "")
64
+ ?.trim()
65
+ .orEmpty()
66
+
67
+ fun sessionCookie(): String =
68
+ AiWidgetPrefs.read(context)
69
+ .getString(AiWidgetPrefs.KEY_SESSION_COOKIE, "")
70
+ ?.trim()
71
+ .orEmpty()
72
+
73
+ fun saveCachedWidgets(rawJson: String) {
74
+ AiWidgetPrefs.read(context)
75
+ .edit()
76
+ .putString(AiWidgetPrefs.KEY_CACHED_WIDGETS, rawJson)
77
+ .putString(AiWidgetPrefs.KEY_LAST_SYNC_AT, java.time.Instant.now().toString())
78
+ .remove(AiWidgetPrefs.KEY_LAST_ERROR)
79
+ .apply()
80
+ }
81
+
82
+ fun setLastError(message: String) {
83
+ AiWidgetPrefs.read(context)
84
+ .edit()
85
+ .putString(AiWidgetPrefs.KEY_LAST_ERROR, message)
86
+ .apply()
87
+ }
88
+
89
+ fun cachedWidgets(): List<CachedAiWidget> {
90
+ val raw =
91
+ AiWidgetPrefs.read(context).getString(AiWidgetPrefs.KEY_CACHED_WIDGETS, null)
92
+ ?: return emptyList()
93
+ return try {
94
+ val array = JSONArray(raw)
95
+ buildList {
96
+ for (index in 0 until array.length()) {
97
+ val item = array.optJSONObject(index) ?: continue
98
+ add(
99
+ CachedAiWidget(
100
+ id = item.optString("id").trim(),
101
+ name = item.optString("name").trim(),
102
+ template = item.optString("template", "summary").trim(),
103
+ layoutVariant =
104
+ item.optString("layoutVariant", item.optString("layout_variant"))
105
+ .trim(),
106
+ refreshCron =
107
+ item.optString("refreshCron", item.optString("refresh_cron"))
108
+ .trim(),
109
+ enabled = item.optBoolean("enabled", true),
110
+ lastError =
111
+ item.optString("lastError")
112
+ .trim()
113
+ .ifEmpty { item.optString("last_error").trim() }
114
+ .ifEmpty { "" }
115
+ .let { if (it.isBlank()) null else it },
116
+ latestSnapshot = item.optJSONObject("latestSnapshot"),
117
+ tasks = buildList {
118
+ val tasksArray = item.optJSONArray("tasks")
119
+ if (tasksArray != null) {
120
+ for (t in 0 until tasksArray.length()) {
121
+ val tItem = tasksArray.optJSONObject(t) ?: continue
122
+ add(
123
+ CachedAiWidgetTask(
124
+ id = tItem.optString("id"),
125
+ name = tItem.optString("name", "Task"),
126
+ triggerSummary = tItem.optString("triggerSummary"),
127
+ )
128
+ )
129
+ }
130
+ }
131
+ },
132
+ ),
133
+ )
134
+ }
135
+ }
136
+ } catch (_: Exception) {
137
+ emptyList()
138
+ }
139
+ }
140
+
141
+ fun findWidget(widgetId: String): CachedAiWidget? =
142
+ cachedWidgets().firstOrNull { it.id == widgetId }
143
+
144
+ fun bindAppWidget(appWidgetId: Int, widgetId: String) {
145
+ AiWidgetPrefs.read(context)
146
+ .edit()
147
+ .putString("${AiWidgetPrefs.KEY_BINDING_PREFIX}$appWidgetId", widgetId)
148
+ .apply()
149
+ }
150
+
151
+ fun widgetIdForAppWidget(appWidgetId: Int): String? =
152
+ AiWidgetPrefs.read(context)
153
+ .getString("${AiWidgetPrefs.KEY_BINDING_PREFIX}$appWidgetId", null)
154
+ ?.trim()
155
+ ?.let { if (it.isBlank()) null else it }
156
+
157
+ fun clearBinding(appWidgetId: Int) {
158
+ AiWidgetPrefs.read(context)
159
+ .edit()
160
+ .remove("${AiWidgetPrefs.KEY_BINDING_PREFIX}$appWidgetId")
161
+ .apply()
162
+ }
163
+
164
+ fun setPendingOpenWidgetId(widgetId: String?) {
165
+ AiWidgetPrefs.read(context)
166
+ .edit()
167
+ .putString(AiWidgetPrefs.KEY_PENDING_OPEN_WIDGET_ID, widgetId?.trim())
168
+ .apply()
169
+ }
170
+
171
+ fun consumePendingOpenWidgetId(): String? {
172
+ synchronized(pendingOpenLock) {
173
+ val prefs = AiWidgetPrefs.read(context)
174
+ val widgetId =
175
+ prefs.getString(AiWidgetPrefs.KEY_PENDING_OPEN_WIDGET_ID, null)
176
+ ?.trim()
177
+ ?.let { if (it.isBlank()) null else it }
178
+ if (widgetId != null) {
179
+ prefs.edit().remove(AiWidgetPrefs.KEY_PENDING_OPEN_WIDGET_ID).commit()
180
+ }
181
+ return widgetId
182
+ }
183
+ }
184
+
185
+ fun isTasksExpanded(appWidgetId: Int): Boolean =
186
+ AiWidgetPrefs.read(context).getBoolean("${AiWidgetPrefs.KEY_EXPANDED_PREFIX}$appWidgetId", false)
187
+
188
+ fun toggleTasksExpanded(appWidgetId: Int) {
189
+ val prefs = AiWidgetPrefs.read(context)
190
+ val key = "${AiWidgetPrefs.KEY_EXPANDED_PREFIX}$appWidgetId"
191
+ val current = prefs.getBoolean(key, false)
192
+ prefs.edit().putBoolean(key, !current).apply()
193
+ }
194
+ }
@@ -0,0 +1,67 @@
1
+ package com.neoagent.flutter_app.widgets
2
+
3
+ import android.app.PendingIntent
4
+ import android.appwidget.AppWidgetManager
5
+ import android.appwidget.AppWidgetProvider
6
+ import android.content.ComponentName
7
+ import android.content.Context
8
+ import android.content.Intent
9
+ import android.widget.RemoteViews
10
+ import com.neoagent.flutter_app.MainActivity
11
+ import com.neoagent.flutter_app.R
12
+
13
+ class VoiceLaunchWidgetProvider : AppWidgetProvider() {
14
+
15
+ override fun onUpdate(
16
+ context: Context,
17
+ appWidgetManager: AppWidgetManager,
18
+ appWidgetIds: IntArray,
19
+ ) {
20
+ refreshAll(context, appWidgetManager, appWidgetIds)
21
+ }
22
+
23
+ companion object {
24
+ const val ACTION_OPEN_VOICE_ASSISTANT =
25
+ "com.neoagent.flutter_app.widgets.OPEN_VOICE_ASSISTANT"
26
+ const val OPEN_TARGET_VOICE_ASSISTANT = "voice_assistant"
27
+
28
+ fun refreshAll(context: Context) {
29
+ val manager = AppWidgetManager.getInstance(context)
30
+ val componentName = ComponentName(context, VoiceLaunchWidgetProvider::class.java)
31
+ val ids = manager.getAppWidgetIds(componentName)
32
+ refreshAll(context, manager, ids)
33
+ }
34
+
35
+ fun refreshAll(
36
+ context: Context,
37
+ manager: AppWidgetManager,
38
+ appWidgetIds: IntArray,
39
+ ) {
40
+ appWidgetIds.forEach { appWidgetId ->
41
+ manager.updateAppWidget(appWidgetId, buildRemoteViews(context, appWidgetId))
42
+ }
43
+ }
44
+
45
+ private fun buildRemoteViews(context: Context, appWidgetId: Int): RemoteViews {
46
+ val views = RemoteViews(context.packageName, R.layout.neoagent_voice_widget)
47
+ val intent =
48
+ Intent(context, MainActivity::class.java).apply {
49
+ action = ACTION_OPEN_VOICE_ASSISTANT
50
+ flags =
51
+ Intent.FLAG_ACTIVITY_NEW_TASK or
52
+ Intent.FLAG_ACTIVITY_CLEAR_TOP or
53
+ Intent.FLAG_ACTIVITY_SINGLE_TOP
54
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
55
+ }
56
+ val pendingIntent =
57
+ PendingIntent.getActivity(
58
+ context,
59
+ appWidgetId,
60
+ intent,
61
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
62
+ )
63
+ views.setOnClickPendingIntent(R.id.voice_widget_root, pendingIntent)
64
+ return views
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,228 @@
1
+ package com.neoagent.flutter_app.widgets
2
+
3
+ import android.app.Activity
4
+ import android.appwidget.AppWidgetManager
5
+ import android.content.Intent
6
+ import android.graphics.Color
7
+ import android.graphics.Typeface
8
+ import android.graphics.drawable.GradientDrawable
9
+ import android.os.Bundle
10
+ import android.util.TypedValue
11
+ import android.view.View
12
+ import android.view.ViewGroup
13
+ import android.widget.LinearLayout
14
+ import android.widget.ScrollView
15
+ import android.widget.TextView
16
+ import com.neoagent.flutter_app.R
17
+
18
+ class WidgetConfigActivity : Activity() {
19
+
20
+ override fun onCreate(savedInstanceState: Bundle?) {
21
+ super.onCreate(savedInstanceState)
22
+
23
+ val appWidgetId =
24
+ intent?.extras?.getInt(
25
+ AppWidgetManager.EXTRA_APPWIDGET_ID,
26
+ AppWidgetManager.INVALID_APPWIDGET_ID,
27
+ ) ?: AppWidgetManager.INVALID_APPWIDGET_ID
28
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
29
+ finish()
30
+ return
31
+ }
32
+
33
+ setResult(RESULT_CANCELED)
34
+
35
+ val store = AiWidgetStore(this)
36
+ val widgets = store.cachedWidgets().filter { it.id.isNotBlank() }
37
+
38
+ val root =
39
+ ScrollView(this).apply {
40
+ setBackgroundColor(Color.parseColor("#0D1118"))
41
+ isFillViewport = true
42
+ }
43
+ val content =
44
+ LinearLayout(this).apply {
45
+ orientation = LinearLayout.VERTICAL
46
+ setPadding(dp(20), dp(28), dp(20), dp(24))
47
+ }
48
+ root.addView(
49
+ content,
50
+ ViewGroup.LayoutParams(
51
+ ViewGroup.LayoutParams.MATCH_PARENT,
52
+ ViewGroup.LayoutParams.WRAP_CONTENT,
53
+ ),
54
+ )
55
+
56
+ content.addView(
57
+ textView(
58
+ text = getString(R.string.widget_config_title),
59
+ sizeSp = 28f,
60
+ color = Color.WHITE,
61
+ style = Typeface.BOLD,
62
+ ),
63
+ )
64
+ content.addView(spacer(8))
65
+ content.addView(
66
+ textView(
67
+ text = "Choose the widget you want to place on your home screen.",
68
+ sizeSp = 15f,
69
+ color = Color.parseColor("#A7B2C5"),
70
+ ),
71
+ )
72
+ content.addView(spacer(22))
73
+
74
+ if (widgets.isEmpty()) {
75
+ content.addView(
76
+ buildCard(
77
+ title = "No widgets available yet",
78
+ subtitle = getString(R.string.widget_config_empty_state),
79
+ meta = "Open NeoAgent and refresh widgets first",
80
+ ) {},
81
+ )
82
+ setContentView(root)
83
+ return
84
+ }
85
+
86
+ widgets.forEachIndexed { index, widget ->
87
+ val cadence =
88
+ if (widget.enabled) formatCadence(widget.refreshCron) else "Paused"
89
+ val subtitle =
90
+ widget.latestSnapshot?.optString("subtitle")
91
+ ?.trim()
92
+ ?.takeIf { it.isNotEmpty() && !it.equals("null", ignoreCase = true) }
93
+ ?: if (widget.latestSnapshot == null) {
94
+ "Waiting for first update"
95
+ } else {
96
+ "Ready on home screen"
97
+ }
98
+ val card =
99
+ buildCard(
100
+ title = displayName(widget.name),
101
+ subtitle = subtitle,
102
+ meta = cadence,
103
+ ) {
104
+ store.bindAppWidget(appWidgetId, widget.id)
105
+ AiHomeWidgetProvider.refreshAll(this)
106
+ val resultIntent =
107
+ Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
108
+ setResult(RESULT_OK, resultIntent)
109
+ finish()
110
+ }
111
+ content.addView(card)
112
+ if (index < widgets.lastIndex) {
113
+ content.addView(spacer(14))
114
+ }
115
+ }
116
+
117
+ setContentView(root)
118
+ }
119
+
120
+ private fun buildCard(
121
+ title: String,
122
+ subtitle: String,
123
+ meta: String,
124
+ onClick: () -> Unit,
125
+ ): View {
126
+ val card =
127
+ LinearLayout(this).apply {
128
+ orientation = LinearLayout.VERTICAL
129
+ setPadding(dp(18), dp(18), dp(18), dp(18))
130
+ background =
131
+ GradientDrawable().apply {
132
+ cornerRadius = dp(24).toFloat()
133
+ setColor(Color.parseColor("#171E29"))
134
+ setStroke(dp(1), Color.parseColor("#22FFFFFF"))
135
+ }
136
+ isClickable = true
137
+ isFocusable = true
138
+ setOnClickListener { onClick() }
139
+ }
140
+ card.addView(
141
+ textView(
142
+ text = title,
143
+ sizeSp = 20f,
144
+ color = Color.WHITE,
145
+ style = Typeface.BOLD,
146
+ ),
147
+ )
148
+ card.addView(spacer(8))
149
+ card.addView(
150
+ textView(
151
+ text = subtitle,
152
+ sizeSp = 14f,
153
+ color = Color.parseColor("#C7D0E0"),
154
+ ),
155
+ )
156
+ card.addView(spacer(14))
157
+ card.addView(
158
+ textView(
159
+ text = meta,
160
+ sizeSp = 13f,
161
+ color = Color.parseColor("#8FB8FF"),
162
+ style = Typeface.BOLD,
163
+ ),
164
+ )
165
+ return card
166
+ }
167
+
168
+ private fun textView(
169
+ text: String,
170
+ sizeSp: Float,
171
+ color: Int,
172
+ style: Int = Typeface.NORMAL,
173
+ ): TextView {
174
+ return TextView(this).apply {
175
+ this.text = text
176
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, sizeSp)
177
+ setTextColor(color)
178
+ setTypeface(typeface, style)
179
+ }
180
+ }
181
+
182
+ private fun spacer(heightDp: Int): View =
183
+ View(this).apply {
184
+ layoutParams =
185
+ LinearLayout.LayoutParams(
186
+ ViewGroup.LayoutParams.MATCH_PARENT,
187
+ dp(heightDp),
188
+ )
189
+ }
190
+
191
+ private fun formatCadence(refreshCron: String): String {
192
+ val normalized = refreshCron.trim()
193
+ if (normalized == "0 * * * *") {
194
+ return "Updates hourly"
195
+ }
196
+ val hours = Regex("\\*/(\\d+)").find(normalized)?.groupValues?.getOrNull(1)?.toIntOrNull()
197
+ if (hours != null && hours > 1) {
198
+ return "Every $hours hours"
199
+ }
200
+ return "Refreshes automatically"
201
+ }
202
+
203
+ private fun displayName(raw: String): String {
204
+ val normalized =
205
+ raw.trim()
206
+ .replace(Regex("[_-]+"), " ")
207
+ .replace(Regex("\\s+"), " ")
208
+ if (normalized.isBlank()) {
209
+ return "AI Widget"
210
+ }
211
+ return normalized.split(" ")
212
+ .filter { it.isNotBlank() }
213
+ .joinToString(" ") { part ->
214
+ if (part.length <= 2 && part.uppercase() == part) {
215
+ part
216
+ } else {
217
+ part.substring(0, 1).uppercase() + part.substring(1)
218
+ }
219
+ }
220
+ }
221
+
222
+ private fun dp(value: Int): Int =
223
+ TypedValue.applyDimension(
224
+ TypedValue.COMPLEX_UNIT_DIP,
225
+ value.toFloat(),
226
+ resources.displayMetrics,
227
+ ).toInt()
228
+ }
@@ -0,0 +1,72 @@
1
+ package com.neoagent.flutter_app.widgets
2
+
3
+ import android.content.Context
4
+ import androidx.work.Constraints
5
+ import androidx.work.ExistingPeriodicWorkPolicy
6
+ import androidx.work.ExistingWorkPolicy
7
+ import androidx.work.NetworkType
8
+ import androidx.work.OneTimeWorkRequestBuilder
9
+ import androidx.work.PeriodicWorkRequestBuilder
10
+ import androidx.work.WorkManager
11
+ import java.util.concurrent.TimeUnit
12
+
13
+ class WidgetSyncScheduler(private val context: Context) {
14
+
15
+ fun configure(
16
+ enabled: Boolean,
17
+ backendUrl: String,
18
+ sessionCookie: String,
19
+ ) {
20
+ val store = AiWidgetStore(context)
21
+ store.saveConfig(
22
+ enabled = enabled,
23
+ backendUrl = backendUrl,
24
+ sessionCookie = sessionCookie,
25
+ )
26
+
27
+ val workManager = WorkManager.getInstance(context)
28
+ if (!enabled) {
29
+ workManager.cancelUniqueWork(UNIQUE_PERIODIC_WORK)
30
+ workManager.cancelUniqueWork(UNIQUE_IMMEDIATE_WORK)
31
+ return
32
+ }
33
+
34
+ val constraints = Constraints.Builder()
35
+ .setRequiredNetworkType(NetworkType.CONNECTED)
36
+ .build()
37
+
38
+ val periodicRequest =
39
+ PeriodicWorkRequestBuilder<WidgetSyncWorker>(1, TimeUnit.HOURS)
40
+ .setConstraints(constraints)
41
+ .build()
42
+
43
+ workManager.enqueueUniquePeriodicWork(
44
+ UNIQUE_PERIODIC_WORK,
45
+ ExistingPeriodicWorkPolicy.UPDATE,
46
+ periodicRequest,
47
+ )
48
+ }
49
+
50
+ fun syncNow() {
51
+ if (!AiWidgetStore(context).isEnabled()) {
52
+ return
53
+ }
54
+ val constraints = Constraints.Builder()
55
+ .setRequiredNetworkType(NetworkType.CONNECTED)
56
+ .build()
57
+ val request =
58
+ OneTimeWorkRequestBuilder<WidgetSyncWorker>()
59
+ .setConstraints(constraints)
60
+ .build()
61
+ WorkManager.getInstance(context).enqueueUniqueWork(
62
+ UNIQUE_IMMEDIATE_WORK,
63
+ ExistingWorkPolicy.REPLACE,
64
+ request,
65
+ )
66
+ }
67
+
68
+ companion object {
69
+ private const val UNIQUE_PERIODIC_WORK = "neoagent_widgets_periodic_sync"
70
+ private const val UNIQUE_IMMEDIATE_WORK = "neoagent_widgets_immediate_sync"
71
+ }
72
+ }