Files
tripz-llc/apps/rider/lib/features/onboarding/view/onboarding_screen.dart
T

232 lines
7.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../core/design/tokens.dart';
import '../../../core/l10n/app_localizations.dart';
import '../../../core/ui/tripz_scaffold.dart';
import '../../../core/ui/tripz_button.dart';
class OnboardingScreen extends StatefulWidget {
const OnboardingScreen({super.key});
@override
State<OnboardingScreen> createState() => _OnboardingScreenState();
}
class _OnboardingScreenState extends State<OnboardingScreen>
with SingleTickerProviderStateMixin {
final PageController _pageController = PageController();
int _currentIndex = 0;
late final AnimationController _pulseController;
late final Animation<double> _pulseAnimation;
static const _onboardingKey = 'onboarding_seen';
@override
void initState() {
super.initState();
_pulseController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1400),
)..repeat(reverse: true);
_pulseAnimation = Tween<double>(begin: 1.0, end: 1.08).animate(
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_pulseController.dispose();
_pageController.dispose();
super.dispose();
}
Future<void> _markSeen() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_onboardingKey, true);
}
void _nextPage(int pageCount) {
if (_currentIndex < pageCount - 1) {
_pageController.nextPage(
duration: T.slow,
curve: Curves.easeInOut,
);
} else {
_markSeen();
context.go('/phone');
}
}
void _skip() {
_markSeen();
context.go('/phone');
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cs = theme.colorScheme;
final l10n = AppLocalizations.of(context)!;
final pages = [
_OnboardingPageData(
title: l10n.onboardingTitle1,
subtitle: l10n.onboardingSubtitle1,
image: 'assets/images/onboarding_1.png',
fallbackIcon: Icons.touch_app_rounded,
),
_OnboardingPageData(
title: l10n.onboardingTitle2,
subtitle: l10n.onboardingSubtitle2,
image: 'assets/images/onboarding_2.png',
fallbackIcon: Icons.location_on_rounded,
),
_OnboardingPageData(
title: l10n.onboardingTitle3,
subtitle: l10n.onboardingSubtitle3,
image: 'assets/images/onboarding_3.png',
fallbackIcon: Icons.payment_rounded,
),
];
return TripzScaffold(
backgroundColor: cs.surface,
padding: false,
bottomAction: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── Page Indicators ──
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(pages.length, (index) {
final isActive = index == _currentIndex;
return AnimatedContainer(
duration: T.medium,
margin: const EdgeInsets.symmetric(horizontal: T.s4),
height: T.s8,
width: isActive ? T.s24 : T.s8,
decoration: BoxDecoration(
color: isActive
? cs.primary
: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(T.rCircle),
),
);
}),
),
const SizedBox(height: T.s24),
// ── Primary Button ──
TripzButton(
onPressed: () => _nextPage(pages.length),
label: _currentIndex == pages.length - 1 ? l10n.getStarted : l10n.next,
),
],
),
child: Column(
children: [
// ── Skip Button ──
Align(
alignment: AlignmentDirectional.centerEnd,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: T.pagePadding,
vertical: T.s8,
),
child: TripzButton(
onPressed: _skip,
style: TripzButtonStyle.text,
label: l10n.skip,
fullWidth: false,
),
),
),
// ── PageView ──
Expanded(
child: PageView.builder(
controller: _pageController,
itemCount: pages.length,
onPageChanged: (index) {
setState(() => _currentIndex = index);
},
itemBuilder: (context, index) {
final data = pages[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: T.s24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ── الرسم التوضيحي (أصل مسجّل) مع نبض خفيف ──
AnimatedScale(
scale: _pulseAnimation.value,
duration: const Duration(milliseconds: 100),
child: SizedBox(
width: 240,
height: 240,
child: Image.asset(
data.image,
fit: BoxFit.contain,
errorBuilder: (_, __, ___) => Container(
decoration: BoxDecoration(
color: cs.primaryContainer,
shape: BoxShape.circle,
),
child: Icon(
data.fallbackIcon,
size: 88,
color: cs.onPrimaryContainer,
),
),
),
),
),
const SizedBox(height: T.s48),
// ── Title ──
Text(
data.title,
style: theme.textTheme.headlineMedium?.copyWith(
color: cs.onSurface,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: T.s12),
// ── Subtitle ──
Text(
data.subtitle,
style: theme.textTheme.bodyLarge?.copyWith(
color: cs.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
);
},
),
),
],
),
);
}
}
class _OnboardingPageData {
final String title;
final String subtitle;
final String image;
final IconData fallbackIcon;
const _OnboardingPageData({
required this.title,
required this.subtitle,
required this.image,
required this.fallbackIcon,
});
}