joss banget anjing
This commit is contained in:
parent
4b01a7e41b
commit
a08bb1e710
|
|
@ -6,12 +6,14 @@ import 'package:klimatologiot/app.dart';
|
||||||
import 'package:user_repository/user_repository.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
|
await initializeDateFormatting('id_ID', null);
|
||||||
Bloc.observer = SimpleBlocObserver();
|
Bloc.observer = SimpleBlocObserver();
|
||||||
runApp(MyApp(
|
runApp(MyApp(
|
||||||
FirebaseUserRepo(),
|
FirebaseUserRepo(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
import 'package:pdf/pdf.dart';
|
||||||
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
|
import 'package:printing/printing.dart';
|
||||||
|
import 'pdf_components.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// Atmospheric PDF Builder
|
||||||
|
/// Letakkan di: lib/screens/monitoring/shared/utils/pdf/atmospheric_pdf_builder.dart
|
||||||
|
/// ============================================================
|
||||||
|
|
||||||
|
Future<void> exportAtmosphericPdf({
|
||||||
|
required double temperature,
|
||||||
|
required double humidity,
|
||||||
|
required double pressure,
|
||||||
|
required double altitude,
|
||||||
|
required DateTime timestamp,
|
||||||
|
List<Map<String, dynamic>>? historyData,
|
||||||
|
}) async {
|
||||||
|
final pdf = pw.Document();
|
||||||
|
|
||||||
|
pdf.addPage(
|
||||||
|
pw.MultiPage(
|
||||||
|
pageFormat: PdfPageFormat.a4,
|
||||||
|
margin: const pw.EdgeInsets.all(32),
|
||||||
|
header: (ctx) => buildPdfHeader('Laporan Kondisi Atmosfer', timestamp),
|
||||||
|
footer: (ctx) => buildPdfFooter(ctx),
|
||||||
|
build: (ctx) => [
|
||||||
|
// — Summary card
|
||||||
|
buildSummaryCard(
|
||||||
|
title: 'Data Terkini',
|
||||||
|
subtitle: pdfFullFormat.format(timestamp),
|
||||||
|
items: [
|
||||||
|
SummaryItem('Suhu', '${temperature.toStringAsFixed(1)} °C', '🌡️'),
|
||||||
|
SummaryItem('Kelembapan', '${humidity.toStringAsFixed(1)} %', '💧'),
|
||||||
|
SummaryItem('Tekanan', '${pressure.toStringAsFixed(1)} hPa', '🔵'),
|
||||||
|
SummaryItem('Ketinggian', '${altitude.toStringAsFixed(1)} m', '⛰️'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 24),
|
||||||
|
|
||||||
|
// — Tabel key-value ringkasan
|
||||||
|
buildSectionTitle('Detail Parameter'),
|
||||||
|
pw.SizedBox(height: 8),
|
||||||
|
buildKeyValueTable([
|
||||||
|
['Parameter', 'Nilai', 'Satuan'],
|
||||||
|
['Suhu', temperature.toStringAsFixed(1), '°C'],
|
||||||
|
['Kelembapan', humidity.toStringAsFixed(1), '%'],
|
||||||
|
['Tekanan Udara', pressure.toStringAsFixed(1), 'hPa'],
|
||||||
|
['Ketinggian', altitude.toStringAsFixed(1), 'm'],
|
||||||
|
]),
|
||||||
|
|
||||||
|
// — Tabel history jika ada
|
||||||
|
if (historyData != null && historyData.isNotEmpty) ...[
|
||||||
|
pw.SizedBox(height: 24),
|
||||||
|
buildSectionTitle('Riwayat Data'),
|
||||||
|
pw.SizedBox(height: 8),
|
||||||
|
_buildHistoryTable(historyData),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Printing.layoutPdf(onLayout: (_) => pdf.save());
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.Widget _buildHistoryTable(List<Map<String, dynamic>> data) {
|
||||||
|
return buildDataTable(
|
||||||
|
headers: ['No', 'Waktu', 'Suhu (°C)', 'Kelembapan (%)', 'Tekanan (hPa)', 'Ketinggian (m)'],
|
||||||
|
rows: data.asMap().entries.map((e) {
|
||||||
|
final d = e.value;
|
||||||
|
final ts = d['timestamp'] as DateTime?;
|
||||||
|
return [
|
||||||
|
'${e.key + 1}',
|
||||||
|
ts != null ? pdfFullFormat.format(ts) : '-',
|
||||||
|
(d['temperature'] as double? ?? 0).toStringAsFixed(1),
|
||||||
|
(d['humidity'] as double? ?? 0).toStringAsFixed(1),
|
||||||
|
(d['pressure'] as double? ?? 0).toStringAsFixed(1),
|
||||||
|
(d['altitude'] as double? ?? 0).toStringAsFixed(1),
|
||||||
|
];
|
||||||
|
}).toList(),
|
||||||
|
colWidths: {
|
||||||
|
0: const pw.FixedColumnWidth(25),
|
||||||
|
1: const pw.FlexColumnWidth(2),
|
||||||
|
2: const pw.FlexColumnWidth(1),
|
||||||
|
3: const pw.FlexColumnWidth(1.2),
|
||||||
|
4: const pw.FlexColumnWidth(1.2),
|
||||||
|
5: const pw.FlexColumnWidth(1.2),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import 'package:pdf/pdf.dart';
|
||||||
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
|
import 'package:printing/printing.dart';
|
||||||
|
import 'pdf_components.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// Evaporasi PDF Builder
|
||||||
|
/// Letakkan di: lib/screens/monitoring/shared/utils/pdf/evaporasi_pdf_builder.dart
|
||||||
|
/// ============================================================
|
||||||
|
|
||||||
|
Future<void> exportEvaporasiPdf({
|
||||||
|
required double evaporasi,
|
||||||
|
required double suhu,
|
||||||
|
required double tinggiAir,
|
||||||
|
required DateTime timestamp,
|
||||||
|
List<Map<String, dynamic>>? historyData,
|
||||||
|
}) async {
|
||||||
|
final pdf = pw.Document();
|
||||||
|
|
||||||
|
pdf.addPage(
|
||||||
|
pw.MultiPage(
|
||||||
|
pageFormat: PdfPageFormat.a4,
|
||||||
|
margin: const pw.EdgeInsets.all(32),
|
||||||
|
header: (ctx) => buildPdfHeader('Laporan Evaporasi', timestamp),
|
||||||
|
footer: (ctx) => buildPdfFooter(ctx),
|
||||||
|
build: (ctx) => [
|
||||||
|
// — Summary card
|
||||||
|
buildSummaryCard(
|
||||||
|
title: 'Data Terkini',
|
||||||
|
subtitle: pdfFullFormat.format(timestamp),
|
||||||
|
items: [
|
||||||
|
SummaryItem('Evaporasi', '${evaporasi.toStringAsFixed(2)} mm', '💧'),
|
||||||
|
SummaryItem('Suhu', '${suhu.toStringAsFixed(1)} °C', '🌡️'),
|
||||||
|
SummaryItem('Tinggi Air', '${tinggiAir.toStringAsFixed(1)} cm', '📏'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 24),
|
||||||
|
|
||||||
|
// — Tabel key-value ringkasan
|
||||||
|
buildSectionTitle('Detail Parameter'),
|
||||||
|
pw.SizedBox(height: 8),
|
||||||
|
buildKeyValueTable([
|
||||||
|
['Parameter', 'Nilai', 'Satuan'],
|
||||||
|
['Evaporasi', evaporasi.toStringAsFixed(2), 'mm'],
|
||||||
|
['Suhu', suhu.toStringAsFixed(1), '°C'],
|
||||||
|
['Tinggi Air', tinggiAir.toStringAsFixed(1), 'cm'],
|
||||||
|
]),
|
||||||
|
|
||||||
|
// — Tabel history jika ada
|
||||||
|
if (historyData != null && historyData.isNotEmpty) ...[
|
||||||
|
pw.SizedBox(height: 24),
|
||||||
|
buildSectionTitle('Riwayat Data'),
|
||||||
|
pw.SizedBox(height: 8),
|
||||||
|
_buildHistoryTable(historyData),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Printing.layoutPdf(onLayout: (_) => pdf.save());
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.Widget _buildHistoryTable(List<Map<String, dynamic>> data) {
|
||||||
|
return buildDataTable(
|
||||||
|
headers: ['No', 'Waktu', 'Evaporasi (mm)', 'Suhu (°C)', 'Tinggi Air (cm)'],
|
||||||
|
rows: data.asMap().entries.map((e) {
|
||||||
|
final d = e.value;
|
||||||
|
final ts = d['timestamp'] as DateTime?;
|
||||||
|
return [
|
||||||
|
'${e.key + 1}',
|
||||||
|
ts != null ? pdfFullFormat.format(ts) : '-',
|
||||||
|
(d['evaporasi'] as double? ?? 0).toStringAsFixed(2),
|
||||||
|
(d['suhu'] as double? ?? 0).toStringAsFixed(1),
|
||||||
|
(d['tinggiAir'] as double? ?? 0).toStringAsFixed(1),
|
||||||
|
];
|
||||||
|
}).toList(),
|
||||||
|
colWidths: {
|
||||||
|
0: const pw.FixedColumnWidth(25),
|
||||||
|
1: const pw.FlexColumnWidth(2),
|
||||||
|
2: const pw.FlexColumnWidth(1.3),
|
||||||
|
3: const pw.FlexColumnWidth(1),
|
||||||
|
4: const pw.FlexColumnWidth(1.3),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
import 'package:pdf/pdf.dart';
|
||||||
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// PDF Components — Reusable builders
|
||||||
|
/// Letakkan di: lib/screens/monitoring/shared/utils/pdf/pdf_components.dart
|
||||||
|
/// ============================================================
|
||||||
|
|
||||||
|
// ─── Warna tema ───────────────────────────────────────────────
|
||||||
|
const kPdfPrimary = PdfColor.fromInt(0xFF1565C0);
|
||||||
|
const kPdfAccent = PdfColor.fromInt(0xFF42A5F5);
|
||||||
|
const kPdfRowEven = PdfColor.fromInt(0xFFF5F9FF);
|
||||||
|
const kPdfBorder = PdfColor.fromInt(0xFFBBDEFB);
|
||||||
|
|
||||||
|
// ─── Format tanggal ───────────────────────────────────────────
|
||||||
|
final pdfDateFormat = DateFormat('dd MMMM yyyy', 'id_ID');
|
||||||
|
final pdfTimeFormat = DateFormat('HH:mm:ss');
|
||||||
|
final pdfFullFormat = DateFormat('dd MMM yyyy, HH:mm', 'id_ID');
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// HEADER — muncul di setiap halaman
|
||||||
|
// ================================================================
|
||||||
|
pw.Widget buildPdfHeader(String title, DateTime date) {
|
||||||
|
return pw.Container(
|
||||||
|
margin: const pw.EdgeInsets.only(bottom: 16),
|
||||||
|
decoration: const pw.BoxDecoration(
|
||||||
|
border: pw.Border(
|
||||||
|
bottom: pw.BorderSide(color: kPdfBorder, width: 1.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
pw.Text(
|
||||||
|
title,
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
|
color: kPdfPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
pw.Text(
|
||||||
|
'Sistem Monitoring Klimatologi',
|
||||||
|
style: pw.TextStyle(fontSize: 10, color: PdfColors.grey600),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
pw.Text(
|
||||||
|
pdfDateFormat.format(date),
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
|
color: kPdfPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pw.Text(
|
||||||
|
pdfTimeFormat.format(date),
|
||||||
|
style: pw.TextStyle(fontSize: 9, color: PdfColors.grey600),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// FOOTER — muncul di setiap halaman
|
||||||
|
// ================================================================
|
||||||
|
pw.Widget buildPdfFooter(pw.Context context) {
|
||||||
|
return pw.Container(
|
||||||
|
margin: const pw.EdgeInsets.only(top: 12),
|
||||||
|
decoration: const pw.BoxDecoration(
|
||||||
|
border: pw.Border(top: pw.BorderSide(color: kPdfBorder, width: 1)),
|
||||||
|
),
|
||||||
|
child: pw.Padding(
|
||||||
|
padding: const pw.EdgeInsets.only(top: 6),
|
||||||
|
child: pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
pw.Text(
|
||||||
|
'Digenerate otomatis oleh Aplikasi Klimatologi',
|
||||||
|
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey500),
|
||||||
|
),
|
||||||
|
pw.Text(
|
||||||
|
'Halaman ${context.pageNumber} / ${context.pagesCount}',
|
||||||
|
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey500),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// SECTION TITLE — judul bagian dengan background biru
|
||||||
|
// ================================================================
|
||||||
|
pw.Widget buildSectionTitle(String title) {
|
||||||
|
return pw.Container(
|
||||||
|
padding: const pw.EdgeInsets.symmetric(vertical: 6, horizontal: 10),
|
||||||
|
decoration: const pw.BoxDecoration(
|
||||||
|
color: kPdfPrimary,
|
||||||
|
borderRadius: pw.BorderRadius.all(pw.Radius.circular(4)),
|
||||||
|
),
|
||||||
|
child: pw.Text(
|
||||||
|
title,
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
|
color: PdfColors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// SUMMARY CARD — grid kartu nilai terkini
|
||||||
|
// ================================================================
|
||||||
|
pw.Widget buildSummaryCard({
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required List<SummaryItem> items,
|
||||||
|
}) {
|
||||||
|
return pw.Container(
|
||||||
|
padding: const pw.EdgeInsets.all(16),
|
||||||
|
decoration: pw.BoxDecoration(
|
||||||
|
color: const PdfColor.fromInt(0xFFE3F2FD),
|
||||||
|
borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)),
|
||||||
|
border: pw.Border.all(color: kPdfBorder),
|
||||||
|
),
|
||||||
|
child: pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
pw.Text(
|
||||||
|
title,
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
|
color: kPdfPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pw.Text(
|
||||||
|
subtitle,
|
||||||
|
style: pw.TextStyle(fontSize: 9, color: PdfColors.grey600),
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 12),
|
||||||
|
pw.Wrap(
|
||||||
|
spacing: 12,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: items.map((item) => _summaryItemCard(item)).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.Widget _summaryItemCard(SummaryItem item) {
|
||||||
|
return pw.Container(
|
||||||
|
width: 200,
|
||||||
|
padding: const pw.EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
|
decoration: pw.BoxDecoration(
|
||||||
|
color: PdfColors.white,
|
||||||
|
borderRadius: const pw.BorderRadius.all(pw.Radius.circular(6)),
|
||||||
|
border: pw.Border.all(color: kPdfBorder),
|
||||||
|
),
|
||||||
|
child: pw.Row(
|
||||||
|
children: [
|
||||||
|
pw.Text(item.emoji, style: const pw.TextStyle(fontSize: 18)),
|
||||||
|
pw.SizedBox(width: 8),
|
||||||
|
pw.Column(
|
||||||
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
pw.Text(
|
||||||
|
item.label,
|
||||||
|
style: pw.TextStyle(fontSize: 9, color: PdfColors.grey600),
|
||||||
|
),
|
||||||
|
pw.Text(
|
||||||
|
item.value,
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
|
color: kPdfPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// BASE TABLE — semua tabel pakai ini
|
||||||
|
// ================================================================
|
||||||
|
pw.Widget buildDataTable({
|
||||||
|
required List<String> headers,
|
||||||
|
required List<List<String>> rows,
|
||||||
|
Map<int, pw.TableColumnWidth>? colWidths,
|
||||||
|
}) {
|
||||||
|
return pw.Table(
|
||||||
|
border: pw.TableBorder.all(color: kPdfBorder, width: 0.5),
|
||||||
|
columnWidths: colWidths,
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
pw.TableRow(
|
||||||
|
decoration: const pw.BoxDecoration(color: kPdfPrimary),
|
||||||
|
children: headers
|
||||||
|
.map((h) => pw.Padding(
|
||||||
|
padding: const pw.EdgeInsets.symmetric(
|
||||||
|
vertical: 7, horizontal: 8),
|
||||||
|
child: pw.Text(
|
||||||
|
h,
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
|
color: PdfColors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
// Data rows
|
||||||
|
...rows.asMap().entries.map((entry) {
|
||||||
|
final isEven = entry.key % 2 == 0;
|
||||||
|
return pw.TableRow(
|
||||||
|
decoration: pw.BoxDecoration(
|
||||||
|
color: isEven ? kPdfRowEven : PdfColors.white,
|
||||||
|
),
|
||||||
|
children: entry.value
|
||||||
|
.map((cell) => pw.Padding(
|
||||||
|
padding: const pw.EdgeInsets.symmetric(
|
||||||
|
vertical: 5, horizontal: 8),
|
||||||
|
child: pw.Text(cell,
|
||||||
|
style: const pw.TextStyle(fontSize: 9)),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// KEY-VALUE TABLE — tabel 3 kolom: Parameter | Nilai | Satuan
|
||||||
|
// ================================================================
|
||||||
|
pw.Widget buildKeyValueTable(List<List<String>> rows) {
|
||||||
|
return buildDataTable(
|
||||||
|
headers: rows.first,
|
||||||
|
rows: rows.skip(1).toList(),
|
||||||
|
colWidths: {
|
||||||
|
0: const pw.FlexColumnWidth(2),
|
||||||
|
1: const pw.FlexColumnWidth(1.5),
|
||||||
|
2: const pw.FlexColumnWidth(1),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// HELPER CLASS
|
||||||
|
// ================================================================
|
||||||
|
class SummaryItem {
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
final String emoji;
|
||||||
|
const SummaryItem(this.label, this.value, this.emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// STATS HELPER
|
||||||
|
// ================================================================
|
||||||
|
Map<String, double> calcStats(List<double> data) {
|
||||||
|
if (data.isEmpty) return {'avg': 0, 'max': 0, 'min': 0};
|
||||||
|
final sorted = List<double>.from(data)..sort();
|
||||||
|
final avg = data.reduce((a, b) => a + b) / data.length;
|
||||||
|
return {'avg': avg, 'max': sorted.last, 'min': sorted.first};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'atmospheric_pdf_builder.dart';
|
||||||
|
import 'evaporasi_pdf_builder.dart';
|
||||||
|
import 'wind_speed_pdf_builder.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// PDF Export Service — Entry Point
|
||||||
|
/// Letakkan di: lib/screens/monitoring/shared/utils/pdf/pdf_export_service.dart
|
||||||
|
///
|
||||||
|
/// Ini cuma facade — semua logic ada di builder masing-masing.
|
||||||
|
/// Import file ini saja di screen kamu.
|
||||||
|
/// ============================================================
|
||||||
|
|
||||||
|
class PdfExportService {
|
||||||
|
PdfExportService._(); // tidak bisa di-instantiate
|
||||||
|
|
||||||
|
static Future<void> atmospheric({
|
||||||
|
required double temperature,
|
||||||
|
required double humidity,
|
||||||
|
required double pressure,
|
||||||
|
required double altitude,
|
||||||
|
required DateTime timestamp,
|
||||||
|
List<Map<String, dynamic>>? historyData,
|
||||||
|
}) =>
|
||||||
|
exportAtmosphericPdf(
|
||||||
|
temperature: temperature,
|
||||||
|
humidity: humidity,
|
||||||
|
pressure: pressure,
|
||||||
|
altitude: altitude,
|
||||||
|
timestamp: timestamp,
|
||||||
|
historyData: historyData,
|
||||||
|
);
|
||||||
|
|
||||||
|
static Future<void> evaporasi({
|
||||||
|
required double evaporasi,
|
||||||
|
required double suhu,
|
||||||
|
required double tinggiAir,
|
||||||
|
required DateTime timestamp,
|
||||||
|
List<Map<String, dynamic>>? historyData,
|
||||||
|
}) =>
|
||||||
|
exportEvaporasiPdf(
|
||||||
|
evaporasi: evaporasi,
|
||||||
|
suhu: suhu,
|
||||||
|
tinggiAir: tinggiAir,
|
||||||
|
timestamp: timestamp,
|
||||||
|
historyData: historyData,
|
||||||
|
);
|
||||||
|
|
||||||
|
static Future<void> windSpeed({
|
||||||
|
required double currentSpeed,
|
||||||
|
required String period,
|
||||||
|
required List<double> speeds,
|
||||||
|
required DateTime timestamp,
|
||||||
|
List<Map<String, dynamic>>? historyData,
|
||||||
|
}) =>
|
||||||
|
exportWindSpeedPdf(
|
||||||
|
currentSpeed: currentSpeed,
|
||||||
|
period: period,
|
||||||
|
speeds: speeds,
|
||||||
|
timestamp: timestamp,
|
||||||
|
historyData: historyData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
import 'package:pdf/pdf.dart';
|
||||||
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
|
import 'package:printing/printing.dart';
|
||||||
|
import 'pdf_components.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// Wind Speed PDF Builder
|
||||||
|
/// Letakkan di: lib/screens/monitoring/shared/utils/pdf/wind_speed_pdf_builder.dart
|
||||||
|
/// ============================================================
|
||||||
|
|
||||||
|
Future<void> exportWindSpeedPdf({
|
||||||
|
required double currentSpeed,
|
||||||
|
required String period,
|
||||||
|
required List<double> speeds,
|
||||||
|
required DateTime timestamp,
|
||||||
|
List<Map<String, dynamic>>? historyData,
|
||||||
|
}) async {
|
||||||
|
final pdf = pw.Document();
|
||||||
|
final stats = calcStats(speeds);
|
||||||
|
|
||||||
|
pdf.addPage(
|
||||||
|
pw.MultiPage(
|
||||||
|
pageFormat: PdfPageFormat.a4,
|
||||||
|
margin: const pw.EdgeInsets.all(32),
|
||||||
|
header: (ctx) => buildPdfHeader('Laporan Kecepatan Angin', timestamp),
|
||||||
|
footer: (ctx) => buildPdfFooter(ctx),
|
||||||
|
build: (ctx) => [
|
||||||
|
// — Summary card
|
||||||
|
buildSummaryCard(
|
||||||
|
title: 'Data Terkini',
|
||||||
|
subtitle: 'Periode: $period',
|
||||||
|
items: [
|
||||||
|
SummaryItem('Kecepatan', '${currentSpeed.toStringAsFixed(1)} m/s', '💨'),
|
||||||
|
SummaryItem('Rata-rata', '${stats['avg']!.toStringAsFixed(1)} m/s', '📊'),
|
||||||
|
SummaryItem('Maksimum', '${stats['max']!.toStringAsFixed(1)} m/s', '⬆️'),
|
||||||
|
SummaryItem('Minimum', '${stats['min']!.toStringAsFixed(1)} m/s', '⬇️'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 24),
|
||||||
|
|
||||||
|
// — Tabel data per periode
|
||||||
|
if (speeds.isNotEmpty) ...[
|
||||||
|
buildSectionTitle('Data Periode ($period)'),
|
||||||
|
pw.SizedBox(height: 8),
|
||||||
|
_buildPeriodTable(speeds, period),
|
||||||
|
],
|
||||||
|
|
||||||
|
// — Tabel history jika ada
|
||||||
|
if (historyData != null && historyData.isNotEmpty) ...[
|
||||||
|
pw.SizedBox(height: 24),
|
||||||
|
buildSectionTitle('Riwayat Detail'),
|
||||||
|
pw.SizedBox(height: 8),
|
||||||
|
_buildHistoryTable(historyData),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Printing.layoutPdf(onLayout: (_) => pdf.save());
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.Widget _buildPeriodTable(List<double> speeds, String period) {
|
||||||
|
final labels = _periodLabels(speeds.length, period);
|
||||||
|
return buildDataTable(
|
||||||
|
headers: ['No', 'Label', 'Kecepatan (m/s)'],
|
||||||
|
rows: speeds.asMap().entries.map((e) => [
|
||||||
|
'${e.key + 1}',
|
||||||
|
labels[e.key],
|
||||||
|
e.value.toStringAsFixed(2),
|
||||||
|
]).toList(),
|
||||||
|
colWidths: {
|
||||||
|
0: const pw.FixedColumnWidth(30),
|
||||||
|
1: const pw.FlexColumnWidth(2),
|
||||||
|
2: const pw.FlexColumnWidth(1.5),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.Widget _buildHistoryTable(List<Map<String, dynamic>> data) {
|
||||||
|
return buildDataTable(
|
||||||
|
headers: ['No', 'Waktu', 'Kecepatan (m/s)', 'Pulse'],
|
||||||
|
rows: data.asMap().entries.map((e) {
|
||||||
|
final d = e.value;
|
||||||
|
final ts = d['timestamp'] as DateTime?;
|
||||||
|
return [
|
||||||
|
'${e.key + 1}',
|
||||||
|
ts != null ? pdfFullFormat.format(ts) : '-',
|
||||||
|
(d['speed'] as double? ?? 0).toStringAsFixed(2),
|
||||||
|
'${d['pulse'] ?? 0}',
|
||||||
|
];
|
||||||
|
}).toList(),
|
||||||
|
colWidths: {
|
||||||
|
0: const pw.FixedColumnWidth(25),
|
||||||
|
1: const pw.FlexColumnWidth(2.5),
|
||||||
|
2: const pw.FlexColumnWidth(1.5),
|
||||||
|
3: const pw.FlexColumnWidth(1),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _periodLabels(int count, String period) {
|
||||||
|
if (period == 'Hari Ini') {
|
||||||
|
return List.generate(count, (i) => 'Jam ${i.toString().padLeft(2, '0')}:00');
|
||||||
|
} else if (period == 'Minggu Ini') {
|
||||||
|
const days = ['Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min'];
|
||||||
|
return List.generate(count, (i) => days[i % 7]);
|
||||||
|
} else {
|
||||||
|
return List.generate(count, (i) => 'Minggu ${i + 1}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// Export PDF Button — Reusable Widget
|
||||||
|
/// Letakkan di: lib/screens/monitoring/shared/widgets/export_pdf_button.dart
|
||||||
|
/// ============================================================
|
||||||
|
///
|
||||||
|
/// Cara pakai (contoh di WindSpeedScreen):
|
||||||
|
///
|
||||||
|
/// ExportPdfButton(
|
||||||
|
/// onExport: () async {
|
||||||
|
/// await PdfExportService.exportWindSpeedPdf(
|
||||||
|
/// currentSpeed: state.currentSpeed,
|
||||||
|
/// period: state.selectedPeriod,
|
||||||
|
/// speeds: data,
|
||||||
|
/// timestamp: DateTime.now(),
|
||||||
|
/// );
|
||||||
|
/// },
|
||||||
|
/// )
|
||||||
|
|
||||||
|
class ExportPdfButton extends StatefulWidget {
|
||||||
|
/// Callback async yang memanggil PdfExportService
|
||||||
|
final Future<void> Function() onExport;
|
||||||
|
|
||||||
|
/// Label tombol (default: "Export PDF")
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
/// Style: FAB (floating) atau ElevatedButton (inline)
|
||||||
|
final ExportButtonStyle style;
|
||||||
|
|
||||||
|
const ExportPdfButton({
|
||||||
|
super.key,
|
||||||
|
required this.onExport,
|
||||||
|
this.label = 'Export PDF',
|
||||||
|
this.style = ExportButtonStyle.elevated,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ExportPdfButton> createState() => _ExportPdfButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExportPdfButtonState extends State<ExportPdfButton> {
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
Future<void> _handleExport() async {
|
||||||
|
if (_isLoading) return;
|
||||||
|
setState(() => _isLoading = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await widget.onExport();
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Gagal export PDF: $e'),
|
||||||
|
backgroundColor: Colors.red.shade600,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _isLoading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.style == ExportButtonStyle.fab
|
||||||
|
? _buildFab()
|
||||||
|
: _buildElevated();
|
||||||
|
}
|
||||||
|
|
||||||
|
// — FAB style (floating action button)
|
||||||
|
Widget _buildFab() {
|
||||||
|
return FloatingActionButton.extended(
|
||||||
|
onPressed: _isLoading ? null : _handleExport,
|
||||||
|
backgroundColor: Colors.blue.shade700,
|
||||||
|
icon: _isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.picture_as_pdf, color: Colors.white),
|
||||||
|
label: Text(
|
||||||
|
_isLoading ? 'Menyiapkan...' : widget.label,
|
||||||
|
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// — Elevated button style (inline di dalam konten)
|
||||||
|
Widget _buildElevated() {
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: _isLoading ? null : _handleExport,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue.shade700,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: _isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.picture_as_pdf, color: Colors.white),
|
||||||
|
label: Text(
|
||||||
|
_isLoading ? 'Menyiapkan PDF...' : widget.label,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ExportButtonStyle { fab, elevated }
|
||||||
|
|
@ -48,9 +48,35 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
|
||||||
);
|
);
|
||||||
final dailyGraph = TimeSeriesMapper.smooth(raw);
|
final dailyGraph = TimeSeriesMapper.smooth(raw);
|
||||||
|
|
||||||
|
final daily = TimeSeriesMapper.smooth(
|
||||||
|
TimeSeriesMapper.toDaily(
|
||||||
|
data: history,
|
||||||
|
getTime: (e) => e.timestamp,
|
||||||
|
getValue: (e) => e.speed,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final weekly = TimeSeriesMapper.smooth(
|
||||||
|
TimeSeriesMapper.toWeekly(
|
||||||
|
data: history,
|
||||||
|
getTime: (e) => e.timestamp,
|
||||||
|
getValue: (e) => e.speed,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final monthly = TimeSeriesMapper.smooth(
|
||||||
|
TimeSeriesMapper.toMonthly(
|
||||||
|
data: history,
|
||||||
|
getTime: (e) => e.timestamp,
|
||||||
|
getValue: (e) => e.speed,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
history: history,
|
history: history,
|
||||||
dailySpeeds: dailyGraph,
|
dailySpeeds: dailyGraph,
|
||||||
|
weeklySpeeds: weekly,
|
||||||
|
monthlySpeeds: monthly,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
@ -91,7 +117,7 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updated[index] = newValue;
|
updated[index] = newValue.clamp(0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
|
|
@ -107,7 +133,7 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
|
||||||
WindSpeedPeriodChanged event,
|
WindSpeedPeriodChanged event,
|
||||||
Emitter<WindSpeedState> emit,
|
Emitter<WindSpeedState> emit,
|
||||||
) async {
|
) async {
|
||||||
emit(state.copyWith(isLoading: true, selectedPeriod: event.period));
|
emit(state.copyWith(selectedPeriod: event.period));
|
||||||
|
|
||||||
final history = state.history;
|
final history = state.history;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ part of 'wind_speed_bloc.dart';
|
||||||
class WindSpeedState extends Equatable {
|
class WindSpeedState extends Equatable {
|
||||||
final double currentSpeed;
|
final double currentSpeed;
|
||||||
final String selectedPeriod;
|
final String selectedPeriod;
|
||||||
final List<double> dailySpeeds; // Untuk grafik
|
final List<double> dailySpeeds;
|
||||||
|
final List<double> weeklySpeeds;
|
||||||
|
final List<double> monthlySpeeds;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final List<MyWindSpeed> history;
|
final List<MyWindSpeed> history;
|
||||||
|
|
||||||
|
|
@ -13,12 +15,16 @@ class WindSpeedState extends Equatable {
|
||||||
this.dailySpeeds = const [],
|
this.dailySpeeds = const [],
|
||||||
this.isLoading = true,
|
this.isLoading = true,
|
||||||
this.history = const [],
|
this.history = const [],
|
||||||
|
this.monthlySpeeds = const [],
|
||||||
|
this.weeklySpeeds = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
WindSpeedState copyWith({
|
WindSpeedState copyWith({
|
||||||
double? currentSpeed,
|
double? currentSpeed,
|
||||||
String? selectedPeriod,
|
String? selectedPeriod,
|
||||||
List<double>? dailySpeeds,
|
List<double>? dailySpeeds,
|
||||||
|
List<double>? weeklySpeeds,
|
||||||
|
List<double>? monthlySpeeds,
|
||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
List<MyWindSpeed>? history,
|
List<MyWindSpeed>? history,
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -26,6 +32,8 @@ class WindSpeedState extends Equatable {
|
||||||
currentSpeed: currentSpeed ?? this.currentSpeed,
|
currentSpeed: currentSpeed ?? this.currentSpeed,
|
||||||
selectedPeriod: selectedPeriod ?? this.selectedPeriod,
|
selectedPeriod: selectedPeriod ?? this.selectedPeriod,
|
||||||
dailySpeeds: dailySpeeds ?? this.dailySpeeds,
|
dailySpeeds: dailySpeeds ?? this.dailySpeeds,
|
||||||
|
weeklySpeeds: weeklySpeeds ?? this.weeklySpeeds,
|
||||||
|
monthlySpeeds: monthlySpeeds ?? this.monthlySpeeds,
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
history: history ?? this.history,
|
history: history ?? this.history,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import '../blocs/wind_speed_bloc.dart';
|
||||||
import './widgets/wind_speed_chart_widget.dart';
|
import './widgets/wind_speed_chart_widget.dart';
|
||||||
import 'widgets/period_selector.dart';
|
import 'widgets/period_selector.dart';
|
||||||
|
|
||||||
|
import '../../shared/utils/pdf/pdf_export_service.dart';
|
||||||
|
import '../../shared/widgets/export_pdf_button.dart';
|
||||||
|
|
||||||
class WindSpeedScreen extends StatelessWidget {
|
class WindSpeedScreen extends StatelessWidget {
|
||||||
const WindSpeedScreen({super.key});
|
const WindSpeedScreen({super.key});
|
||||||
|
|
||||||
|
|
@ -30,6 +33,32 @@ class WindSpeedScreen extends StatelessWidget {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pilih data sesuai periode
|
||||||
|
final List<double> data = switch (state.selectedPeriod) {
|
||||||
|
"Minggu Ini" => state.weeklySpeeds,
|
||||||
|
"Bulan Ini" => state.monthlySpeeds,
|
||||||
|
_ => state.dailySpeeds,
|
||||||
|
};
|
||||||
|
|
||||||
|
// List<double> data;
|
||||||
|
|
||||||
|
// if (state.selectedPeriod == "Minggu Ini") {
|
||||||
|
// data = state.weeklySpeeds;
|
||||||
|
// } else if (state.selectedPeriod == "Bulan Ini") {
|
||||||
|
// data = state.monthlySpeeds;
|
||||||
|
// } else {
|
||||||
|
// data = state.dailySpeeds;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Konversi history MyWindSpeed → Map (untuk tabel PDF)
|
||||||
|
final historyMaps = state.history
|
||||||
|
.map((e) => {
|
||||||
|
'timestamp': e.timestamp,
|
||||||
|
'speed': e.speed,
|
||||||
|
'pulse': e.pulse,
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -46,7 +75,7 @@ class WindSpeedScreen extends StatelessWidget {
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
|
|
||||||
WindSpeedChartWidget(
|
WindSpeedChartWidget(
|
||||||
dailySpeeds: state.dailySpeeds,
|
dailySpeeds: data,
|
||||||
period: state.selectedPeriod,
|
period: state.selectedPeriod,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
@ -54,6 +83,18 @@ class WindSpeedScreen extends StatelessWidget {
|
||||||
|
|
||||||
// 3. Info Tambahan (Status/Periode)
|
// 3. Info Tambahan (Status/Periode)
|
||||||
_buildDetailRow(state.selectedPeriod),
|
_buildDetailRow(state.selectedPeriod),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// ← Tombol Export PDF
|
||||||
|
ExportPdfButton(
|
||||||
|
onExport: () => PdfExportService.windSpeed(
|
||||||
|
currentSpeed: state.currentSpeed,
|
||||||
|
period: state.selectedPeriod,
|
||||||
|
speeds: data,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
historyData: historyMaps.isNotEmpty ? historyMaps : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <printing/printing_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) printing_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
|
||||||
|
printing_plugin_register_with_registrar(printing_registrar);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
printing
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import firebase_auth
|
||||||
import firebase_core
|
import firebase_core
|
||||||
import firebase_database
|
import firebase_database
|
||||||
import google_sign_in_ios
|
import google_sign_in_ios
|
||||||
|
import printing
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
|
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
|
||||||
|
|
@ -17,4 +18,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
|
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
|
||||||
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
||||||
|
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
96
pubspec.lock
96
pubspec.lock
|
|
@ -9,6 +9,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.66"
|
version: "1.3.66"
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.9"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -25,6 +33,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.0"
|
version: "2.13.0"
|
||||||
|
barcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: barcode
|
||||||
|
sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.9"
|
||||||
|
bidi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bidi
|
||||||
|
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.13"
|
||||||
bloc:
|
bloc:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -352,6 +376,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.8.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -487,6 +519,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_parsing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_parsing
|
||||||
|
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -535,6 +575,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
pdf:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: pdf
|
||||||
|
sha256: e47a275b267873d5944ad5f5ff0dcc7ac2e36c02b3046a0ffac9b72fd362c44b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.12.0"
|
||||||
|
pdf_widget_wrapper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pdf_widget_wrapper
|
||||||
|
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -551,6 +615,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
posix:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: posix
|
||||||
|
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
|
printing:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: printing
|
||||||
|
sha256: "689170c9ddb1bda85826466ba80378aa8993486d3c959a71cd7d2d80cb606692"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.14.3"
|
||||||
provider:
|
provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -567,6 +647,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
qr:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: qr
|
||||||
|
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -683,6 +771,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.6.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ dependencies:
|
||||||
rxdart: ^0.27.7
|
rxdart: ^0.27.7
|
||||||
user_repository:
|
user_repository:
|
||||||
path: packages/user_repository
|
path: packages/user_repository
|
||||||
|
pdf: ^3.11.1
|
||||||
|
printing: ^5.13.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^3.0.0
|
flutter_lints: ^3.0.0
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <cloud_firestore/cloud_firestore_plugin_c_api.h>
|
#include <cloud_firestore/cloud_firestore_plugin_c_api.h>
|
||||||
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
|
#include <printing/printing_plugin.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
CloudFirestorePluginCApiRegisterWithRegistrar(
|
CloudFirestorePluginCApiRegisterWithRegistrar(
|
||||||
|
|
@ -17,4 +18,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
|
PrintingPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("PrintingPlugin"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
cloud_firestore
|
cloud_firestore
|
||||||
firebase_auth
|
firebase_auth
|
||||||
firebase_core
|
firebase_core
|
||||||
|
printing
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue