Files
iiEsaywebUIapp/lib/features/onboarding/views/onboarding_sheet.dart
2025-08-12 13:07:10 +05:30

299 lines
9.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../../../shared/theme/theme_extensions.dart';
import 'package:flutter_animate/flutter_animate.dart';
class OnboardingSheet extends StatefulWidget {
const OnboardingSheet({super.key});
@override
State<OnboardingSheet> createState() => _OnboardingSheetState();
}
class _OnboardingSheetState extends State<OnboardingSheet> {
final PageController _controller = PageController();
int _index = 0;
final List<_OnboardingPage> _pages = const [
_OnboardingPage(
title: 'Start a conversation',
subtitle:
'Choose a model, then type below to begin. Tap New Chat anytime.',
icon: CupertinoIcons.chat_bubble_2,
bullets: [
'Tap the model name in the top bar to switch models',
'Use New Chat to reset context',
],
),
_OnboardingPage(
title: 'Attach context',
subtitle: 'Ground responses by adding files or images.',
icon: CupertinoIcons.doc_on_doc,
bullets: ['Files: PDFs, docs, datasets', 'Images: photos or screenshots'],
),
_OnboardingPage(
title: 'Speak naturally',
subtitle: 'Tap the mic to dictate with live waveform feedback.',
icon: CupertinoIcons.mic_fill,
bullets: [
'Stop anytime; partial text is preserved',
'Great for quick notes or long prompts',
],
),
_OnboardingPage(
title: 'Quick actions',
subtitle:
'Longpress the topleft menu to open shortcuts like New Chat, Files, and Profile.',
icon: CupertinoIcons.line_horizontal_3,
bullets: [
'Tap to open chats list; longpress for Quick Actions',
'Jump instantly to New Chat, Files, or Profile',
],
),
];
void _next() {
if (_index < _pages.length - 1) {
_controller.nextPage(
duration: AnimationDuration.fast,
curve: AnimationCurves.easeInOut,
);
} else {
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
final isSmall = height < 720;
return Container(
height: height * 0.7,
decoration: BoxDecoration(
color: context.conduitTheme.surfaceBackground,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppBorderRadius.modal),
),
boxShadow: ConduitShadows.modal,
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(Spacing.lg),
child: Column(
children: [
// Handle bar
Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: Spacing.md),
decoration: BoxDecoration(
color: context.conduitTheme.dividerColor,
borderRadius: BorderRadius.circular(AppBorderRadius.xs),
),
),
Expanded(
child: PageView.builder(
controller: _controller,
itemCount: _pages.length,
onPageChanged: (i) => setState(() => _index = i),
itemBuilder: (context, i) {
final page = _pages[i];
final content = _IllustratedPage(page: page);
return isSmall
? SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.lg,
),
child: content,
)
: content;
},
),
),
const SizedBox(height: Spacing.md),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(_pages.length, (i) {
final active = i == _index;
return AnimatedContainer(
duration: AnimationDuration.fast,
margin: const EdgeInsets.symmetric(horizontal: 4),
height: 6,
width: active ? 20 : 6,
decoration: BoxDecoration(
color: active
? context.conduitTheme.buttonPrimary
: context.conduitTheme.dividerColor,
borderRadius: BorderRadius.circular(
AppBorderRadius.badge,
),
),
);
}),
),
const SizedBox(height: Spacing.lg),
Row(
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
'Skip',
style: TextStyle(
color: context.conduitTheme.textSecondary,
),
),
),
const Spacer(),
FilledButton(
onPressed: _next,
style: FilledButton.styleFrom(
backgroundColor: context.conduitTheme.buttonPrimary,
foregroundColor: context.conduitTheme.buttonPrimaryText,
padding: const EdgeInsets.symmetric(
horizontal: Spacing.lg,
vertical: Spacing.sm,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
AppBorderRadius.button,
),
),
),
child: Text(_index == _pages.length - 1 ? 'Done' : 'Next'),
),
],
),
],
),
),
),
);
}
}
class _OnboardingPage {
final String title;
final String subtitle;
final IconData icon;
final List<String>? bullets;
const _OnboardingPage({
required this.title,
required this.subtitle,
required this.icon,
this.bullets,
});
}
class _IllustratedPage extends StatelessWidget {
final _OnboardingPage page;
const _IllustratedPage({required this.page});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Aurora blob illustration
SizedBox(
height: 160,
child: Stack(
alignment: Alignment.center,
children: [
Positioned(top: 10, left: 24, child: _blob(context, 90, 0.18)),
Positioned(
bottom: 0,
right: 16,
child: _blob(context, 130, 0.12),
),
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: context.conduitTheme.buttonPrimary,
borderRadius: BorderRadius.circular(AppBorderRadius.avatar),
boxShadow: ConduitShadows.glow,
),
child: Icon(page.icon, color: context.conduitTheme.textInverse),
).animate().scale(duration: AnimationDuration.fast),
],
),
),
const SizedBox(height: Spacing.lg),
Text(
page.title,
style: TextStyle(
fontSize: AppTypography.headlineMedium,
fontWeight: FontWeight.w700,
color: context.conduitTheme.textPrimary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: Spacing.sm),
Text(
page.subtitle,
style: TextStyle(
fontSize: AppTypography.bodyLarge,
color: context.conduitTheme.textSecondary,
),
textAlign: TextAlign.center,
),
if (page.bullets != null && page.bullets!.isNotEmpty) ...[
const SizedBox(height: Spacing.md),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: page.bullets!
.map(
(b) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.lg,
vertical: 4,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 6,
height: 6,
margin: const EdgeInsets.only(top: 8, right: 8),
decoration: BoxDecoration(
color: context.conduitTheme.buttonPrimary,
shape: BoxShape.circle,
),
),
Expanded(
child: Text(
b,
style: TextStyle(
color: context.conduitTheme.textSecondary,
fontSize: AppTypography.bodyMedium,
),
),
),
],
),
),
)
.toList(),
),
],
],
);
}
Widget _blob(BuildContext context, double size, double alpha) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: context.conduitTheme.buttonPrimary.withValues(alpha: alpha),
boxShadow: ConduitShadows.glow,
),
);
}
}