4 Commits

5 changed files with 323 additions and 271 deletions

View File

@@ -557,21 +557,24 @@ class _AddSupplementScreenState extends State<AddSupplementScreen> {
void _saveSupplement() async { void _saveSupplement() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
// Validate that we have at least one ingredient with name and amount // Validate that we have at least one ingredient with name and amount
final validIngredients = _ingredientControllers.where((controller) => final validIngredients = _ingredientControllers
.where((controller) =>
controller.nameController.text.trim().isNotEmpty && controller.nameController.text.trim().isNotEmpty &&
(double.tryParse(controller.amountController.text) ?? 0) > 0 (double.tryParse(controller.amountController.text) ?? 0) > 0)
).map((controller) => Ingredient( .map((controller) => Ingredient(
name: controller.nameController.text.trim(), name: controller.nameController.text.trim(),
amount: double.tryParse(controller.amountController.text) ?? 0.0, amount: double.tryParse(controller.amountController.text) ?? 0.0,
unit: controller.selectedUnit, unit: controller.selectedUnit,
syncId: const Uuid().v4(), syncId: const Uuid().v4(),
lastModified: DateTime.now(), lastModified: DateTime.now(),
)).toList(); ))
.toList();
if (validIngredients.isEmpty) { if (validIngredients.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Please add at least one ingredient with name and amount'), content:
Text('Please add at least one ingredient with name and amount'),
), ),
); );
return; return;
@@ -580,14 +583,20 @@ class _AddSupplementScreenState extends State<AddSupplementScreen> {
final supplement = Supplement( final supplement = Supplement(
id: widget.supplement?.id, id: widget.supplement?.id,
name: _nameController.text.trim(), name: _nameController.text.trim(),
brand: _brandController.text.trim().isNotEmpty ? _brandController.text.trim() : null, brand: _brandController.text.trim().isNotEmpty
? _brandController.text.trim()
: null,
ingredients: validIngredients, ingredients: validIngredients,
numberOfUnits: int.parse(_numberOfUnitsController.text), numberOfUnits: int.parse(_numberOfUnitsController.text),
unitType: _selectedUnitType, unitType: _selectedUnitType,
frequencyPerDay: _frequencyPerDay, frequencyPerDay: _frequencyPerDay,
reminderTimes: _reminderTimes, reminderTimes: _reminderTimes,
notes: _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null, notes: _notesController.text.trim().isNotEmpty
? _notesController.text.trim()
: null,
createdAt: widget.supplement?.createdAt ?? DateTime.now(), createdAt: widget.supplement?.createdAt ?? DateTime.now(),
syncId: widget.supplement?.syncId, // Preserve syncId on update
lastModified: DateTime.now(), // Always update lastModified on save
); );
final provider = context.read<SupplementProvider>(); final provider = context.read<SupplementProvider>();

View File

@@ -272,9 +272,12 @@ class _SimpleSyncSettingsScreenState extends State<SimpleSyncSettingsScreen> {
} }
Widget _buildConfigurationSection(SimpleSyncProvider syncProvider) { Widget _buildConfigurationSection(SimpleSyncProvider syncProvider) {
return Card( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(12.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -282,14 +285,24 @@ class _SimpleSyncSettingsScreenState extends State<SimpleSyncSettingsScreen> {
'Sync Configuration', 'Sync Configuration',
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
), ),
const SizedBox(height: 16), const SizedBox(height: 8),
_buildAutoSyncSection(), _buildAutoSyncSection(),
const SizedBox(height: 24), ],
),
),
),
const SizedBox(height: 12),
Card(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text( Text(
'WebDAV Settings', 'WebDAV Settings',
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
const SizedBox(height: 16), const SizedBox(height: 8),
TextFormField( TextFormField(
controller: _serverUrlController, controller: _serverUrlController,
decoration: const InputDecoration( decoration: const InputDecoration(
@@ -305,7 +318,7 @@ class _SimpleSyncSettingsScreenState extends State<SimpleSyncSettingsScreen> {
return null; return null;
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 8),
TextFormField( TextFormField(
controller: _usernameController, controller: _usernameController,
decoration: const InputDecoration( decoration: const InputDecoration(
@@ -320,9 +333,9 @@ class _SimpleSyncSettingsScreenState extends State<SimpleSyncSettingsScreen> {
}, },
), ),
if (_previewUrl.isNotEmpty) ...[ if (_previewUrl.isNotEmpty) ...[
const SizedBox(height: 8), const SizedBox(height: 6),
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -345,7 +358,7 @@ class _SimpleSyncSettingsScreenState extends State<SimpleSyncSettingsScreen> {
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 6),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@@ -364,7 +377,7 @@ class _SimpleSyncSettingsScreenState extends State<SimpleSyncSettingsScreen> {
), ),
), ),
], ],
const SizedBox(height: 16), const SizedBox(height: 8),
TextFormField( TextFormField(
controller: _passwordController, controller: _passwordController,
decoration: const InputDecoration( decoration: const InputDecoration(
@@ -379,7 +392,7 @@ class _SimpleSyncSettingsScreenState extends State<SimpleSyncSettingsScreen> {
return null; return null;
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 8),
TextFormField( TextFormField(
controller: _remotePathController, controller: _remotePathController,
decoration: const InputDecoration( decoration: const InputDecoration(
@@ -391,6 +404,8 @@ class _SimpleSyncSettingsScreenState extends State<SimpleSyncSettingsScreen> {
], ],
), ),
), ),
),
],
); );
} }

View File

@@ -289,19 +289,23 @@ class DatabaseSyncService {
// Get all supplements from remote database // Get all supplements from remote database
final remoteMaps = await remoteDb.query('supplements'); final remoteMaps = await remoteDb.query('supplements');
final remoteSupplements = remoteMaps.map((map) => Supplement.fromMap(map)).toList(); final remoteSupplements =
remoteMaps.map((map) => Supplement.fromMap(map)).toList();
if (kDebugMode) { if (kDebugMode) {
print('SupplementsLog: Found ${remoteSupplements.length} supplements in remote database'); print(
'SupplementsLog: Found ${remoteSupplements.length} supplements in remote database');
for (final supplement in remoteSupplements) { for (final supplement in remoteSupplements) {
print('SupplementsLog: Remote supplement: ${supplement.name} (syncId: ${supplement.syncId}, deleted: ${supplement.isDeleted})'); print(
'SupplementsLog: Remote supplement: ${supplement.name} (syncId: ${supplement.syncId}, deleted: ${supplement.isDeleted})');
} }
} }
for (final remoteSupplement in remoteSupplements) { for (final remoteSupplement in remoteSupplements) {
if (remoteSupplement.syncId.isEmpty) { if (remoteSupplement.syncId.isEmpty) {
if (kDebugMode) { if (kDebugMode) {
print('SupplementsLog: Skipping supplement ${remoteSupplement.name} - no syncId'); print(
'SupplementsLog: Skipping supplement ${remoteSupplement.name} - no syncId');
} }
continue; continue;
} }
@@ -316,22 +320,28 @@ class DatabaseSyncService {
if (existingMaps.isEmpty) { if (existingMaps.isEmpty) {
// New supplement from remote - insert it // New supplement from remote - insert it
if (!remoteSupplement.isDeleted) { if (!remoteSupplement.isDeleted) {
final supplementToInsert = remoteSupplement.copyWith(id: null); // Manually create a new map without the id to ensure it's null
await localDb.insert('supplements', supplementToInsert.toMap()); final mapToInsert = remoteSupplement.toMap();
mapToInsert.remove('id');
await localDb.insert('supplements', mapToInsert);
if (kDebugMode) { if (kDebugMode) {
print('SupplementsLog: ✓ Inserted new supplement: ${remoteSupplement.name}'); print(
'SupplementsLog: ✓ Inserted new supplement: ${remoteSupplement.name}');
} }
} else { } else {
if (kDebugMode) { if (kDebugMode) {
print('SupplementsLog: Skipping deleted supplement: ${remoteSupplement.name}'); print(
'SupplementsLog: Skipping deleted supplement: ${remoteSupplement.name}');
} }
} }
} else { } else {
// Existing supplement - update if remote is newer // Existing supplement - update if remote is newer
final existingSupplement = Supplement.fromMap(existingMaps.first); final existingSupplement = Supplement.fromMap(existingMaps.first);
if (remoteSupplement.lastModified.isAfter(existingSupplement.lastModified)) { if (remoteSupplement.lastModified
final supplementToUpdate = remoteSupplement.copyWith(id: existingSupplement.id); .isAfter(existingSupplement.lastModified)) {
final supplementToUpdate =
remoteSupplement.copyWith(id: existingSupplement.id);
await localDb.update( await localDb.update(
'supplements', 'supplements',
supplementToUpdate.toMap(), supplementToUpdate.toMap(),
@@ -339,11 +349,13 @@ class DatabaseSyncService {
whereArgs: [existingSupplement.id], whereArgs: [existingSupplement.id],
); );
if (kDebugMode) { if (kDebugMode) {
print('SupplementsLog: ✓ Updated supplement: ${remoteSupplement.name}'); print(
'SupplementsLog: ✓ Updated supplement: ${remoteSupplement.name}');
} }
} else { } else {
if (kDebugMode) { if (kDebugMode) {
print('SupplementsLog: Local supplement ${remoteSupplement.name} is newer, keeping local version'); print(
'SupplementsLog: Local supplement ${remoteSupplement.name} is newer, keeping local version');
} }
} }
} }

View File

@@ -173,20 +173,35 @@ class NotificationService {
// Call the callback to record the intake // Call the callback to record the intake
if (_onTakeSupplementCallback != null) { if (_onTakeSupplementCallback != null) {
print('SupplementsLog: 📱 Calling supplement callback...'); print('SupplementsLog: 📱 Calling supplement callback...');
_onTakeSupplementCallback!(supplementId, supplementName, units, unitType); _onTakeSupplementCallback!(
supplementId, supplementName, units, unitType);
print('SupplementsLog: 📱 Callback completed'); print('SupplementsLog: 📱 Callback completed');
} else { } else {
print('SupplementsLog: 📱 ERROR: No callback registered!'); print('SupplementsLog: 📱 ERROR: No callback registered!');
} }
// For retry notifications, the original notification ID is in the payload
int originalNotificationId;
if (parts.length > 4 && int.tryParse(parts[4]) != null) {
originalNotificationId = int.parse(parts[4]);
print(
'SupplementsLog: 📱 Retry notification detected. Original ID: $originalNotificationId');
} else if (notificationId != null) {
originalNotificationId = notificationId;
} else {
print(
'SupplementsLog: 📱 ERROR: Could not determine notification ID to cancel.');
return;
}
// Mark notification as taken in database (this will cancel any pending retries) // Mark notification as taken in database (this will cancel any pending retries)
if (notificationId != null) { print(
print('SupplementsLog: 📱 Marking notification $notificationId as taken'); 'SupplementsLog: 📱 Marking notification $originalNotificationId as taken');
await DatabaseHelper.instance.markNotificationTaken(notificationId); await DatabaseHelper.instance
.markNotificationTaken(originalNotificationId);
// Cancel any pending retry notifications for this notification // Cancel any pending retry notifications for this notification
_cancelRetryNotifications(notificationId); _cancelRetryNotifications(originalNotificationId);
}
// Show a confirmation notification // Show a confirmation notification
print('SupplementsLog: 📱 Showing confirmation notification...'); print('SupplementsLog: 📱 Showing confirmation notification...');
@@ -195,7 +210,8 @@ class NotificationService {
'$supplementName has been recorded at ${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}', '$supplementName has been recorded at ${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}',
); );
} else { } else {
print('SupplementsLog: 📱 ERROR: Invalid payload format - not enough parts'); print(
'SupplementsLog: 📱 ERROR: Invalid payload format - not enough parts');
} }
} catch (e) { } catch (e) {
print('SupplementsLog: 📱 ERROR in _handleTakeAction: $e'); print('SupplementsLog: 📱 ERROR in _handleTakeAction: $e');

View File

@@ -1,7 +1,7 @@
name: supplements name: supplements
description: "A supplement tracking app for managing your daily supplements" description: "A supplement tracking app for managing your daily supplements"
publish_to: "none" publish_to: "none"
version: 1.0.4+27082025 version: 1.0.5+27082025
environment: environment:
sdk: ^3.9.0 sdk: ^3.9.0