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();
}
}