404 lines
14 KiB
Dart
404 lines
14 KiB
Dart
import 'dart:typed_data';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:pdf/pdf.dart';
|
|
import 'package:pdf/widgets.dart' as pw;
|
|
import 'package:printing/printing.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import '../constants/app_colors.dart';
|
|
|
|
class ImageResultScreen extends StatelessWidget {
|
|
const ImageResultScreen({super.key});
|
|
|
|
// ============================================================
|
|
// GENERATE PDF
|
|
// ============================================================
|
|
|
|
Future<void> _exportPDF(
|
|
BuildContext context, Map<String, dynamic> args) async {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (_) => const Center(
|
|
child: CircularProgressIndicator(color: AppColors.primary),
|
|
),
|
|
);
|
|
|
|
try {
|
|
final pdf = pw.Document();
|
|
|
|
pw.MemoryImage? fotoImage;
|
|
final urlFoto = args['url_foto']?.toString() ?? '';
|
|
if (urlFoto.isNotEmpty) {
|
|
try {
|
|
final response = await http.get(Uri.parse(urlFoto));
|
|
if (response.statusCode == 200) {
|
|
fotoImage = pw.MemoryImage(response.bodyBytes);
|
|
}
|
|
} catch (_) {
|
|
// Foto gagal didownload, lanjut tanpa foto
|
|
}
|
|
}
|
|
|
|
pdf.addPage(
|
|
pw.Page(
|
|
pageFormat: PdfPageFormat.a4,
|
|
margin: const pw.EdgeInsets.all(32),
|
|
build: (pw.Context context) {
|
|
return pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
pw.Container(
|
|
width: double.infinity,
|
|
padding: const pw.EdgeInsets.all(16),
|
|
decoration: pw.BoxDecoration(
|
|
color: PdfColors.green800,
|
|
borderRadius: pw.BorderRadius.circular(8),
|
|
),
|
|
child: pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
pw.Text(
|
|
'Dataset Pengeringan Biji Kopi',
|
|
style: pw.TextStyle(
|
|
color: PdfColors.white,
|
|
fontSize: 18,
|
|
fontWeight: pw.FontWeight.bold,
|
|
),
|
|
),
|
|
pw.SizedBox(height: 4),
|
|
pw.Text(
|
|
'Sistem Monitoring Otomatis',
|
|
style: const pw.TextStyle(
|
|
color: PdfColors.white,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
pw.SizedBox(height: 20),
|
|
|
|
pw.Text(
|
|
'Tanggal & Waktu: ${args['date']} - ${args['time']}',
|
|
style: pw.TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: pw.FontWeight.bold,
|
|
),
|
|
),
|
|
|
|
pw.SizedBox(height: 16),
|
|
pw.Divider(),
|
|
pw.SizedBox(height: 16),
|
|
|
|
if (fotoImage != null) ...[
|
|
pw.Center(
|
|
child: pw.Container(
|
|
height: 180,
|
|
width: double.infinity,
|
|
decoration: pw.BoxDecoration(
|
|
borderRadius: pw.BorderRadius.circular(8),
|
|
border: pw.Border.all(color: PdfColors.grey300),
|
|
),
|
|
child: pw.ClipRRect(
|
|
horizontalRadius: 8,
|
|
verticalRadius: 8,
|
|
child: pw.Image(
|
|
fotoImage,
|
|
fit: pw.BoxFit.contain,
|
|
height: 180,
|
|
width: double.infinity,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
pw.SizedBox(height: 16),
|
|
pw.Divider(),
|
|
pw.SizedBox(height: 16),
|
|
],
|
|
|
|
pw.Text(
|
|
'Data Sensor',
|
|
style: pw.TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: pw.FontWeight.bold,
|
|
),
|
|
),
|
|
pw.SizedBox(height: 10),
|
|
|
|
pw.Table(
|
|
border: pw.TableBorder.all(color: PdfColors.grey300),
|
|
columnWidths: {
|
|
0: const pw.FlexColumnWidth(1),
|
|
1: const pw.FlexColumnWidth(2),
|
|
},
|
|
children: [
|
|
_pdfRow('Suhu', args['temp']),
|
|
_pdfRow('Kelembapan', args['humidity']),
|
|
_pdfRow('Intensitas Cahaya', args['light']),
|
|
_pdfRow('Fan Intake (Kipas 1)', args['intake']),
|
|
_pdfRow('Fan Exhaust (Kipas 2)', args['exhaust']),
|
|
],
|
|
),
|
|
|
|
pw.SizedBox(height: 24),
|
|
pw.Divider(),
|
|
pw.SizedBox(height: 8),
|
|
|
|
pw.Text(
|
|
'Diekspor dari Aplikasi Monitoring Kopi',
|
|
style: const pw.TextStyle(
|
|
color: PdfColors.grey,
|
|
fontSize: 10,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
if (context.mounted) Navigator.pop(context);
|
|
|
|
await Printing.layoutPdf(
|
|
onLayout: (PdfPageFormat format) async => pdf.save(),
|
|
name:
|
|
'dataset_kopi_${args['date']?.replaceAll('/', '-')}_${args['time']?.replaceAll(':', '-')}.pdf',
|
|
);
|
|
} catch (e) {
|
|
if (context.mounted) {
|
|
Navigator.pop(context);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Gagal membuat PDF: $e'),
|
|
backgroundColor: AppColors.error,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
margin: const EdgeInsets.all(16),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pw.TableRow _pdfRow(String label, String? value) {
|
|
return pw.TableRow(
|
|
children: [
|
|
pw.Padding(
|
|
padding: const pw.EdgeInsets.all(8),
|
|
child: pw.Text(
|
|
label,
|
|
style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 11),
|
|
),
|
|
),
|
|
pw.Padding(
|
|
padding: const pw.EdgeInsets.all(8),
|
|
child: pw.Text(value ?? '-', style: const pw.TextStyle(fontSize: 11)),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// BUILD UI
|
|
// ============================================================
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final args = ModalRoute.of(context)?.settings.arguments
|
|
as Map<String, dynamic>? ??
|
|
{
|
|
'id': '1',
|
|
'date': '2025/11/18',
|
|
'time': '14:35:00',
|
|
'temp': '45.5 °C',
|
|
'humidity': '52 %',
|
|
'light': '200 Lux',
|
|
'intake': 'ON',
|
|
'exhaust': 'ON',
|
|
'url_foto': '',
|
|
};
|
|
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
title: const Text(
|
|
'Detail Data Citra',
|
|
style: TextStyle(color: AppColors.textMain),
|
|
),
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: AppColors.primary),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: Column(
|
|
children: [
|
|
// ── Foto ──
|
|
Container(
|
|
height: 250,
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.cardBg,
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
|
),
|
|
clipBehavior: Clip.hardEdge,
|
|
child: args['url_foto'] != null &&
|
|
args['url_foto'].toString().isNotEmpty
|
|
? Image.network(
|
|
args['url_foto'],
|
|
fit: BoxFit.cover,
|
|
loadingBuilder: (context, child, progress) {
|
|
if (progress == null) return child;
|
|
return const Center(
|
|
child: CircularProgressIndicator(color: AppColors.primary),
|
|
);
|
|
},
|
|
errorBuilder: (context, error, stack) => Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(Icons.broken_image,
|
|
size: 80, color: AppColors.textSecondary),
|
|
Text('Citra #${args['id']}',
|
|
style: const TextStyle(color: AppColors.textSecondary)),
|
|
],
|
|
),
|
|
)
|
|
: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(Icons.image,
|
|
size: 80, color: AppColors.textSecondary),
|
|
Text('Citra #${args['id']}',
|
|
style: const TextStyle(color: AppColors.textSecondary)),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// ── Data Card ──
|
|
Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.cardBg,
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(Icons.calendar_today,
|
|
color: AppColors.primary, size: 16),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'${args['date']} - ${args['time']}',
|
|
style: const TextStyle(
|
|
color: AppColors.primary,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16),
|
|
),
|
|
],
|
|
),
|
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3), height: 30),
|
|
|
|
_buildDataRow(
|
|
'Suhu', args['temp'], 'Kelembaban', args['humidity']),
|
|
const SizedBox(height: 15),
|
|
|
|
// --- BERUBAH: Mengirimkan string kosong ('') untuk menghapus kolom Mode ---
|
|
_buildDataRow(
|
|
'Cahaya', args['light'], '', ''),
|
|
|
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3), height: 30),
|
|
|
|
_buildDataRow(
|
|
'Kipas 1 (Intake)', args['intake'],
|
|
'Kipas 2 (Exhaust)', args['exhaust'],
|
|
valueColor: AppColors.primary,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Spacer(),
|
|
|
|
// ── Tombol Export PDF ──
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 55,
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _exportPDF(context, args),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.primary,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
),
|
|
),
|
|
icon: const Icon(Icons.picture_as_pdf, color: Colors.white),
|
|
label: const Text(
|
|
'Export Data Lengkap (.PDF)',
|
|
style: TextStyle(
|
|
color: Colors.white, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// --- BERUBAH: Menambahkan logika untuk menyembunyikan kolom kanan jika string kosong ---
|
|
Widget _buildDataRow(
|
|
String label1,
|
|
String value1,
|
|
String label2,
|
|
String value2, {
|
|
Color? valueColor,
|
|
}) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label1,
|
|
style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)),
|
|
const SizedBox(height: 4),
|
|
Text(': $value1',
|
|
style: TextStyle(
|
|
color: valueColor ?? AppColors.textMain,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 15)),
|
|
],
|
|
),
|
|
),
|
|
Expanded(
|
|
child: label2.isEmpty
|
|
? const SizedBox.shrink() // Jika kosong, sembunyikan sepenuhnya
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label2,
|
|
style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)),
|
|
const SizedBox(height: 4),
|
|
Text(': $value2',
|
|
style: TextStyle(
|
|
color: valueColor ?? AppColors.textMain,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 15)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |