Merge branch 'branch' of https://github.com/kleponijo/klimatologi into branch
This commit is contained in:
commit
d4d07399a0
|
|
@ -1,7 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:klimatologiot/simple_bloc_observer.dart';
|
||||
import 'package:klimatologiot/app.dart';
|
||||
import 'package:user_repository/user_repository.dart';
|
||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
|
|
@ -14,7 +12,6 @@ void main() async {
|
|||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
await initializeDateFormatting('id_ID', null);
|
||||
Bloc.observer = SimpleBlocObserver();
|
||||
runApp(MyApp(
|
||||
FirebaseUserRepo(),
|
||||
FirebaseMonitoringRepo(),
|
||||
|
|
|
|||
|
|
@ -82,18 +82,16 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
const tempMin = 0.0;
|
||||
const tempMax = 30.0;
|
||||
|
||||
// Deduplicate X by index for evaporasi
|
||||
final evapSpotsAll = dailyValues.asMap().entries.map((e) {
|
||||
return FlSpot(e.key.toDouble(), _safeValue(e.value));
|
||||
}).toList();
|
||||
// Evaporasi mm biasanya >= 0, kita pakai min 0 agar estetik.
|
||||
final evapMin = 0.0;
|
||||
final evapMax = _clampDouble(evapMaxRaw * 1.15, 8.0, 50.0);
|
||||
|
||||
final Map<int, double> evapByX = {};
|
||||
for (final s in evapSpotsAll) {
|
||||
evapByX[s.x.toInt()] = s.y;
|
||||
}
|
||||
|
||||
final dedupEvapSpots = evapByX.entries
|
||||
.toList()
|
||||
final dedupEvapSpots = evapByX.entries.toList()
|
||||
..sort((a, b) => a.key.compareTo(b.key));
|
||||
|
||||
final tempByX = <int, double>{};
|
||||
|
|
@ -114,12 +112,8 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
return FlSpot(x, y);
|
||||
}).toList();
|
||||
|
||||
final evapSpots = dedupEvapSpots
|
||||
.map((e) => FlSpot(
|
||||
e.key.toDouble(),
|
||||
e.value.clamp(evapMin, evapMax),
|
||||
))
|
||||
.toList();
|
||||
final evapSpots =
|
||||
dedupEvapSpots.map((e) => FlSpot(e.key.toDouble(), e.value)).toList();
|
||||
|
||||
double getRightTitle(double y) {
|
||||
return _evapScaleToTemp(
|
||||
|
|
@ -135,7 +129,17 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
LineChartData(
|
||||
minY: evapMin,
|
||||
maxY: evapMax,
|
||||
gridData: FlGridData(show: false),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: (evapMax - evapMin) / 4,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return FlLine(
|
||||
color: Colors.grey.withOpacity(0.15),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
|
||||
|
||||
|
|
@ -144,7 +148,11 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 32,
|
||||
interval: 1,
|
||||
interval: period == 'Hari Ini'
|
||||
? 3
|
||||
: period == 'Minggu Ini'
|
||||
? 1
|
||||
: 5,
|
||||
getTitlesWidget: (value, meta) {
|
||||
final index = value.toInt();
|
||||
return Padding(
|
||||
|
|
@ -200,16 +208,34 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
|
||||
getTooltipItems: (spots) {
|
||||
return spots.map((spot) {
|
||||
final temp = _evapScaleToTemp(
|
||||
yEvap: spot.y,
|
||||
evapMin: evapMin,
|
||||
evapMax: evapMax,
|
||||
tempMin: tempMin,
|
||||
tempMax: tempMax,
|
||||
);
|
||||
final isTemp = spot.bar.color == Colors.orange.shade700;
|
||||
|
||||
if (isTemp) {
|
||||
final temp = _evapScaleToTemp(
|
||||
yEvap: spot.y,
|
||||
evapMin: evapMin,
|
||||
evapMax: evapMax,
|
||||
tempMin: tempMin,
|
||||
tempMax: tempMax,
|
||||
);
|
||||
|
||||
return LineTooltipItem(
|
||||
'🌡 Suhu\n${temp.toStringAsFixed(1)} °C',
|
||||
const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return LineTooltipItem(
|
||||
'Evap: ${spot.y.toStringAsFixed(1)} mm\nSuhu: ${temp.toStringAsFixed(1)} °C',
|
||||
const TextStyle(color: Colors.white, fontSize: 12),
|
||||
'💧 Evaporasi\n${spot.y.toStringAsFixed(1)} mm',
|
||||
const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
|
|
@ -219,27 +245,47 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
if (evapSpots.isNotEmpty)
|
||||
LineChartBarData(
|
||||
spots: evapSpots,
|
||||
isCurved: false,
|
||||
isCurved: true,
|
||||
curveSmoothness: 0.35,
|
||||
isStrokeCapRound: true,
|
||||
color: Colors.blue.shade700,
|
||||
barWidth: 2,
|
||||
dotData: FlDotData(show: false),
|
||||
belowBarData: BarAreaData(show: false),
|
||||
|
||||
|
||||
barWidth: 3,
|
||||
showingIndicators: [0],
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
checkToShowDot: (spot, barData) => false,
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.blue.withAlpha(64),
|
||||
Colors.blue.withAlpha(13),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (tempSpots.isNotEmpty)
|
||||
LineChartBarData(
|
||||
spots: tempSpots,
|
||||
isCurved: false,
|
||||
isCurved: true,
|
||||
curveSmoothness: 0.35,
|
||||
isStrokeCapRound: true,
|
||||
color: Colors.orange.shade700,
|
||||
barWidth: 2,
|
||||
dotData: FlDotData(show: false),
|
||||
belowBarData: BarAreaData(show: false),
|
||||
|
||||
barWidth: 3,
|
||||
showingIndicators: [0],
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
checkToShowDot: (spot, barData) => false,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeOutCubic,
|
||||
);
|
||||
|
||||
return Container(
|
||||
|
|
@ -258,16 +304,20 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
// Legend
|
||||
Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.blue,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade700,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
|
|
@ -283,12 +333,13 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.orange,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade700,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
|
|
@ -305,6 +356,19 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Text(
|
||||
'Monitoring Evaporasi & Suhu',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blueGrey.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
|
|
@ -317,4 +381,3 @@ class EvaporasiChartWidget extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,21 +19,26 @@ class EvaporasiPeriodSelector extends StatelessWidget {
|
|||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildTab(context, "Hari Ini", selectedPeriod, viewMode),
|
||||
_buildTab(context, "Minggu Ini", selectedPeriod, viewMode),
|
||||
_buildTab(context, "Bulan Ini", selectedPeriod, viewMode),
|
||||
const SizedBox(width: 8),
|
||||
// Date picker button
|
||||
_buildDatePickerButton(context, viewMode, selectedDate),
|
||||
],
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildTab(context, "Hari Ini", selectedPeriod, viewMode),
|
||||
_buildTab(context, "Minggu Ini", selectedPeriod, viewMode),
|
||||
_buildTab(context, "Bulan Ini", selectedPeriod, viewMode),
|
||||
const SizedBox(width: 8),
|
||||
// Date picker button
|
||||
_buildDatePickerButton(context, viewMode, selectedDate),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab(BuildContext context, String label, String current, EvaporasiViewMode viewMode) {
|
||||
Widget _buildTab(BuildContext context, String label, String current,
|
||||
EvaporasiViewMode viewMode) {
|
||||
// If in customDate mode, show period tabs as inactive
|
||||
bool isActive = viewMode == EvaporasiViewMode.period && label == current;
|
||||
|
||||
|
|
@ -59,7 +64,8 @@ class EvaporasiPeriodSelector extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode, DateTime? selectedDate) {
|
||||
Widget _buildDatePickerButton(BuildContext context,
|
||||
EvaporasiViewMode viewMode, DateTime? selectedDate) {
|
||||
final isActive = viewMode == EvaporasiViewMode.customDate;
|
||||
|
||||
return GestureDetector(
|
||||
|
|
@ -67,8 +73,8 @@ Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode,
|
|||
// Set mode ke customDate SEBELUM membuka date picker
|
||||
final bloc = context.read<EvaporasiBloc>();
|
||||
bloc.add(
|
||||
const EvaporasiViewModeChanged(EvaporasiViewMode.customDate),
|
||||
);
|
||||
const EvaporasiViewModeChanged(EvaporasiViewMode.customDate),
|
||||
);
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
|
|
@ -79,7 +85,6 @@ Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode,
|
|||
child: const EvaporasiDatePicker(),
|
||||
),
|
||||
);
|
||||
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
|
|
@ -116,4 +121,3 @@ Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode,
|
|||
return "${date.day}/${date.month}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ class FirebaseMonitoringRepo implements MonitoringRepository {
|
|||
final Map<dynamic, dynamic> data = snapshot.value as Map;
|
||||
|
||||
final list = data.values.map((item) {
|
||||
print(item);
|
||||
return mapper(item as Map<dynamic, dynamic>);
|
||||
}).toList();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue