This commit is contained in:
kleponijo 2026-05-01 18:05:30 +07:00
parent a60c6af54b
commit 469c500053
3 changed files with 401 additions and 99 deletions

View File

@ -1,8 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:monitoring_repository/monitoring_repository.dart';
import '../../../blocs/authentication_bloc/authentication_bloc.dart';
import '../../../blocs/notification_bloc/notification_bloc.dart';
import '../../monitoring/wind_speed/blocs/wind_speed_bloc.dart';
import '../../monitoring/evaporasi/blocs/evaporasi_bloc.dart';
import '../../monitoring/atmospheric_conditions/blocs/atmospheric_conditions_bloc.dart';
import '../widgets/notification_panel.dart';
import '../widgets/sensor_grid.dart';
import 'main_drawer.dart';
class HomeScreen extends StatefulWidget {
@ -50,8 +55,27 @@ class _HomeScreenState extends State<HomeScreen>
@override
Widget build(BuildContext context) {
return GestureDetector(
// Tap di luar panel tutup
return MultiBlocProvider(
providers: [
BlocProvider<WindSpeedBloc>(
create: (context) => WindSpeedBloc(
repository: context.read<MonitoringRepository>(),
notificationBloc: context.read<NotificationBloc>(),
)..add(WatchWindSpeedStarted()),
),
BlocProvider<EvaporasiBloc>(
create: (context) => EvaporasiBloc(
repository: context.read<MonitoringRepository>(),
notificationBloc: context.read<NotificationBloc>(),
)..add(WatchEvaporasiStarted()),
),
BlocProvider<AtmosphericConditionsBloc>(
create: (context) => AtmosphericConditionsBloc(
repository: context.read<MonitoringRepository>(),
)..add(WatchAtmosphericConditionsStarted()),
),
],
child: GestureDetector(
onTap: () {
if (_showNotifications) _toggleNotifications();
},
@ -67,15 +91,11 @@ class _HomeScreenState extends State<HomeScreen>
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
title: Row(
children: [
Image.asset(
title: Image.asset(
'images/logo_klimatologi.png',
height: 90,
fit: BoxFit.contain,
),
],
),
actions: [
// Icon lonceng dengan badge
BlocBuilder<NotificationBloc, NotificationState>(
@ -137,8 +157,7 @@ class _HomeScreenState extends State<HomeScreen>
body: Stack(
children: [
// Konten utama
const Center(child: Text('body home page')),
const _HomeBody(),
// Dropdown notification panel
if (_showNotifications)
Positioned(
@ -156,6 +175,32 @@ class _HomeScreenState extends State<HomeScreen>
],
),
),
),
);
}
}
class _HomeBody extends StatelessWidget {
const _HomeBody();
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Monitoring Realtime',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: Colors.grey.shade700,
),
),
const SizedBox(height: 12),
const SensorGrid(),
],
),
);
}
}

View File

