initial commit

Signed-off-by: Menno van Leeuwen <menno@vleeuwen.me>
This commit is contained in:
2025-08-26 01:21:26 +02:00
commit f8c19f9051
132 changed files with 7054 additions and 0 deletions

View File

@@ -0,0 +1,388 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_datetime_picker_plus/flutter_datetime_picker_plus.dart';
import '../models/supplement.dart';
import '../providers/supplement_provider.dart';
class AddSupplementScreen extends StatefulWidget {
final Supplement? supplement;
const AddSupplementScreen({super.key, this.supplement});
@override
State<AddSupplementScreen> createState() => _AddSupplementScreenState();
}
class _AddSupplementScreenState extends State<AddSupplementScreen> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _dosageAmountController = TextEditingController();
final _numberOfUnitsController = TextEditingController();
final _notesController = TextEditingController();
String _selectedUnit = 'mg';
String _selectedUnitType = 'capsules';
int _frequencyPerDay = 1;
List<String> _reminderTimes = ['08:00'];
final List<String> _units = ['mg', 'g', 'μg', 'IU', 'ml'];
final List<String> _unitTypes = ['capsules', 'tablets', 'softgels', 'drops', 'ml', 'scoops', 'gummies'];
@override
void initState() {
super.initState();
if (widget.supplement != null) {
_initializeWithExistingSupplement();
} else {
_numberOfUnitsController.text = '1'; // Default to 1 unit
}
}
void _initializeWithExistingSupplement() {
final supplement = widget.supplement!;
_nameController.text = supplement.name;
_dosageAmountController.text = supplement.dosageAmount.toString();
_numberOfUnitsController.text = supplement.numberOfUnits.toString();
_notesController.text = supplement.notes ?? '';
_selectedUnit = supplement.unit;
_selectedUnitType = supplement.unitType;
_frequencyPerDay = supplement.frequencyPerDay;
_reminderTimes = List.from(supplement.reminderTimes);
}
@override
Widget build(BuildContext context) {
final isEditing = widget.supplement != null;
return Scaffold(
appBar: AppBar(
title: Text(isEditing ? 'Edit Supplement' : 'Add Supplement'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Name field
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Supplement Name *',
border: OutlineInputBorder(),
hintText: 'e.g., Vitamin D3',
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter a supplement name';
}
return null;
},
),
const SizedBox(height: 16),
// Dosage amount per unit
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
controller: _dosageAmountController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Amount per unit *',
border: OutlineInputBorder(),
hintText: '187',
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter amount per unit';
}
if (double.tryParse(value) == null) {
return 'Please enter a valid number';
}
return null;
},
),
),
const SizedBox(width: 12),
Expanded(
flex: 1,
child: DropdownButtonFormField<String>(
value: _selectedUnit,
decoration: const InputDecoration(
labelText: 'Unit',
border: OutlineInputBorder(),
),
items: _units.map((unit) {
return DropdownMenuItem(
value: unit,
child: Text(unit),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedUnit = value!;
});
},
),
),
],
),
const SizedBox(height: 16),
// Number of units to take
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
controller: _numberOfUnitsController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Number to take *',
border: OutlineInputBorder(),
hintText: '2',
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter number to take';
}
if (int.tryParse(value) == null || int.parse(value) < 1) {
return 'Please enter a valid number (1 or more)';
}
return null;
},
),
),
const SizedBox(width: 12),
Expanded(
flex: 1,
child: DropdownButtonFormField<String>(
value: _selectedUnitType,
decoration: const InputDecoration(
labelText: 'Type',
border: OutlineInputBorder(),
),
items: _unitTypes.map((type) {
return DropdownMenuItem(
value: type,
child: Text(type),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedUnitType = value!;
});
},
),
),
],
),
const SizedBox(height: 8),
Text(
'Total per intake: ${_dosageAmountController.text.isNotEmpty && _numberOfUnitsController.text.isNotEmpty ? (double.tryParse(_dosageAmountController.text) ?? 0) * (int.tryParse(_numberOfUnitsController.text) ?? 0) : 0} $_selectedUnit',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontStyle: FontStyle.italic,
),
),
const SizedBox(height: 16),
// Frequency per day
Row(
children: [
const Text('Frequency per day: '),
const SizedBox(width: 8),
DropdownButton<int>(
value: _frequencyPerDay,
items: List.generate(6, (index) => index + 1).map((freq) {
return DropdownMenuItem(
value: freq,
child: Text('$freq time${freq > 1 ? 's' : ''}'),
);
}).toList(),
onChanged: (value) {
setState(() {
_frequencyPerDay = value!;
_adjustReminderTimes();
});
},
),
],
),
const SizedBox(height: 16),
// Reminder times
const Text(
'Reminder Times',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
..._reminderTimes.asMap().entries.map((entry) {
final index = entry.key;
final time = entry.value;
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Expanded(
child: InkWell(
onTap: () => _selectTime(index),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).colorScheme.outline),
borderRadius: BorderRadius.circular(4),
),
child: Text(time),
),
),
),
if (_reminderTimes.length > 1)
IconButton(
onPressed: () => _removeReminderTime(index),
icon: const Icon(Icons.remove_circle_outline),
),
],
),
);
}),
if (_reminderTimes.length < _frequencyPerDay)
TextButton.icon(
onPressed: _addReminderTime,
icon: const Icon(Icons.add),
label: const Text('Add Reminder Time'),
),
const SizedBox(height: 16),
// Notes
TextFormField(
controller: _notesController,
decoration: const InputDecoration(
labelText: 'Notes (optional)',
border: OutlineInputBorder(),
hintText: 'Take with food, before meal, etc.',
),
maxLines: 3,
),
const SizedBox(height: 24),
// Save button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _saveSupplement,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
),
child: Text(isEditing ? 'Update Supplement' : 'Add Supplement'),
),
),
],
),
),
),
);
}
void _adjustReminderTimes() {
if (_reminderTimes.length > _frequencyPerDay) {
_reminderTimes = _reminderTimes.take(_frequencyPerDay).toList();
} else if (_reminderTimes.length < _frequencyPerDay) {
while (_reminderTimes.length < _frequencyPerDay) {
_reminderTimes.add('08:00');
}
}
}
void _selectTime(int index) {
DatePicker.showTimePicker(
context,
showTitleActions: true,
onConfirm: (time) {
setState(() {
_reminderTimes[index] = '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
});
},
currentTime: DateTime.now(),
);
}
void _addReminderTime() {
if (_reminderTimes.length < _frequencyPerDay) {
setState(() {
_reminderTimes.add('08:00');
});
}
}
void _removeReminderTime(int index) {
if (_reminderTimes.length > 1) {
setState(() {
_reminderTimes.removeAt(index);
});
}
}
void _saveSupplement() async {
if (_formKey.currentState!.validate()) {
final supplement = Supplement(
id: widget.supplement?.id,
name: _nameController.text.trim(),
dosageAmount: double.parse(_dosageAmountController.text),
numberOfUnits: int.parse(_numberOfUnitsController.text),
unit: _selectedUnit,
unitType: _selectedUnitType,
frequencyPerDay: _frequencyPerDay,
reminderTimes: _reminderTimes,
notes: _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null,
createdAt: widget.supplement?.createdAt ?? DateTime.now(),
);
final provider = context.read<SupplementProvider>();
try {
if (widget.supplement != null) {
await provider.updateSupplement(supplement);
} else {
await provider.addSupplement(supplement);
}
if (mounted) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.supplement != null
? 'Supplement updated successfully!'
: 'Supplement added successfully!'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
}
}
}
@override
void dispose() {
_nameController.dispose();
_dosageAmountController.dispose();
_numberOfUnitsController.dispose();
_notesController.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,394 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../providers/supplement_provider.dart';
class HistoryScreen extends StatefulWidget {
const HistoryScreen({super.key});
@override
State<HistoryScreen> createState() => _HistoryScreenState();
}
class _HistoryScreenState extends State<HistoryScreen> with SingleTickerProviderStateMixin {
late TabController _tabController;
DateTime _selectedDate = DateTime.now();
int _selectedMonth = DateTime.now().month;
int _selectedYear = DateTime.now().year;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
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,
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: 'Daily View'),
Tab(text: 'Monthly View'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildDailyView(),
_buildMonthlyView(),
],
),
);
}
Widget _buildDailyView() {
return Column(
children: [
Container(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
setState(() {
_selectedDate = _selectedDate.subtract(const Duration(days: 1));
});
},
icon: const Icon(Icons.chevron_left),
),
InkWell(
onTap: _selectDate,
child: Text(
DateFormat('EEEE, MMM d, yyyy').format(_selectedDate),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: _selectedDate.isBefore(DateTime.now())
? () {
setState(() {
_selectedDate = _selectedDate.add(const Duration(days: 1));
});
}
: null,
icon: const Icon(Icons.chevron_right),
),
],
),
),
Expanded(
child: FutureBuilder<List<Map<String, dynamic>>>(
future: context.read<SupplementProvider>().getIntakesForDate(_selectedDate),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.event_note,
size: 64,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No supplements taken on this day',
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
final intakes = snapshot.data!;
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: intakes.length,
itemBuilder: (context, index) {
final intake = intakes[index];
final takenAt = DateTime.parse(intake['takenAt']);
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.primary,
child: Icon(Icons.medication, color: Theme.of(context).colorScheme.onPrimary),
),
title: Text(intake['supplementName']),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${intake['dosageTaken']} ${intake['supplementUnit']}'),
Text(
'Taken at ${DateFormat('HH:mm').format(takenAt)}',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (intake['notes'] != null && intake['notes'].toString().isNotEmpty)
Text(
intake['notes'],
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontStyle: FontStyle.italic,
),
),
],
),
trailing: PopupMenuButton(
onSelected: (value) {
if (value == 'delete') {
_deleteIntake(context, intake['id'], intake['supplementName']);
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'delete',
child: Row(
children: [
Icon(Icons.delete, color: Colors.red),
SizedBox(width: 8),
Text('Delete', style: TextStyle(color: Colors.red)),
],
),
),
],
),
),
);
},
);
},
),
),
],
);
}
Widget _buildMonthlyView() {
return Column(
children: [
Container(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
setState(() {
if (_selectedMonth == 1) {
_selectedMonth = 12;
_selectedYear--;
} else {
_selectedMonth--;
}
});
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
},
icon: const Icon(Icons.chevron_left),
),
Text(
DateFormat('MMMM yyyy').format(DateTime(_selectedYear, _selectedMonth)),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
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++;
}
});
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
}
},
icon: const Icon(Icons.chevron_right),
),
],
),
),
Expanded(
child: Consumer<SupplementProvider>(
builder: (context, provider, child) {
if (provider.monthlyIntakes.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.calendar_month,
size: 64,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No supplements taken this month',
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
// Group intakes by date
final groupedIntakes = <String, List<Map<String, dynamic>>>{};
for (final intake in provider.monthlyIntakes) {
final date = DateTime.parse(intake['takenAt']);
final dateKey = DateFormat('yyyy-MM-dd').format(date);
groupedIntakes.putIfAbsent(dateKey, () => []);
groupedIntakes[dateKey]!.add(intake);
}
final sortedDates = groupedIntakes.keys.toList()
..sort((a, b) => b.compareTo(a)); // Most recent first
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: sortedDates.length,
itemBuilder: (context, index) {
final dateKey = sortedDates[index];
final dayIntakes = groupedIntakes[dateKey]!;
final date = DateTime.parse(dateKey);
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
DateFormat('EEEE, MMM d').format(date),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
...dayIntakes.map((intake) {
final takenAt = DateTime.parse(intake['takenAt']);
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
Icon(
Icons.circle,
size: 8,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'${intake['supplementName']} - ${intake['dosageTaken']} ${intake['supplementUnit']} at ${DateFormat('HH:mm').format(takenAt)}',
style: const TextStyle(fontSize: 14),
),
),
],
),
);
}),
const SizedBox(height: 4),
Text(
'${dayIntakes.length} supplement${dayIntakes.length != 1 ? 's' : ''} taken',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
},
);
},
),
),
],
);
}
void _selectDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(2020),
lastDate: DateTime.now(),
);
if (picked != null) {
setState(() {
_selectedDate = picked;
});
}
}
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: () {
context.read<SupplementProvider>().deleteIntake(intakeId);
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Intake deleted'),
backgroundColor: Colors.red,
),
);
setState(() {}); // Refresh the view
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/supplement_provider.dart';
import 'supplements_list_screen.dart';
import 'history_screen.dart';
import 'add_supplement_screen.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
final List<Widget> _screens = [
const SupplementsListScreen(),
const HistoryScreen(),
];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SupplementProvider>().initialize();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.medication),
label: 'Supplements',
),
BottomNavigationBarItem(
icon: Icon(Icons.history),
label: 'History',
),
],
),
floatingActionButton: _currentIndex == 0
? FloatingActionButton(
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const AddSupplementScreen(),
),
);
// Refresh the list when returning from add screen
if (context.mounted) {
context.read<SupplementProvider>().loadSupplements();
}
},
child: const Icon(Icons.add),
)
: null,
);
}
}

