neoagent 2.3.1-beta.87 → 2.3.1-beta.88
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/flutter_app/lib/features/notifications/notification_interceptor.dart +11 -45
- package/flutter_app/lib/main.dart +1 -0
- package/flutter_app/lib/main_app_shell.dart +23 -14
- package/flutter_app/lib/main_chat.dart +128 -22
- package/flutter_app/lib/main_controller.dart +18 -7
- package/flutter_app/lib/main_settings.dart +16 -14
- package/flutter_app/lib/main_shared.dart +21 -16
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +2 -0
- package/flutter_app/pubspec.lock +24 -0
- package/flutter_app/pubspec.yaml +1 -0
- package/package.json +1 -1
- package/server/db/database.js +42 -4
- package/server/guest_agent.js +8 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +70 -44
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +54424 -54174
- package/server/routes/browser.js +1 -1
- package/server/routes/social_video.js +4 -0
- package/server/services/ai/tools.js +1 -0
- package/server/services/android/android_bootstrap_worker.js +1 -0
- package/server/services/android/controller.js +244 -76
- package/server/services/browser/controller.js +24 -8
- package/server/services/runtime/backends/local-vm.js +29 -5
- package/server/services/social_video/service.js +110 -36
|
@@ -5,7 +5,8 @@ import 'package:notification_listener_service/notification_listener_service.dart
|
|
|
5
5
|
import 'package:notification_listener_service/notification_event.dart';
|
|
6
6
|
|
|
7
7
|
class NotificationInterceptor {
|
|
8
|
-
static final NotificationInterceptor _instance =
|
|
8
|
+
static final NotificationInterceptor _instance =
|
|
9
|
+
NotificationInterceptor._internal();
|
|
9
10
|
factory NotificationInterceptor() => _instance;
|
|
10
11
|
NotificationInterceptor._internal();
|
|
11
12
|
|
|
@@ -13,59 +14,27 @@ class NotificationInterceptor {
|
|
|
13
14
|
String _backendUrl = '';
|
|
14
15
|
String _token = '';
|
|
15
16
|
|
|
16
|
-
Future<void> initialize(
|
|
17
|
+
Future<void> initialize(String backendUrl, String token) async {
|
|
17
18
|
_backendUrl = backendUrl;
|
|
18
19
|
_token = token;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
if (!isGranted) {
|
|
22
|
-
bool userAgreed = await _showPermissionRationale(context);
|
|
23
|
-
if (userAgreed) {
|
|
24
|
-
await NotificationListenerService.requestPermission();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
isGranted = await NotificationListenerService.isPermissionGranted();
|
|
21
|
+
final isGranted = await NotificationListenerService.isPermissionGranted();
|
|
29
22
|
if (isGranted && !_isListening) {
|
|
30
23
|
_startListening();
|
|
31
24
|
}
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
Future<bool> _showPermissionRationale(BuildContext context) async {
|
|
35
|
-
return await showDialog<bool>(
|
|
36
|
-
context: context,
|
|
37
|
-
builder: (context) => AlertDialog(
|
|
38
|
-
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
39
|
-
title: const Text('Notification Access Needed'),
|
|
40
|
-
content: const Text(
|
|
41
|
-
'NeoAgent can read your incoming notifications (like Calendar alarms or urgent messages) and proactively act on them.\n\n'
|
|
42
|
-
'For example, if you receive a reminder, NeoAgent can automatically prepare relevant context for you.',
|
|
43
|
-
),
|
|
44
|
-
actions: [
|
|
45
|
-
TextButton(
|
|
46
|
-
onPressed: () => Navigator.of(context).pop(false),
|
|
47
|
-
child: const Text('Not Now'),
|
|
48
|
-
),
|
|
49
|
-
ElevatedButton(
|
|
50
|
-
style: ElevatedButton.styleFrom(
|
|
51
|
-
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
52
|
-
),
|
|
53
|
-
onPressed: () => Navigator.of(context).pop(true),
|
|
54
|
-
child: const Text('Allow Access'),
|
|
55
|
-
),
|
|
56
|
-
],
|
|
57
|
-
),
|
|
58
|
-
) ?? false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
27
|
void _startListening() {
|
|
62
28
|
_isListening = true;
|
|
63
|
-
NotificationListenerService.notificationsStream.listen((
|
|
29
|
+
NotificationListenerService.notificationsStream.listen((
|
|
30
|
+
ServiceNotificationEvent event,
|
|
31
|
+
) {
|
|
64
32
|
// Filter out noisy system notifications or ongoing foreground services
|
|
65
|
-
if (event.packageName == null ||
|
|
33
|
+
if (event.packageName == null ||
|
|
34
|
+
event.packageName!.contains('android.system')) {
|
|
66
35
|
return;
|
|
67
36
|
}
|
|
68
|
-
|
|
37
|
+
|
|
69
38
|
// We only want to intercept newly posted notifications, not removed ones
|
|
70
39
|
if (event.hasRemoved == true) return;
|
|
71
40
|
|
|
@@ -79,10 +48,7 @@ class NotificationInterceptor {
|
|
|
79
48
|
try {
|
|
80
49
|
await http.post(
|
|
81
50
|
Uri.parse('$_backendUrl/api/triggers/notification'),
|
|
82
|
-
headers: {
|
|
83
|
-
'Content-Type': 'application/json',
|
|
84
|
-
'Cookie': _token,
|
|
85
|
-
},
|
|
51
|
+
headers: {'Content-Type': 'application/json', 'Cookie': _token},
|
|
86
52
|
body: jsonEncode({
|
|
87
53
|
'app_package': event.packageName ?? 'unknown',
|
|
88
54
|
'title': event.title ?? '',
|
|
@@ -11,6 +11,7 @@ import 'package:flutter/gestures.dart';
|
|
|
11
11
|
import 'package:flutter/material.dart';
|
|
12
12
|
import 'package:flutter/services.dart';
|
|
13
13
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
|
14
|
+
import 'package:file_picker/file_picker.dart';
|
|
14
15
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
15
16
|
import 'package:google_fonts/google_fonts.dart';
|
|
16
17
|
import 'package:image/image.dart' as img;
|
|
@@ -13,17 +13,23 @@ class SplashView extends StatelessWidget {
|
|
|
13
13
|
colors: <Color>[_accent, _bgSecondary, _bgPrimary],
|
|
14
14
|
),
|
|
15
15
|
),
|
|
16
|
-
child:
|
|
16
|
+
child: Scaffold(
|
|
17
17
|
backgroundColor: Colors.transparent,
|
|
18
18
|
body: Center(
|
|
19
19
|
child: Column(
|
|
20
20
|
mainAxisSize: MainAxisSize.min,
|
|
21
21
|
children: <Widget>[
|
|
22
22
|
_BrandLockup(logoSize: 52),
|
|
23
|
-
SizedBox(height: 18),
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const SizedBox(height: 18),
|
|
24
|
+
SizedBox(
|
|
25
|
+
width: 180,
|
|
26
|
+
child: ClipRRect(
|
|
27
|
+
borderRadius: BorderRadius.circular(999),
|
|
28
|
+
child: const LinearProgressIndicator(minHeight: 4),
|
|
29
|
+
),
|
|
30
|
+
),
|
|
31
|
+
const SizedBox(height: 14),
|
|
32
|
+
const Text('Loading NeoOS'),
|
|
27
33
|
],
|
|
28
34
|
),
|
|
29
35
|
),
|
|
@@ -1090,11 +1096,7 @@ class _HomeViewState extends State<HomeView> {
|
|
|
1090
1096
|
});
|
|
1091
1097
|
|
|
1092
1098
|
if (Platform.isAndroid) {
|
|
1093
|
-
NotificationInterceptor().initialize(
|
|
1094
|
-
context,
|
|
1095
|
-
backendUrl,
|
|
1096
|
-
sessionCookie,
|
|
1097
|
-
);
|
|
1099
|
+
NotificationInterceptor().initialize(backendUrl, sessionCookie);
|
|
1098
1100
|
}
|
|
1099
1101
|
}
|
|
1100
1102
|
}
|
|
@@ -1242,12 +1244,19 @@ class _HomeViewState extends State<HomeView> {
|
|
|
1242
1244
|
onToggleGroup: _toggleSidebarGroup,
|
|
1243
1245
|
),
|
|
1244
1246
|
appBar: AppBar(
|
|
1247
|
+
toolbarHeight: 48,
|
|
1248
|
+
titleSpacing: 0,
|
|
1249
|
+
leadingWidth: 44,
|
|
1250
|
+
centerTitle: false,
|
|
1245
1251
|
title: Text(controller.selectedSection.navigationTitle),
|
|
1252
|
+
titleTextStyle: Theme.of(
|
|
1253
|
+
context,
|
|
1254
|
+
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
|
|
1246
1255
|
elevation: 0,
|
|
1247
1256
|
),
|
|
1248
1257
|
body: SafeArea(
|
|
1249
1258
|
child: Padding(
|
|
1250
|
-
padding: const EdgeInsets.fromLTRB(12, 0, 12,
|
|
1259
|
+
padding: const EdgeInsets.fromLTRB(12, 0, 12, 10),
|
|
1251
1260
|
child: _GlassSurface(
|
|
1252
1261
|
borderRadius: BorderRadius.circular(26),
|
|
1253
1262
|
blurSigma: 24,
|
|
@@ -2014,7 +2023,7 @@ class _MobileDrawer extends StatelessWidget {
|
|
|
2014
2023
|
child: Column(
|
|
2015
2024
|
children: <Widget>[
|
|
2016
2025
|
Padding(
|
|
2017
|
-
padding: const EdgeInsets.fromLTRB(16,
|
|
2026
|
+
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
|
|
2018
2027
|
child: Column(
|
|
2019
2028
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2020
2029
|
children: <Widget>[
|
|
@@ -2032,7 +2041,7 @@ class _MobileDrawer extends StatelessWidget {
|
|
|
2032
2041
|
],
|
|
2033
2042
|
),
|
|
2034
2043
|
if (controller.agentProfiles.isNotEmpty) ...<Widget>[
|
|
2035
|
-
const SizedBox(height:
|
|
2044
|
+
const SizedBox(height: 10),
|
|
2036
2045
|
_AgentSwitcher(
|
|
2037
2046
|
controller: controller,
|
|
2038
2047
|
onChanged: () => Navigator.of(context).pop(),
|
|
@@ -2056,7 +2065,7 @@ class _MobileDrawer extends StatelessWidget {
|
|
|
2056
2065
|
),
|
|
2057
2066
|
),
|
|
2058
2067
|
Padding(
|
|
2059
|
-
padding: const EdgeInsets.
|
|
2068
|
+
padding: const EdgeInsets.fromLTRB(8, 6, 8, 8),
|
|
2060
2069
|
child: Row(
|
|
2061
2070
|
children: <Widget>[
|
|
2062
2071
|
const Spacer(),
|
|
@@ -14,6 +14,7 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
14
14
|
final ScrollController _scrollController = ScrollController();
|
|
15
15
|
List<SharedChatAttachment> _pendingSharedAttachments =
|
|
16
16
|
const <SharedChatAttachment>[];
|
|
17
|
+
String? _appliedSharedPayloadSignature;
|
|
17
18
|
int _lastMessageCount = 0;
|
|
18
19
|
int _lastToolCount = 0;
|
|
19
20
|
String _lastStream = '';
|
|
@@ -27,6 +28,17 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
27
28
|
_consumeQueuedDraft();
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
@override
|
|
32
|
+
void didUpdateWidget(covariant ChatPanel oldWidget) {
|
|
33
|
+
super.didUpdateWidget(oldWidget);
|
|
34
|
+
if (oldWidget.controller != widget.controller) {
|
|
35
|
+
oldWidget.controller.removeListener(_consumeQueuedDraft);
|
|
36
|
+
widget.controller.addListener(_consumeQueuedDraft);
|
|
37
|
+
_appliedSharedPayloadSignature = null;
|
|
38
|
+
_consumeQueuedDraft();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
@override
|
|
31
43
|
void dispose() {
|
|
32
44
|
widget.controller.removeListener(_consumeQueuedDraft);
|
|
@@ -36,26 +48,116 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
void _consumeQueuedDraft() {
|
|
39
|
-
final draft = widget.controller.
|
|
40
|
-
final attachments = widget.controller.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
final draft = widget.controller.peekPendingChatDraft();
|
|
52
|
+
final attachments = widget.controller.peekPendingSharedChatAttachments();
|
|
53
|
+
final signature = _sharedPayloadSignature(draft, attachments);
|
|
54
|
+
if (draft == null && attachments.isEmpty) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (_appliedSharedPayloadSignature == signature) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
_appliedSharedPayloadSignature = signature;
|
|
61
|
+
if (!mounted) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
setState(() {
|
|
65
|
+
if ((draft ?? '').isNotEmpty && _composerController.text.trim().isEmpty) {
|
|
66
|
+
_composerController
|
|
67
|
+
..text = draft!
|
|
68
|
+
..selection = TextSelection.collapsed(offset: draft.length);
|
|
46
69
|
}
|
|
70
|
+
_pendingSharedAttachments = attachments;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
String _sharedPayloadSignature(
|
|
75
|
+
String? draft,
|
|
76
|
+
List<SharedChatAttachment> attachments,
|
|
77
|
+
) {
|
|
78
|
+
final attachmentSignature = attachments
|
|
79
|
+
.map((item) => '${item.uri}|${item.name}|${item.mimeType}')
|
|
80
|
+
.join('::');
|
|
81
|
+
return '${draft ?? ''}::$attachmentSignature';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
void _clearSharedPayload() {
|
|
85
|
+
widget.controller.clearPendingSharedChatPayload();
|
|
86
|
+
_appliedSharedPayloadSignature = null;
|
|
87
|
+
if (!mounted) {
|
|
47
88
|
return;
|
|
48
89
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
90
|
+
setState(() {
|
|
91
|
+
_pendingSharedAttachments = const <SharedChatAttachment>[];
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
String _mimeTypeForFileName(String fileName) {
|
|
96
|
+
final ext = fileName.split('.').last.toLowerCase();
|
|
97
|
+
switch (ext) {
|
|
98
|
+
case 'jpg':
|
|
99
|
+
case 'jpeg':
|
|
100
|
+
return 'image/jpeg';
|
|
101
|
+
case 'png':
|
|
102
|
+
return 'image/png';
|
|
103
|
+
case 'webp':
|
|
104
|
+
return 'image/webp';
|
|
105
|
+
case 'gif':
|
|
106
|
+
return 'image/gif';
|
|
107
|
+
case 'mp4':
|
|
108
|
+
return 'video/mp4';
|
|
109
|
+
case 'mov':
|
|
110
|
+
return 'video/quicktime';
|
|
111
|
+
case 'm4v':
|
|
112
|
+
return 'video/x-m4v';
|
|
113
|
+
case 'mp3':
|
|
114
|
+
return 'audio/mpeg';
|
|
115
|
+
case 'm4a':
|
|
116
|
+
return 'audio/mp4';
|
|
117
|
+
case 'wav':
|
|
118
|
+
return 'audio/wav';
|
|
119
|
+
case 'pdf':
|
|
120
|
+
return 'application/pdf';
|
|
121
|
+
case 'txt':
|
|
122
|
+
return 'text/plain';
|
|
123
|
+
default:
|
|
124
|
+
return 'application/octet-stream';
|
|
56
125
|
}
|
|
57
126
|
}
|
|
58
127
|
|
|
128
|
+
Future<void> _attachFiles() async {
|
|
129
|
+
final result = await FilePicker.platform.pickFiles(
|
|
130
|
+
allowMultiple: true,
|
|
131
|
+
withData: false,
|
|
132
|
+
type: FileType.any,
|
|
133
|
+
);
|
|
134
|
+
if (!mounted || result == null || result.files.isEmpty) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
final attachments = result.files
|
|
138
|
+
.where((file) => file.path?.trim().isNotEmpty == true)
|
|
139
|
+
.map(
|
|
140
|
+
(file) => SharedChatAttachment(
|
|
141
|
+
uri: file.path!,
|
|
142
|
+
name: file.name,
|
|
143
|
+
mimeType: _mimeTypeForFileName(file.name),
|
|
144
|
+
sizeBytes: file.size,
|
|
145
|
+
source: 'file_picker',
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
.where((item) => item.isValid)
|
|
149
|
+
.toList(growable: false);
|
|
150
|
+
if (attachments.isEmpty) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
setState(() {
|
|
154
|
+
_pendingSharedAttachments = <SharedChatAttachment>[
|
|
155
|
+
..._pendingSharedAttachments,
|
|
156
|
+
...attachments,
|
|
157
|
+
];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
59
161
|
void _scrollToBottom() {
|
|
60
162
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
61
163
|
if (!mounted || !_scrollController.hasClients) {
|
|
@@ -178,12 +280,12 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
178
280
|
.map((entry) => entry.value)
|
|
179
281
|
.toList(growable: false);
|
|
180
282
|
});
|
|
283
|
+
if (_pendingSharedAttachments.isEmpty) {
|
|
284
|
+
_clearSharedPayload();
|
|
285
|
+
}
|
|
181
286
|
},
|
|
182
287
|
onClear: () {
|
|
183
|
-
|
|
184
|
-
_pendingSharedAttachments =
|
|
185
|
-
const <SharedChatAttachment>[];
|
|
186
|
-
});
|
|
288
|
+
_clearSharedPayload();
|
|
187
289
|
},
|
|
188
290
|
),
|
|
189
291
|
),
|
|
@@ -215,6 +317,13 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
215
317
|
),
|
|
216
318
|
),
|
|
217
319
|
const SizedBox(width: 8),
|
|
320
|
+
IconButton(
|
|
321
|
+
tooltip: 'Attach files',
|
|
322
|
+
onPressed: _attachFiles,
|
|
323
|
+
icon: const Icon(Icons.attach_file_rounded),
|
|
324
|
+
color: _textSecondary,
|
|
325
|
+
),
|
|
326
|
+
const SizedBox(width: 2),
|
|
218
327
|
FilledButton(
|
|
219
328
|
onPressed: () => controller.setSelectedSection(
|
|
220
329
|
AppSection.voiceAssistant,
|
|
@@ -246,10 +355,7 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
246
355
|
_composerController.clear();
|
|
247
356
|
final outgoingAttachments =
|
|
248
357
|
_pendingSharedAttachments;
|
|
249
|
-
|
|
250
|
-
_pendingSharedAttachments =
|
|
251
|
-
const <SharedChatAttachment>[];
|
|
252
|
-
});
|
|
358
|
+
_clearSharedPayload();
|
|
253
359
|
try {
|
|
254
360
|
await controller.sendMessage(
|
|
255
361
|
task,
|
|
@@ -2860,6 +2860,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2860
2860
|
() => _backendClient.launchBrowser(backendUrl),
|
|
2861
2861
|
browser: true,
|
|
2862
2862
|
);
|
|
2863
|
+
browserScreenshotPath = null;
|
|
2863
2864
|
}
|
|
2864
2865
|
|
|
2865
2866
|
Future<void> navigateBrowserRuntime({
|
|
@@ -2971,6 +2972,8 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2971
2972
|
() => _backendClient.closeBrowser(backendUrl),
|
|
2972
2973
|
browser: true,
|
|
2973
2974
|
);
|
|
2975
|
+
browserScreenshotPath = null;
|
|
2976
|
+
notifyListeners();
|
|
2974
2977
|
}
|
|
2975
2978
|
|
|
2976
2979
|
Future<void> startAndroidRuntime() async {
|
|
@@ -5695,21 +5698,29 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
5695
5698
|
setSelectedSection(AppSection.chat);
|
|
5696
5699
|
}
|
|
5697
5700
|
|
|
5701
|
+
bool get hasPendingSharedChatPayload =>
|
|
5702
|
+
(_pendingChatDraft?.trim().isNotEmpty ?? false) ||
|
|
5703
|
+
_pendingSharedChatAttachments.isNotEmpty;
|
|
5704
|
+
|
|
5698
5705
|
bool get _isMobilePlatform =>
|
|
5699
5706
|
!kIsWeb &&
|
|
5700
5707
|
(defaultTargetPlatform == TargetPlatform.android ||
|
|
5701
5708
|
defaultTargetPlatform == TargetPlatform.iOS);
|
|
5702
5709
|
|
|
5703
|
-
String?
|
|
5704
|
-
final draft = _pendingChatDraft;
|
|
5705
|
-
|
|
5706
|
-
return draft;
|
|
5710
|
+
String? peekPendingChatDraft() {
|
|
5711
|
+
final draft = _pendingChatDraft?.trim() ?? '';
|
|
5712
|
+
return draft.isEmpty ? null : draft;
|
|
5707
5713
|
}
|
|
5708
5714
|
|
|
5709
|
-
List<SharedChatAttachment>
|
|
5710
|
-
|
|
5715
|
+
List<SharedChatAttachment> peekPendingSharedChatAttachments() {
|
|
5716
|
+
return List<SharedChatAttachment>.unmodifiable(
|
|
5717
|
+
_pendingSharedChatAttachments,
|
|
5718
|
+
);
|
|
5719
|
+
}
|
|
5720
|
+
|
|
5721
|
+
void clearPendingSharedChatPayload() {
|
|
5722
|
+
_pendingChatDraft = null;
|
|
5711
5723
|
_pendingSharedChatAttachments = const <SharedChatAttachment>[];
|
|
5712
|
-
return pending;
|
|
5713
5724
|
}
|
|
5714
5725
|
|
|
5715
5726
|
String _taskWithSharedAttachments(
|
|
@@ -495,6 +495,20 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
495
495
|
);
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
+
Widget _inlineProgressIndicator() {
|
|
499
|
+
return SizedBox(
|
|
500
|
+
width: 28,
|
|
501
|
+
child: ClipRRect(
|
|
502
|
+
borderRadius: BorderRadius.circular(999),
|
|
503
|
+
child: LinearProgressIndicator(
|
|
504
|
+
minHeight: 3,
|
|
505
|
+
backgroundColor: Colors.white.withValues(alpha: 0.28),
|
|
506
|
+
color: Colors.white,
|
|
507
|
+
),
|
|
508
|
+
),
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
498
512
|
Widget _buildSettingsOverview(
|
|
499
513
|
NeoAgentController controller,
|
|
500
514
|
int availableModelCount,
|
|
@@ -1421,13 +1435,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
1421
1435
|
: () => controller.checkForAppUpdates(),
|
|
1422
1436
|
style: FilledButton.styleFrom(backgroundColor: _accent),
|
|
1423
1437
|
icon: controller.isCheckingAppUpdate
|
|
1424
|
-
?
|
|
1425
|
-
dimension: 16,
|
|
1426
|
-
child: CircularProgressIndicator(
|
|
1427
|
-
strokeWidth: 2,
|
|
1428
|
-
color: Colors.white,
|
|
1429
|
-
),
|
|
1430
|
-
)
|
|
1438
|
+
? _inlineProgressIndicator()
|
|
1431
1439
|
: const Icon(Icons.sync),
|
|
1432
1440
|
label: Text(
|
|
1433
1441
|
controller.isCheckingAppUpdate
|
|
@@ -1605,13 +1613,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
1605
1613
|
backgroundColor: _accent,
|
|
1606
1614
|
),
|
|
1607
1615
|
icon: controller.isOpeningAppUpdate
|
|
1608
|
-
?
|
|
1609
|
-
dimension: 16,
|
|
1610
|
-
child: CircularProgressIndicator(
|
|
1611
|
-
strokeWidth: 2,
|
|
1612
|
-
color: Colors.white,
|
|
1613
|
-
),
|
|
1614
|
-
)
|
|
1616
|
+
? _inlineProgressIndicator()
|
|
1615
1617
|
: const Icon(Icons.system_update_alt),
|
|
1616
1618
|
label: Text(
|
|
1617
1619
|
controller.isOpeningAppUpdate
|
|
@@ -384,26 +384,30 @@ class _PageTitle extends StatelessWidget {
|
|
|
384
384
|
@override
|
|
385
385
|
Widget build(BuildContext context) {
|
|
386
386
|
final compact = MediaQuery.sizeOf(context).width < 760;
|
|
387
|
+
final titleStyle = compact
|
|
388
|
+
? _displayTitleStyle(26)
|
|
389
|
+
: _displayTitleStyle(32);
|
|
390
|
+
final subtitleStyle = TextStyle(
|
|
391
|
+
color: _textSecondary,
|
|
392
|
+
height: compact ? 1.38 : 1.5,
|
|
393
|
+
);
|
|
387
394
|
return _EntranceMotion(
|
|
388
395
|
child: Padding(
|
|
389
|
-
padding:
|
|
396
|
+
padding: EdgeInsets.only(bottom: compact ? 16 : 24),
|
|
390
397
|
child: compact
|
|
391
398
|
? Column(
|
|
392
399
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
393
400
|
children: <Widget>[
|
|
394
401
|
Text('CONTROL SURFACE', style: _sectionEyebrowStyle()),
|
|
402
|
+
const SizedBox(height: 6),
|
|
403
|
+
Text(title, style: titleStyle),
|
|
395
404
|
const SizedBox(height: 8),
|
|
396
|
-
Text(title, style: _displayTitleStyle(30)),
|
|
397
|
-
const SizedBox(height: 10),
|
|
398
405
|
ConstrainedBox(
|
|
399
406
|
constraints: const BoxConstraints(maxWidth: 720),
|
|
400
|
-
child: Text(
|
|
401
|
-
subtitle,
|
|
402
|
-
style: TextStyle(color: _textSecondary, height: 1.5),
|
|
403
|
-
),
|
|
407
|
+
child: Text(subtitle, style: subtitleStyle),
|
|
404
408
|
),
|
|
405
409
|
if (trailing != null) ...<Widget>[
|
|
406
|
-
const SizedBox(height:
|
|
410
|
+
const SizedBox(height: 12),
|
|
407
411
|
trailing!,
|
|
408
412
|
],
|
|
409
413
|
],
|
|
@@ -417,17 +421,11 @@ class _PageTitle extends StatelessWidget {
|
|
|
417
421
|
children: <Widget>[
|
|
418
422
|
Text('CONTROL SURFACE', style: _sectionEyebrowStyle()),
|
|
419
423
|
const SizedBox(height: 8),
|
|
420
|
-
Text(title, style:
|
|
424
|
+
Text(title, style: titleStyle),
|
|
421
425
|
const SizedBox(height: 10),
|
|
422
426
|
ConstrainedBox(
|
|
423
427
|
constraints: const BoxConstraints(maxWidth: 760),
|
|
424
|
-
child: Text(
|
|
425
|
-
subtitle,
|
|
426
|
-
style: TextStyle(
|
|
427
|
-
color: _textSecondary,
|
|
428
|
-
height: 1.5,
|
|
429
|
-
),
|
|
430
|
-
),
|
|
428
|
+
child: Text(subtitle, style: subtitleStyle),
|
|
431
429
|
),
|
|
432
430
|
],
|
|
433
431
|
),
|
|
@@ -1881,6 +1879,13 @@ class _GlobalWebUpdateBanner extends StatelessWidget {
|
|
|
1881
1879
|
monitor.isReloading ? 'Reloading...' : 'Reload now',
|
|
1882
1880
|
),
|
|
1883
1881
|
),
|
|
1882
|
+
if (monitor.isReloading) ...<Widget>[
|
|
1883
|
+
const SizedBox(height: 8),
|
|
1884
|
+
ClipRRect(
|
|
1885
|
+
borderRadius: BorderRadius.circular(999),
|
|
1886
|
+
child: const LinearProgressIndicator(minHeight: 3),
|
|
1887
|
+
),
|
|
1888
|
+
],
|
|
1884
1889
|
],
|
|
1885
1890
|
)
|
|
1886
1891
|
: Row(
|
|
@@ -8,6 +8,7 @@ import Foundation
|
|
|
8
8
|
import audioplayers_darwin
|
|
9
9
|
import connectivity_plus
|
|
10
10
|
import desktop_audio_capture
|
|
11
|
+
import file_picker
|
|
11
12
|
import flutter_secure_storage_macos
|
|
12
13
|
import geolocator_apple
|
|
13
14
|
import hotkey_manager_macos
|
|
@@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|
|
28
29
|
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
|
29
30
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
|
30
31
|
AudioCapturePlugin.register(with: registry.registrar(forPlugin: "AudioCapturePlugin"))
|
|
32
|
+
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
|
31
33
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
|
32
34
|
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
|
33
35
|
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
|
package/flutter_app/pubspec.lock
CHANGED
|
@@ -137,6 +137,14 @@ packages:
|
|
|
137
137
|
url: "https://pub.dev"
|
|
138
138
|
source: hosted
|
|
139
139
|
version: "2.1.0"
|
|
140
|
+
cross_file:
|
|
141
|
+
dependency: transitive
|
|
142
|
+
description:
|
|
143
|
+
name: cross_file
|
|
144
|
+
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
|
145
|
+
url: "https://pub.dev"
|
|
146
|
+
source: hosted
|
|
147
|
+
version: "0.3.5+2"
|
|
140
148
|
crypto:
|
|
141
149
|
dependency: transitive
|
|
142
150
|
description:
|
|
@@ -200,6 +208,14 @@ packages:
|
|
|
200
208
|
url: "https://pub.dev"
|
|
201
209
|
source: hosted
|
|
202
210
|
version: "7.0.1"
|
|
211
|
+
file_picker:
|
|
212
|
+
dependency: "direct main"
|
|
213
|
+
description:
|
|
214
|
+
name: file_picker
|
|
215
|
+
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
|
|
216
|
+
url: "https://pub.dev"
|
|
217
|
+
source: hosted
|
|
218
|
+
version: "8.3.7"
|
|
203
219
|
fixnum:
|
|
204
220
|
dependency: transitive
|
|
205
221
|
description:
|
|
@@ -269,6 +285,14 @@ packages:
|
|
|
269
285
|
url: "https://pub.dev"
|
|
270
286
|
source: hosted
|
|
271
287
|
version: "0.7.7+1"
|
|
288
|
+
flutter_plugin_android_lifecycle:
|
|
289
|
+
dependency: transitive
|
|
290
|
+
description:
|
|
291
|
+
name: flutter_plugin_android_lifecycle
|
|
292
|
+
sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
|
|
293
|
+
url: "https://pub.dev"
|
|
294
|
+
source: hosted
|
|
295
|
+
version: "2.0.34"
|
|
272
296
|
flutter_secure_storage:
|
|
273
297
|
dependency: "direct main"
|
|
274
298
|
description:
|
package/flutter_app/pubspec.yaml
CHANGED