import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:excel/excel.dart'; 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'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { String _namaLengkap = ''; bool _isLoadingProfile = true; bool _isExporting = false; @override void initState() { super.initState(); _loadProfile(); } Future _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 ── Future _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( const SnackBar( content: Text('Belum ada data dataset untuk diexport!'), backgroundColor: Colors.orange, ), ); } 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 (Exhaust)', 'Kipas 2 (Intake)', 'URL Foto', ]; // Style header 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 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() ?? '-'); sheet .cell(CellIndex.indexByColumnRow( columnIndex: 7, rowIndex: rowIndex)) .value = TextCellValue(row['url_foto']?.toString() ?? '-'); } // Set lebar kolom 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); sheet.setColumnWidth(7, 50); // 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'), backgroundColor: Colors.green, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal export: $e'), backgroundColor: Colors.red, ), ); } } finally { if (mounted) setState(() => _isExporting = false); } } @override Widget build(BuildContext context) { final mqtt = Provider.of(context); final user = SupabaseService().currentUser; return Scaffold( backgroundColor: Colors.black, 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( color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(20), ), child: Row( children: [ const CircleAvatar( radius: 30, backgroundColor: Colors.green, 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( color: Colors.green, backgroundColor: Colors.grey, ), ) : Text( _namaLengkap.isNotEmpty ? _namaLengkap : 'Admin Kopi', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( user?.email ?? 'admin@coffee.io', style: const TextStyle( color: Colors.grey, fontSize: 12), ), const Text( 'Status: Active User', style: TextStyle( color: Colors.green, fontSize: 12), ), ], ), ], ), ), const SizedBox(height: 30), // ── Sistem & Koneksi ── _buildSectionTitle('Sistem & Koneksi'), _buildSettingsCard([ _buildConnectionStatus( 'Database Supabase', true, Icons.storage), const Divider(color: Colors.grey, 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( color: Colors.green, strokeWidth: 2), ), title: Text( 'Menyiapkan file Excel...', style: TextStyle( color: Colors.grey, fontSize: 14), ), ) : _buildSettingItem( Icons.table_chart, 'Export Data Dataset (.xlsx)', _exportExcel, ), ]), const SizedBox(height: 20), // ── Keamanan ── _buildSectionTitle('Keamanan'), _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 { await SupabaseService().signOut(); if (context.mounted) { Navigator.pushReplacementNamed(context, '/login'); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.redAccent.withOpacity(0.8), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), ), icon: const Icon(Icons.logout, color: Colors.white), label: const Text( 'Keluar dari Akun', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white), ), ), ), const SizedBox(height: 100), ], ), ), ), ), ); } Widget _buildSectionTitle(String title) { return Padding( padding: const EdgeInsets.only(bottom: 10, left: 5), child: Text( title, style: const TextStyle( color: Colors.grey, fontSize: 13, fontWeight: FontWeight.bold), ), ); } Widget _buildSettingsCard(List children) { return Container( decoration: BoxDecoration( color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(20), ), child: Column(children: children), ); } Widget _buildSettingItem(IconData icon, String title, VoidCallback? onTap, {String? trailing}) { return ListTile( leading: Icon(icon, color: Colors.green, size: 22), title: Text(title, style: const TextStyle(color: Colors.white, fontSize: 14)), trailing: trailing != null ? Text(trailing, style: const TextStyle(color: Colors.grey, fontSize: 12)) : const Icon(Icons.chevron_right, color: Colors.grey, size: 18), onTap: onTap, ); } Widget _buildConnectionStatus( String title, bool isConnected, IconData icon) { return ListTile( leading: Icon(icon, color: isConnected ? Colors.blue : Colors.red, size: 22), title: Text(title, style: const TextStyle(color: Colors.white, fontSize: 14)), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, color: isConnected ? Colors.green : Colors.red, ), ), const SizedBox(width: 8), Text( isConnected ? 'Online' : 'Offline', style: TextStyle( color: isConnected ? Colors.green : Colors.red, fontSize: 12, fontWeight: FontWeight.bold), ), ], ), ); } }