Refactor supplement intake handling to support fractional units and update notification service initialization for Linux

This commit is contained in:
2025-08-26 01:35:43 +02:00
parent 05a9d13164
commit e6181add08
7 changed files with 127 additions and 54 deletions

View File

@@ -120,62 +120,98 @@ class _HistoryScreenState extends State<HistoryScreen> with SingleTickerProvider
}
final intakes = snapshot.data!;
// Group intakes by supplement
final Map<String, List<Map<String, dynamic>>> groupedIntakes = {};
for (final intake in intakes) {
final supplementName = intake['supplementName'] as String;
groupedIntakes.putIfAbsent(supplementName, () => []);
groupedIntakes[supplementName]!.add(intake);
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: intakes.length,
itemCount: groupedIntakes.length,
itemBuilder: (context, index) {
final intake = intakes[index];
final takenAt = DateTime.parse(intake['takenAt']);
final supplementName = groupedIntakes.keys.elementAt(index);
final supplementIntakes = groupedIntakes[supplementName]!;
// Calculate totals
double totalDosage = 0;
double totalUnits = 0;
final firstIntake = supplementIntakes.first;
for (final intake in supplementIntakes) {
totalDosage += intake['dosageTaken'] as double;
totalUnits += (intake['unitsTaken'] as num?)?.toDouble() ?? 1.0;
}
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
margin: const EdgeInsets.only(bottom: 12),
child: ExpansionTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.primary,
child: Icon(Icons.medication, color: Theme.of(context).colorScheme.onPrimary),
),
title: Text(intake['supplementName']),
title: Text(
supplementName,
style: const TextStyle(fontWeight: FontWeight.w600),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${intake['dosageTaken']} ${intake['supplementUnit']}'),
Text(
'Taken at ${DateFormat('HH:mm').format(takenAt)}',
'${totalDosage.toStringAsFixed(totalDosage % 1 == 0 ? 0 : 1)} ${firstIntake['supplementUnit']} total',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.primary,
),
),
Text(
'${totalUnits.toStringAsFixed(totalUnits % 1 == 0 ? 0 : 1)} ${firstIntake['supplementUnitType'] ?? 'units'}${supplementIntakes.length} intake${supplementIntakes.length > 1 ? 's' : ''}',
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)),
],
),
children: supplementIntakes.map((intake) {
final takenAt = DateTime.parse(intake['takenAt']);
final units = (intake['unitsTaken'] as num?)?.toDouble() ?? 1.0;
return ListTile(
contentPadding: const EdgeInsets.only(left: 72, right: 16),
title: Text(
'${(intake['dosageTaken'] as double).toStringAsFixed((intake['dosageTaken'] as double) % 1 == 0 ? 0 : 1)} ${intake['supplementUnit']}',
style: const TextStyle(fontSize: 14),
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${units.toStringAsFixed(units % 1 == 0 ? 0 : 1)} ${intake['supplementUnitType'] ?? 'units'} at ${DateFormat('HH:mm').format(takenAt)}',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (intake['notes'] != null && intake['notes'].toString().isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
intake['notes'],
style: TextStyle(
fontSize: 12,
fontStyle: FontStyle.italic,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
);
}).toList(),
),
);
},

View File

@@ -133,7 +133,7 @@ class SupplementsListScreen extends StatelessWidget {
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) {
final units = int.tryParse(unitsController.text) ?? supplement.numberOfUnits;
final units = double.tryParse(unitsController.text) ?? supplement.numberOfUnits.toDouble();
final totalDosage = supplement.dosageAmount * units;
return AlertDialog(
@@ -146,7 +146,7 @@ class SupplementsListScreen extends StatelessWidget {
Expanded(
child: TextField(
controller: unitsController,
keyboardType: TextInputType.number,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
labelText: 'Number of ${supplement.unitType}',
border: const OutlineInputBorder(),
@@ -175,7 +175,7 @@ class SupplementsListScreen extends StatelessWidget {
),
),
Text(
'${totalDosage.toStringAsFixed(1)} ${supplement.unit}',
'${totalDosage.toStringAsFixed(totalDosage % 1 == 0 ? 0 : 1)} ${supplement.unit}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@@ -203,7 +203,7 @@ class SupplementsListScreen extends StatelessWidget {
),
ElevatedButton(
onPressed: () {
final unitsTaken = int.tryParse(unitsController.text) ?? supplement.numberOfUnits;
final unitsTaken = double.tryParse(unitsController.text) ?? supplement.numberOfUnits.toDouble();
final totalDosageTaken = supplement.dosageAmount * unitsTaken;
context.read<SupplementProvider>().recordIntake(
supplement.id!,