TKK_E32230206/lib/features/iot/widgets/user_guide_sheet.dart

362 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import '../../../core/app_theme.dart';
class UserGuideSheet extends StatefulWidget {
const UserGuideSheet({super.key});
static void show(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => const UserGuideSheet(),
);
}
@override
State<UserGuideSheet> createState() => _UserGuideSheetState();
}
class _UserGuideSheetState extends State<UserGuideSheet> {
final PageController _pageController = PageController();
int _currentStep = 0;
final int _totalSteps = 4;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _next() {
if (_currentStep < _totalSteps - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
} else {
Navigator.pop(context);
}
}
void _previous() {
if (_currentStep > 0) {
_pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
}
@override
Widget build(BuildContext context) {
final bottomPadding = MediaQuery.of(context).viewInsets.bottom;
return Container(
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 50),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
padding: EdgeInsets.only(bottom: bottomPadding),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Drag Handle bar
const SizedBox(height: 12),
Container(
width: 40,
height: 5,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 16),
// Header
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
const Icon(
Icons.menu_book,
color: AppTheme.primary,
size: 24,
),
const SizedBox(width: 8),
const Text(
"Panduan Penggunaan",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const Spacer(),
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.close, color: AppTheme.textSecondary),
),
],
),
),
const Divider(height: 1),
// Step Stepper Indicator Row
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(_totalSteps, (index) {
final isDone = index < _currentStep;
final isCurrent = index == _currentStep;
return Row(
children: [
// Step Dot
AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: 28,
height: 28,
decoration: BoxDecoration(
color: isDone
? AppTheme.statusNormal
: (isCurrent ? AppTheme.primary : Colors.grey.shade200),
shape: BoxShape.circle,
border: isCurrent
? Border.all(color: AppTheme.primaryDark, width: 1)
: null,
),
child: Center(
child: isDone
? const Icon(Icons.check, color: Colors.white, size: 16)
: Text(
"${index + 1}",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: isCurrent ? Colors.white : AppTheme.textSecondary,
),
),
),
),
// Line connector
if (index < _totalSteps - 1)
AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: MediaQuery.of(context).size.width * 0.12,
height: 3,
color: isDone ? AppTheme.statusNormal : Colors.grey.shade200,
),
],
);
}),
),
),
// Sliding step contents
SizedBox(
height: 340,
child: PageView(
controller: _pageController,
onPageChanged: (step) {
setState(() {
_currentStep = step;
});
},
children: [
_buildStepContent(
icon: Icons.developer_board,
title: "1. Persiapan Alat ESP32",
description: "Nyalakan dan sambungkan alat fisik ke jaringan internet Anda.",
points: [
"Hubungkan ESP32 ke adaptor daya micro-USB 5V.",
"Lampu indikator merah pada alat akan menyala saat aktif.",
"Hubungkan ESP32 ke jaringan WiFi kumbung agar alat dapat mengirim data ke internet secara real-time.",
],
),
_buildStepContent(
icon: Icons.qr_code_scanner,
title: "2. Hubungkan Alat ke Aplikasi",
description: "Gunakan Claim Code perangkat untuk sinkronisasi dengan akun Anda.",
points: [
"Cari 'Claim Code' pada stiker casing luar alat atau layar LCD (contoh: JTA-X97AB3).",
"Di halaman Monitoring, ketuk tombol tambah (+) pada kartu 'Belum Ada Alat Terhubung'.",
"Ketikkan Claim Code perangkat Anda, lalu ketuk 'Hubungkan'. Perangkat Anda akan terikat secara permanen ke akun Anda.",
],
),
_buildStepContent(
icon: Icons.analytics_outlined,
title: "3. Memantau Kondisi Kumbung",
description: "Pahami indikator suhu & kelembapan jamur tiram Anda.",
points: [
"Setelah terhubung, nilai Suhu (°C) dan Kelembapan (%) real-time akan terus diperbarui.",
"Status warna kartu mempermudah monitoring: Hijau (Optimal), Oranye (Perhatian/Warning), Merah (Kritis/Bahaya).",
"Gunakan filter rentang waktu (Hari, Minggu, Bulan) di bawah grafik untuk memantau tren perkembangan lingkungan kumbung.",
],
),
_buildStepContent(
icon: Icons.tune,
title: "4. Mengatur Kontrol & Jadwal",
description: "Gunakan mode penyiraman otomatis atau manual sesuai kebutuhan.",
points: [
"Buka tab 'Control' di bagian bawah aplikasi untuk manajemen penyiraman.",
"Mode Otomatis: Pompa menyala otomatis ketika suhu melewati batas maksimal atau kelembapan di bawah batas minimal.",
"Mode Manual: Ketuk tombol pompa, pilih durasi waktu siram (15s s/d 5m) dan nyalakan.",
"Jadwal: Klik '+ Tambah Jadwal' untuk menentukan penyiraman terjadwal rutin secara otomatis.",
],
),
],
),
),
const Divider(height: 1),
// Actions Area
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Back Button
Opacity(
opacity: _currentStep > 0 ? 1.0 : 0.0,
child: TextButton(
onPressed: _currentStep > 0 ? _previous : null,
child: const Row(
children: [
Icon(Icons.arrow_back, size: 18),
SizedBox(width: 4),
Text(
"Kembali",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
// Next / Finish Button
SizedBox(
height: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
padding: const EdgeInsets.symmetric(horizontal: 24),
),
onPressed: _next,
child: Row(
children: [
Text(
_currentStep == _totalSteps - 1 ? "Selesai" : "Lanjut",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
if (_currentStep < _totalSteps - 1) ...[
const SizedBox(width: 4),
const Icon(Icons.arrow_forward, size: 18, color: Colors.white),
],
],
),
),
),
],
),
),
const SizedBox(height: 10),
],
),
);
}
Widget _buildStepContent({
required IconData icon,
required String title,
required String description,
required List<String> points,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: AppTheme.accentLight,
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: AppTheme.primary, size: 30),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 2),
Text(
description,
style: const TextStyle(
fontSize: 13,
color: AppTheme.textSecondary,
),
),
],
),
),
],
),
const SizedBox(height: 20),
...points.map((point) => Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 4),
width: 6,
height: 6,
decoration: const BoxDecoration(
color: AppTheme.primary,
shape: BoxShape.circle,
),
),
const SizedBox(width: 10),
Expanded(
child: Text(
point,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textPrimary,
height: 1.45,
),
),
),
],
),
)),
],
),
);
}
}