neoagent 2.3.1-beta.57 → 2.3.1-beta.59

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,133 @@ 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),
28
- child: Column(
29
- crossAxisAlignment: CrossAxisAlignment.start,
30
- 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,
39
- color: Colors.white,
40
- ),
41
- ).animate()
42
- .fadeIn(duration: 600.ms)
43
- .slideX(begin: -0.1, end: 0, curve: Curves.easeOutCubic),
44
-
45
- const SizedBox(height: 16),
46
- Text(
47
- 'NeoOS works where you do. Connect a platform to allow Neo to intercept and assist with your messages securely.',
48
- style: TextStyle(
49
- fontSize: 18,
50
- color: Colors.white.withValues(alpha: 0.7),
51
- height: 1.5,
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 a\nmessaging platform.',
36
+ description: 'Choose one to get started now. You can add more later.',
37
+ footer: Row(
38
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
39
+ children: <Widget>[
40
+ OnboardingGhostButton(
41
+ label: 'Skip for now',
42
+ onPressed: widget.onNext,
43
+ ),
44
+ Wrap(
45
+ spacing: 12,
46
+ runSpacing: 12,
47
+ children: <Widget>[
48
+ if (_selectedPlatform != null)
49
+ OnboardingGhostButton(
50
+ label: 'Configure',
51
+ icon: Icons.settings_rounded,
52
+ onPressed: () async {
53
+ try {
54
+ await openMessagingConfig(
55
+ context,
56
+ widget.controller,
57
+ _selectedPlatform!,
58
+ );
59
+ } catch (e) {
60
+ if (context.mounted) {
61
+ ScaffoldMessenger.of(context).showSnackBar(
62
+ SnackBar(content: Text('Failed to connect: $e')),
63
+ );
64
+ }
65
+ }
66
+ },
67
+ ),
68
+ OnboardingPrimaryButton(
69
+ label: _selectedPlatform == null ? 'Continue' : 'Next',
70
+ icon: Icons.arrow_forward_rounded,
71
+ onPressed: widget.onNext,
52
72
  ),
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
- ),
73
+ ],
74
+ ),
75
+ ],
76
+ ),
77
+ child: ListView.separated(
78
+ itemCount: platforms.length + 1,
79
+ separatorBuilder: (_, __) => const SizedBox(height: 14),
80
+ itemBuilder: (context, index) {
81
+ if (index == platforms.length) {
82
+ return const SizedBox.shrink();
83
+ }
84
+
85
+ final platform = platforms[index];
86
+ final selected = _selectedPlatform?.id == platform.id;
87
+ return OnboardingOptionCard(
88
+ selected: selected,
89
+ accent: platform.accent,
90
+ onTap: () => setState(() => _selectedPlatform = platform),
91
+ child: Row(
92
+ children: <Widget>[
93
+ Container(
94
+ width: 58,
95
+ height: 58,
96
+ decoration: BoxDecoration(
97
+ color: platform.accent.withValues(alpha: 0.18),
98
+ borderRadius: BorderRadius.circular(18),
77
99
  ),
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
- ),
98
-
99
- // Bottom Action
100
- Row(
101
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
102
- 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,
100
+ child: Icon(
101
+ platform.icon,
102
+ color: platform.accent,
103
+ size: 30,
158
104
  ),
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),
105
+ ),
106
+ const SizedBox(width: 18),
107
+ Expanded(
108
+ child: Column(
109
+ crossAxisAlignment: CrossAxisAlignment.start,
110
+ children: <Widget>[
111
+ Text(
112
+ platform.label,
113
+ style: const TextStyle(
114
+ color: Colors.white,
115
+ fontSize: 20,
116
+ fontWeight: FontWeight.w800,
117
+ ),
118
+ ),
119
+ const SizedBox(height: 5),
120
+ Text(
121
+ platform.subtitle,
122
+ style: TextStyle(
123
+ color: Colors.white.withValues(alpha: 0.68),
124
+ fontSize: 14,
125
+ height: 1.45,
126
+ ),
127
+ ),
128
+ ],
163
129
  ),
164
- ).animate().fadeIn(delay: 800.ms),
165
- ],
166
- ),
167
- ],
168
- ),
169
- ],
170
- ),
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,
206
- ),
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,
222
- ),
223
- child: Icon(
224
- platform.icon,
225
- color: color,
226
- size: 32,
227
- ),
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,
241
130
  ),
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),
131
+ AnimatedSwitcher(
132
+ duration: const Duration(milliseconds: 220),
133
+ child: selected
134
+ ? Icon(
135
+ Icons.check_circle_rounded,
136
+ key: ValueKey<String>(platform.id),
137
+ color: platform.accent,
138
+ size: 28,
139
+ )
140
+ : Icon(
141
+ Icons.add_circle_outline_rounded,
142
+ key: ValueKey<String>('idle-${platform.id}'),
143
+ color: Colors.white.withValues(alpha: 0.26),
144
+ size: 28,
145
+ ),
249
146
  ),
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
- ),
147
+ ],
148
+ ),
149
+ )
150
+ .animate()
151
+ .fadeIn(duration: 420.ms, delay: (180 + (index * 80)).ms)
152
+ .slideY(begin: 0.16, end: 0);
153
+ },
259
154
  ),
260
155
  );
261
156
  }