update search periode
This commit is contained in:
parent
08aefa5634
commit
2d7ee2ea98
37
TODO.md
37
TODO.md
|
|
@ -1,11 +1,30 @@
|
|||
# TODO: Fitur Baru Evaporasi (Period Selector + Status + Notifikasi)
|
||||
# TODO - Evaporasi Date Picker Feature
|
||||
|
||||
## Steps
|
||||
- [x] 1. Edit `evaporasi_state.dart` — tambahkan `weatherStatus` & `willRain`
|
||||
- [x] 2. Edit `evaporasi_bloc.dart` — hitung status & update global notifier
|
||||
- [x] 3. Create `notification_notifier.dart` — global ValueNotifier untuk alert
|
||||
- [x] 4. Create `evaporasi_period_selector.dart` — tombol periode
|
||||
- [x] 5. Edit `evaporasi_screen.dart` — tambahkan period selector & status card
|
||||
- [x] 6. Edit `home_screen.dart` — badge merah di icon lonceng
|
||||
- [x] 7. Verifikasi kompilasi
|
||||
## Status: COMPLETED ✅
|
||||
|
||||
### Steps:
|
||||
|
||||
- [x] 1. Add table_calendar package to pubspec.yaml
|
||||
- [x] 2. Update EvaporasiState - add selectedDate and EvaporasiViewMode
|
||||
- [x] 3. Update EvaporasiEvent - add EvaporasiDateSelected and EvaporasiViewModeChanged
|
||||
- [x] 4. Update EvaporasiBloc - handle date selection + view mode
|
||||
- [x] 5. Add toSpecificDate function to TimeSeriesMapper
|
||||
- [x] 6. Create EvaporasiDatePicker widget (WhatsApp-style calendar)
|
||||
- [x] 7. Update EvaporasiPeriodSelector - add date picker button
|
||||
- [x] 8. Update EvaporasiScreen - integrate date picker + show selected date
|
||||
- [x] 9. Update EvaporasiChartWidget - handle custom date display (already supports "Tanggal Khusus")
|
||||
|
||||
### Summary:
|
||||
|
||||
Feature implemented successfully:
|
||||
|
||||
1. **Date Picker Button**: Added next to period tabs (Hari Ini, Minggu Ini, Bulan Ini)
|
||||
2. **WhatsApp-style Calendar**: Using table_calendar with Indonesian format
|
||||
3. **Custom Date Display**: Shows selected date above the period selector
|
||||
4. **24-hour Chart**: For custom date, shows hourly data (same as "Hari Ini")
|
||||
5. **Period Mode**: Users can switch back to period mode by clicking any period tab
|
||||
|
||||
### Notes:
|
||||
- Using table_calendar: ^3.1.2
|
||||
- Date format: Indonesian locale (id_ID)
|
||||
- Similar to WhatsApp chat date picker functionality
|
||||
|
|
|
|||
|
|
@ -91,6 +91,34 @@ class TimeSeriesMapper {
|
|||
});
|
||||
}
|
||||
|
||||
/// =========================
|
||||
/// 📅 SPECIFIC DATE (24 JAM - TANGGAL KHUSUS)
|
||||
/// =========================
|
||||
static List<double> toSpecificDate<T>({
|
||||
required List<T> data,
|
||||
required DateTime Function(T) getTime,
|
||||
required double Function(T) getValue,
|
||||
required DateTime targetDate,
|
||||
}) {
|
||||
final sums = List<double>.filled(24, 0.0);
|
||||
final counts = List<int>.filled(24, 0);
|
||||
|
||||
for (final item in data) {
|
||||
final time = getTime(item);
|
||||
|
||||
if (_isSameDay(time, targetDate)) {
|
||||
final hour = time.hour;
|
||||
sums[hour] += getValue(item);
|
||||
counts[hour]++;
|
||||
}
|
||||
}
|
||||
|
||||
return List.generate(24, (i) {
|
||||
if (counts[i] == 0) return 0;
|
||||
return sums[i] / counts[i];
|
||||
});
|
||||
}
|
||||
|
||||
/// =========================
|
||||
/// 🧠 HELPER
|
||||
/// =========================
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ class EvaporasiBloc extends Bloc<EvaporasiEvent, EvaporasiState> {
|
|||
on<WatchEvaporasiStarted>(_onStarted);
|
||||
on<_EvaporasiRealtimeUpdated>(_onRealtimeUpdated);
|
||||
on<EvaporasiPeriodChanged>(_onPeriodChanged);
|
||||
on<EvaporasiDateSelected>(_onDateSelected);
|
||||
on<EvaporasiViewModeChanged>(_onViewModeChanged);
|
||||
}
|
||||
|
||||
/// =========================
|
||||
|
|
@ -149,7 +151,7 @@ class EvaporasiBloc extends Bloc<EvaporasiEvent, EvaporasiState> {
|
|||
);
|
||||
}
|
||||
|
||||
// Pertahankan status cuaca saat ini (tidak berubah karena hanya ganti periode)
|
||||
// Pertahankan status cuaca saat ini (tidak berubah karena hanya ganti periode)
|
||||
emit(state.copyWith(
|
||||
dailyValues: updated,
|
||||
dailyTemperatures: updatedTemp,
|
||||
|
|
@ -157,6 +159,57 @@ class EvaporasiBloc extends Bloc<EvaporasiEvent, EvaporasiState> {
|
|||
));
|
||||
}
|
||||
|
||||
/// =========================
|
||||
/// 📅 DATE SELECTED (Custom Date)
|
||||
/// =========================
|
||||
Future<void> _onDateSelected(
|
||||
EvaporasiDateSelected event,
|
||||
Emitter<EvaporasiState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(
|
||||
isLoading: true,
|
||||
selectedDate: event.date,
|
||||
viewMode: EvaporasiViewMode.customDate,
|
||||
));
|
||||
|
||||
final history = state.history;
|
||||
|
||||
final updated = TimeSeriesMapper.toSpecificDate(
|
||||
data: history,
|
||||
getTime: (e) => e.timestamp,
|
||||
getValue: (e) => e.evaporasi,
|
||||
targetDate: event.date,
|
||||
);
|
||||
|
||||
final updatedTemp = TimeSeriesMapper.toSpecificDate(
|
||||
data: history,
|
||||
getTime: (e) => e.timestamp,
|
||||
getValue: (e) => e.suhu,
|
||||
targetDate: event.date,
|
||||
);
|
||||
|
||||
emit(state.copyWith(
|
||||
dailyValues: updated,
|
||||
dailyTemperatures: updatedTemp,
|
||||
isLoading: false,
|
||||
));
|
||||
}
|
||||
|
||||
/// =========================
|
||||
/// 🔄 VIEW MODE CHANGED
|
||||
/// =========================
|
||||
Future<void> _onViewModeChanged(
|
||||
EvaporasiViewModeChanged event,
|
||||
Emitter<EvaporasiState> emit,
|
||||
) async {
|
||||
if (event.mode == EvaporasiViewMode.period) {
|
||||
// Kembali ke mode period - trigger period change
|
||||
add(EvaporasiPeriodChanged(state.selectedPeriod));
|
||||
} else {
|
||||
emit(state.copyWith(viewMode: event.mode));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _subscription?.cancel();
|
||||
|
|
|
|||
|
|
@ -19,3 +19,23 @@ class EvaporasiPeriodChanged extends EvaporasiEvent {
|
|||
@override
|
||||
List<Object> get props => [period];
|
||||
}
|
||||
|
||||
/// 📅 PILIH TANGGAL KHUSUS (Custom Date Picker)
|
||||
class EvaporasiDateSelected extends EvaporasiEvent {
|
||||
final DateTime date;
|
||||
|
||||
const EvaporasiDateSelected(this.date);
|
||||
|
||||
@override
|
||||
List<Object> get props => [date];
|
||||
}
|
||||
|
||||
/// 🔄 KEMBALI KE MODE PERIOD
|
||||
class EvaporasiViewModeChanged extends EvaporasiEvent {
|
||||
final EvaporasiViewMode mode;
|
||||
|
||||
const EvaporasiViewModeChanged(this.mode);
|
||||
|
||||
@override
|
||||
List<Object> get props => [mode];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
part of 'evaporasi_bloc.dart';
|
||||
|
||||
enum EvaporasiViewMode { period, customDate }
|
||||
|
||||
class EvaporasiState extends Equatable {
|
||||
final double currentValue; // nilai evaporasi realtime
|
||||
final double temperature; // suhu (opsional dari firebase)
|
||||
final double waterLevel; // tinggi air
|
||||
final String selectedPeriod;
|
||||
final DateTime? selectedDate; // tanggal spesifik untuk custom date
|
||||
final EvaporasiViewMode viewMode; // period vs custom date
|
||||
|
||||
final List<double> dailyValues; // untuk grafik evaporasi
|
||||
final List<double> dailyTemperatures; // untuk grafik suhu
|
||||
|
|
@ -20,6 +24,8 @@ class EvaporasiState extends Equatable {
|
|||
this.temperature = 0.0,
|
||||
this.waterLevel = 0.0,
|
||||
this.selectedPeriod = "Hari Ini",
|
||||
this.selectedDate,
|
||||
this.viewMode = EvaporasiViewMode.period,
|
||||
this.dailyValues = const [],
|
||||
this.dailyTemperatures = const [],
|
||||
this.history = const [],
|
||||
|
|
@ -28,11 +34,13 @@ class EvaporasiState extends Equatable {
|
|||
this.isLoading = true,
|
||||
});
|
||||
|
||||
EvaporasiState copyWith({
|
||||
EvaporasiState copyWith({
|
||||
double? currentValue,
|
||||
double? temperature,
|
||||
double? waterLevel,
|
||||
String? selectedPeriod,
|
||||
DateTime? selectedDate,
|
||||
EvaporasiViewMode? viewMode,
|
||||
List<double>? dailyValues,
|
||||
List<double>? dailyTemperatures,
|
||||
List<Evaporasi>? history,
|
||||
|
|
@ -45,6 +53,8 @@ class EvaporasiState extends Equatable {
|
|||
temperature: temperature ?? this.temperature,
|
||||
waterLevel: waterLevel ?? this.waterLevel,
|
||||
selectedPeriod: selectedPeriod ?? this.selectedPeriod,
|
||||
selectedDate: selectedDate ?? this.selectedDate,
|
||||
viewMode: viewMode ?? this.viewMode,
|
||||
dailyValues: dailyValues ?? this.dailyValues,
|
||||
dailyTemperatures: dailyTemperatures ?? this.dailyTemperatures,
|
||||
history: history ?? this.history,
|
||||
|
|
@ -55,11 +65,13 @@ class EvaporasiState extends Equatable {
|
|||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
List<Object?> get props => [
|
||||
currentValue,
|
||||
temperature,
|
||||
waterLevel,
|
||||
selectedPeriod,
|
||||
selectedDate,
|
||||
viewMode,
|
||||
dailyValues,
|
||||
dailyTemperatures,
|
||||
history,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../blocs/evaporasi_bloc.dart';
|
||||
import 'widgets/evaporasi_chart_widget.dart';
|
||||
import 'widgets/evaporasi_period_selector.dart';
|
||||
|
|
@ -52,12 +53,44 @@ class EvaporasiScreen extends StatelessWidget {
|
|||
style:
|
||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 15),
|
||||
const EvaporasiPeriodSelector(),
|
||||
// Period selector with date picker
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final state = context.watch<EvaporasiBloc>().state;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Show selected date info when in custom mode
|
||||
if (state.viewMode == EvaporasiViewMode.customDate &&
|
||||
state.selectedDate != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
"📅 ${_formatDateInfo(state.selectedDate!)}",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.blue.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const EvaporasiPeriodSelector(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
EvaporasiChartWidget(
|
||||
dailyValues: state.dailyValues,
|
||||
dailyTemperatures: state.dailyTemperatures,
|
||||
period: state.selectedPeriod,
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final state = context.watch<EvaporasiBloc>().state;
|
||||
return EvaporasiChartWidget(
|
||||
dailyValues: state.dailyValues,
|
||||
dailyTemperatures: state.dailyTemperatures,
|
||||
period: state.viewMode == EvaporasiViewMode.customDate
|
||||
? "Tanggal Khusus"
|
||||
: state.selectedPeriod,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
ExportPdfButton(
|
||||
|
|
@ -223,8 +256,26 @@ class EvaporasiScreen extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// =========================
|
||||
/// 📅 FORMAT DATE INFO (for custom date display)
|
||||
/// =========================
|
||||
String _formatDateInfo(DateTime date) {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final yesterday = today.subtract(const Duration(days: 1));
|
||||
final selected = DateTime(date.year, date.month, date.day);
|
||||
|
||||
if (selected == today) {
|
||||
return "Hari Ini";
|
||||
} else if (selected == yesterday) {
|
||||
return "Kemarin";
|
||||
} else {
|
||||
return DateFormat('EEEE, d MMMM yyyy', 'id_ID').format(date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../blocs/evaporasi_bloc.dart';
|
||||
|
||||
/// 📅 EVAPORASI DATE PICKER - WhatsApp Style
|
||||
class EvaporasiDatePicker extends StatefulWidget {
|
||||
const EvaporasiDatePicker({super.key});
|
||||
|
||||
@override
|
||||
State<EvaporasiDatePicker> createState() => _EvaporasiDatePickerState();
|
||||
}
|
||||
|
||||
class _EvaporasiDatePickerState extends State<EvaporasiDatePicker> {
|
||||
CalendarFormat _calendarFormat = CalendarFormat.month;
|
||||
DateTime _focusedDay = DateTime.now();
|
||||
DateTime? _selectedDay;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedDay = _focusedDay;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 400,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Header with close button
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Kembali ke mode period DAN TUTUP bottom sheet
|
||||
context.read<EvaporasiBloc>().add(
|
||||
const EvaporasiViewModeChanged(
|
||||
EvaporasiViewMode.period),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text(
|
||||
"Kembali",
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"Pilih Tanggal",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (_selectedDay != null) {
|
||||
context.read<EvaporasiBloc>().add(
|
||||
EvaporasiDateSelected(_selectedDay!),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
"OK",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Calendar
|
||||
Expanded(
|
||||
child: TableCalendar(
|
||||
firstDay: DateTime.utc(2020, 1, 1),
|
||||
lastDay: DateTime.now(),
|
||||
focusedDay: _focusedDay,
|
||||
calendarFormat: _calendarFormat,
|
||||
selectedDayPredicate: (day) {
|
||||
return isSameDay(_selectedDay, day);
|
||||
},
|
||||
onDaySelected: (selectedDay, focusedDay) {
|
||||
setState(() {
|
||||
_selectedDay = selectedDay;
|
||||
_focusedDay = focusedDay;
|
||||
});
|
||||
},
|
||||
onFormatChanged: (format) {
|
||||
if (_calendarFormat != format) {
|
||||
setState(() {
|
||||
_calendarFormat = format;
|
||||
});
|
||||
}
|
||||
},
|
||||
onPageChanged: (focusedDay) {
|
||||
_focusedDay = focusedDay;
|
||||
},
|
||||
calendarStyle: CalendarStyle(
|
||||
// Default
|
||||
defaultDecoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
// Today
|
||||
todayDecoration: BoxDecoration(
|
||||
color: Colors.blue.shade100,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
todayTextStyle: TextStyle(
|
||||
color: Colors.blue.shade700,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
// Selected
|
||||
selectedDecoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
selectedTextStyle: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
// Outside days
|
||||
outsideDaysVisible: false,
|
||||
weekendTextStyle: TextStyle(color: Colors.grey.shade600),
|
||||
),
|
||||
headerStyle: HeaderStyle(
|
||||
formatButtonVisible: true,
|
||||
titleCentered: true,
|
||||
formatButtonShowsNext: false,
|
||||
formatButtonDecoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.blue),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
formatButtonTextStyle: const TextStyle(
|
||||
color: Colors.blue,
|
||||
),
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
leftChevronIcon: Icon(
|
||||
Icons.chevron_left,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
rightChevronIcon: Icon(
|
||||
Icons.chevron_right,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
daysOfWeekStyle: DaysOfWeekStyle(
|
||||
weekdayStyle: TextStyle(
|
||||
color: Colors.grey.shade500,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
),
|
||||
weekendStyle: TextStyle(
|
||||
color: Colors.grey.shade500,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Selected date display
|
||||
if (_selectedDay != null)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(20),
|
||||
bottomRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
_formatDate(_selectedDay!),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final yesterday = today.subtract(const Duration(days: 1));
|
||||
final selected = DateTime(date.year, date.month, date.day);
|
||||
|
||||
if (selected == today) {
|
||||
return "Hari Ini";
|
||||
} else if (selected == yesterday) {
|
||||
return "Kemarin";
|
||||
} else {
|
||||
return DateFormat('EEEE, d MMMM yyyy', 'id_ID').format(date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 🔹 Show Date Picker Dialog
|
||||
void showEvaporasiDatePicker(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
height: 450,
|
||||
child: const EvaporasiDatePicker(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../blocs/evaporasi_bloc.dart';
|
||||
import 'evaporasi_date_picker.dart';
|
||||
|
||||
class EvaporasiPeriodSelector extends StatelessWidget {
|
||||
const EvaporasiPeriodSelector({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedPeriod =
|
||||
context.select((EvaporasiBloc bloc) => bloc.state.selectedPeriod);
|
||||
final state = context.select((EvaporasiBloc bloc) => bloc.state);
|
||||
final selectedPeriod = state.selectedPeriod;
|
||||
final viewMode = state.viewMode;
|
||||
final selectedDate = state.selectedDate;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
|
|
@ -19,16 +22,20 @@ class EvaporasiPeriodSelector extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildTab(context, "Hari Ini", selectedPeriod),
|
||||
_buildTab(context, "Minggu Ini", selectedPeriod),
|
||||
_buildTab(context, "Bulan Ini", selectedPeriod),
|
||||
_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) {
|
||||
bool isActive = label == current;
|
||||
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;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
|
|
@ -51,5 +58,51 @@ class EvaporasiPeriodSelector extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode, DateTime? selectedDate) {
|
||||
final isActive = viewMode == EvaporasiViewMode.customDate;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
// Set mode ke customDate SEBELUM membuka date picker
|
||||
context.read<EvaporasiBloc>().add(
|
||||
const EvaporasiViewModeChanged(EvaporasiViewMode.customDate),
|
||||
);
|
||||
showEvaporasiDatePicker(context);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? Colors.blue : Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
size: 14,
|
||||
color: isActive ? Colors.white : Colors.grey.shade600,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
isActive && selectedDate != null
|
||||
? _formatDateShort(selectedDate)
|
||||
: "Pilih Tanggal",
|
||||
style: TextStyle(
|
||||
color: isActive ? Colors.white : Colors.grey.shade600,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDateShort(DateTime date) {
|
||||
return "${date.day}/${date.month}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
16
pubspec.lock
16
pubspec.lock
|
|
@ -687,6 +687,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
simple_gesture_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: simple_gesture_detector
|
||||
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
@ -732,6 +740,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
table_calendar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: table_calendar
|
||||
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ publish_to: 'none'
|
|||
version: 1.0.0+1
|
||||
environment:
|
||||
sdk: ^3.1.0
|
||||
|
||||
dependencies:
|
||||
bloc: ^8.1.0
|
||||
bloc_concurrency: ^0.2.5
|
||||
|
|
@ -15,6 +16,7 @@ dependencies:
|
|||
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
|
||||
|
|
@ -33,6 +35,7 @@ dev_dependencies:
|
|||
flutter_lints: ^3.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
|
|
|
|||
Loading…
Reference in New Issue