penambahan dan perbaikan wind_speed/anemometer
ditambahkan history dan list data dan tanggal
This commit is contained in:
parent
546f1287f8
commit
6379502e28
|
|
@ -34,13 +34,14 @@ class EvaporasiBloc extends Bloc<EvaporasiEvent, EvaporasiState> {
|
|||
emit(state.copyWith(isLoading: true));
|
||||
|
||||
// Ambil history yang akan dipakai untuk list + agregasi grafik.
|
||||
final history = await _repository.getSensorHistory(
|
||||
'Monitoring/History',
|
||||
(json) => Evaporasi.fromJson(json),
|
||||
);
|
||||
final history = List<Evaporasi>.from(
|
||||
await _repository.getSensorHistory(
|
||||
'Monitoring/History',
|
||||
(json) => Evaporasi.fromJson(json),
|
||||
),
|
||||
)..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
final listData = List<Evaporasi>.from(history)
|
||||
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
final listData = List<Evaporasi>.from(history);
|
||||
|
||||
final dailyGraph = TimeSeriesMapper.toDaily(
|
||||
data: history,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
|
||||
double _safeValue(double value) {
|
||||
if (value.isNaN || value.isInfinite) return 0.0;
|
||||
|
||||
// anti spike
|
||||
if (value > 1000 || value < -1000) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return value < 0 ? 0.0 : value;
|
||||
}
|
||||
|
||||
|
|
@ -117,14 +123,7 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
|
||||
final evapSpotsAll = _evapSpots();
|
||||
|
||||
// Deduplicate X=hour agar garis tidak kelihatan dobel/acak.
|
||||
final Map<int, double> evapByX = {};
|
||||
for (final s in evapSpotsAll) {
|
||||
evapByX[s.x.toInt()] = s.y;
|
||||
}
|
||||
|
||||
final dedupEvapSpots = evapByX.entries.toList()
|
||||
..sort((a, b) => a.key.compareTo(b.key));
|
||||
final dedupEvapSpots = evapSpotsAll;
|
||||
|
||||
final Map<int, double> tempByX = {};
|
||||
for (final entry in dailyTemperatures.asMap().entries) {
|
||||
|
|
@ -143,8 +142,7 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
return FlSpot(x, y);
|
||||
}).toList();
|
||||
|
||||
final evapSpots =
|
||||
dedupEvapSpots.map((e) => FlSpot(e.key.toDouble(), e.value)).toList();
|
||||
final evapSpots = dedupEvapSpots;
|
||||
|
||||
if (evapSpots.isEmpty && tempSpots.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,348 @@
|
|||
// ===========================================================
|
||||
// wind_speed_excel_service.dart
|
||||
// Lokasi: lib/screens/monitoring/shared/utils/excel/
|
||||
//
|
||||
// Dependensi (tambahkan ke pubspec.yaml):
|
||||
// excel: ^4.0.6
|
||||
// path_provider: ^2.1.4
|
||||
// share_plus: ^10.0.2
|
||||
// ===========================================================
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:excel/excel.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class WindSpeedExcelService {
|
||||
// ── Format helper ──────────────────────────────────────────
|
||||
static final _dateFmt = DateFormat('dd/MM/yyyy HH:mm:ss', 'id_ID');
|
||||
static final _fileFmt = DateFormat('yyyyMMdd_HHmmss');
|
||||
static final _headerFmt = DateFormat('dd MMMM yyyy, HH:mm', 'id_ID');
|
||||
|
||||
// ── Warna tema ─────────────────────────────────────────────
|
||||
static const _colorHeader = '1A4A8C'; // biru tua
|
||||
static const _colorSubHead = '2E75B6'; // biru medium
|
||||
static const _colorNormal = 'E8F4FD'; // biru sangat muda
|
||||
static const _colorWaspada = 'FFF3CD'; // kuning muda
|
||||
static const _colorBahaya = 'F8D7DA'; // merah muda
|
||||
static const _colorWhite = 'FFFFFF';
|
||||
|
||||
/// Export data kecepatan angin ke file Excel dan buka share sheet
|
||||
static Future<void> export({
|
||||
required double currentSpeed,
|
||||
required String alertLevel,
|
||||
required String period,
|
||||
required List<MyWindSpeed> history,
|
||||
DateTime? filterDate,
|
||||
}) async {
|
||||
final excel = Excel.createExcel();
|
||||
|
||||
// Hapus sheet default 'Sheet1'
|
||||
excel.delete('Sheet1');
|
||||
|
||||
// ── Buat dua sheet ──────────────────────────────────────
|
||||
_buildSummarySheet(
|
||||
excel, currentSpeed, alertLevel, period, history, filterDate);
|
||||
_buildHistorySheet(excel, history, filterDate);
|
||||
|
||||
// ── Simpan file ─────────────────────────────────────────
|
||||
final bytes = excel.save();
|
||||
if (bytes == null) throw Exception('Gagal membuat file Excel');
|
||||
|
||||
final dir = await getTemporaryDirectory();
|
||||
final name = 'wind_speed_${_fileFmt.format(DateTime.now())}.xlsx';
|
||||
final file = File('${dir.path}/$name');
|
||||
await file.writeAsBytes(bytes, flush: true);
|
||||
|
||||
// ── Bagikan via share sheet ─────────────────────────────
|
||||
await Share.shareXFiles(
|
||||
[
|
||||
XFile(file.path,
|
||||
mimeType:
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
],
|
||||
subject: 'Data Kecepatan Angin – ${_headerFmt.format(DateTime.now())}',
|
||||
);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// SHEET 1: Ringkasan
|
||||
// ════════════════════════════════════════════════════════════
|
||||
static void _buildSummarySheet(
|
||||
Excel excel,
|
||||
double currentSpeed,
|
||||
String alertLevel,
|
||||
String period,
|
||||
List<MyWindSpeed> history,
|
||||
DateTime? filterDate,
|
||||
) {
|
||||
final sheet = excel['Ringkasan'];
|
||||
|
||||
// -- Judul --
|
||||
_setCell(sheet, 0, 0, 'DATA KECEPATAN ANGIN – ANEMOMETER',
|
||||
bold: true,
|
||||
fontSize: 14,
|
||||
bgColor: _colorHeader,
|
||||
fontColor: _colorWhite);
|
||||
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0),
|
||||
CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: 0));
|
||||
|
||||
// -- Metadata --
|
||||
_setCell(sheet, 1, 0, 'Diekspor pada',
|
||||
bold: true, bgColor: _colorSubHead, fontColor: _colorWhite);
|
||||
_setCell(sheet, 1, 1, _headerFmt.format(DateTime.now()),
|
||||
bgColor: _colorNormal);
|
||||
_setCell(sheet, 2, 0, 'Periode grafik',
|
||||
bold: true, bgColor: _colorSubHead, fontColor: _colorWhite);
|
||||
_setCell(sheet, 2, 1, period, bgColor: _colorNormal);
|
||||
|
||||
if (filterDate != null) {
|
||||
_setCell(sheet, 3, 0, 'Filter tanggal',
|
||||
bold: true, bgColor: _colorSubHead, fontColor: _colorWhite);
|
||||
_setCell(
|
||||
sheet, 3, 1, DateFormat('dd MMMM yyyy', 'id_ID').format(filterDate),
|
||||
bgColor: _colorNormal);
|
||||
}
|
||||
|
||||
// -- Kecepatan saat ini --
|
||||
_setCell(sheet, 5, 0, 'KECEPATAN SAAT INI',
|
||||
bold: true, bgColor: _colorSubHead, fontColor: _colorWhite);
|
||||
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 5),
|
||||
CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: 5));
|
||||
|
||||
_setCell(sheet, 6, 0, 'Kecepatan (m/s)', bold: true);
|
||||
_setCellDouble(sheet, 6, 1, currentSpeed);
|
||||
_setCell(sheet, 7, 0, 'Kecepatan (km/h)', bold: true);
|
||||
_setCellDouble(sheet, 7, 1, currentSpeed * 3.6);
|
||||
_setCell(sheet, 8, 0, 'Status', bold: true);
|
||||
final statusColor = _alertBgColor(alertLevel);
|
||||
_setCell(sheet, 8, 1, alertLevel, bgColor: statusColor);
|
||||
|
||||
// -- Statistik ringkasan --
|
||||
if (history.isNotEmpty) {
|
||||
final speeds = history.map((e) => e.speed).toList();
|
||||
final avg = speeds.reduce((a, b) => a + b) / speeds.length;
|
||||
final max = speeds.reduce((a, b) => a > b ? a : b);
|
||||
final min = speeds.reduce((a, b) => a < b ? a : b);
|
||||
|
||||
_setCell(sheet, 10, 0, 'STATISTIK HISTORY',
|
||||
bold: true, bgColor: _colorSubHead, fontColor: _colorWhite);
|
||||
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 10),
|
||||
CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: 10));
|
||||
|
||||
_setCell(sheet, 11, 0, 'Jumlah data', bold: true);
|
||||
_setCellInt(sheet, 11, 1, history.length);
|
||||
_setCell(sheet, 12, 0, 'Rata-rata (m/s)', bold: true);
|
||||
_setCellDouble(sheet, 12, 1, avg);
|
||||
_setCell(sheet, 13, 0, 'Maksimum (m/s)', bold: true);
|
||||
_setCellDouble(sheet, 13, 1, max);
|
||||
_setCell(sheet, 14, 0, 'Minimum (m/s)', bold: true);
|
||||
_setCellDouble(sheet, 14, 1, min);
|
||||
_setCell(sheet, 15, 0, 'Rata-rata (km/h)', bold: true);
|
||||
_setCellDouble(sheet, 15, 1, avg * 3.6);
|
||||
}
|
||||
|
||||
// Set lebar kolom
|
||||
sheet.setColumnWidth(0, 22);
|
||||
sheet.setColumnWidth(1, 22);
|
||||
sheet.setColumnWidth(2, 18);
|
||||
sheet.setColumnWidth(3, 18);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// SHEET 2: Data History
|
||||
// ════════════════════════════════════════════════════════════
|
||||
static void _buildHistorySheet(
|
||||
Excel excel,
|
||||
List<MyWindSpeed> history,
|
||||
DateTime? filterDate,
|
||||
) {
|
||||
final sheet = excel['Data History'];
|
||||
|
||||
// -- Header kolom --
|
||||
final headers = [
|
||||
'No',
|
||||
'Tanggal',
|
||||
'Waktu',
|
||||
'Kecepatan (m/s)',
|
||||
'Kecepatan (km/h)',
|
||||
'Status'
|
||||
];
|
||||
for (var i = 0; i < headers.length; i++) {
|
||||
_setCell(sheet, 0, i, headers[i],
|
||||
bold: true,
|
||||
bgColor: _colorHeader,
|
||||
fontColor: _colorWhite,
|
||||
centered: true);
|
||||
}
|
||||
|
||||
// -- Filter jika ada tanggal dipilih --
|
||||
final data = filterDate != null
|
||||
? history
|
||||
.where((e) =>
|
||||
e.timestamp.year == filterDate.year &&
|
||||
e.timestamp.month == filterDate.month &&
|
||||
e.timestamp.day == filterDate.day)
|
||||
.toList()
|
||||
: history;
|
||||
|
||||
// -- Isi baris data --
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
final item = data[i];
|
||||
final rowIdx = i + 1;
|
||||
final kmh = item.speed * 3.6;
|
||||
final status = _getAlertLevel(item.speed);
|
||||
final bgColor = i.isEven ? _colorNormal : _colorWhite;
|
||||
final statusBg = _alertBgColor(status);
|
||||
|
||||
_setCellInt(sheet, rowIdx, 0, i + 1, bgColor: bgColor, centered: true);
|
||||
_setCell(sheet, rowIdx, 1,
|
||||
DateFormat('dd/MM/yyyy', 'id_ID').format(item.timestamp),
|
||||
bgColor: bgColor);
|
||||
_setCell(sheet, rowIdx, 2, DateFormat('HH:mm:ss').format(item.timestamp),
|
||||
bgColor: bgColor, centered: true);
|
||||
_setCellDouble(sheet, rowIdx, 3, item.speed,
|
||||
bgColor: bgColor, centered: true);
|
||||
_setCellDouble(sheet, rowIdx, 4, kmh, bgColor: bgColor, centered: true);
|
||||
_setCell(sheet, rowIdx, 5, status,
|
||||
bgColor: statusBg, centered: true, bold: true);
|
||||
}
|
||||
|
||||
// -- Footer jika kosong --
|
||||
if (data.isEmpty) {
|
||||
_setCell(sheet, 1, 0, 'Tidak ada data untuk tanggal yang dipilih',
|
||||
bgColor: _colorWaspada, centered: true);
|
||||
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 1),
|
||||
CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: 1));
|
||||
}
|
||||
|
||||
// Set lebar kolom
|
||||
sheet.setColumnWidth(0, 6);
|
||||
sheet.setColumnWidth(1, 14);
|
||||
sheet.setColumnWidth(2, 12);
|
||||
sheet.setColumnWidth(3, 18);
|
||||
sheet.setColumnWidth(4, 18);
|
||||
sheet.setColumnWidth(5, 12);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// HELPER CELLS
|
||||
// ════════════════════════════════════════════════════════════
|
||||
static void _setCell(
|
||||
Sheet sheet,
|
||||
int row,
|
||||
int col,
|
||||
String value, {
|
||||
bool bold = false,
|
||||
double fontSize = 11,
|
||||
String bgColor = _colorWhite,
|
||||
String fontColor = '000000',
|
||||
bool centered = false,
|
||||
}) {
|
||||
final cell =
|
||||
sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: row));
|
||||
cell.value = TextCellValue(value);
|
||||
cell.cellStyle = CellStyle(
|
||||
bold: bold,
|
||||
fontSize: fontSize.toInt(),
|
||||
backgroundColorHex: ExcelColor.fromHexString('#$bgColor'),
|
||||
fontColorHex: ExcelColor.fromHexString('#$fontColor'),
|
||||
horizontalAlign: centered ? HorizontalAlign.Center : HorizontalAlign.Left,
|
||||
verticalAlign: VerticalAlign.Center,
|
||||
textWrapping: TextWrapping.WrapText,
|
||||
leftBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
rightBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
topBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
bottomBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
);
|
||||
}
|
||||
|
||||
static void _setCellDouble(
|
||||
Sheet sheet,
|
||||
int row,
|
||||
int col,
|
||||
double value, {
|
||||
String bgColor = _colorWhite,
|
||||
bool centered = false,
|
||||
}) {
|
||||
final cell =
|
||||
sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: row));
|
||||
cell.value = DoubleCellValue(double.parse(value.toStringAsFixed(4)));
|
||||
cell.cellStyle = CellStyle(
|
||||
backgroundColorHex: ExcelColor.fromHexString('#$bgColor'),
|
||||
horizontalAlign:
|
||||
centered ? HorizontalAlign.Center : HorizontalAlign.Right,
|
||||
verticalAlign: VerticalAlign.Center,
|
||||
leftBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
rightBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
topBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
bottomBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
);
|
||||
}
|
||||
|
||||
static void _setCellInt(
|
||||
Sheet sheet,
|
||||
int row,
|
||||
int col,
|
||||
int value, {
|
||||
String bgColor = _colorWhite,
|
||||
bool centered = false,
|
||||
}) {
|
||||
final cell =
|
||||
sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: row));
|
||||
cell.value = IntCellValue(value);
|
||||
cell.cellStyle = CellStyle(
|
||||
backgroundColorHex: ExcelColor.fromHexString('#$bgColor'),
|
||||
horizontalAlign:
|
||||
centered ? HorizontalAlign.Center : HorizontalAlign.Right,
|
||||
verticalAlign: VerticalAlign.Center,
|
||||
leftBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
rightBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
topBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
bottomBorder: Border(
|
||||
borderStyle: BorderStyle.Thin,
|
||||
borderColorHex: ExcelColor.fromHexString('#CCCCCC')),
|
||||
);
|
||||
}
|
||||
|
||||
static String _alertBgColor(String level) {
|
||||
switch (level) {
|
||||
case 'Bahaya':
|
||||
return _colorBahaya;
|
||||
case 'Waspada':
|
||||
return _colorWaspada;
|
||||
default:
|
||||
return _colorNormal;
|
||||
}
|
||||
}
|
||||
|
||||
static String _getAlertLevel(double speed) {
|
||||
if (speed >= 12.5) return 'Bahaya';
|
||||
if (speed >= 8.0) return 'Waspada';
|
||||
return 'Normal';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,448 @@
|
|||
// ===========================================================
|
||||
// wind_speed_history_list.dart
|
||||
// Lokasi: lib/screens/monitoring/wind_speed/views/widgets/
|
||||
// ===========================================================
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
|
||||
class WindSpeedHistoryList extends StatelessWidget {
|
||||
final List<MyWindSpeed> history;
|
||||
final DateTime? selectedDate;
|
||||
final VoidCallback onPickDate;
|
||||
final VoidCallback onClearDate;
|
||||
|
||||
const WindSpeedHistoryList({
|
||||
super.key,
|
||||
required this.history,
|
||||
required this.selectedDate,
|
||||
required this.onPickDate,
|
||||
required this.onClearDate,
|
||||
});
|
||||
|
||||
// ── Grouping per tanggal ─────────────────────────────────────
|
||||
Map<String, List<MyWindSpeed>> _groupByDate(List<MyWindSpeed> list) {
|
||||
final map = <String, List<MyWindSpeed>>{};
|
||||
for (final item in list) {
|
||||
final key = DateFormat('yyyy-MM-dd').format(item.timestamp);
|
||||
map.putIfAbsent(key, () => []).add(item);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final grouped = _groupByDate(history);
|
||||
final sortedKeys = grouped.keys.toList()
|
||||
..sort((a, b) => b.compareTo(a)); // terbaru di atas
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ── Header bar ──────────────────────────────────────────
|
||||
_HeaderBar(
|
||||
selectedDate: selectedDate,
|
||||
totalCount: history.length,
|
||||
onPickDate: onPickDate,
|
||||
onClearDate: onClearDate,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
if (history.isEmpty)
|
||||
_EmptyState(hasFilter: selectedDate != null)
|
||||
else
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: sortedKeys.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final dateKey = sortedKeys[idx];
|
||||
final items = grouped[dateKey]!
|
||||
..sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||
final label = _formatDateLabel(dateKey);
|
||||
|
||||
return _DateGroup(
|
||||
label: label,
|
||||
items: items,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDateLabel(String key) {
|
||||
final dt = DateTime.parse(key);
|
||||
final today = DateTime.now();
|
||||
if (dt.year == today.year &&
|
||||
dt.month == today.month &&
|
||||
dt.day == today.day) {
|
||||
return 'Hari Ini — ${DateFormat('dd MMMM yyyy', 'id_ID').format(dt)}';
|
||||
}
|
||||
final yesterday = today.subtract(const Duration(days: 1));
|
||||
if (dt.year == yesterday.year &&
|
||||
dt.month == yesterday.month &&
|
||||
dt.day == yesterday.day) {
|
||||
return 'Kemarin — ${DateFormat('dd MMMM yyyy', 'id_ID').format(dt)}';
|
||||
}
|
||||
return DateFormat('EEEE, dd MMMM yyyy', 'id_ID').format(dt);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Header dengan date picker & counter
|
||||
// ════════════════════════════════════════════════════════════
|
||||
class _HeaderBar extends StatelessWidget {
|
||||
final DateTime? selectedDate;
|
||||
final int totalCount;
|
||||
final VoidCallback onPickDate;
|
||||
final VoidCallback onClearDate;
|
||||
|
||||
const _HeaderBar({
|
||||
required this.selectedDate,
|
||||
required this.totalCount,
|
||||
required this.onPickDate,
|
||||
required this.onClearDate,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filtered = selectedDate != null;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
// Judul + badge jumlah
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Riwayat Data',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87),
|
||||
),
|
||||
Text(
|
||||
filtered
|
||||
? '${DateFormat('dd MMM yyyy', 'id_ID').format(selectedDate!)} • $totalCount data'
|
||||
: '$totalCount data tersimpan',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
|
||||
// Tombol filter tanggal
|
||||
if (filtered)
|
||||
_ChipButton(
|
||||
label: DateFormat('dd MMM yyyy', 'id_ID').format(selectedDate!),
|
||||
icon: Icons.close_rounded,
|
||||
color: Colors.blue.shade700,
|
||||
onTap: onClearDate,
|
||||
)
|
||||
else
|
||||
_ChipButton(
|
||||
label: 'Filter Tanggal',
|
||||
icon: Icons.calendar_month_rounded,
|
||||
color: Colors.blue.shade700,
|
||||
onTap: onPickDate,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChipButton extends StatelessWidget {
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _ChipButton({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: color.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 14, color: color),
|
||||
const SizedBox(width: 6),
|
||||
Text(label,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: color, fontWeight: FontWeight.w600)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Group per tanggal
|
||||
// ════════════════════════════════════════════════════════════
|
||||
class _DateGroup extends StatefulWidget {
|
||||
final String label;
|
||||
final List<MyWindSpeed> items;
|
||||
|
||||
const _DateGroup({required this.label, required this.items});
|
||||
|
||||
@override
|
||||
State<_DateGroup> createState() => _DateGroupState();
|
||||
}
|
||||
|
||||
class _DateGroupState extends State<_DateGroup> {
|
||||
bool _expanded = true; // default terbuka
|
||||
|
||||
double get _avgSpeed {
|
||||
if (widget.items.isEmpty) return 0;
|
||||
return widget.items.map((e) => e.speed).reduce((a, b) => a + b) /
|
||||
widget.items.length;
|
||||
}
|
||||
|
||||
double get _maxSpeed {
|
||||
if (widget.items.isEmpty) return 0;
|
||||
return widget.items.map((e) => e.speed).reduce((a, b) => a > b ? a : b);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2)),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// ── Group header ──────────────────────────────────
|
||||
InkWell(
|
||||
onTap: () => setState(() => _expanded = !_expanded),
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 4,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade600,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(widget.label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 13)),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${widget.items.length} data • rata-rata ${_avgSpeed.toStringAsFixed(2)} m/s • maks ${_maxSpeed.toStringAsFixed(2)} m/s',
|
||||
style: TextStyle(
|
||||
fontSize: 11, color: Colors.grey.shade600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
_expanded
|
||||
? Icons.keyboard_arrow_up_rounded
|
||||
: Icons.keyboard_arrow_down_rounded,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Item list ─────────────────────────────────────
|
||||
if (_expanded)
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: widget.items.length,
|
||||
separatorBuilder: (_, __) =>
|
||||
Divider(height: 1, color: Colors.grey.shade100),
|
||||
itemBuilder: (context, i) =>
|
||||
_HistoryItemTile(item: widget.items[i]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Satu baris data history
|
||||
// ════════════════════════════════════════════════════════════
|
||||
class _HistoryItemTile extends StatelessWidget {
|
||||
final MyWindSpeed item;
|
||||
|
||||
const _HistoryItemTile({required this.item});
|
||||
|
||||
String get _alertLevel {
|
||||
if (item.speed >= 12.5) return 'Bahaya';
|
||||
if (item.speed >= 8.0) return 'Waspada';
|
||||
return 'Normal';
|
||||
}
|
||||
|
||||
Color get _alertColor {
|
||||
switch (_alertLevel) {
|
||||
case 'Bahaya':
|
||||
return Colors.red.shade600;
|
||||
case 'Waspada':
|
||||
return Colors.orange.shade700;
|
||||
default:
|
||||
return Colors.green.shade600;
|
||||
}
|
||||
}
|
||||
|
||||
IconData get _alertIcon {
|
||||
switch (_alertLevel) {
|
||||
case 'Bahaya':
|
||||
return Icons.warning_rounded;
|
||||
case 'Waspada':
|
||||
return Icons.info_outline_rounded;
|
||||
default:
|
||||
return Icons.check_circle_outline_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final kmh = item.speed * 3.6;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
// Jam
|
||||
SizedBox(
|
||||
width: 52,
|
||||
child: Text(
|
||||
DateFormat('HH:mm:ss').format(item.timestamp),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
|
||||
// Speed m/s
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(color: Colors.black87),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: item.speed.toStringAsFixed(3),
|
||||
style: const TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const TextSpan(
|
||||
text: ' m/s',
|
||||
style: TextStyle(fontSize: 11, color: Colors.black54),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${kmh.toStringAsFixed(2)} km/h',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey.shade500),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Badge status
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: _alertColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: _alertColor.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(_alertIcon, size: 12, color: _alertColor),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_alertLevel,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: _alertColor,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Empty state
|
||||
// ════════════════════════════════════════════════════════════
|
||||
class _EmptyState extends StatelessWidget {
|
||||
final bool hasFilter;
|
||||
const _EmptyState({required this.hasFilter});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 40),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
hasFilter ? Icons.search_off_rounded : Icons.inbox_rounded,
|
||||
size: 48,
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
hasFilter
|
||||
? 'Tidak ada data untuk tanggal ini'
|
||||
: 'Belum ada data history',
|
||||
style: TextStyle(color: Colors.grey.shade500, fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,13 @@
|
|||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <printing/printing_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
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);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
printing
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import firebase_core
|
|||
import firebase_database
|
||||
import google_sign_in_ios
|
||||
import printing
|
||||
import share_plus
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin"))
|
||||
|
|
@ -21,4 +22,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
|
||||
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
||||
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,12 +55,15 @@ class FirebaseMonitoringRepo implements MonitoringRepository {
|
|||
return mapper(item as Map<dynamic, dynamic>);
|
||||
}).toList();
|
||||
|
||||
// ✅ Sort by timestamp — Firebase push tidak jamin urutan
|
||||
// ✅ Sort by timestamp — Firebase push tidak selalu menghasilkan urutan kronologis.
|
||||
list.sort((a, b) {
|
||||
if (a is MyWindSpeed && b is MyWindSpeed) {
|
||||
return a.timestamp.compareTo(b.timestamp);
|
||||
try {
|
||||
final aTimestamp = (a as dynamic).timestamp as DateTime;
|
||||
final bTimestamp = (b as dynamic).timestamp as DateTime;
|
||||
return aTimestamp.compareTo(bTimestamp);
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return list;
|
||||
|
|
|
|||
|
|
@ -94,36 +94,70 @@ class Evaporasi {
|
|||
|
||||
final tinggiFiltered = (tinggiVal < 0 || tinggiVal > 100) ? 0.0 : tinggiVal;
|
||||
|
||||
// Default timestamp: fallback now (kalau field waktu tidak ada).
|
||||
// Catatan: untuk Firebase seharusnya timestamp dikirim konsisten (ms atau ISO string).
|
||||
DateTime timestamp = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
DateTime parseTimestamp(dynamic rawTimestamp) {
|
||||
try {
|
||||
// UNIX INT
|
||||
if (rawTimestamp is int) {
|
||||
if (rawTimestamp < 1000000000000) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(
|
||||
rawTimestamp * 1000,
|
||||
).toLocal();
|
||||
}
|
||||
|
||||
// dukung beberapa kemungkinan penamaan timestamp
|
||||
return DateTime.fromMillisecondsSinceEpoch(rawTimestamp).toLocal();
|
||||
}
|
||||
|
||||
// DOUBLE
|
||||
if (rawTimestamp is double) {
|
||||
final value = rawTimestamp.toInt();
|
||||
|
||||
if (value < 1000000000000) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(value * 1000).toLocal();
|
||||
}
|
||||
|
||||
return DateTime.fromMillisecondsSinceEpoch(value).toLocal();
|
||||
}
|
||||
|
||||
// STRING
|
||||
if (rawTimestamp is String) {
|
||||
String s = rawTimestamp.trim();
|
||||
|
||||
// UNIX STRING
|
||||
final unixValue = int.tryParse(s);
|
||||
|
||||
if (unixValue != null) {
|
||||
if (unixValue < 1000000000000) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(
|
||||
unixValue * 1000,
|
||||
).toLocal();
|
||||
}
|
||||
|
||||
return DateTime.fromMillisecondsSinceEpoch(unixValue).toLocal();
|
||||
}
|
||||
|
||||
// FIX FORMAT FIREBASE
|
||||
// 2026-05-14 16:07:23
|
||||
if (s.contains(' ') && !s.contains('T')) {
|
||||
s = s.replaceFirst(' ', 'T');
|
||||
}
|
||||
|
||||
final parsed = DateTime.tryParse(s);
|
||||
|
||||
if (parsed != null) {
|
||||
return parsed.toLocal();
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
|
||||
// Default timestamp: fallback nilai kosong agar tidak pakai waktu lokal sekarang.
|
||||
DateTime timestamp = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
final rawTimestamp = json['timestamp'] ?? json['time'] ?? json['datetime'];
|
||||
|
||||
if (rawTimestamp != null) {
|
||||
if (rawTimestamp is int) {
|
||||
timestamp = DateTime.fromMillisecondsSinceEpoch(rawTimestamp);
|
||||
} else if (rawTimestamp is double) {
|
||||
timestamp = DateTime.fromMillisecondsSinceEpoch(rawTimestamp.toInt());
|
||||
} else if (rawTimestamp is String) {
|
||||
final s = rawTimestamp.trim();
|
||||
// jika string berupa angka (ms/seconds)
|
||||
final unixMs = int.tryParse(s);
|
||||
if (unixMs != null) {
|
||||
// heuristik: kalau nilainya terlalu kecil kemungkinan seconds
|
||||
if (unixMs < 1000000000000) {
|
||||
timestamp = DateTime.fromMillisecondsSinceEpoch(unixMs * 1000);
|
||||
} else {
|
||||
timestamp = DateTime.fromMillisecondsSinceEpoch(unixMs);
|
||||
}
|
||||
} else {
|
||||
final parsed = DateTime.tryParse(s);
|
||||
if (parsed != null) {
|
||||
timestamp = parsed.toLocal();
|
||||
}
|
||||
}
|
||||
}
|
||||
timestamp = parseTimestamp(rawTimestamp);
|
||||
} else {
|
||||
// legacy fallback dari field terpisah
|
||||
final datetimeStr = json['datetime'] as String?;
|
||||
|
|
|
|||
114
pubspec.lock
114
pubspec.lock
|
|
@ -21,10 +21,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.9"
|
||||
version: "3.6.1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -137,6 +137,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -177,6 +185,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
excel:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: excel
|
||||
sha256: "1a15327dcad260d5db21d1f6e04f04838109b39a2f6a84ea486ceda36e468780"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.6"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -193,6 +209,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
ffi_leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi_leak_tracker
|
||||
sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -273,6 +297,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.7+3"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
fl_chart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -404,10 +436,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
version: "4.3.0"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -560,7 +592,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
|
|
@ -647,14 +679,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
|
|
@ -695,6 +719,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: a857d8b1479250aff6b57a51b2c02d31ca05848d441817c43f1640c885c286c0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.1.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: "7f7ae28cf400d13f811e297ff37742dba83b79e0a6f5dce14eec0248274e6ce9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
simple_gesture_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -780,6 +820,38 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
user_repository:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -787,6 +859,14 @@ packages:
|
|||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.3"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -811,6 +891,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: a1fc9eb9248baa05dfc12ed5b66e377b3e23f095eec078e0371622b9033810d9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ dependencies:
|
|||
cupertino_icons: ^1.0.8
|
||||
dio: ^5.9.2
|
||||
equatable: ^2.0.5
|
||||
excel: ^4.0.6
|
||||
firebase_auth: ^6.1.3
|
||||
firebase_core: ^4.3.0
|
||||
firebase_database: ^12.1.3
|
||||
fl_chart: ^1.1.1
|
||||
table_calendar: ^3.1.2
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_bloc: ^8.1.0
|
||||
|
|
@ -26,9 +26,12 @@ dependencies:
|
|||
intl: ^0.20.2
|
||||
monitoring_repository:
|
||||
path: packages/monitoring_repository
|
||||
path_provider: ^2.1.5
|
||||
pdf: ^3.11.1
|
||||
printing: ^5.13.1
|
||||
rxdart: ^0.27.7
|
||||
share_plus: ^13.1.0
|
||||
table_calendar: ^3.1.2
|
||||
user_repository:
|
||||
path: packages/user_repository
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
CloudFirestorePluginCApiRegisterWithRegistrar(
|
||||
|
|
@ -20,4 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||
PrintingPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PrintingPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
firebase_auth
|
||||
firebase_core
|
||||
printing
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
|
|||
Loading…
Reference in New Issue