mirror of
https://github.com/vleeuwenmenno/supplements.git
synced 2025-09-11 18:29:12 +02:00
Refactor supplement intake handling to support fractional units and update notification service initialization for Linux
This commit is contained in:
@@ -33,8 +33,12 @@
|
||||
</activity>
|
||||
|
||||
<!-- Notification receiver for local notifications -->
|
||||
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
|
||||
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
|
||||
<receiver
|
||||
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"
|
||||
android:exported="false" />
|
||||
<receiver
|
||||
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
|
||||
|
@@ -3,7 +3,7 @@ class SupplementIntake {
|
||||
final int supplementId;
|
||||
final DateTime takenAt;
|
||||
final double dosageTaken; // Total dosage amount taken
|
||||
final int unitsTaken; // Number of units taken
|
||||
final double unitsTaken; // Number of units taken (can be fractional)
|
||||
final String? notes;
|
||||
|
||||
SupplementIntake({
|
||||
@@ -32,7 +32,7 @@ class SupplementIntake {
|
||||
supplementId: map['supplementId'],
|
||||
takenAt: DateTime.parse(map['takenAt']),
|
||||
dosageTaken: map['dosageTaken'],
|
||||
unitsTaken: map['unitsTaken'] ?? 1, // Default for backwards compatibility
|
||||
unitsTaken: (map['unitsTaken'] ?? 1).toDouble(), // Default for backwards compatibility
|
||||
notes: map['notes'],
|
||||
);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class SupplementIntake {
|
||||
int? supplementId,
|
||||
DateTime? takenAt,
|
||||
double? dosageTaken,
|
||||
int? unitsTaken,
|
||||
double? unitsTaken,
|
||||
String? notes,
|
||||
}) {
|
||||
return SupplementIntake(
|
||||
|
@@ -103,13 +103,13 @@ class SupplementProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> recordIntake(int supplementId, double dosage, {int? unitsTaken, String? notes}) async {
|
||||
Future<void> recordIntake(int supplementId, double dosage, {double? unitsTaken, String? notes}) async {
|
||||
try {
|
||||
final intake = SupplementIntake(
|
||||
supplementId: supplementId,
|
||||
takenAt: DateTime.now(),
|
||||
dosageTaken: dosage,
|
||||
unitsTaken: unitsTaken ?? 1,
|
||||
unitsTaken: unitsTaken ?? 1.0,
|
||||
notes: notes,
|
||||
);
|
||||
|
||||
@@ -118,7 +118,7 @@ class SupplementProvider with ChangeNotifier {
|
||||
|
||||
// Show confirmation notification
|
||||
final supplement = _supplements.firstWhere((s) => s.id == supplementId);
|
||||
final unitsText = unitsTaken != null && unitsTaken > 1 ? '$unitsTaken ${supplement.unitType}' : '';
|
||||
final unitsText = unitsTaken != null && unitsTaken != 1 ? '${unitsTaken.toStringAsFixed(unitsTaken % 1 == 0 ? 0 : 1)} ${supplement.unitType}' : '';
|
||||
await _notificationService.showInstantNotification(
|
||||
'Supplement Taken',
|
||||
'Recorded ${supplement.name}${unitsText.isNotEmpty ? ' - $unitsText' : ''} ($dosage ${supplement.unit})',
|
||||
|
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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)
|
||||
Text(
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
intake['notes'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@@ -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!,
|
||||
|
@@ -68,7 +68,7 @@ class DatabaseHelper {
|
||||
supplementId INTEGER NOT NULL,
|
||||
takenAt TEXT NOT NULL,
|
||||
dosageTaken REAL NOT NULL,
|
||||
unitsTaken INTEGER NOT NULL DEFAULT 1,
|
||||
unitsTaken REAL NOT NULL DEFAULT 1,
|
||||
notes TEXT,
|
||||
FOREIGN KEY (supplementId) REFERENCES $supplementsTable (id)
|
||||
)
|
||||
@@ -77,20 +77,49 @@ class DatabaseHelper {
|
||||
|
||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||
if (oldVersion < 2) {
|
||||
// Add new columns for version 2
|
||||
// First, add new columns
|
||||
await db.execute('ALTER TABLE $supplementsTable ADD COLUMN dosageAmount REAL DEFAULT 0');
|
||||
await db.execute('ALTER TABLE $supplementsTable ADD COLUMN numberOfUnits INTEGER DEFAULT 1');
|
||||
await db.execute('ALTER TABLE $supplementsTable ADD COLUMN unitType TEXT DEFAULT "units"');
|
||||
await db.execute('ALTER TABLE $intakesTable ADD COLUMN unitsTaken INTEGER DEFAULT 1');
|
||||
await db.execute('ALTER TABLE $intakesTable ADD COLUMN unitsTaken REAL DEFAULT 1');
|
||||
|
||||
// Migrate existing data
|
||||
// Migrate existing data from old dosage column to new dosageAmount column
|
||||
await db.execute('''
|
||||
UPDATE $supplementsTable
|
||||
SET dosageAmount = dosage,
|
||||
SET dosageAmount = COALESCE(dosage, 0),
|
||||
numberOfUnits = 1,
|
||||
unitType = 'units'
|
||||
WHERE dosageAmount = 0
|
||||
''');
|
||||
|
||||
// Create new table with correct schema
|
||||
await db.execute('''
|
||||
CREATE TABLE ${supplementsTable}_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
dosageAmount REAL NOT NULL,
|
||||
numberOfUnits INTEGER NOT NULL DEFAULT 1,
|
||||
unit TEXT NOT NULL,
|
||||
unitType TEXT NOT NULL DEFAULT 'units',
|
||||
frequencyPerDay INTEGER NOT NULL,
|
||||
reminderTimes TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
createdAt TEXT NOT NULL,
|
||||
isActive INTEGER NOT NULL DEFAULT 1
|
||||
)
|
||||
''');
|
||||
|
||||
// Copy data to new table
|
||||
await db.execute('''
|
||||
INSERT INTO ${supplementsTable}_new
|
||||
(id, name, dosageAmount, numberOfUnits, unit, unitType, frequencyPerDay, reminderTimes, notes, createdAt, isActive)
|
||||
SELECT id, name, dosageAmount, numberOfUnits, unit, unitType, frequencyPerDay, reminderTimes, notes, createdAt, isActive
|
||||
FROM $supplementsTable
|
||||
''');
|
||||
|
||||
// Drop old table and rename new table
|
||||
await db.execute('DROP TABLE $supplementsTable');
|
||||
await db.execute('ALTER TABLE ${supplementsTable}_new RENAME TO $supplementsTable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +213,7 @@ class DatabaseHelper {
|
||||
String endDate = DateTime(date.year, date.month, date.day, 23, 59, 59).toIso8601String();
|
||||
|
||||
List<Map<String, dynamic>> result = await db.rawQuery('''
|
||||
SELECT i.*, s.name as supplementName, s.unit as supplementUnit
|
||||
SELECT i.*, s.name as supplementName, s.unit as supplementUnit, s.unitType as supplementUnitType
|
||||
FROM $intakesTable i
|
||||
JOIN $supplementsTable s ON i.supplementId = s.id
|
||||
WHERE i.takenAt >= ? AND i.takenAt <= ?
|
||||
@@ -200,7 +229,7 @@ class DatabaseHelper {
|
||||
String endDate = DateTime(year, month + 1, 0, 23, 59, 59).toIso8601String();
|
||||
|
||||
List<Map<String, dynamic>> result = await db.rawQuery('''
|
||||
SELECT i.*, s.name as supplementName, s.unit as supplementUnit
|
||||
SELECT i.*, s.name as supplementName, s.unit as supplementUnit, s.unitType as supplementUnitType
|
||||
FROM $intakesTable i
|
||||
JOIN $supplementsTable s ON i.supplementId = s.id
|
||||
WHERE i.takenAt >= ? AND i.takenAt <= ?
|
||||
|
@@ -19,10 +19,14 @@ class NotificationService {
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
);
|
||||
const LinuxInitializationSettings linuxSettings = LinuxInitializationSettings(
|
||||
defaultActionName: 'Open notification',
|
||||
);
|
||||
|
||||
const InitializationSettings initSettings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
linux: linuxSettings,
|
||||
);
|
||||
|
||||
await _notifications.initialize(initSettings);
|
||||
|
Reference in New Issue
Block a user