mirror of
https://github.com/vleeuwenmenno/supplements.git
synced 2025-09-11 18:29:12 +02:00
201 lines
5.8 KiB
Dart
201 lines
5.8 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
import '../services/database_sync_service.dart';
|
|
import 'ingredient.dart';
|
|
|
|
class Supplement {
|
|
final int? id;
|
|
final String name;
|
|
final String? brand;
|
|
final List<Ingredient> ingredients;
|
|
final int numberOfUnits; // Number of units to take (e.g., 2 capsules)
|
|
final String unitType; // capsules, tablets, ml, etc.
|
|
final int frequencyPerDay;
|
|
final List<String> reminderTimes; // e.g., ['08:00', '20:00']
|
|
final String? notes;
|
|
final DateTime createdAt;
|
|
final bool isActive;
|
|
|
|
// Sync metadata
|
|
final String syncId;
|
|
final DateTime lastModified;
|
|
final RecordSyncStatus syncStatus;
|
|
final bool isDeleted;
|
|
|
|
Supplement({
|
|
this.id,
|
|
required this.name,
|
|
this.brand,
|
|
this.ingredients = const [],
|
|
required this.numberOfUnits,
|
|
required this.unitType,
|
|
required this.frequencyPerDay,
|
|
required this.reminderTimes,
|
|
this.notes,
|
|
required this.createdAt,
|
|
this.isActive = true,
|
|
String? syncId,
|
|
DateTime? lastModified,
|
|
this.syncStatus = RecordSyncStatus.pending,
|
|
this.isDeleted = false,
|
|
}) : syncId = syncId ?? const Uuid().v4(),
|
|
lastModified = lastModified ?? DateTime.now();
|
|
|
|
// Helper getters
|
|
double get totalDosagePerIntake {
|
|
// This concept doesn't apply well to multi-ingredient supplements
|
|
// Return 0 as it should be handled per ingredient
|
|
return 0.0;
|
|
}
|
|
|
|
// Get formatted ingredients string for display
|
|
String get ingredientsDisplay {
|
|
if (ingredients.isEmpty) {
|
|
return 'No ingredients specified';
|
|
}
|
|
return ingredients.map((ingredient) =>
|
|
'${ingredient.amount * numberOfUnits}${ingredient.unit} ${ingredient.name}'
|
|
).join(', ');
|
|
}
|
|
|
|
// Get ingredients per single unit
|
|
String get ingredientsPerUnit {
|
|
if (ingredients.isEmpty) {
|
|
return 'No ingredients specified';
|
|
}
|
|
return ingredients.map((ingredient) => ingredient.toString()).join(', ');
|
|
}
|
|
|
|
Map<String, dynamic> toMap() {
|
|
final map = <String, dynamic>{
|
|
'name': name,
|
|
'brand': brand,
|
|
'ingredients': jsonEncode(ingredients.map((ingredient) => ingredient.toMap()).toList()),
|
|
'numberOfUnits': numberOfUnits,
|
|
'unitType': unitType,
|
|
'frequencyPerDay': frequencyPerDay,
|
|
'reminderTimes': reminderTimes.join(','),
|
|
'notes': notes,
|
|
'createdAt': createdAt.toIso8601String(),
|
|
'isActive': isActive ? 1 : 0,
|
|
'syncId': syncId,
|
|
'lastModified': lastModified.toIso8601String(),
|
|
'syncStatus': syncStatus.name,
|
|
'isDeleted': isDeleted ? 1 : 0,
|
|
};
|
|
|
|
if (id != null) {
|
|
map['id'] = id;
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
factory Supplement.fromMap(Map<String, dynamic> map) {
|
|
List<Ingredient> ingredients = [];
|
|
|
|
// Try to parse ingredients if they exist
|
|
if (map['ingredients'] != null && map['ingredients'].isNotEmpty) {
|
|
try {
|
|
final ingredientsJson = map['ingredients'] as String;
|
|
final ingredientsList = jsonDecode(ingredientsJson) as List;
|
|
ingredients = ingredientsList
|
|
.map((ingredient) => Ingredient.fromMap(ingredient as Map<String, dynamic>))
|
|
.toList();
|
|
} catch (e) {
|
|
// If parsing fails, fall back to empty list
|
|
ingredients = [];
|
|
}
|
|
}
|
|
|
|
return Supplement(
|
|
id: map['id'],
|
|
name: map['name'],
|
|
brand: map['brand'],
|
|
ingredients: ingredients,
|
|
numberOfUnits: map['numberOfUnits'] ?? 1,
|
|
unitType: map['unitType'] ?? 'units',
|
|
frequencyPerDay: map['frequencyPerDay'],
|
|
reminderTimes: map['reminderTimes'].split(','),
|
|
notes: map['notes'],
|
|
createdAt: DateTime.parse(map['createdAt']),
|
|
isActive: map['isActive'] == 1,
|
|
syncId: map['syncId'] ?? const Uuid().v4(),
|
|
lastModified: map['lastModified'] != null
|
|
? DateTime.parse(map['lastModified'])
|
|
: DateTime.now(),
|
|
syncStatus: map['syncStatus'] != null
|
|
? RecordSyncStatus.values.firstWhere(
|
|
(e) => e.name == map['syncStatus'],
|
|
orElse: () => RecordSyncStatus.pending,
|
|
)
|
|
: RecordSyncStatus.pending,
|
|
isDeleted: (map['isDeleted'] ?? 0) == 1,
|
|
);
|
|
}
|
|
|
|
Supplement copyWith({
|
|
int? id,
|
|
bool setNullId = false,
|
|
String? name,
|
|
String? brand,
|
|
List<Ingredient>? ingredients,
|
|
int? numberOfUnits,
|
|
String? unitType,
|
|
int? frequencyPerDay,
|
|
List<String>? reminderTimes,
|
|
String? notes,
|
|
DateTime? createdAt,
|
|
bool? isActive,
|
|
String? syncId,
|
|
bool newSyncId = false,
|
|
DateTime? lastModified,
|
|
RecordSyncStatus? syncStatus,
|
|
bool? isDeleted,
|
|
}) {
|
|
return Supplement(
|
|
id: setNullId ? null : (id ?? this.id),
|
|
name: name ?? this.name,
|
|
brand: brand ?? this.brand,
|
|
ingredients: ingredients ?? this.ingredients,
|
|
numberOfUnits: numberOfUnits ?? this.numberOfUnits,
|
|
unitType: unitType ?? this.unitType,
|
|
frequencyPerDay: frequencyPerDay ?? this.frequencyPerDay,
|
|
reminderTimes: reminderTimes ?? this.reminderTimes,
|
|
notes: notes ?? this.notes,
|
|
createdAt: createdAt ?? this.createdAt,
|
|
isActive: isActive ?? this.isActive,
|
|
syncId: newSyncId ? null : (syncId ?? this.syncId),
|
|
lastModified: lastModified ?? this.lastModified,
|
|
syncStatus: syncStatus ?? this.syncStatus,
|
|
isDeleted: isDeleted ?? this.isDeleted,
|
|
);
|
|
}
|
|
|
|
/// Create a new supplement with updated sync status and timestamp
|
|
Supplement markAsModified() {
|
|
return copyWith(
|
|
lastModified: DateTime.now(),
|
|
syncStatus: RecordSyncStatus.modified,
|
|
);
|
|
}
|
|
|
|
/// Create a new supplement marked as synced
|
|
Supplement markAsSynced() {
|
|
return copyWith(
|
|
syncStatus: RecordSyncStatus.synced,
|
|
);
|
|
}
|
|
|
|
/// Create a new supplement marked for deletion
|
|
Supplement markAsDeleted() {
|
|
return copyWith(
|
|
isDeleted: true,
|
|
lastModified: DateTime.now(),
|
|
syncStatus: RecordSyncStatus.modified,
|
|
);
|
|
}
|
|
}
|