TKK_E32222428/lib/screens/settings_screen.dart

477 lines
18 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:excel/excel.dart' hide Border;
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';
import '../services/supabase_service.dart';
import '../services/mqtt_service.dart';
import 'package:mqtt_client/mqtt_client.dart';
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
String _namaLengkap = '';
bool _isLoadingProfile = true;
bool _isExporting = false;
@override
void initState() {
super.initState();
_loadProfile();
}
Future<void> _loadProfile() async {
try {
final profile = await SupabaseService().getProfile();
if (mounted) {
setState(() {
_namaLengkap = profile?['nama_lengkap'] ?? '';
_isLoadingProfile = false;
});
}
} catch (e) {
if (mounted) setState(() => _isLoadingProfile = false);
}
}
// ── Export foto_dataset ke Excel (Warna Excel Tetap Dipertahankan) ──
Future<void> _exportExcel() async {
setState(() => _isExporting = true);
try {
// Ambil semua data dari foto_dataset
final data = await SupabaseService().getFotoDataset();
if (data.isEmpty) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Belum ada data dataset untuk diexport!'),
// --- BERUBAH: Warna Notifikasi Peringatan ---
backgroundColor: AppColors.warning,
),
);
}
return;
}
// Buat file Excel
final excel = Excel.createExcel();
final sheet = excel['Dataset Kopi'];
// Header kolom
final headers = [
'No',
'Timestamp',
'Suhu (°C)',
'Kelembapan (%)',
'Intensitas Cahaya (Lux)',
'Kipas 1 (Intake)',
'Kipas 2 (Exhaust)',
];
// Style header (Dibiarkan tetap hijau standar Excel)
for (int i = 0; i < headers.length; i++) {
final cell = sheet.cell(
CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0));
cell.value = TextCellValue(headers[i]);
cell.cellStyle = CellStyle(
bold: true,
backgroundColorHex: ExcelColor.fromHexString('#16A34A'),
fontColorHex: ExcelColor.fromHexString('#FFFFFF'),
);
}
// Isi data — tanpa URL Foto
for (int i = 0; i < data.length; i++) {
final row = data[i];
final rowIndex = i + 1;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: rowIndex))
.value = IntCellValue(i + 1);
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: rowIndex))
.value = TextCellValue(row['timestamp']?.toString() ?? '-');
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: rowIndex))
.value = DoubleCellValue(double.tryParse(row['suhu'].toString()) ?? 0);
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: rowIndex))
.value = DoubleCellValue(double.tryParse(row['kelembapan'].toString()) ?? 0);
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 4, rowIndex: rowIndex))
.value = DoubleCellValue(double.tryParse(row['intensitas'].toString()) ?? 0);
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: rowIndex))
.value = TextCellValue(row['kipas1']?.toString() ?? '-');
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 6, rowIndex: rowIndex))
.value = TextCellValue(row['kipas2']?.toString() ?? '-');
}
// Set lebar kolom — tanpa kolom URL
sheet.setColumnWidth(0, 5);
sheet.setColumnWidth(1, 22);
sheet.setColumnWidth(2, 12);
sheet.setColumnWidth(3, 16);
sheet.setColumnWidth(4, 24);
sheet.setColumnWidth(5, 18);
sheet.setColumnWidth(6, 18);
// Simpan file
final dir = await getApplicationDocumentsDirectory();
final tanggal = DateTime.now()
.toString()
.substring(0, 10)
.replaceAll('-', '');
final fileName = 'dataset_kopi_$tanggal.xlsx';
final filePath = '${dir.path}/$fileName';
final fileBytes = excel.save();
if (fileBytes == null) throw Exception('Gagal generate file Excel');
final file = File(filePath);
await file.writeAsBytes(fileBytes);
// Share file
await OpenFile.open(filePath);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'✓ Export berhasil — ${data.length} data tersimpan'),
// --- BERUBAH: Warna Notifikasi Sukses ---
backgroundColor: AppColors.success,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal export: $e'),
// --- BERUBAH: Warna Notifikasi Error ---
backgroundColor: AppColors.error,
),
);
}
} finally {
if (mounted) setState(() => _isExporting = false);
}
}
@override
Widget build(BuildContext context) {
final mqtt = Provider.of<MqttService>(context);
final user = SupabaseService().currentUser;
return Scaffold(
// --- BERUBAH: Background UI Utama ---
backgroundColor: AppColors.background,
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── Profile Section ──
Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
// --- BERUBAH: Background Kartu Profil ---
color: AppColors.cardBg,
borderRadius: BorderRadius.circular(20),
// --- BERUBAH: Garis pinggir halus ---
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
),
child: Row(
children: [
const CircleAvatar(
radius: 30,
// --- BERUBAH: Warna Avatar ---
backgroundColor: AppColors.primary,
child: Icon(Icons.person,
color: Colors.white, size: 40),
),
const SizedBox(width: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_isLoadingProfile
? const SizedBox(
width: 100,
height: 16,
child: LinearProgressIndicator(
// --- BERUBAH: Warna Loading ---
color: AppColors.primary,
backgroundColor: AppColors.background,
),
)
: Text(
_namaLengkap.isNotEmpty
? _namaLengkap
: 'Admin Kopi',
style: const TextStyle(
// --- BERUBAH: Warna Nama Profil ---
color: AppColors.textMain,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
user?.email ?? 'admin@coffee.io',
style: const TextStyle(
// --- BERUBAH: Warna Email Profil ---
color: AppColors.textSecondary,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
const Text(
'Status: Active User',
style: TextStyle(
// --- BERUBAH: Warna Status Profil ---
color: AppColors.success,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
],
),
),
const SizedBox(height: 30),
// ── Sistem & Koneksi ──
_buildSectionTitle('Sistem & Koneksi'),
_buildSettingsCard([
_buildConnectionStatus(
'Database Supabase', true, Icons.storage),
// --- BERUBAH: Warna Garis Pemisah Menu ---
Divider(color: AppColors.textSecondary.withValues(alpha: 0.2), height: 1),
_buildConnectionStatus(
'MQTT Broker (EMQX)',
mqtt.client?.connectionStatus?.state ==
MqttConnectionState.connected,
Icons.cloud_sync,
),
]),
const SizedBox(height: 20),
// ── Manajemen Data ──
_buildSectionTitle('Manajemen Data'),
_buildSettingsCard([
_isExporting
? const ListTile(
leading: SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(
// --- BERUBAH: Warna Loading ---
color: AppColors.primary, strokeWidth: 2),
),
title: Text(
'Menyiapkan file Excel...',
style: TextStyle(
// --- BERUBAH: Warna Teks Loading ---
color: AppColors.textSecondary, fontSize: 14),
),
)
: _buildSettingItem(
Icons.table_chart,
'Export Data Dataset (.xlsx)',
_exportExcel,
),
]),
const SizedBox(height: 20),
// ── Keamanan ──
_buildSectionTitle('Keamanan'),
_buildSettingsCard([
_buildSettingItem(
Icons.lock_reset, 'Ganti Password Akun', () {
Navigator.pushNamed(
context,
'/change_password',
arguments: {'dari_reset': false},
);
}),
]),
const SizedBox(height: 30),
// ── Tombol Keluar ──
SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton.icon(
onPressed: () async {
final konfirmasi = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
// --- BERUBAH: Latar Belakang Dialog ---
backgroundColor: AppColors.cardBg,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: const Row(
children: [
Icon(Icons.logout, color: AppColors.error),
SizedBox(width: 10),
Text('Keluar dari Akun',
// --- BERUBAH: Warna Judul Dialog ---
style: TextStyle(color: AppColors.textMain, fontSize: 18)),
],
),
content: const Text(
'Apakah kamu yakin ingin keluar dari akun ini?',
// --- BERUBAH: Warna Teks Konten Dialog ---
style: TextStyle(color: AppColors.textSecondary),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Batal',
// --- BERUBAH: Warna Tombol Batal ---
style: TextStyle(color: AppColors.primary)),
),
ElevatedButton(
onPressed: () => Navigator.pop(ctx, true),
style: ElevatedButton.styleFrom(
// --- BERUBAH: Warna Tombol Keluar di Dialog ---
backgroundColor: AppColors.error,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
child: const Text('Keluar',
style: TextStyle(color: Colors.white)),
),
],
),
);
if (konfirmasi == true) {
await SupabaseService().signOut();
if (context.mounted) {
Navigator.pushReplacementNamed(context, '/login');
}
}
},
style: ElevatedButton.styleFrom(
// --- BERUBAH: Warna Latar Tombol Keluar ---
backgroundColor: AppColors.error.withValues(alpha: 0.8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
icon: const Icon(Icons.logout, color: Colors.white), // Tetap putih
label: const Text(
'Keluar dari Akun',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white), // Tetap putih
),
),
),
const SizedBox(height: 100),
],
),
),
),
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 10, left: 5),
child: Text(
title,
style: const TextStyle(
// --- BERUBAH: Warna Judul Seksi/Kategori ---
color: AppColors.textSecondary,
fontSize: 13,
fontWeight: FontWeight.bold),
),
);
}
Widget _buildSettingsCard(List<Widget> children) {
return Container(
decoration: BoxDecoration(
// --- BERUBAH: Latar Belakang Kartu Pengaturan ---
color: AppColors.cardBg,
borderRadius: BorderRadius.circular(20),
// --- BERUBAH: Garis pinggir halus ---
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
),
child: Column(children: children),
);
}
Widget _buildSettingItem(IconData icon, String title, VoidCallback? onTap,
{String? trailing}) {
return ListTile(
// --- BERUBAH: Warna Ikon Leading ---
leading: Icon(icon, color: AppColors.primary, size: 22),
title: Text(title,
// --- BERUBAH: Warna Teks Menu ---
style: const TextStyle(color: AppColors.textMain, fontSize: 14)),
trailing: trailing != null
? Text(trailing,
// --- BERUBAH: Warna Teks Trailing ---
style: const TextStyle(color: AppColors.textSecondary, fontSize: 12))
// --- BERUBAH: Warna Ikon Panah Kanan ---
: const Icon(Icons.chevron_right, color: AppColors.textSecondary, size: 18),
onTap: onTap,
);
}
Widget _buildConnectionStatus(
String title, bool isConnected, IconData icon) {
return ListTile(
// --- BERUBAH: Warna Ikon Status Database/MQTT ---
leading: Icon(icon,
color: isConnected ? AppColors.info : AppColors.error, size: 22),
title: Text(title,
// --- BERUBAH: Warna Teks Nama Layanan ---
style: const TextStyle(color: AppColors.textMain, fontSize: 14)),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
// --- BERUBAH: Warna Titik Indikator ---
color: isConnected ? AppColors.success : AppColors.error,
),
),
const SizedBox(width: 8),
Text(
isConnected ? 'Online' : 'Offline',
style: TextStyle(
// --- BERUBAH: Warna Teks Online/Offline ---
color: isConnected ? AppColors.success : AppColors.error,
fontSize: 12,
fontWeight: FontWeight.bold),
),
],
),
);
}
}