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:flutter/material.dart';
|
||||||
import 'package:firebase_core/firebase_core.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: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';
|
||||||
|
|
@ -14,7 +12,6 @@ void main() async {
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
await initializeDateFormatting('id_ID', null);
|
await initializeDateFormatting('id_ID', null);
|
||||||
Bloc.observer = SimpleBlocObserver();
|
|
||||||
runApp(MyApp(
|
runApp(MyApp(
|
||||||
FirebaseUserRepo(),
|
FirebaseUserRepo(),
|
||||||
FirebaseMonitoringRepo(),
|
FirebaseMonitoringRepo(),
|
||||||
|
|
|
||||||
|
|
@ -82,18 +82,16 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
const tempMin = 0.0;
|
const tempMin = 0.0;
|
||||||
const tempMax = 30.0;
|
const tempMax = 30.0;
|
||||||
|
|
||||||
// Deduplicate X by index for evaporasi
|
// Evaporasi mm biasanya >= 0, kita pakai min 0 agar estetik.
|
||||||
final evapSpotsAll = dailyValues.asMap().entries.map((e) {
|
final evapMin = 0.0;
|
||||||
return FlSpot(e.key.toDouble(), _safeValue(e.value));
|
final evapMax = _clampDouble(evapMaxRaw * 1.15, 8.0, 50.0);
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final Map<int, double> evapByX = {};
|
final Map<int, double> evapByX = {};
|
||||||
for (final s in evapSpotsAll) {
|
for (final s in evapSpotsAll) {
|
||||||
evapByX[s.x.toInt()] = s.y;
|
evapByX[s.x.toInt()] = s.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
final dedupEvapSpots = evapByX.entries
|
final dedupEvapSpots = evapByX.entries.toList()
|
||||||
.toList()
|
|
||||||
..sort((a, b) => a.key.compareTo(b.key));
|
..sort((a, b) => a.key.compareTo(b.key));
|
||||||
|
|
||||||
final tempByX = <int, double>{};
|
final tempByX = <int, double>{};
|
||||||
|
|
@ -114,12 +112,8 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
return FlSpot(x, y);
|
return FlSpot(x, y);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
final evapSpots = dedupEvapSpots
|
final evapSpots =
|
||||||
.map((e) => FlSpot(
|
dedupEvapSpots.map((e) => FlSpot(e.key.toDouble(), e.value)).toList();
|
||||||
e.key.toDouble(),
|
|
||||||
e.value.clamp(evapMin, evapMax),
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
double getRightTitle(double y) {
|
double getRightTitle(double y) {
|
||||||
return _evapScaleToTemp(
|
return _evapScaleToTemp(
|
||||||
|
|
@ -135,7 +129,17 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
LineChartData(
|
LineChartData(
|
||||||
minY: evapMin,
|
minY: evapMin,
|
||||||
maxY: evapMax,
|
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),
|
borderData: FlBorderData(show: false),
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -144,7 +148,11 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
reservedSize: 32,
|
reservedSize: 32,
|
||||||
interval: 1,
|
interval: period == 'Hari Ini'
|
||||||
|
? 3
|
||||||
|
: period == 'Minggu Ini'
|
||||||
|
? 1
|
||||||
|
: 5,
|
||||||
getTitlesWidget: (value, meta) {
|
getTitlesWidget: (value, meta) {
|
||||||
final index = value.toInt();
|
final index = value.toInt();
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|
@ -200,6 +208,9 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
|
|
||||||
getTooltipItems: (spots) {
|
getTooltipItems: (spots) {
|
||||||
return spots.map((spot) {
|
return spots.map((spot) {
|
||||||
|
final isTemp = spot.bar.color == Colors.orange.shade700;
|
||||||
|
|
||||||
|
if (isTemp) {
|
||||||
final temp = _evapScaleToTemp(
|
final temp = _evapScaleToTemp(
|
||||||
yEvap: spot.y,
|
yEvap: spot.y,
|
||||||
evapMin: evapMin,
|
evapMin: evapMin,
|
||||||
|
|
@ -207,9 +218,24 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
tempMin: tempMin,
|
tempMin: tempMin,
|
||||||
tempMax: tempMax,
|
tempMax: tempMax,
|
||||||
);
|
);
|
||||||
|
|
||||||
return LineTooltipItem(
|
return LineTooltipItem(
|
||||||
'Evap: ${spot.y.toStringAsFixed(1)} mm\nSuhu: ${temp.toStringAsFixed(1)} °C',
|
'🌡 Suhu\n${temp.toStringAsFixed(1)} °C',
|
||||||
const TextStyle(color: Colors.white, fontSize: 12),
|
const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LineTooltipItem(
|
||||||
|
'💧 Evaporasi\n${spot.y.toStringAsFixed(1)} mm',
|
||||||
|
const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
},
|
},
|
||||||
|
|
@ -219,27 +245,47 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
if (evapSpots.isNotEmpty)
|
if (evapSpots.isNotEmpty)
|
||||||
LineChartBarData(
|
LineChartBarData(
|
||||||
spots: evapSpots,
|
spots: evapSpots,
|
||||||
isCurved: false,
|
isCurved: true,
|
||||||
|
curveSmoothness: 0.35,
|
||||||
|
isStrokeCapRound: true,
|
||||||
color: Colors.blue.shade700,
|
color: Colors.blue.shade700,
|
||||||
barWidth: 2,
|
barWidth: 3,
|
||||||
dotData: FlDotData(show: false),
|
showingIndicators: [0],
|
||||||
belowBarData: BarAreaData(show: false),
|
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)
|
if (tempSpots.isNotEmpty)
|
||||||
LineChartBarData(
|
LineChartBarData(
|
||||||
spots: tempSpots,
|
spots: tempSpots,
|
||||||
isCurved: false,
|
isCurved: true,
|
||||||
|
curveSmoothness: 0.35,
|
||||||
|
isStrokeCapRound: true,
|
||||||
color: Colors.orange.shade700,
|
color: Colors.orange.shade700,
|
||||||
barWidth: 2,
|
barWidth: 3,
|
||||||
dotData: FlDotData(show: false),
|
showingIndicators: [0],
|
||||||
belowBarData: BarAreaData(show: false),
|
dotData: FlDotData(
|
||||||
|
show: false,
|
||||||
|
checkToShowDot: (spot, barData) => false,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
duration: const Duration(milliseconds: 350),
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
|
@ -258,16 +304,20 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
// Legend
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
Wrap(
|
||||||
|
spacing: 16,
|
||||||
|
runSpacing: 8,
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 10,
|
width: 10,
|
||||||
height: 10,
|
height: 10,
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue,
|
color: Colors.blue.shade700,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -283,12 +333,13 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 10,
|
width: 10,
|
||||||
height: 10,
|
height: 10,
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange,
|
color: Colors.orange.shade700,
|
||||||
shape: BoxShape.circle,
|
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),
|
const SizedBox(height: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
|
|
@ -317,4 +381,3 @@ class EvaporasiChartWidget extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ class EvaporasiPeriodSelector extends StatelessWidget {
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -30,10 +33,12 @@ class EvaporasiPeriodSelector extends StatelessWidget {
|
||||||
_buildDatePickerButton(context, viewMode, selectedDate),
|
_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
|
// If in customDate mode, show period tabs as inactive
|
||||||
bool isActive = viewMode == EvaporasiViewMode.period && label == current;
|
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;
|
final isActive = viewMode == EvaporasiViewMode.customDate;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
|
@ -79,7 +85,6 @@ Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode,
|
||||||
child: const EvaporasiDatePicker(),
|
child: const EvaporasiDatePicker(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
|
@ -116,4 +121,3 @@ Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode,
|
||||||
return "${date.day}/${date.month}";
|
return "${date.day}/${date.month}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ class FirebaseMonitoringRepo implements MonitoringRepository {
|
||||||
final Map<dynamic, dynamic> data = snapshot.value as Map;
|
final Map<dynamic, dynamic> data = snapshot.value as Map;
|
||||||
|
|
||||||
final list = data.values.map((item) {
|
final list = data.values.map((item) {
|
||||||
print(item);
|
|
||||||
return mapper(item as Map<dynamic, dynamic>);
|
return mapper(item as Map<dynamic, dynamic>);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue