neoagent 2.3.1-beta.56 → 2.3.1-beta.58

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.
@@ -1,7 +1,8 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_animate/flutter_animate.dart';
3
- import 'package:google_fonts/google_fonts.dart';
3
+
4
4
  import '../../main.dart';
5
+ import 'onboarding_chrome.dart';
5
6
 
6
7
  class OnboardingMessagingStep extends StatefulWidget {
7
8
  const OnboardingMessagingStep({
@@ -14,7 +15,8 @@ class OnboardingMessagingStep extends StatefulWidget {
14
15
  final NeoAgentController controller;
15
16
 
16
17
  @override
17
- State<OnboardingMessagingStep> createState() => _OnboardingMessagingStepState();
18
+ State<OnboardingMessagingStep> createState() =>
19
+ _OnboardingMessagingStepState();
18
20
  }
19
21
 
20
22
  class _OnboardingMessagingStepState extends State<OnboardingMessagingStep> {
@@ -22,240 +24,177 @@ class _OnboardingMessagingStepState extends State<OnboardingMessagingStep> {
22
24
 
23
25
  @override
24
26
  Widget build(BuildContext context) {
25
- return SafeArea(
26
- child: Padding(
27
- padding: const EdgeInsets.all(40),
27
+ final platforms = messagingPlatforms.length > 5
28
+ ? messagingPlatforms.take(5).toList()
29
+ : messagingPlatforms;
30
+
31
+ return OnboardingScaffold(
32
+ step: 2,
33
+ totalSteps: 4,
34
+ eyebrow: 'COMMUNICATION',
35
+ title: 'Connect the channels\nyou actually use.',
36
+ description:
37
+ 'Choose one messaging surface to start. NeoOS can prepare context, monitor the right signals, and keep the interaction layer close to your real workflow.',
38
+ sidePanel: OnboardingPanel(
28
39
  child: Column(
29
40
  crossAxisAlignment: CrossAxisAlignment.start,
30
41
  children: <Widget>[
31
- const SizedBox(height: 40),
32
- Text(
33
- 'Connect your\nCommunication',
34
- style: GoogleFonts.spaceGrotesk(
35
- fontSize: 48,
36
- fontWeight: FontWeight.w800,
37
- height: 1.1,
38
- letterSpacing: -1.5,
42
+ const Text(
43
+ 'Setup principle',
44
+ style: TextStyle(
39
45
  color: Colors.white,
46
+ fontSize: 20,
47
+ fontWeight: FontWeight.w800,
40
48
  ),
41
- ).animate()
42
- .fadeIn(duration: 600.ms)
43
- .slideX(begin: -0.1, end: 0, curve: Curves.easeOutCubic),
44
-
45
- const SizedBox(height: 16),
49
+ ),
50
+ const SizedBox(height: 10),
46
51
  Text(
47
- 'NeoOS works where you do. Connect a platform to allow Neo to intercept and assist with your messages securely.',
52
+ 'Start with one high-value integration first. The product feels smarter when the first connection is relevant, not exhaustive.',
48
53
  style: TextStyle(
49
- fontSize: 18,
50
- color: Colors.white.withValues(alpha: 0.7),
54
+ color: Colors.white.withValues(alpha: 0.72),
55
+ fontSize: 15,
51
56
  height: 1.5,
52
57
  ),
53
- ).animate()
54
- .fadeIn(duration: 600.ms, delay: 200.ms)
55
- .slideX(begin: -0.1, end: 0, curve: Curves.easeOutCubic),
56
-
57
- const SizedBox(height: 60),
58
-
59
- Expanded(
60
- child: ListView.separated(
61
- itemCount: messagingPlatforms.length > 5 ? 6 : messagingPlatforms.length,
62
- separatorBuilder: (context, index) => const SizedBox(height: 16),
63
- itemBuilder: (context, index) {
64
- final platformCount = messagingPlatforms.length > 5 ? 5 : messagingPlatforms.length;
65
- if (index == platformCount) {
66
- return Padding(
67
- padding: const EdgeInsets.only(top: 16, bottom: 32),
68
- child: Center(
69
- child: Text(
70
- 'More providers available in Settings',
71
- style: TextStyle(
72
- color: Colors.white.withValues(alpha: 0.5),
73
- fontSize: 14,
74
- fontWeight: FontWeight.w500,
75
- ),
76
- ),
77
- ),
78
- ).animate().fadeIn(delay: 1000.ms);
79
- }
80
-
81
- final platform = messagingPlatforms[index];
82
- final isSelected = _selectedPlatform?.id == platform.id;
83
-
84
- return _PlatformCard(
85
- platform: platform,
86
- isSelected: isSelected,
87
- onTap: () {
88
- setState(() {
89
- _selectedPlatform = platform;
90
- });
91
- },
92
- ).animate()
93
- .fadeIn(duration: 500.ms, delay: (400 + index * 100).ms)
94
- .slideY(begin: 0.2, end: 0, curve: Curves.easeOutCubic);
95
- },
96
- ),
97
58
  ),
98
-
99
- // Bottom Action
100
- Row(
101
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
59
+ const SizedBox(height: 18),
60
+ const Wrap(
61
+ spacing: 12,
62
+ runSpacing: 12,
102
63
  children: <Widget>[
103
- TextButton(
104
- onPressed: widget.onNext,
105
- style: TextButton.styleFrom(
106
- foregroundColor: Colors.white54,
107
- padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
108
- ),
109
- child: const Text('Skip for now', style: TextStyle(fontSize: 16)),
110
- ).animate().fadeIn(delay: 800.ms),
111
-
112
- Row(
113
- mainAxisSize: MainAxisSize.min,
114
- children: [
115
- if (_selectedPlatform != null)
116
- FilledButton.icon(
117
- onPressed: () async {
118
- try {
119
- await openMessagingConfig(context, widget.controller, _selectedPlatform!);
120
- } catch (e) {
121
- if (context.mounted) {
122
- ScaffoldMessenger.of(context).showSnackBar(
123
- SnackBar(
124
- content: Text('Failed to connect: $e'),
125
- backgroundColor: Colors.redAccent,
126
- ),
127
- );
128
- }
129
- }
130
- },
131
- style: FilledButton.styleFrom(
132
- padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
133
- shape: RoundedRectangleBorder(
134
- borderRadius: BorderRadius.circular(20),
135
- ),
136
- ),
137
- icon: const Icon(Icons.settings_rounded),
138
- label: const Text(
139
- 'Configure',
140
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
141
- ),
142
- ).animate().fadeIn(delay: 800.ms),
143
-
144
- if (_selectedPlatform != null)
145
- const SizedBox(width: 8),
146
-
147
- FilledButton.icon(
148
- onPressed: widget.onNext,
149
- style: FilledButton.styleFrom(
150
- padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20),
151
- shape: RoundedRectangleBorder(
152
- borderRadius: BorderRadius.circular(20),
153
- ),
154
- backgroundColor: _selectedPlatform != null
155
- ? Colors.white.withValues(alpha: 0.1)
156
- : Theme.of(context).colorScheme.primary,
157
- foregroundColor: Colors.white,
158
- ),
159
- icon: const Icon(Icons.arrow_forward_rounded),
160
- label: Text(
161
- _selectedPlatform != null ? 'Next' : 'Continue',
162
- style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
163
- ),
164
- ).animate().fadeIn(delay: 800.ms),
165
- ],
166
- ),
64
+ OnboardingMetricPill(label: 'Signal', value: 'Relevant only'),
65
+ OnboardingMetricPill(label: 'Access', value: 'Configurable'),
167
66
  ],
168
67
  ),
169
68
  ],
170
69
  ),
171
- ),
172
- );
173
- }
174
- }
175
-
176
- class _PlatformCard extends StatelessWidget {
177
- const _PlatformCard({
178
- required this.platform,
179
- required this.isSelected,
180
- required this.onTap,
181
- });
182
-
183
- final MessagingPlatformDescriptor platform;
184
- final bool isSelected;
185
- final VoidCallback onTap;
186
-
187
- @override
188
- Widget build(BuildContext context) {
189
- final color = platform.accent;
190
-
191
- return InkWell(
192
- onTap: onTap,
193
- borderRadius: BorderRadius.circular(24),
194
- child: AnimatedContainer(
195
- duration: const Duration(milliseconds: 300),
196
- curve: Curves.easeOutCubic,
197
- padding: const EdgeInsets.all(24),
198
- decoration: BoxDecoration(
199
- color: isSelected
200
- ? color.withValues(alpha: 0.15)
201
- : Colors.white.withValues(alpha: 0.05),
202
- borderRadius: BorderRadius.circular(24),
203
- border: Border.all(
204
- color: isSelected ? color : Colors.white.withValues(alpha: 0.1),
205
- width: isSelected ? 2 : 1,
70
+ ).animate().fadeIn(duration: 600.ms, delay: 200.ms),
71
+ footer: Row(
72
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
73
+ children: <Widget>[
74
+ OnboardingGhostButton(
75
+ label: 'Skip for now',
76
+ onPressed: widget.onNext,
206
77
  ),
207
- boxShadow: isSelected ? [
208
- BoxShadow(
209
- color: color.withValues(alpha: 0.2),
210
- blurRadius: 20,
211
- offset: const Offset(0, 8),
212
- )
213
- ] : [],
214
- ),
215
- child: Row(
216
- children: <Widget>[
217
- Container(
218
- padding: const EdgeInsets.all(12),
219
- decoration: BoxDecoration(
220
- color: color.withValues(alpha: 0.2),
221
- shape: BoxShape.circle,
78
+ Wrap(
79
+ spacing: 12,
80
+ runSpacing: 12,
81
+ children: <Widget>[
82
+ if (_selectedPlatform != null)
83
+ OnboardingGhostButton(
84
+ label: 'Configure',
85
+ icon: Icons.settings_rounded,
86
+ onPressed: () async {
87
+ try {
88
+ await openMessagingConfig(
89
+ context,
90
+ widget.controller,
91
+ _selectedPlatform!,
92
+ );
93
+ } catch (e) {
94
+ if (context.mounted) {
95
+ ScaffoldMessenger.of(context).showSnackBar(
96
+ SnackBar(content: Text('Failed to connect: $e')),
97
+ );
98
+ }
99
+ }
100
+ },
101
+ ),
102
+ OnboardingPrimaryButton(
103
+ label: _selectedPlatform == null ? 'Continue' : 'Next',
104
+ icon: Icons.arrow_forward_rounded,
105
+ onPressed: widget.onNext,
222
106
  ),
223
- child: Icon(
224
- platform.icon,
225
- color: color,
226
- size: 32,
107
+ ],
108
+ ),
109
+ ],
110
+ ),
111
+ child: ListView.separated(
112
+ itemCount: platforms.length + 1,
113
+ separatorBuilder: (_, __) => const SizedBox(height: 14),
114
+ itemBuilder: (context, index) {
115
+ if (index == platforms.length) {
116
+ return Padding(
117
+ padding: const EdgeInsets.only(top: 10, bottom: 10),
118
+ child: Text(
119
+ 'More providers stay available later in Settings.',
120
+ style: TextStyle(
121
+ color: Colors.white.withValues(alpha: 0.52),
122
+ fontSize: 14,
123
+ fontWeight: FontWeight.w600,
124
+ ),
227
125
  ),
228
- ),
229
- const SizedBox(width: 20),
230
- Expanded(
231
- child: Column(
232
- crossAxisAlignment: CrossAxisAlignment.start,
233
- mainAxisSize: MainAxisSize.min,
234
- children: [
235
- Text(
236
- platform.label,
237
- style: const TextStyle(
238
- fontSize: 22,
239
- fontWeight: FontWeight.w600,
240
- color: Colors.white,
126
+ );
127
+ }
128
+
129
+ final platform = platforms[index];
130
+ final selected = _selectedPlatform?.id == platform.id;
131
+ return OnboardingOptionCard(
132
+ selected: selected,
133
+ accent: platform.accent,
134
+ onTap: () => setState(() => _selectedPlatform = platform),
135
+ child: Row(
136
+ children: <Widget>[
137
+ Container(
138
+ width: 58,
139
+ height: 58,
140
+ decoration: BoxDecoration(
141
+ color: platform.accent.withValues(alpha: 0.18),
142
+ borderRadius: BorderRadius.circular(18),
143
+ ),
144
+ child: Icon(
145
+ platform.icon,
146
+ color: platform.accent,
147
+ size: 30,
148
+ ),
241
149
  ),
242
- ),
243
- const SizedBox(height: 4),
244
- Text(
245
- platform.subtitle,
246
- style: TextStyle(
247
- fontSize: 14,
248
- color: Colors.white.withValues(alpha: 0.6),
150
+ const SizedBox(width: 18),
151
+ Expanded(
152
+ child: Column(
153
+ crossAxisAlignment: CrossAxisAlignment.start,
154
+ children: <Widget>[
155
+ Text(
156
+ platform.label,
157
+ style: const TextStyle(
158
+ color: Colors.white,
159
+ fontSize: 20,
160
+ fontWeight: FontWeight.w800,
161
+ ),
162
+ ),
163
+ const SizedBox(height: 5),
164
+ Text(
165
+ platform.subtitle,
166
+ style: TextStyle(
167
+ color: Colors.white.withValues(alpha: 0.68),
168
+ fontSize: 14,
169
+ height: 1.45,
170
+ ),
171
+ ),
172
+ ],
173
+ ),
249
174
  ),
250
- ),
251
- ],
252
- ),
253
- ),
254
- if (isSelected)
255
- Icon(Icons.check_circle_rounded, color: color, size: 28)
256
- .animate().scale(curve: Curves.elasticOut),
257
- ],
258
- ),
175
+ AnimatedSwitcher(
176
+ duration: const Duration(milliseconds: 220),
177
+ child: selected
178
+ ? Icon(
179
+ Icons.check_circle_rounded,
180
+ key: ValueKey<String>(platform.id),
181
+ color: platform.accent,
182
+ size: 28,
183
+ )
184
+ : Icon(
185
+ Icons.add_circle_outline_rounded,
186
+ key: ValueKey<String>('idle-${platform.id}'),
187
+ color: Colors.white.withValues(alpha: 0.26),
188
+ size: 28,
189
+ ),
190
+ ),
191
+ ],
192
+ ),
193
+ )
194
+ .animate()
195
+ .fadeIn(duration: 420.ms, delay: (180 + (index * 80)).ms)
196
+ .slideY(begin: 0.16, end: 0);
197
+ },
259
198
  ),
260
199
  );
261
200
  }