mirror of
https://github.com/vleeuwenmenno/supplements.git
synced 2025-09-11 18:29:12 +02:00
feat: Add settings provider for theme and time range management
- Implemented SettingsProvider to manage user preferences for theme options and time ranges for reminders. - Added persistent reminder settings with configurable retry intervals and maximum attempts. - Created UI for settings screen to allow users to customize their preferences. - Integrated shared_preferences for persistent storage of user settings. feat: Introduce Ingredient model - Created Ingredient model to represent nutritional components with properties for id, name, amount, and unit. - Added methods for serialization and deserialization of Ingredient objects. feat: Develop Archived Supplements Screen - Implemented ArchivedSupplementsScreen to display archived supplements with options to unarchive or delete. - Added UI components for listing archived supplements and handling user interactions. chore: Update dependencies in pubspec.yaml and pubspec.lock - Updated shared_preferences dependency to the latest version. - Removed flutter_datetime_picker_plus dependency and added file dependency. - Updated Flutter SDK constraint to >=3.27.0.
This commit is contained in:
@@ -2,15 +2,17 @@ import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import '../models/supplement.dart';
|
||||
import '../models/supplement_intake.dart';
|
||||
|
||||
class DatabaseHelper {
|
||||
static const _databaseName = 'supplements.db';
|
||||
static const _databaseVersion = 2; // Increment version for schema changes
|
||||
static const _databaseVersion = 5; // Increment version for notification tracking
|
||||
|
||||
static const supplementsTable = 'supplements';
|
||||
static const intakesTable = 'supplement_intakes';
|
||||
static const notificationTrackingTable = 'notification_tracking';
|
||||
|
||||
DatabaseHelper._privateConstructor();
|
||||
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
|
||||
@@ -50,9 +52,9 @@ class DatabaseHelper {
|
||||
CREATE TABLE $supplementsTable (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
dosageAmount REAL NOT NULL,
|
||||
brand TEXT,
|
||||
ingredients TEXT NOT NULL DEFAULT '[]',
|
||||
numberOfUnits INTEGER NOT NULL DEFAULT 1,
|
||||
unit TEXT NOT NULL,
|
||||
unitType TEXT NOT NULL DEFAULT 'units',
|
||||
frequencyPerDay INTEGER NOT NULL,
|
||||
reminderTimes TEXT NOT NULL,
|
||||
@@ -73,6 +75,20 @@ class DatabaseHelper {
|
||||
FOREIGN KEY (supplementId) REFERENCES $supplementsTable (id)
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE $notificationTrackingTable (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
notificationId INTEGER NOT NULL UNIQUE,
|
||||
supplementId INTEGER NOT NULL,
|
||||
scheduledTime TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
retryCount INTEGER NOT NULL DEFAULT 0,
|
||||
lastRetryTime TEXT,
|
||||
createdAt TEXT NOT NULL,
|
||||
FOREIGN KEY (supplementId) REFERENCES $supplementsTable (id)
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||
@@ -97,6 +113,7 @@ class DatabaseHelper {
|
||||
CREATE TABLE ${supplementsTable}_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
brand TEXT,
|
||||
dosageAmount REAL NOT NULL,
|
||||
numberOfUnits INTEGER NOT NULL DEFAULT 1,
|
||||
unit TEXT NOT NULL,
|
||||
@@ -112,8 +129,8 @@ class DatabaseHelper {
|
||||
// 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
|
||||
(id, name, brand, dosageAmount, numberOfUnits, unit, unitType, frequencyPerDay, reminderTimes, notes, createdAt, isActive)
|
||||
SELECT id, name, NULL as brand, dosageAmount, numberOfUnits, unit, unitType, frequencyPerDay, reminderTimes, notes, createdAt, isActive
|
||||
FROM $supplementsTable
|
||||
''');
|
||||
|
||||
@@ -121,6 +138,86 @@ class DatabaseHelper {
|
||||
await db.execute('DROP TABLE $supplementsTable');
|
||||
await db.execute('ALTER TABLE ${supplementsTable}_new RENAME TO $supplementsTable');
|
||||
}
|
||||
|
||||
if (oldVersion < 3) {
|
||||
// Add brand column for version 3
|
||||
await db.execute('ALTER TABLE $supplementsTable ADD COLUMN brand TEXT');
|
||||
}
|
||||
|
||||
if (oldVersion < 4) {
|
||||
// Complete migration to new ingredient-based schema
|
||||
// Add ingredients column and migrate old data
|
||||
await db.execute('ALTER TABLE $supplementsTable ADD COLUMN ingredients TEXT DEFAULT "[]"');
|
||||
|
||||
// Migrate existing supplements to use ingredients format
|
||||
final supplements = await db.query(supplementsTable);
|
||||
for (final supplement in supplements) {
|
||||
final dosageAmount = supplement['dosageAmount'] as double?;
|
||||
final unit = supplement['unit'] as String?;
|
||||
final name = supplement['name'] as String;
|
||||
|
||||
if (dosageAmount != null && unit != null && dosageAmount > 0) {
|
||||
// Create a single ingredient from the old dosage data
|
||||
final ingredient = {
|
||||
'name': name,
|
||||
'amount': dosageAmount,
|
||||
'unit': unit,
|
||||
};
|
||||
final ingredientsJson = jsonEncode([ingredient]);
|
||||
|
||||
await db.update(
|
||||
supplementsTable,
|
||||
{'ingredients': ingredientsJson},
|
||||
where: 'id = ?',
|
||||
whereArgs: [supplement['id']],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove old columns
|
||||
await db.execute('''
|
||||
CREATE TABLE ${supplementsTable}_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
brand TEXT,
|
||||
ingredients TEXT NOT NULL DEFAULT '[]',
|
||||
numberOfUnits INTEGER NOT NULL DEFAULT 1,
|
||||
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
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
INSERT INTO ${supplementsTable}_new
|
||||
(id, name, brand, ingredients, numberOfUnits, unitType, frequencyPerDay, reminderTimes, notes, createdAt, isActive)
|
||||
SELECT id, name, brand, ingredients, numberOfUnits, unitType, frequencyPerDay, reminderTimes, notes, createdAt, isActive
|
||||
FROM $supplementsTable
|
||||
''');
|
||||
|
||||
await db.execute('DROP TABLE $supplementsTable');
|
||||
await db.execute('ALTER TABLE ${supplementsTable}_new RENAME TO $supplementsTable');
|
||||
}
|
||||
|
||||
if (oldVersion < 5) {
|
||||
// Add notification tracking table
|
||||
await db.execute('''
|
||||
CREATE TABLE $notificationTrackingTable (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
notificationId INTEGER NOT NULL UNIQUE,
|
||||
supplementId INTEGER NOT NULL,
|
||||
scheduledTime TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
retryCount INTEGER NOT NULL DEFAULT 0,
|
||||
lastRetryTime TEXT,
|
||||
createdAt TEXT NOT NULL,
|
||||
FOREIGN KEY (supplementId) REFERENCES $supplementsTable (id)
|
||||
)
|
||||
''');
|
||||
}
|
||||
}
|
||||
|
||||
// Supplement CRUD operations
|
||||
@@ -140,6 +237,37 @@ class DatabaseHelper {
|
||||
return List.generate(maps.length, (i) => Supplement.fromMap(maps[i]));
|
||||
}
|
||||
|
||||
Future<List<Supplement>> getArchivedSupplements() async {
|
||||
Database db = await database;
|
||||
List<Map<String, dynamic>> maps = await db.query(
|
||||
supplementsTable,
|
||||
where: 'isActive = ?',
|
||||
whereArgs: [0],
|
||||
orderBy: 'name ASC',
|
||||
);
|
||||
return List.generate(maps.length, (i) => Supplement.fromMap(maps[i]));
|
||||
}
|
||||
|
||||
Future<void> archiveSupplement(int id) async {
|
||||
Database db = await database;
|
||||
await db.update(
|
||||
supplementsTable,
|
||||
{'isActive': 0},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> unarchiveSupplement(int id) async {
|
||||
Database db = await database;
|
||||
await db.update(
|
||||
supplementsTable,
|
||||
{'isActive': 1},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<Supplement?> getSupplement(int id) async {
|
||||
Database db = await database;
|
||||
List<Map<String, dynamic>> maps = await db.query(
|
||||
@@ -213,7 +341,10 @@ 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, s.unitType as supplementUnitType
|
||||
SELECT i.*,
|
||||
i.supplementId as supplement_id,
|
||||
s.name as supplementName,
|
||||
s.unitType as supplementUnitType
|
||||
FROM $intakesTable i
|
||||
JOIN $supplementsTable s ON i.supplementId = s.id
|
||||
WHERE i.takenAt >= ? AND i.takenAt <= ?
|
||||
@@ -229,7 +360,10 @@ 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, s.unitType as supplementUnitType
|
||||
SELECT i.*,
|
||||
i.supplementId as supplement_id,
|
||||
s.name as supplementName,
|
||||
s.unitType as supplementUnitType
|
||||
FROM $intakesTable i
|
||||
JOIN $supplementsTable s ON i.supplementId = s.id
|
||||
WHERE i.takenAt >= ? AND i.takenAt <= ?
|
||||
@@ -247,4 +381,90 @@ class DatabaseHelper {
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
// Notification tracking methods
|
||||
Future<int> trackNotification({
|
||||
required int notificationId,
|
||||
required int supplementId,
|
||||
required DateTime scheduledTime,
|
||||
}) async {
|
||||
Database db = await database;
|
||||
|
||||
// Use INSERT OR REPLACE to handle both new and existing notifications
|
||||
await db.rawInsert('''
|
||||
INSERT OR REPLACE INTO $notificationTrackingTable
|
||||
(notificationId, supplementId, scheduledTime, status, retryCount, lastRetryTime, createdAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
''', [
|
||||
notificationId,
|
||||
supplementId,
|
||||
scheduledTime.toIso8601String(),
|
||||
'pending',
|
||||
0,
|
||||
null,
|
||||
DateTime.now().toIso8601String(),
|
||||
]);
|
||||
|
||||
return notificationId;
|
||||
}
|
||||
|
||||
Future<void> markNotificationTaken(int notificationId) async {
|
||||
Database db = await database;
|
||||
await db.update(
|
||||
notificationTrackingTable,
|
||||
{'status': 'taken'},
|
||||
where: 'notificationId = ?',
|
||||
whereArgs: [notificationId],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> incrementRetryCount(int notificationId) async {
|
||||
Database db = await database;
|
||||
await db.rawUpdate('''
|
||||
UPDATE $notificationTrackingTable
|
||||
SET retryCount = retryCount + 1,
|
||||
lastRetryTime = ?,
|
||||
status = 'retrying'
|
||||
WHERE notificationId = ?
|
||||
''', [DateTime.now().toIso8601String(), notificationId]);
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getPendingNotifications() async {
|
||||
Database db = await database;
|
||||
return await db.query(
|
||||
notificationTrackingTable,
|
||||
where: 'status IN (?, ?)',
|
||||
whereArgs: ['pending', 'retrying'],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> markNotificationExpired(int notificationId) async {
|
||||
Database db = await database;
|
||||
await db.update(
|
||||
notificationTrackingTable,
|
||||
{'status': 'expired'},
|
||||
where: 'notificationId = ?',
|
||||
whereArgs: [notificationId],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> cleanupOldNotificationTracking() async {
|
||||
Database db = await database;
|
||||
// Remove tracking records older than 7 days
|
||||
final cutoffDate = DateTime.now().subtract(const Duration(days: 7)).toIso8601String();
|
||||
await db.delete(
|
||||
notificationTrackingTable,
|
||||
where: 'createdAt < ?',
|
||||
whereArgs: [cutoffDate],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> clearNotificationTracking(int supplementId) async {
|
||||
Database db = await database;
|
||||
await db.delete(
|
||||
notificationTrackingTable,
|
||||
where: 'supplementId = ?',
|
||||
whereArgs: [supplementId],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user