2026-04-26 14:21:12 +10:00
|
|
|
import 'package:flutter/material.dart';
|
2026-04-26 14:25:20 +10:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
|
import '../../core/providers/mood_provider.dart';
|
2026-04-26 14:29:00 +10:00
|
|
|
import 'day_detail_screen.dart';
|
2026-04-26 14:25:20 +10:00
|
|
|
import 'widgets/calendar_heatmap.dart';
|
|
|
|
|
import 'widgets/day_entry_card.dart';
|
2026-04-26 14:21:12 +10:00
|
|
|
|
2026-04-26 14:25:20 +10:00
|
|
|
const _months = [
|
|
|
|
|
'January', 'February', 'March', 'April', 'May', 'June',
|
|
|
|
|
'July', 'August', 'September', 'October', 'November', 'December',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
class CalendarScreen extends ConsumerStatefulWidget {
|
2026-04-26 14:21:12 +10:00
|
|
|
const CalendarScreen({super.key});
|
|
|
|
|
|
2026-04-26 14:25:20 +10:00
|
|
|
@override
|
|
|
|
|
ConsumerState<CalendarScreen> createState() => _CalendarScreenState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|
|
|
|
late DateTime _month;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
final now = DateTime.now();
|
|
|
|
|
_month = DateTime(now.year, now.month);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _prevMonth() => setState(() => _month = DateTime(_month.year, _month.month - 1));
|
|
|
|
|
|
|
|
|
|
void _nextMonth() {
|
|
|
|
|
final now = DateTime.now();
|
|
|
|
|
final next = DateTime(_month.year, _month.month + 1);
|
|
|
|
|
if (!next.isAfter(DateTime(now.year, now.month))) {
|
|
|
|
|
setState(() => _month = next);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 14:29:00 +10:00
|
|
|
static String _dayLabel(DateTime day) {
|
|
|
|
|
final now = DateTime.now();
|
|
|
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
|
|
|
final yesterday = today.subtract(const Duration(days: 1));
|
|
|
|
|
if (day == today) return 'Today';
|
|
|
|
|
if (day == yesterday) return 'Yesterday';
|
|
|
|
|
return '${_months[day.month - 1]} ${day.day}';
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 14:25:20 +10:00
|
|
|
bool get _canGoForward {
|
|
|
|
|
final now = DateTime.now();
|
|
|
|
|
return _month.year < now.year ||
|
|
|
|
|
(_month.year == now.year && _month.month < now.month);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 14:21:12 +10:00
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2026-04-26 14:25:20 +10:00
|
|
|
final selectedDay = ref.watch(selectedDayProvider);
|
|
|
|
|
final entriesAsync = ref.watch(moodEntriesForDayProvider(selectedDay));
|
|
|
|
|
|
|
|
|
|
return Scaffold(
|
|
|
|
|
appBar: AppBar(
|
|
|
|
|
title: const Text('History'),
|
|
|
|
|
centerTitle: false,
|
|
|
|
|
),
|
|
|
|
|
body: Column(
|
|
|
|
|
children: [
|
|
|
|
|
_MonthNav(
|
|
|
|
|
label: '${_months[_month.month - 1]} ${_month.year}',
|
|
|
|
|
onPrev: _prevMonth,
|
|
|
|
|
onNext: _canGoForward ? _nextMonth : null,
|
|
|
|
|
),
|
|
|
|
|
CalendarHeatmap(
|
|
|
|
|
month: _month,
|
|
|
|
|
selectedDay: selectedDay,
|
|
|
|
|
onDayTap: (day) {
|
|
|
|
|
ref.read(selectedDayProvider.notifier).select(day);
|
|
|
|
|
// Follow the selected day's month
|
|
|
|
|
final m = DateTime(day.year, day.month);
|
|
|
|
|
if (m != _month) setState(() => _month = m);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
const Divider(height: 1),
|
2026-04-26 14:29:00 +10:00
|
|
|
InkWell(
|
|
|
|
|
onTap: () => Navigator.of(context).push(
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
builder: (_) => DayDetailScreen(day: selectedDay),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Text(
|
|
|
|
|
_dayLabel(selectedDay),
|
|
|
|
|
style: Theme.of(context).textTheme.titleSmall,
|
|
|
|
|
),
|
|
|
|
|
const Spacer(),
|
|
|
|
|
Icon(Icons.chevron_right,
|
|
|
|
|
color: Theme.of(context).colorScheme.outline),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const Divider(height: 1),
|
2026-04-26 14:25:20 +10:00
|
|
|
Expanded(
|
|
|
|
|
child: entriesAsync.when(
|
|
|
|
|
data: (entries) => entries.isEmpty
|
|
|
|
|
? _EmptyDay(day: selectedDay)
|
|
|
|
|
: ListView.builder(
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
|
|
|
itemCount: entries.length,
|
|
|
|
|
itemBuilder: (context, i) =>
|
|
|
|
|
DayEntryCard(entry: entries[i]),
|
|
|
|
|
),
|
|
|
|
|
loading: () =>
|
|
|
|
|
const Center(child: CircularProgressIndicator()),
|
|
|
|
|
error: (_, __) => const SizedBox.shrink(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _MonthNav extends StatelessWidget {
|
|
|
|
|
const _MonthNav({
|
|
|
|
|
required this.label,
|
|
|
|
|
required this.onPrev,
|
|
|
|
|
required this.onNext,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
final String label;
|
|
|
|
|
final VoidCallback onPrev;
|
|
|
|
|
final VoidCallback? onNext;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
IconButton(icon: const Icon(Icons.chevron_left), onPressed: onPrev),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Text(
|
|
|
|
|
label,
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
IconButton(
|
|
|
|
|
icon: const Icon(Icons.chevron_right),
|
|
|
|
|
onPressed: onNext,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _EmptyDay extends StatelessWidget {
|
|
|
|
|
const _EmptyDay({required this.day});
|
|
|
|
|
|
|
|
|
|
final DateTime day;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
final now = DateTime.now();
|
|
|
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
|
|
|
final label = day == today ? 'today' : '${day.day}/${day.month}/${day.year}';
|
|
|
|
|
|
|
|
|
|
return Center(
|
|
|
|
|
child: Text(
|
|
|
|
|
'No entries for $label',
|
|
|
|
|
style: Theme.of(context)
|
|
|
|
|
.textTheme
|
|
|
|
|
.bodyMedium
|
|
|
|
|
?.copyWith(color: Theme.of(context).colorScheme.outline),
|
|
|
|
|
),
|
2026-04-26 14:21:12 +10:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|