amoriai/lib/screens/onboarding_screen.dart

274 lines
8.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'auth/login_screen.dart';
class OnboardingScreen extends StatefulWidget {
const OnboardingScreen({super.key});
@override
State<OnboardingScreen> createState() => _OnboardingScreenState();
}
class _OnboardingScreenState extends State<OnboardingScreen> {
final PageController _pageController = PageController();
int _currentPage = 0;
final List<OnboardingData> _pages = [
OnboardingData(
image: 'assets/images/onboard1.png',
title: 'Bidik Menumu, Cepat dan Akurat',
description:
'Teknologi Google Vision OCR kami akan membaca daftar menu Amor Coffee secara instan dalam hitungan detik',
),
OnboardingData(
image: 'assets/images/onboard2.png',
title: 'Beri Tahu Amori Seleramu',
description:
'Apakah kamu sedang butuh kafein tinggi untuk bekerja atau sekadar camilan manis untuk bersantai? Amori ingin mengenalmu lebih dekat',
),
OnboardingData(
image: 'assets/images/onboard3.png',
title: 'Rekomendasi Cerdas',
description:
'Saran Terbaik dari Gemini AI. Dapatkan rekomendasi menu yang paling cocok dengan preferensi pribadimu saat ini',
),
];
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _onSkip() async {
// Mark onboarding as completed
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('is_first_launch', false);
if (!mounted) return;
Navigator.of(context).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const LoginScreen(),
transitionDuration: const Duration(milliseconds: 400),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation.drive(
Tween<double>(
begin: 0.0,
end: 1.0,
).chain(CurveTween(curve: Curves.easeOut)),
),
child: child,
);
},
),
);
}
void _onNext() {
if (_currentPage < _pages.length - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
} else {
_onSkip();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
// PageView
Expanded(
child: PageView.builder(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentPage = index;
});
},
itemCount: _pages.length,
itemBuilder: (context, index) {
return OnboardingPage(data: _pages[index]);
},
),
),
// Bottom controls: Skip, Page Indicator, Arrow Button
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Skip button (left)
TextButton(
onPressed: _onSkip,
style: TextButton.styleFrom(
minimumSize: const Size(60, 40),
padding: EdgeInsets.zero,
),
child: Text(
'Skip',
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 16,
color: const Color(0xFF4A3C3C).withOpacity(0.6),
fontWeight: FontWeight.w400,
),
),
),
// Page Indicator (center)
Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
_pages.length,
(index) => AnimatedContainer(
duration: const Duration(milliseconds: 300),
margin: const EdgeInsets.symmetric(horizontal: 4),
width: _currentPage == index ? 32 : 8,
height: 8,
decoration: BoxDecoration(
color: _currentPage == index
? const Color(0xFF5A4A3A)
: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
),
),
),
// Arrow button (right) with shadow overlay - CIRCULAR
Stack(
clipBehavior: Clip.none,
children: [
// Shadow overlay (offset to top)
Positioned(
top: -4,
left: 4,
right: -4,
bottom: 4,
child: Container(
width: 52,
height: 52,
decoration: BoxDecoration(
color: const Color(0xFF5A4A3A).withOpacity(0.3),
shape: BoxShape.circle,
),
),
),
// Main button - CIRCULAR with touch animation
Material(
color: const Color(0xFF5A4A3A),
shape: const CircleBorder(),
child: InkWell(
onTap: _onNext,
customBorder: const CircleBorder(),
splashColor: Colors.white.withOpacity(0.3),
highlightColor: Colors.white.withOpacity(0.1),
child: Container(
width: 52,
height: 52,
decoration: const BoxDecoration(
shape: BoxShape.circle,
),
child: const Icon(
Icons.arrow_forward,
color: Colors.white,
size: 24,
),
),
),
),
],
),
],
),
),
],
),
),
);
}
}
class OnboardingPage extends StatelessWidget {
final OnboardingData data;
const OnboardingPage({super.key, required this.data});
@override
Widget build(BuildContext context) {
// Define the color with 85% opacity
final textColor = const Color(0xFF4A3C3C).withOpacity(0.85);
return Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Image
Expanded(
flex: 3,
child: Image.asset(data.image, fit: BoxFit.contain),
),
const SizedBox(height: 48),
// Title
Text(
data.title,
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 24,
fontWeight: FontWeight.w600, // SemiBold
color: textColor,
height: 1.3,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
// Description
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
data.description,
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 14,
fontWeight: FontWeight.w400, // Regular
color: textColor.withOpacity(0.8),
height: 1.6,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 48),
],
),
);
}
}
class OnboardingData {
final String image;
final String title;
final String description;
OnboardingData({
required this.image,
required this.title,
required this.description,
});
}