TKK_E32220332/lib/screens/settings_screen.dart

420 lines
14 KiB
Dart

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<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 ──
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(
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<MqttService>(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<Widget> 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),
),
],
),
);
}
}