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.
- package/.env.example +39 -0
- package/README.md +2 -0
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +13 -5
- package/docs/integrations.md +4 -1
- package/flutter_app/.metadata +42 -0
- package/flutter_app/README.md +21 -0
- package/flutter_app/analysis_options.yaml +32 -0
- package/flutter_app/android/app/build.gradle.kts +109 -0
- package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
- package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
- package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
- package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
- package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
- package/flutter_app/android/build.gradle.kts +24 -0
- package/flutter_app/android/ci-release.keystore +0 -0
- package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/flutter_app/android/gradle.properties +3 -0
- package/flutter_app/android/key.properties +4 -0
- package/flutter_app/android/settings.gradle.kts +26 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +119 -0
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
- package/flutter_app/lib/main.dart +23057 -0
- package/flutter_app/lib/main_app_shell.dart +1682 -0
- package/flutter_app/lib/main_integrations.dart +931 -0
- package/flutter_app/lib/main_launcher.dart +959 -0
- package/flutter_app/lib/main_launcher_entry.dart +5 -0
- package/flutter_app/lib/main_models.dart +3473 -0
- package/flutter_app/lib/main_shared.dart +2861 -0
- package/flutter_app/lib/main_theme.dart +204 -0
- package/flutter_app/lib/main_voice_assistant.dart +831 -0
- package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
- package/flutter_app/lib/src/android_app_installer.dart +22 -0
- package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
- package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
- package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
- package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
- package/flutter_app/lib/src/app_release_updater.dart +511 -0
- package/flutter_app/lib/src/backend_client.dart +1833 -0
- package/flutter_app/lib/src/desktop_companion.dart +2 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
- package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
- package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
- package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
- package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
- package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
- package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
- package/flutter_app/lib/src/health_bridge.dart +136 -0
- package/flutter_app/lib/src/live_voice_capture.dart +85 -0
- package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
- package/flutter_app/lib/src/network/app_http_client.dart +53 -0
- package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
- package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
- package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
- package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
- package/flutter_app/lib/src/oauth_launcher.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
- package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
- package/flutter_app/lib/src/recording_bridge.dart +232 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
- package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
- package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
- package/flutter_app/lib/src/recording_payloads.dart +86 -0
- package/flutter_app/lib/src/theme/palette.dart +81 -0
- package/flutter_app/lib/src/widget_bridge.dart +49 -0
- package/flutter_app/linux/CMakeLists.txt +128 -0
- package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
- package/flutter_app/linux/runner/CMakeLists.txt +26 -0
- package/flutter_app/linux/runner/main.cc +6 -0
- package/flutter_app/linux/runner/my_application.cc +144 -0
- package/flutter_app/linux/runner/my_application.h +18 -0
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
- package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
- package/flutter_app/macos/Podfile +42 -0
- package/flutter_app/macos/Podfile.lock +87 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
- package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
- package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
- package/flutter_app/macos/Runner/Info.plist +36 -0
- package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
- package/flutter_app/macos/Runner/Release.entitlements +12 -0
- package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
- package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
- package/flutter_app/patch_strings.py +12 -0
- package/flutter_app/pubspec.lock +1088 -0
- package/flutter_app/pubspec.yaml +53 -0
- package/flutter_app/test/messaging_access_summary_test.dart +22 -0
- package/flutter_app/test/recording_payloads_test.dart +53 -0
- package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
- package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
- package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
- package/flutter_app/tool/generate_desktop_branding.py +219 -0
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +12 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/web/index.html +39 -0
- package/flutter_app/web/manifest.json +35 -0
- package/flutter_app/windows/CMakeLists.txt +108 -0
- package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
- package/flutter_app/windows/runner/CMakeLists.txt +41 -0
- package/flutter_app/windows/runner/Runner.rc +121 -0
- package/flutter_app/windows/runner/flutter_window.cpp +533 -0
- package/flutter_app/windows/runner/flutter_window.h +37 -0
- package/flutter_app/windows/runner/main.cpp +53 -0
- package/flutter_app/windows/runner/resource.h +16 -0
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/flutter_app/windows/runner/runner.exe.manifest +14 -0
- package/flutter_app/windows/runner/utils.cpp +65 -0
- package/flutter_app/windows/runner/utils.h +19 -0
- package/flutter_app/windows/runner/win32_window.cpp +299 -0
- package/flutter_app/windows/runner/win32_window.h +102 -0
- package/lib/manager.js +231 -7
- package/package.json +3 -1
- package/server/db/database.js +68 -0
- package/server/http/middleware.js +50 -0
- package/server/http/routes.js +3 -1
- package/server/index.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +61 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +65262 -64422
- package/server/routes/integrations.js +86 -0
- package/server/routes/memory.js +11 -2
- package/server/routes/screenHistory.js +46 -0
- package/server/routes/triggers.js +81 -0
- package/server/services/ai/models.js +30 -0
- package/server/services/ai/providers/githubCopilot.js +97 -0
- package/server/services/ai/providers/openai.js +2 -1
- package/server/services/ai/providers/openaiCodex.js +31 -0
- package/server/services/ai/settings.js +20 -0
- package/server/services/ai/systemPrompt.js +1 -1
- package/server/services/ai/tools.js +35 -6
- package/server/services/browser/controller.js +47 -3
- package/server/services/desktop/screenRecorder.js +172 -0
- package/server/services/integrations/env.js +5 -0
- package/server/services/integrations/github/common.js +106 -0
- package/server/services/integrations/github/provider.js +499 -0
- package/server/services/integrations/github/repos.js +1124 -0
- package/server/services/integrations/home_assistant/provider.js +306 -26
- package/server/services/integrations/manager.js +63 -7
- package/server/services/integrations/oauth_provider.js +13 -6
- package/server/services/integrations/provider_config_store.js +76 -0
- package/server/services/integrations/registry.js +4 -0
- package/server/services/integrations/trello/provider.js +744 -0
- package/server/services/integrations/whatsapp/provider.js +6 -2
- package/server/services/manager.js +22 -0
- package/server/services/memory/manager.js +39 -2
- package/server/services/skills/base_catalog.js +33 -0
- package/server/services/tasks/adapters/index.js +1 -0
- package/server/services/tasks/adapters/manual.js +12 -0
- package/server/services/tasks/runtime.js +1 -1
- package/server/services/voice/openaiClient.js +4 -1
- package/server/services/voice/providers.js +2 -1
- package/server/services/widgets/service.js +49 -4
- package/server/utils/local_secrets.js +56 -0
- package/server/utils/logger.js +37 -9
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import 'dart:convert';
|
|
2
|
+
|
|
3
|
+
import 'package:flutter/foundation.dart';
|
|
4
|
+
import 'package:http/http.dart' as http;
|
|
5
|
+
import 'package:package_info_plus/package_info_plus.dart';
|
|
6
|
+
|
|
7
|
+
import 'android_app_installer.dart';
|
|
8
|
+
import 'oauth_launcher.dart';
|
|
9
|
+
|
|
10
|
+
const String appUpdaterGithubOwner = String.fromEnvironment(
|
|
11
|
+
'NEOAGENT_UPDATES_GITHUB_OWNER',
|
|
12
|
+
defaultValue: 'NeoLabs-Systems',
|
|
13
|
+
);
|
|
14
|
+
const String appUpdaterGithubRepo = String.fromEnvironment(
|
|
15
|
+
'NEOAGENT_UPDATES_GITHUB_REPO',
|
|
16
|
+
defaultValue: 'NeoAgent',
|
|
17
|
+
);
|
|
18
|
+
const String appUpdaterGithubToken = String.fromEnvironment(
|
|
19
|
+
'NEOAGENT_UPDATES_GITHUB_TOKEN',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
bool get appUpdaterConfigured =>
|
|
23
|
+
appUpdaterGithubOwner.trim().isNotEmpty &&
|
|
24
|
+
appUpdaterGithubRepo.trim().isNotEmpty;
|
|
25
|
+
|
|
26
|
+
class AppUpdateAsset {
|
|
27
|
+
const AppUpdateAsset({
|
|
28
|
+
required this.name,
|
|
29
|
+
required this.downloadUrl,
|
|
30
|
+
this.contentType,
|
|
31
|
+
this.sizeBytes,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
final String name;
|
|
35
|
+
final String downloadUrl;
|
|
36
|
+
final String? contentType;
|
|
37
|
+
final int? sizeBytes;
|
|
38
|
+
|
|
39
|
+
String get sizeLabel {
|
|
40
|
+
final size = sizeBytes;
|
|
41
|
+
if (size == null || size <= 0) {
|
|
42
|
+
return 'Unknown size';
|
|
43
|
+
}
|
|
44
|
+
if (size >= 1024 * 1024 * 1024) {
|
|
45
|
+
return '${(size / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
|
|
46
|
+
}
|
|
47
|
+
if (size >= 1024 * 1024) {
|
|
48
|
+
return '${(size / (1024 * 1024)).toStringAsFixed(1)} MB';
|
|
49
|
+
}
|
|
50
|
+
if (size >= 1024) {
|
|
51
|
+
return '${(size / 1024).toStringAsFixed(1)} KB';
|
|
52
|
+
}
|
|
53
|
+
return '$size B';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class AppReleaseInfo {
|
|
58
|
+
const AppReleaseInfo({
|
|
59
|
+
required this.version,
|
|
60
|
+
required this.title,
|
|
61
|
+
required this.body,
|
|
62
|
+
required this.channel,
|
|
63
|
+
required this.htmlUrl,
|
|
64
|
+
required this.publishedAt,
|
|
65
|
+
required this.asset,
|
|
66
|
+
required this.prerelease,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
final String version;
|
|
70
|
+
final String title;
|
|
71
|
+
final String body;
|
|
72
|
+
final String channel;
|
|
73
|
+
final String htmlUrl;
|
|
74
|
+
final DateTime? publishedAt;
|
|
75
|
+
final AppUpdateAsset asset;
|
|
76
|
+
final bool prerelease;
|
|
77
|
+
|
|
78
|
+
String get channelLabel => channel == 'beta' ? 'Beta' : 'Stable';
|
|
79
|
+
|
|
80
|
+
String get publishedLabel {
|
|
81
|
+
final date = publishedAt;
|
|
82
|
+
if (date == null) {
|
|
83
|
+
return 'Unknown publish time';
|
|
84
|
+
}
|
|
85
|
+
final month = <int, String>{
|
|
86
|
+
1: 'Jan',
|
|
87
|
+
2: 'Feb',
|
|
88
|
+
3: 'Mar',
|
|
89
|
+
4: 'Apr',
|
|
90
|
+
5: 'May',
|
|
91
|
+
6: 'Jun',
|
|
92
|
+
7: 'Jul',
|
|
93
|
+
8: 'Aug',
|
|
94
|
+
9: 'Sep',
|
|
95
|
+
10: 'Oct',
|
|
96
|
+
11: 'Nov',
|
|
97
|
+
12: 'Dec',
|
|
98
|
+
}[date.month];
|
|
99
|
+
return '$month ${date.day}, ${date.year}';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class AppUpdateCheckResult {
|
|
104
|
+
const AppUpdateCheckResult({
|
|
105
|
+
required this.currentVersion,
|
|
106
|
+
required this.channel,
|
|
107
|
+
required this.updateAvailable,
|
|
108
|
+
this.release,
|
|
109
|
+
this.errorMessage,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
final String currentVersion;
|
|
113
|
+
final String channel;
|
|
114
|
+
final bool updateAvailable;
|
|
115
|
+
final AppReleaseInfo? release;
|
|
116
|
+
final String? errorMessage;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class AppReleaseUpdater {
|
|
120
|
+
AppReleaseUpdater({http.Client? client})
|
|
121
|
+
: _client = client ?? http.Client(),
|
|
122
|
+
_androidAppInstaller = createAndroidAppInstaller();
|
|
123
|
+
|
|
124
|
+
final http.Client _client;
|
|
125
|
+
final AndroidAppInstaller _androidAppInstaller;
|
|
126
|
+
|
|
127
|
+
void dispose() {
|
|
128
|
+
_client.close();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Future<String> currentVersion() async {
|
|
132
|
+
final info = await PackageInfo.fromPlatform();
|
|
133
|
+
final version = info.version.trim();
|
|
134
|
+
final build = info.buildNumber.trim();
|
|
135
|
+
if (build.isEmpty) {
|
|
136
|
+
return version;
|
|
137
|
+
}
|
|
138
|
+
return '$version+$build';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Future<AppUpdateCheckResult> checkForUpdate({
|
|
142
|
+
required String channel,
|
|
143
|
+
bool launcherMode = false,
|
|
144
|
+
}) async {
|
|
145
|
+
final normalizedChannel = channel.trim().toLowerCase() == 'beta'
|
|
146
|
+
? 'beta'
|
|
147
|
+
: 'stable';
|
|
148
|
+
final installedVersion = await currentVersion();
|
|
149
|
+
|
|
150
|
+
if (!appUpdaterConfigured) {
|
|
151
|
+
return AppUpdateCheckResult(
|
|
152
|
+
currentVersion: installedVersion,
|
|
153
|
+
channel: normalizedChannel,
|
|
154
|
+
updateAvailable: false,
|
|
155
|
+
errorMessage: 'App updates are not configured for this build.',
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
final response = await _client.get(
|
|
161
|
+
Uri.https(
|
|
162
|
+
'api.github.com',
|
|
163
|
+
'/repos/$appUpdaterGithubOwner/$appUpdaterGithubRepo/releases',
|
|
164
|
+
<String, String>{'per_page': '20'},
|
|
165
|
+
),
|
|
166
|
+
headers: <String, String>{
|
|
167
|
+
'Accept': 'application/vnd.github+json',
|
|
168
|
+
'User-Agent': 'NeoAgent Flutter Updater',
|
|
169
|
+
if (appUpdaterGithubToken.trim().isNotEmpty)
|
|
170
|
+
'Authorization': 'Bearer ${appUpdaterGithubToken.trim()}',
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
175
|
+
return AppUpdateCheckResult(
|
|
176
|
+
currentVersion: installedVersion,
|
|
177
|
+
channel: normalizedChannel,
|
|
178
|
+
updateAvailable: false,
|
|
179
|
+
errorMessage:
|
|
180
|
+
'GitHub release check failed with HTTP ${response.statusCode}.',
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
final decoded = jsonDecode(response.body);
|
|
185
|
+
if (decoded is! List) {
|
|
186
|
+
return AppUpdateCheckResult(
|
|
187
|
+
currentVersion: installedVersion,
|
|
188
|
+
channel: normalizedChannel,
|
|
189
|
+
updateAvailable: false,
|
|
190
|
+
errorMessage: 'GitHub release payload was not a release list.',
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
final release = _selectRelease(
|
|
195
|
+
decoded,
|
|
196
|
+
normalizedChannel,
|
|
197
|
+
launcherMode: launcherMode,
|
|
198
|
+
);
|
|
199
|
+
if (release == null) {
|
|
200
|
+
return AppUpdateCheckResult(
|
|
201
|
+
currentVersion: installedVersion,
|
|
202
|
+
channel: normalizedChannel,
|
|
203
|
+
updateAvailable: false,
|
|
204
|
+
errorMessage:
|
|
205
|
+
'No ${normalizedChannel == 'beta' ? 'beta' : 'stable'} release asset matched this platform.',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
final newer = _isNewer(release.version, installedVersion);
|
|
210
|
+
return AppUpdateCheckResult(
|
|
211
|
+
currentVersion: installedVersion,
|
|
212
|
+
channel: normalizedChannel,
|
|
213
|
+
updateAvailable: newer,
|
|
214
|
+
release: release,
|
|
215
|
+
);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return AppUpdateCheckResult(
|
|
218
|
+
currentVersion: installedVersion,
|
|
219
|
+
channel: normalizedChannel,
|
|
220
|
+
updateAvailable: false,
|
|
221
|
+
errorMessage: error.toString(),
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
Future<OAuthLaunchResult> openReleaseAsset({
|
|
227
|
+
required OAuthLauncher launcher,
|
|
228
|
+
required AppReleaseInfo release,
|
|
229
|
+
}) async {
|
|
230
|
+
if (_androidAppInstaller.supported) {
|
|
231
|
+
try {
|
|
232
|
+
final installResult = await _androidAppInstaller.installApkFromUrl(
|
|
233
|
+
downloadUrl: release.asset.downloadUrl,
|
|
234
|
+
fileName: release.asset.name,
|
|
235
|
+
headers: <String, String>{
|
|
236
|
+
'User-Agent': 'NeoAgent Flutter Updater',
|
|
237
|
+
if (appUpdaterGithubToken.trim().isNotEmpty)
|
|
238
|
+
'Authorization': 'Bearer ${appUpdaterGithubToken.trim()}',
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
if (installResult.launched) {
|
|
242
|
+
return const OAuthLaunchResult(launched: true, completed: false);
|
|
243
|
+
}
|
|
244
|
+
return OAuthLaunchResult(
|
|
245
|
+
launched: false,
|
|
246
|
+
completed: false,
|
|
247
|
+
error: installResult.error,
|
|
248
|
+
);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return OAuthLaunchResult(
|
|
251
|
+
launched: false,
|
|
252
|
+
completed: false,
|
|
253
|
+
error: error.toString(),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return launcher.openExternal(
|
|
258
|
+
url: release.asset.downloadUrl,
|
|
259
|
+
label: 'app_update_${release.channel}',
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
AppReleaseInfo? _selectRelease(
|
|
264
|
+
List<dynamic> releases,
|
|
265
|
+
String channel, {
|
|
266
|
+
required bool launcherMode,
|
|
267
|
+
}) {
|
|
268
|
+
for (final candidate in releases) {
|
|
269
|
+
if (candidate is! Map) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
final prerelease = candidate['prerelease'] == true;
|
|
273
|
+
final draft = candidate['draft'] == true;
|
|
274
|
+
if (draft) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (channel == 'stable' && prerelease) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
final release = _parseRelease(
|
|
281
|
+
Map<dynamic, dynamic>.from(candidate),
|
|
282
|
+
launcherMode: launcherMode,
|
|
283
|
+
);
|
|
284
|
+
if (release != null) {
|
|
285
|
+
return release;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
AppReleaseInfo? _parseRelease(
|
|
292
|
+
Map<dynamic, dynamic> json, {
|
|
293
|
+
required bool launcherMode,
|
|
294
|
+
}) {
|
|
295
|
+
final assets = json['assets'];
|
|
296
|
+
if (assets is! List) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
final asset = _pickAsset(assets, launcherMode: launcherMode);
|
|
300
|
+
if (asset == null) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
final rawTag = json['tag_name']?.toString().trim();
|
|
304
|
+
final version = _normalizeVersionLabel(
|
|
305
|
+
rawTag?.isNotEmpty == true ? rawTag! : json['name']?.toString() ?? '',
|
|
306
|
+
);
|
|
307
|
+
if (version.isEmpty) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
return AppReleaseInfo(
|
|
311
|
+
version: version,
|
|
312
|
+
title: json['name']?.toString().trim().isNotEmpty == true
|
|
313
|
+
? json['name']!.toString().trim()
|
|
314
|
+
: version,
|
|
315
|
+
body: json['body']?.toString() ?? '',
|
|
316
|
+
channel: json['prerelease'] == true ? 'beta' : 'stable',
|
|
317
|
+
htmlUrl: json['html_url']?.toString() ?? '',
|
|
318
|
+
publishedAt: DateTime.tryParse(json['published_at']?.toString() ?? ''),
|
|
319
|
+
asset: asset,
|
|
320
|
+
prerelease: json['prerelease'] == true,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
AppUpdateAsset? _pickAsset(
|
|
325
|
+
List<dynamic> assets, {
|
|
326
|
+
required bool launcherMode,
|
|
327
|
+
}) {
|
|
328
|
+
final candidates = assets
|
|
329
|
+
.whereType<Map<dynamic, dynamic>>()
|
|
330
|
+
.map(
|
|
331
|
+
(asset) => AppUpdateAsset(
|
|
332
|
+
name: asset['name']?.toString() ?? '',
|
|
333
|
+
downloadUrl: asset['browser_download_url']?.toString() ?? '',
|
|
334
|
+
contentType: asset['content_type']?.toString(),
|
|
335
|
+
sizeBytes: asset['size'] is num
|
|
336
|
+
? (asset['size'] as num).toInt()
|
|
337
|
+
: null,
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
.where(
|
|
341
|
+
(asset) =>
|
|
342
|
+
asset.name.trim().isNotEmpty &&
|
|
343
|
+
asset.downloadUrl.trim().isNotEmpty,
|
|
344
|
+
)
|
|
345
|
+
.toList();
|
|
346
|
+
if (candidates.isEmpty) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
final matchers = _assetMatchersForCurrentPlatform(
|
|
351
|
+
launcherMode: launcherMode,
|
|
352
|
+
);
|
|
353
|
+
for (final matcher in matchers) {
|
|
354
|
+
for (final asset in candidates) {
|
|
355
|
+
if (matcher(asset.name.toLowerCase())) {
|
|
356
|
+
return asset;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
List<bool Function(String)> _assetMatchersForCurrentPlatform({
|
|
364
|
+
required bool launcherMode,
|
|
365
|
+
}) {
|
|
366
|
+
if (kIsWeb) {
|
|
367
|
+
return const <bool Function(String)>[];
|
|
368
|
+
}
|
|
369
|
+
switch (defaultTargetPlatform) {
|
|
370
|
+
case TargetPlatform.android:
|
|
371
|
+
return launcherMode
|
|
372
|
+
? <bool Function(String)>[
|
|
373
|
+
(name) => name.endsWith('.apk') && name.contains('launcher'),
|
|
374
|
+
]
|
|
375
|
+
: <bool Function(String)>[
|
|
376
|
+
(name) => name.endsWith('.apk') && !name.contains('launcher'),
|
|
377
|
+
(name) => name.endsWith('.apk'),
|
|
378
|
+
];
|
|
379
|
+
case TargetPlatform.macOS:
|
|
380
|
+
return <bool Function(String)>[
|
|
381
|
+
(name) => name.endsWith('.dmg'),
|
|
382
|
+
(name) => name.endsWith('.pkg'),
|
|
383
|
+
(name) => name.endsWith('.zip'),
|
|
384
|
+
];
|
|
385
|
+
case TargetPlatform.windows:
|
|
386
|
+
return <bool Function(String)>[
|
|
387
|
+
(name) => name.endsWith('.exe'),
|
|
388
|
+
(name) => name.endsWith('.msix'),
|
|
389
|
+
(name) => name.endsWith('.msi'),
|
|
390
|
+
(name) => name.endsWith('.zip'),
|
|
391
|
+
];
|
|
392
|
+
case TargetPlatform.linux:
|
|
393
|
+
return <bool Function(String)>[
|
|
394
|
+
(name) => name.endsWith('.deb'),
|
|
395
|
+
(name) => name.endsWith('.appimage'),
|
|
396
|
+
(name) => name.endsWith('.rpm'),
|
|
397
|
+
(name) => name.endsWith('.tar.gz'),
|
|
398
|
+
(name) => name.endsWith('.zip'),
|
|
399
|
+
];
|
|
400
|
+
case TargetPlatform.iOS:
|
|
401
|
+
return const <bool Function(String)>[];
|
|
402
|
+
case TargetPlatform.fuchsia:
|
|
403
|
+
return const <bool Function(String)>[];
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
String _normalizeVersionLabel(String raw) {
|
|
408
|
+
final trimmed = raw.trim();
|
|
409
|
+
if (trimmed.isEmpty) {
|
|
410
|
+
return '';
|
|
411
|
+
}
|
|
412
|
+
return trimmed.replaceFirst(RegExp(r'^[vV]'), '');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
bool _isNewer(String candidate, String installed) {
|
|
416
|
+
final candidateVersion = _ParsedVersion.tryParse(candidate);
|
|
417
|
+
final installedVersion = _ParsedVersion.tryParse(installed);
|
|
418
|
+
if (candidateVersion == null || installedVersion == null) {
|
|
419
|
+
return _normalizeVersionLabel(candidate) !=
|
|
420
|
+
_normalizeVersionLabel(installed);
|
|
421
|
+
}
|
|
422
|
+
return candidateVersion.compareTo(installedVersion) > 0;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
class _ParsedVersion implements Comparable<_ParsedVersion> {
|
|
427
|
+
const _ParsedVersion({
|
|
428
|
+
required this.major,
|
|
429
|
+
required this.minor,
|
|
430
|
+
required this.patch,
|
|
431
|
+
required this.build,
|
|
432
|
+
required this.prereleaseLabel,
|
|
433
|
+
required this.prereleaseNumber,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
final int major;
|
|
437
|
+
final int minor;
|
|
438
|
+
final int patch;
|
|
439
|
+
final int build;
|
|
440
|
+
final String? prereleaseLabel;
|
|
441
|
+
final int? prereleaseNumber;
|
|
442
|
+
|
|
443
|
+
static _ParsedVersion? tryParse(String raw) {
|
|
444
|
+
final normalized = raw.trim().replaceFirst(RegExp(r'^[vV]'), '');
|
|
445
|
+
final match = RegExp(
|
|
446
|
+
r'^(\d+)\.(\d+)\.(\d+)(?:-([A-Za-z]+)(?:\.(\d+))?)?(?:\+(\d+))?$',
|
|
447
|
+
).firstMatch(normalized);
|
|
448
|
+
if (match == null) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
return _ParsedVersion(
|
|
452
|
+
major: int.parse(match.group(1)!),
|
|
453
|
+
minor: int.parse(match.group(2)!),
|
|
454
|
+
patch: int.parse(match.group(3)!),
|
|
455
|
+
prereleaseLabel: match.group(4)?.toLowerCase(),
|
|
456
|
+
prereleaseNumber: match.group(5) == null
|
|
457
|
+
? null
|
|
458
|
+
: int.parse(match.group(5)!),
|
|
459
|
+
build: match.group(6) == null ? 0 : int.parse(match.group(6)!),
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
@override
|
|
464
|
+
int compareTo(_ParsedVersion other) {
|
|
465
|
+
final core = _compareInts(major, other.major) != 0
|
|
466
|
+
? _compareInts(major, other.major)
|
|
467
|
+
: _compareInts(minor, other.minor) != 0
|
|
468
|
+
? _compareInts(minor, other.minor)
|
|
469
|
+
: _compareInts(patch, other.patch);
|
|
470
|
+
if (core != 0) {
|
|
471
|
+
return core;
|
|
472
|
+
}
|
|
473
|
+
final prerelease = _comparePrerelease(this, other);
|
|
474
|
+
if (prerelease != 0) {
|
|
475
|
+
return prerelease;
|
|
476
|
+
}
|
|
477
|
+
return _compareInts(build, other.build);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
static int _compareInts(int left, int right) {
|
|
481
|
+
if (left < right) {
|
|
482
|
+
return -1;
|
|
483
|
+
}
|
|
484
|
+
if (left > right) {
|
|
485
|
+
return 1;
|
|
486
|
+
}
|
|
487
|
+
return 0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
static int _comparePrerelease(_ParsedVersion left, _ParsedVersion right) {
|
|
491
|
+
final leftLabel = left.prereleaseLabel;
|
|
492
|
+
final rightLabel = right.prereleaseLabel;
|
|
493
|
+
if (leftLabel == null && rightLabel == null) {
|
|
494
|
+
return 0;
|
|
495
|
+
}
|
|
496
|
+
if (leftLabel == null) {
|
|
497
|
+
return 1;
|
|
498
|
+
}
|
|
499
|
+
if (rightLabel == null) {
|
|
500
|
+
return -1;
|
|
501
|
+
}
|
|
502
|
+
final labelCompare = leftLabel.compareTo(rightLabel);
|
|
503
|
+
if (labelCompare != 0) {
|
|
504
|
+
return labelCompare;
|
|
505
|
+
}
|
|
506
|
+
return _compareInts(
|
|
507
|
+
left.prereleaseNumber ?? 0,
|
|
508
|
+
right.prereleaseNumber ?? 0,
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|