Update page_intro.dart

This commit is contained in:
Greek-Cp 2025-03-23 01:02:13 +07:00
parent 77fc37ee52
commit 96d1c1ea3c
1 changed files with 511 additions and 218 deletions

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:harvest_guard_app/routes/app_routes.dart';
import 'dart:math' as math;
class IntroScreen extends StatefulWidget {
const IntroScreen({super.key});
@ -9,274 +10,566 @@ class IntroScreen extends StatefulWidget {
State<IntroScreen> createState() => _IntroScreenState();
}
class _IntroScreenState extends State<IntroScreen> {
final PageController _pageController = PageController();
class _IntroScreenState extends State<IntroScreen>
with TickerProviderStateMixin {
// Controller untuk animasi
late final AnimationController _backgroundAnimController;
late final AnimationController _contentAnimController;
// Halaman saat ini
int _currentPage = 0;
final List<IntroPage> _introPages = [
IntroPage(
// Data halaman intro
final List<IntroData> _introPages = [
IntroData(
title: 'Pilih Varietas Padi',
description:
'Pilih jenis padi yang ingin Anda periksa dari daftar varietas yang tersedia',
icon: Icons.grass_rounded,
color: Colors.green.shade400,
'Pilih jenis padi yang ingin Anda periksa dari berbagai varietas tanaman padi yang tersedia',
image: 'icons/ic_rice_plant.png',
bgColor: const Color(0xFF4CAF50),
overlayColor: const Color(0xFF2E7D32),
lightColor: const Color(0xFFA5D6A7),
),
IntroPage(
IntroData(
title: 'Scan Tanaman Padi',
description:
'Arahkan kamera ponsel Anda ke bagian tanaman padi yang ingin diperiksa',
icon: Icons.document_scanner_rounded,
color: Colors.blue.shade400,
'Arahkan kamera ke bagian tanaman yang ingin diperiksa untuk analisis dan deteksi penyakit',
image: 'icons/ic_camera.png',
bgColor: const Color(0xFF2196F3),
overlayColor: const Color(0xFF1565C0),
lightColor: const Color(0xFF90CAF9),
),
IntroPage(
title: 'Cek Hasil Diagnosis',
IntroData(
title: 'Hasil Diagnosis',
description:
'Dapatkan hasil diagnosis penyakit dan rekomendasi penanganannya',
icon: Icons.checklist_rounded,
color: Colors.orange.shade400,
'Lihat hasil diagnosis penyakit beserta rekomendasi cara penanganan dan pencegahannya',
image: 'icons/ic_result.png',
bgColor: const Color(0xFFFF9800),
overlayColor: const Color(0xFFEF6C00),
lightColor: const Color(0xFFFFCC80),
),
];
// PageController tunggal hanya untuk pergantian halaman
late final PageController _pageController;
@override
void initState() {
super.initState();
// Inisialisasi page controller
_pageController = PageController();
_pageController.addListener(_handlePageChange);
// Inisialisasi animasi controller
_backgroundAnimController = AnimationController(
vsync: this,
duration: const Duration(seconds: 20),
)..repeat();
_contentAnimController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800),
)..forward();
}
void _handlePageChange() {
// Jika halaman saat ini berbeda dari _currentPage
final page = (_pageController.page ?? 0).round();
if (page != _currentPage) {
setState(() {
_currentPage = page;
// Reset dan mulai animasi konten
_contentAnimController.reset();
_contentAnimController.forward();
});
}
}
@override
void dispose() {
_pageController.removeListener(_handlePageChange);
_pageController.dispose();
_backgroundAnimController.dispose();
_contentAnimController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
return Scaffold(
body: Stack(
children: [
// Background animasi bergerak
AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
top: -100 + (_currentPage * 50),
right: -100 + (_currentPage * 40),
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [
_introPages[_currentPage].color.withOpacity(0.7),
_introPages[_currentPage].color.withOpacity(0.0),
],
stops: const [0.1, 1.0],
),
shape: BoxShape.circle,
),
),
),
AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
bottom: -80 + (_currentPage * 30),
left: -50 + (_currentPage * 20),
child: Container(
height: 250,
width: 250,
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [
_introPages[_currentPage].color.withOpacity(0.5),
_introPages[_currentPage].color.withOpacity(0.0),
],
stops: const [0.1, 1.0],
),
shape: BoxShape.circle,
),
),
),
// Background animasi
_buildAnimatedBackground(),
// Content
Column(
children: [
Expanded(
child: PageView.builder(
controller: _pageController,
itemCount: _introPages.length,
onPageChanged: (int page) {
setState(() {
_currentPage = page;
});
},
itemBuilder: (context, index) {
return _buildIntroPage(_introPages[index]);
},
),
),
// Pagination indicator
Container(
padding: const EdgeInsets.symmetric(vertical: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_introPages.length,
(index) => _buildDotIndicator(index),
),
),
),
// Bottom buttons
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Next/Finish button
ElevatedButton(
onPressed: () {
if (_currentPage < _introPages.length - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
} else {
Get.offNamed(AppRoutes.dashboard);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: _introPages[_currentPage].color,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 30, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: Text(
_currentPage < _introPages.length - 1
? 'Lanjut'
: 'Mulai',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
],
// Konten utama dengan PageView
PageView.builder(
controller: _pageController,
itemCount: _introPages.length,
onPageChanged: (page) {
setState(() {
_currentPage = page;
// Reset animasi konten saat halaman berubah
_contentAnimController.reset();
_contentAnimController.forward();
});
},
itemBuilder: (context, index) {
return _buildFullPageContent(_introPages[index], screenSize);
},
),
],
),
);
}
Widget _buildIntroPage(IntroPage page) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Icon with animated container
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 600),
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Container(
padding: const EdgeInsets.all(25),
// Background dengan animasi
Widget _buildAnimatedBackground() {
return AnimatedBuilder(
animation: _backgroundAnimController,
builder: (context, child) {
return Stack(
children: [
// Base background color
AnimatedContainer(
duration: const Duration(milliseconds: 500),
color: _introPages[_currentPage].bgColor,
),
// Animated shapes - circles
Positioned(
right: -100,
top: -50,
child: Transform.rotate(
angle: _backgroundAnimController.value * 2 * math.pi,
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
width: 300,
height: 300,
decoration: BoxDecoration(
color: page.color.withOpacity(0.2),
shape: BoxShape.circle,
color:
_introPages[_currentPage].lightColor.withOpacity(0.3),
),
),
),
),
// Animated shapes - blob
Positioned(
left: -150 +
(math.sin(_backgroundAnimController.value * math.pi) * 50),
bottom: 100 +
(math.cos(_backgroundAnimController.value * math.pi) * 50),
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
width: 350,
height: 350,
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
_introPages[_currentPage].overlayColor.withOpacity(0.2),
),
),
),
// Small decorative elements
for (int i = 0; i < 5; i++)
Positioned(
top: (i * 100) +
(math.sin(
_backgroundAnimController.value * 2 * math.pi + i) *
20),
right: (i % 2 == 0) ? 40 + (i * 30) : null,
left: (i % 2 != 0) ? 40 + (i * 20) : null,
child: Opacity(
opacity: 0.3,
child: Container(
width: 15 + (i * 3),
height: 15 + (i * 3),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.6),
),
),
),
),
],
);
},
);
}
// Widget untuk konten halaman penuh
Widget _buildFullPageContent(IntroData data, Size screenSize) {
return Column(
children: [
// Bagian Header - Occupies 40% of screen
SizedBox(
height: screenSize.height * 0.45,
child: _buildHeaderContent(data),
),
// Bagian konten bawah
Expanded(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, -5),
),
],
),
child: Column(
children: [
const SizedBox(height: 30),
// Indikator halaman
_buildPageIndicator(),
const SizedBox(height: 30),
// Konten deskripsi
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: _buildDescriptionContent(data),
),
),
// Tombol navigasi
_buildNavigationButtons(),
],
),
),
),
],
);
}
// Widget untuk bagian header
Widget _buildHeaderContent(IntroData data) {
return SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo atau branding
Padding(
padding: const EdgeInsets.only(top: 16),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.eco_rounded,
color: Colors.white,
size: 24,
),
),
const SizedBox(width: 12),
const Text(
'Harvest Guard',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
const Spacer(),
// Ilustrasi tengah
AnimatedBuilder(
animation: _contentAnimController,
builder: (context, child) {
// Scale up animation
return Transform.scale(
scale: _contentAnimController.value,
child: Opacity(
opacity: _contentAnimController.value,
child: child,
),
);
},
child: Container(
width: 180,
height: 180,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.3),
shape: BoxShape.circle,
),
child: Icon(
page.icon,
size: 80,
color: page.color,
),
),
);
},
),
const SizedBox(height: 40),
// Title
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 800),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.translate(
offset: Offset(0, 20 * (1 - value)),
child: Text(
page.title,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.black87,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
textAlign: TextAlign.center,
),
),
);
},
),
const SizedBox(height: 20),
// Description
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 1000),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.translate(
offset: Offset(0, 30 * (1 - value)),
child: Text(
page.description,
style: const TextStyle(
fontSize: 16,
color: Colors.black54,
height: 1.5,
child: Icon(
_getIconForImage(data.image),
size: 60,
color: data.bgColor,
),
textAlign: TextAlign.center,
),
),
);
},
),
),
const Spacer(),
],
),
),
);
}
// Widget untuk konten deskripsi
Widget _buildDescriptionContent(IntroData data) {
return AnimatedBuilder(
animation: _contentAnimController,
builder: (context, child) {
// Slide up and fade in animation
return Transform.translate(
offset: Offset(0, 50 * (1 - _contentAnimController.value)),
child: Opacity(
opacity: _contentAnimController.value,
child: child,
),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title
Text(
data.title,
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: data.bgColor,
height: 1.2,
),
),
const SizedBox(height: 20),
// Description
Text(
data.description,
style: TextStyle(
fontSize: 16,
color: Colors.grey.shade700,
height: 1.6,
),
),
const SizedBox(height: 20),
// Feature points - menambahkan beberapa fitur spesifik
...(_getFeaturesForPage(data))
.map((feature) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: data.bgColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
feature.icon,
size: 16,
color: data.bgColor,
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
feature.text,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
),
],
),
))
.toList(),
// Padding di bawah untuk memastikan scroll aman
const SizedBox(height: 20),
],
),
),
);
}
// Widget untuk indikator halaman
Widget _buildPageIndicator() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(_introPages.length, (index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
margin: const EdgeInsets.symmetric(horizontal: 5),
height: 8,
width: _currentPage == index ? 30 : 8,
decoration: BoxDecoration(
color: _currentPage == index
? _introPages[_currentPage].bgColor
: Colors.grey.shade300,
borderRadius: BorderRadius.circular(4),
),
);
}),
);
}
// Widget untuk tombol navigasi
Widget _buildNavigationButtons() {
return Padding(
padding: const EdgeInsets.fromLTRB(30, 20, 30, 40),
child: Row(
children: [
const Spacer(),
// Next/Finish button
AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: _currentPage < _introPages.length - 1 ? 60 : 160,
height: 60,
child: ElevatedButton(
onPressed: () {
if (_currentPage < _introPages.length - 1) {
_pageController.animateToPage(
_currentPage + 1,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
} else {
Get.offNamed(AppRoutes.dashboard);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: _introPages[_currentPage].bgColor,
foregroundColor: Colors.white,
elevation: 2,
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: _currentPage < _introPages.length - 1
? const Icon(Icons.arrow_forward_rounded, size: 24)
: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Mulai Sekarang',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 8),
Icon(Icons.arrow_forward_rounded, size: 20),
],
),
),
),
],
),
);
}
Widget _buildDotIndicator(int index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
margin: const EdgeInsets.symmetric(horizontal: 5),
height: 8,
width: _currentPage == index ? 24 : 8,
decoration: BoxDecoration(
color: _currentPage == index
? _introPages[_currentPage].color
: Colors.grey.shade300,
borderRadius: BorderRadius.circular(4),
),
);
// Helper untuk mendapatkan ikon berdasarkan nama file
IconData _getIconForImage(String imagePath) {
if (imagePath.contains('rice_plant')) {
return Icons.grass_rounded;
} else if (imagePath.contains('camera')) {
return Icons.camera_alt_rounded;
} else if (imagePath.contains('result')) {
return Icons.fact_check_rounded;
}
return Icons.eco_rounded;
}
// Helper untuk mendapatkan fitur spesifik berdasarkan halaman
List<FeatureItem> _getFeaturesForPage(IntroData data) {
if (data.title.contains('Pilih Varietas')) {
return [
FeatureItem(Icons.category_rounded, 'Berbagai varietas padi tersedia'),
FeatureItem(Icons.info_rounded, 'Informasi detail setiap varietas'),
FeatureItem(Icons.bookmark_rounded, 'Simpan favorit untuk akses cepat'),
];
} else if (data.title.contains('Scan')) {
return [
FeatureItem(Icons.autorenew_rounded, 'Deteksi cepat & akurat'),
FeatureItem(Icons.photo_library_rounded, 'Gunakan foto dari galeri'),
FeatureItem(Icons.crop_rounded, 'Crop gambar untuk hasil terbaik'),
];
} else {
return [
FeatureItem(Icons.timeline_rounded, 'Analisis detail penyakit'),
FeatureItem(Icons.healing_rounded, 'Rekomendasi penanganan'),
FeatureItem(Icons.history_rounded, 'Riwayat pemeriksaan'),
];
}
}
}
class IntroPage {
// Model untuk data intro page
class IntroData {
final String title;
final String description;
final IconData icon;
final Color color;
final String image;
final Color bgColor;
final Color overlayColor;
final Color lightColor;
IntroPage({
IntroData({
required this.title,
required this.description,
required this.icon,
required this.color,
required this.image,
required this.bgColor,
required this.overlayColor,
required this.lightColor,
});
}
// Model untuk item fitur
class FeatureItem {
final IconData icon;
final String text;
FeatureItem(this.icon, this.text);
}