TKK_E32230206/lib/features/landing/landing_page.dart

574 lines
20 KiB
Dart

import 'package:flutter/material.dart';
import '../../core/routes.dart';
import '../../core/app_theme.dart';
class LandingPage extends StatefulWidget {
const LandingPage({super.key});
@override
State<LandingPage> createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage> {
final PageController _pageController = PageController();
int _currentPage = 0;
final int _numPages = 4;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _nextPage() {
if (_currentPage < _numPages - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
}
}
void _skipToLast() {
_pageController.animateToPage(
_numPages - 1,
duration: const Duration(milliseconds: 600),
curve: Curves.easeInOut,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
// Top Bar with Logo & Name
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row(
children: [
Hero(
tag: "appLogo",
child: Image.asset(
'assets/images/logo_jamur.png',
height: 40,
width: 40,
fit: BoxFit.contain,
),
),
const SizedBox(width: 10),
const Text(
"JTA Antirogo",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.primary,
),
),
const Spacer(),
if (_currentPage < _numPages - 1)
TextButton(
onPressed: _skipToLast,
child: const Text(
"Lewati",
style: TextStyle(
color: AppTheme.textSecondary,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
// PageView Slider Content
Expanded(
child: PageView(
controller: _pageController,
onPageChanged: (int page) {
setState(() {
_currentPage = page;
});
},
children: [
_buildOnboardingPage(
title: "Monitoring Real-Time",
description: "Pantau suhu dan kelembapan kumbung jamur secara akurat dan real-time kapan saja langsung dari smartphone Anda.",
illustration: _buildMonitoringIllustration(),
),
_buildOnboardingPage(
title: "Kontrol Cerdas",
description: "Pilih Mode Otomatis dengan batas threshold cerdas atau kendalikan penyiram secara manual dengan opsi durasi waktu.",
illustration: _buildControlIllustration(),
),
_buildOnboardingPage(
title: "Penjadwalan Presisi",
description: "Atur jadwal penyiraman otomatis pada hari dan jam tertentu untuk memastikan pertumbuhan jamur tiram yang optimal.",
illustration: _buildSchedulingIllustration(),
),
_buildOnboardingPage(
title: "Hubungkan Alat IoT",
description: "Mudah dan cepat! Cukup masukkan Claim Code perangkat Anda untuk mengaktifkan pemantauan kumbung digital.",
illustration: _buildConnectIllustration(),
),
],
),
),
// Dot Indicators & Action Buttons
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Dot Indicators
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_numPages,
(index) => _buildDot(index),
),
),
const SizedBox(height: 32),
// Actions area based on page index
AnimatedCrossFade(
firstChild: SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: _nextPage,
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Lanjut",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.arrow_forward, color: Colors.white),
],
),
),
),
secondChild: Column(
children: [
// LOGIN BUTTON
SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: () {
Navigator.pushNamed(context, AppRoutes.login);
},
child: const Text(
"Masuk",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
const SizedBox(height: 16),
// REGISTER BUTTON
SizedBox(
width: double.infinity,
height: 55,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: const BorderSide(
color: AppTheme.primary,
width: 1.5,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: () {
Navigator.pushNamed(context, AppRoutes.register);
},
child: const Text(
"Belum ada akun? Daftar dulu",
style: TextStyle(
fontSize: 16,
color: AppTheme.primary,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
crossFadeState: _currentPage < _numPages - 1
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: const Duration(milliseconds: 300),
),
const SizedBox(height: 24),
// Terms and conditions disclaimer
const Text(
"Dengan masuk atau mendaftar, Anda menyetujui\nKetentuan Layanan dan Kebijakan Privasi",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 11,
color: Colors.black45,
),
),
],
),
),
],
),
),
);
}
Widget _buildDot(int index) {
final isActive = _currentPage == index;
return AnimatedContainer(
duration: const Duration(milliseconds: 250),
margin: const EdgeInsets.symmetric(horizontal: 4),
height: 8,
width: isActive ? 24 : 8,
decoration: BoxDecoration(
color: isActive ? AppTheme.primary : AppTheme.textHint.withOpacity(0.5),
borderRadius: BorderRadius.circular(4),
),
);
}
Widget _buildOnboardingPage({
required String title,
required String description,
required Widget illustration,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
flex: 6,
child: Center(
child: SingleChildScrollView(
child: illustration,
),
),
),
const SizedBox(height: 20),
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 12),
Text(
description,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textSecondary,
height: 1.5,
),
),
const Spacer(flex: 1),
],
),
);
}
// ── Illustration Widgets ──────────────────────────────────────
Widget _buildIllustrationContainer({required Widget child}) {
return Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 300, maxHeight: 220),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppTheme.bgPage,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 16,
offset: const Offset(0, 4),
),
],
),
child: child,
);
}
Widget _buildMonitoringIllustration() {
return _buildIllustrationContainer(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
decoration: BoxDecoration(
gradient: AppTheme.suhuGradient,
borderRadius: BorderRadius.circular(16),
),
child: const Column(
children: [
Icon(Icons.thermostat, color: Colors.white, size: 28),
SizedBox(height: 4),
Text("Suhu", style: TextStyle(color: Colors.white70, fontSize: 11)),
Text("27.8°C", style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
],
),
),
),
const SizedBox(width: 12),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
decoration: BoxDecoration(
gradient: AppTheme.humidGradient,
borderRadius: BorderRadius.circular(16),
),
child: const Column(
children: [
Icon(Icons.water_drop, color: Colors.white, size: 28),
SizedBox(height: 4),
Text("Kelembapan", style: TextStyle(color: Colors.white70, fontSize: 11)),
Text("86.5%", style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
],
),
),
),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 10,
height: 10,
decoration: const BoxDecoration(
color: AppTheme.statusNormal,
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
const Text(
"Perangkat Online - Kumbung Optimal",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
],
),
],
),
);
}
Widget _buildControlIllustration() {
return _buildIllustrationContainer(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Operation Mode Card
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
children: [
const Icon(Icons.autorenew, color: AppTheme.primary, size: 24),
const SizedBox(width: 8),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Mode Operasi", style: TextStyle(fontSize: 10, color: AppTheme.textSecondary)),
Text("Otomatis (Aktif)", style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppTheme.textPrimary)),
],
),
),
Switch(value: true, onChanged: (_) {}),
],
),
),
const SizedBox(height: 12),
// Watering Status Card
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppTheme.accentLight,
shape: BoxShape.circle,
),
child: const Icon(Icons.shower, color: AppTheme.accent, size: 22),
),
const SizedBox(width: 10),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Pompa Penyiraman", style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),
Text("Menyala otomatis (30s)", style: TextStyle(fontSize: 10, color: AppTheme.textSecondary)),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppTheme.accent,
borderRadius: BorderRadius.circular(12),
),
child: const Text("ON", style: TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
),
],
),
),
],
),
);
}
Widget _buildSchedulingIllustration() {
return _buildIllustrationContainer(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildScheduleRow("Penyiraman Pagi", "07:30", ["Sen", "Rab", "Jum"], true),
const SizedBox(height: 10),
_buildScheduleRow("Penyiraman Sore", "16:00", ["Setiap Hari"], false),
],
),
);
}
Widget _buildScheduleRow(String title, String time, List<String> days, bool isActive) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
children: [
Icon(Icons.alarm, color: isActive ? AppTheme.primary : AppTheme.textHint, size: 20),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(title, style: const TextStyle(fontSize: 11, color: AppTheme.textSecondary)),
const Spacer(),
Text(time, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppTheme.textPrimary)),
],
),
const SizedBox(height: 4),
Text(
days.join(', '),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
color: isActive ? AppTheme.primaryLight : AppTheme.textHint,
),
),
],
),
),
const SizedBox(width: 8),
Transform.scale(
scale: 0.8,
child: Switch(value: isActive, onChanged: (_) {}),
),
],
),
);
}
Widget _buildConnectIllustration() {
return _buildIllustrationContainer(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.qr_code_scanner, size: 50, color: AppTheme.primary),
const SizedBox(height: 12),
const Text(
"Masukkan Claim Code Alat",
style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppTheme.textPrimary),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppTheme.primary.withOpacity(0.3), width: 1.5),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"JTA - X 9 7 A B 3",
style: TextStyle(
fontFamily: 'Courier',
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppTheme.primary.withOpacity(0.8),
letterSpacing: 2,
),
),
const SizedBox(width: 8),
const Icon(Icons.check_circle, color: AppTheme.statusNormal, size: 16),
],
),
),
],
),
);
}
}