View File

@@ -0,0 +1,268 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../providers/supplement_provider.dart';
import '../models/supplement.dart';
import '../widgets/supplement_card.dart';
import 'add_supplement_screen.dart';
class SupplementsListScreen extends StatelessWidget {
const SupplementsListScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My Supplements'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Consumer<SupplementProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.supplements.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.medication_outlined,
size: 64,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No supplements added yet',
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
'Tap the + button to add your first supplement',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
return RefreshIndicator(
onRefresh: () async {
await provider.loadSupplements();
},
child: Column(
children: [
// Today's Intakes Section
if (provider.todayIntakes.isNotEmpty) ...[
Container(
width: double.infinity,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Theme.of(context).colorScheme.outline),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.check_circle, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 8),
Text(
'Today\'s Intakes',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
],
),
const SizedBox(height: 8),
...provider.todayIntakes.map((intake) {
final takenAt = DateTime.parse(intake['takenAt']);
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
'${intake['supplementName']} - ${intake['dosageTaken']} ${intake['supplementUnit']} at ${DateFormat('HH:mm').format(takenAt)}',
style: TextStyle(color: Theme.of(context).colorScheme.onPrimaryContainer),
),
);
}),
],
),
),
],
// Supplements List
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: provider.supplements.length,
itemBuilder: (context, index) {
final supplement = provider.supplements[index];
return SupplementCard(
supplement: supplement,
onTake: () => _showTakeDialog(context, supplement),
onEdit: () => _editSupplement(context, supplement),
onDelete: () => _deleteSupplement(context, supplement),
);
},
),
),
],
),
);
},
),
);
}
void _showTakeDialog(BuildContext context, Supplement supplement) {
final unitsController = TextEditingController(text: supplement.numberOfUnits.toString());
final notesController = TextEditingController();
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) {
final units = int.tryParse(unitsController.text) ?? supplement.numberOfUnits;
final totalDosage = supplement.dosageAmount * units;
return AlertDialog(
title: Text('Take ${supplement.name}'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: TextField(
controller: unitsController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Number of ${supplement.unitType}',
border: const OutlineInputBorder(),
suffixText: supplement.unitType,
),
onChanged: (value) => setState(() {}),
),
),
],
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(4),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total dosage:',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Text(
'${totalDosage.toStringAsFixed(1)} ${supplement.unit}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),
),
const SizedBox(height: 16),
TextField(
controller: notesController,
decoration: const InputDecoration(
labelText: 'Notes (optional)',
border: OutlineInputBorder(),
),
maxLines: 2,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
final unitsTaken = int.tryParse(unitsController.text) ?? supplement.numberOfUnits;
final totalDosageTaken = supplement.dosageAmount * unitsTaken;
context.read<SupplementProvider>().recordIntake(
supplement.id!,
totalDosageTaken,
unitsTaken: unitsTaken,
notes: notesController.text.isNotEmpty ? notesController.text : null,
);
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${supplement.name} recorded!'),
backgroundColor: Colors.green,
),
);
},
child: const Text('Take'),
),
],
);
},
),
);
}
void _editSupplement(BuildContext context, Supplement supplement) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => AddSupplementScreen(supplement: supplement),
),
);
}
void _deleteSupplement(BuildContext context, Supplement supplement) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Supplement'),
content: Text('Are you sure you want to delete ${supplement.name}?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
context.read<SupplementProvider>().deleteSupplement(supplement.id!);
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${supplement.name} deleted'),
backgroundColor: Colors.red,
),
);
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
}
}