@ -0,0 +1,257 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../monitoring/wind_speed/blocs/wind_speed_bloc.dart';
import '../../monitoring/evaporasi/blocs/evaporasi_bloc.dart';
import '../../monitoring/atmospheric_conditions/blocs/atmospheric_conditions_bloc.dart';
class SensorGrid extends StatelessWidget {
const SensorGrid({super.key});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// Lebar card: 2 kolom dengan gap 12
final cardWidth = (constraints.maxWidth - 12) / 2;
return Wrap(
spacing: 12,
runSpacing: 12,
children: [
// --- ANEMOMETER ---
BlocBuilder<WindSpeedBloc, WindSpeedState>(
builder: (context, state) => SensorCard(
width: cardWidth,
icon: Icons.air,
iconColor: Colors.blue.shade600,
iconBgColor: Colors.blue.shade50,
label: 'Kecepatan Angin',
value: state.isLoading
? ''
: state.currentSpeed.toStringAsFixed(1),
unit: 'm/s',
isLoading: state.isLoading,
),
),
// --- EVAPORASI ---
BlocBuilder<EvaporasiBloc, EvaporasiState>(
builder: (context, state) => SensorCard(
width: cardWidth,
icon: Icons.water_drop_outlined,
iconColor: Colors.teal.shade600,
iconBgColor: Colors.teal.shade50,
label: 'Evaporasi',
value: state.isLoading
? ''
: state.currentValue.toStringAsFixed(1),
unit: 'mm',
isLoading: state.isLoading,
),
),
// --- SUHU AIR (dari Evaporasi) ---
BlocBuilder<EvaporasiBloc, EvaporasiState>(
builder: (context, state) => SensorCard(
width: cardWidth,
icon: Icons.thermostat_outlined,
iconColor: Colors.orange.shade600,
iconBgColor: Colors.orange.shade50,
label: 'Suhu Air',
value: state.isLoading
? ''
: state.temperature.toStringAsFixed(1),
unit: '°C',
isLoading: state.isLoading,
),
),
// --- TINGGI AIR (dari Evaporasi) ---
BlocBuilder<EvaporasiBloc, EvaporasiState>(
builder: (context, state) => SensorCard(
width: cardWidth,
icon: Icons.straighten_outlined,
iconColor: Colors.cyan.shade600,
iconBgColor: Colors.cyan.shade50,
label: 'Tinggi Air',
value:
state.isLoading ? '' : state.waterLevel.toStringAsFixed(1),
unit: 'cm',
isLoading: state.isLoading,
),
),
// --- SUHU UDARA (dari Atmospheric) ---
BlocBuilder<AtmosphericConditionsBloc, AtmosphericConditionsState>(
builder: (context, state) => SensorCard(
width: cardWidth,
icon: Icons.device_thermostat,
iconColor: Colors.red.shade400,
iconBgColor: Colors.red.shade50,
label: 'Suhu Udara',
value: state.isLoading
? ''
: state.temperature.toStringAsFixed(1),
unit: '°C',
isLoading: state.isLoading,
),
),
// --- KELEMBAPAN ---
BlocBuilder<AtmosphericConditionsBloc, AtmosphericConditionsState>(
builder: (context, state) => SensorCard(
width: cardWidth,
icon: Icons.water_outlined,
iconColor: Colors.indigo.shade400,
iconBgColor: Colors.indigo.shade50,
label: 'Kelembapan',
value:
state.isLoading ? '' : state.humidity.toStringAsFixed(1),
unit: '%',
isLoading: state.isLoading,
),
),
// --- TEKANAN UDARA ---
BlocBuilder<AtmosphericConditionsBloc, AtmosphericConditionsState>(
builder: (context, state) => SensorCard(
width: cardWidth,
icon: Icons.compress,
iconColor: Colors.purple.shade400,
iconBgColor: Colors.purple.shade50,
label: 'Tekanan Udara',
value:
state.isLoading ? '' : state.pressure.toStringAsFixed(1),
unit: 'hPa',
isLoading: state.isLoading,
),
),
// --- KETINGGIAN ---
BlocBuilder<AtmosphericConditionsBloc, AtmosphericConditionsState>(
builder: (context, state) => SensorCard(
width: cardWidth,
icon: Icons.terrain_outlined,
iconColor: Colors.green.shade600,
iconBgColor: Colors.green.shade50,
label: 'Ketinggian',
value:
state.isLoading ? '' : state.altitude.toStringAsFixed(1),
unit: 'm',
isLoading: state.isLoading,
),
),
],
);
},
);
}
}
class SensorCard extends StatelessWidget {
final double width;
final IconData icon;
final Color iconColor;
final Color iconBgColor;
final String label;
final String value;
final String unit;
final bool isLoading;
const SensorCard({
super.key,
required this.width,
required this.icon,
required this.iconColor,
required this.iconBgColor,
required this.label,
required this.value,
required this.unit,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Ikon
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: iconBgColor,
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: iconColor, size: 20),
),
const SizedBox(height: 12),
// Nilai + satuan
isLoading
? Container(
height: 24,
width: 60,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(4),
),
)
: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
child: Text(
value,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
height: 1,
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 3),
Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Text(
unit,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
fontWeight: FontWeight.w500,
),
),
),
],
),
const SizedBox(height: 4),
// Label
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
}

View File

@ -11,8 +11,8 @@ part 'wind_speed_state.dart';
/// Threshold kecepatan angin (satuan m/s)
/// Referensi: Beaufort scale & BMKG
const double _kWindWarning = 4.0; // Waspada: 812.5 m/s (~2945 km/h)
const double _kWindDanger = 5.5; // Bahaya: > 12.5 m/s (> 45 km/h)
const double _kWindWarning = 2.0; // Waspada: 812.5 m/s (~2945 km/h)
const double _kWindDanger = 4.0; // Bahaya: > 12.5 m/s (> 45 km/h)
class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
final MonitoringRepository _repository;