Files
supplements/lib/screens/history_screen.dart

381 lines
15 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../providers/supplement_provider.dart';
class HistoryScreen extends StatefulWidget {
const HistoryScreen({super.key});
@override
State<HistoryScreen> createState() => _HistoryScreenState();
}
class _HistoryScreenState extends State<HistoryScreen> {
int _selectedMonth = DateTime.now().month;
int _selectedYear = DateTime.now().year;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Intake History'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: _showMonthPicker,
tooltip: 'Select Month',
),
],
),
body: _buildCompactHistoryView(),
);
}
Widget _buildCompactHistoryView() {
return Column(
children: [
// Month selector header
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
bottom: BorderSide(
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.2),
width: 1,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
setState(() {
if (_selectedMonth == 1) {
_selectedMonth = 12;
_selectedYear--;
} else {
_selectedMonth--;
}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
});
},
icon: const Icon(Icons.chevron_left),
iconSize: 20,
),
Text(
DateFormat('MMMM yyyy').format(DateTime(_selectedYear, _selectedMonth)),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
IconButton(
onPressed: () {
final now = DateTime.now();
if (_selectedYear < now.year || (_selectedYear == now.year && _selectedMonth < now.month)) {
setState(() {
if (_selectedMonth == 12) {
_selectedMonth = 1;
_selectedYear++;
} else {
_selectedMonth++;
}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
});
}
},
icon: const Icon(Icons.chevron_right),
iconSize: 20,
),
],
),
),
// History list
Expanded(
child: Consumer<SupplementProvider>(
builder: (context, provider, child) {
if (provider.monthlyIntakes.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.history,
size: 48,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No intake history for this month',
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
// Sort intakes by date (most recent first)
final sortedIntakes = List<Map<String, dynamic>>.from(provider.monthlyIntakes)
..sort((a, b) => DateTime.parse(b['takenAt']).compareTo(DateTime.parse(a['takenAt'])));
return ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: sortedIntakes.length,
itemBuilder: (context, index) {
final intake = sortedIntakes[index];
final takenAt = DateTime.parse(intake['takenAt']);
final units = (intake['unitsTaken'] as num?)?.toDouble() ?? 1.0;
// Check if we need a date header
final previousIntake = index > 0 ? sortedIntakes[index - 1] : null;
final showDateHeader = previousIntake == null ||
DateFormat('yyyy-MM-dd').format(DateTime.parse(previousIntake['takenAt'])) !=
DateFormat('yyyy-MM-dd').format(takenAt);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showDateHeader) ...[
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
DateFormat('EEEE, MMMM d').format(takenAt),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.primary,
),
),
),
],
Dismissible(
key: Key('intake_${intake['id']}'),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
color: Colors.red.shade400,
child: const Icon(
Icons.delete,
color: Colors.white,
),
),
confirmDismiss: (direction) async {
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Intake'),
content: Text('Delete ${intake['supplementName']} taken at ${DateFormat('HH:mm').format(takenAt)}?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
},
onDismissed: (direction) {
context.read<SupplementProvider>().deleteIntake(intake['id']);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
child: Card(
margin: EdgeInsets.zero,
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: InkWell(
onTap: () => _deleteIntake(context, intake['id'], intake['supplementName']),
borderRadius: BorderRadius.circular(12),
splashColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.4),
highlightColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.medication,
color: Theme.of(context).colorScheme.primary,
size: 18,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
intake['supplementName'],
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 2),
Text(
'${DateFormat('HH:mm').format(takenAt)}${units.toStringAsFixed(units % 1 == 0 ? 0 : 1)} ${intake['supplementUnitType'] ?? 'units'}',
style: TextStyle(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (intake['notes'] != null && intake['notes'].toString().isNotEmpty) ...[
const SizedBox(height: 2),
Text(
intake['notes'],
style: TextStyle(
fontSize: 11,
fontStyle: FontStyle.italic,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.8),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
Icon(
Icons.delete_outline,
size: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
),
],
),
),
),
),
),
),
],
);
},
);
},
),
),
],
);
}
void _showMonthPicker() async {
final now = DateTime.now();
final picked = await showDatePicker(
context: context,
initialDate: DateTime(_selectedYear, _selectedMonth),
firstDate: DateTime(2020),
lastDate: now,
initialDatePickerMode: DatePickerMode.year,
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
dialogTheme: DialogThemeData(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
),
),
child: child!,
);
},
);
if (picked != null) {
if (!context.mounted) return;
setState(() {
_selectedMonth = picked.month;
_selectedYear = picked.year;
});
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
});
}
}
void _deleteIntake(BuildContext context, int intakeId, String supplementName) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Intake'),
content: Text('Are you sure you want to delete this $supplementName intake?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () async {
await context.read<SupplementProvider>().deleteIntake(intakeId);
if (!context.mounted) return;
Navigator.of(context).pop();
// Force refresh of the UI
setState(() {});
// Force refresh of the current view data
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
});
final sonner = ShadSonner.of(context);
final id = DateTime.now().millisecondsSinceEpoch;
sonner.show(
ShadToast(
id: id,
title: Text('$supplementName intake deleted'),
action: ShadButton(
size: ShadButtonSize.sm,
child: const Text('Dismiss'),
onPressed: () => sonner.hide(id),
),
),
);
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
}
@override
void dispose() {
super.dispose();
}
}