513 lines
13 KiB
Dart
513 lines
13 KiB
Dart
import 'package:sqflite/sqflite.dart';
|
|
import 'package:path/path.dart';
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
import '../models/bean.dart';
|
|
import '../models/machine.dart';
|
|
import '../models/recipe.dart';
|
|
import '../models/drink.dart';
|
|
import '../models/journal_entry.dart';
|
|
import 'csv_data_service.dart';
|
|
|
|
class UserDataService {
|
|
static final UserDataService _instance = UserDataService._internal();
|
|
factory UserDataService() => _instance;
|
|
UserDataService._internal();
|
|
|
|
final CsvDataService _csvDataService = CsvDataService();
|
|
Database? _database;
|
|
|
|
// In-memory storage for web
|
|
List<Bean> _webBeans = [];
|
|
List<Machine> _webMachines = [];
|
|
List<Recipe> _webRecipes = [];
|
|
List<Drink> _webDrinks = [];
|
|
List<JournalEntry> _webJournalEntries = [];
|
|
|
|
Future<Database> get database async {
|
|
if (kIsWeb) {
|
|
throw UnsupportedError('SQLite is not supported on web. Use in-memory storage instead.');
|
|
}
|
|
if (_database != null) return _database!;
|
|
_database = await _initDatabase();
|
|
return _database!;
|
|
}
|
|
|
|
Future<Database> _initDatabase() async {
|
|
if (kIsWeb) {
|
|
throw UnsupportedError('SQLite is not supported on web. Please use a mobile device or desktop application.');
|
|
}
|
|
|
|
final dbPath = await getDatabasesPath();
|
|
final path = join(dbPath, 'coffee_user_data.db');
|
|
|
|
return await openDatabase(
|
|
path,
|
|
version: 1,
|
|
onCreate: _createTables,
|
|
);
|
|
}
|
|
|
|
Future<void> _createTables(Database db, int version) async {
|
|
// User-owned beans table (references to CSV beans)
|
|
await db.execute('''
|
|
CREATE TABLE user_beans (
|
|
id TEXT PRIMARY KEY,
|
|
quantity REAL NOT NULL,
|
|
notes TEXT,
|
|
date_added TEXT NOT NULL
|
|
)
|
|
''');
|
|
|
|
// User-owned machines table (references to CSV machines)
|
|
await db.execute('''
|
|
CREATE TABLE user_machines (
|
|
id TEXT PRIMARY KEY,
|
|
notes TEXT,
|
|
date_added TEXT NOT NULL
|
|
)
|
|
''');
|
|
|
|
// User-owned recipes table (references to CSV recipes)
|
|
await db.execute('''
|
|
CREATE TABLE user_recipes (
|
|
id TEXT PRIMARY KEY,
|
|
notes TEXT,
|
|
date_added TEXT NOT NULL
|
|
)
|
|
''');
|
|
|
|
// Drinks table
|
|
await db.execute('''
|
|
CREATE TABLE drinks (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
details TEXT NOT NULL,
|
|
image TEXT,
|
|
notes TEXT NOT NULL,
|
|
preferred INTEGER NOT NULL,
|
|
rating REAL NOT NULL,
|
|
size TEXT NOT NULL,
|
|
bean_id TEXT,
|
|
machine_id TEXT,
|
|
recipe_id TEXT,
|
|
date_created TEXT NOT NULL
|
|
)
|
|
''');
|
|
|
|
// Journal entries table
|
|
await db.execute('''
|
|
CREATE TABLE journal_entries (
|
|
id TEXT PRIMARY KEY,
|
|
date TEXT NOT NULL,
|
|
drink_id TEXT NOT NULL,
|
|
notes TEXT,
|
|
mood TEXT,
|
|
weather TEXT,
|
|
FOREIGN KEY (drink_id) REFERENCES drinks (id)
|
|
)
|
|
''');
|
|
}
|
|
|
|
// Drink operations
|
|
Future<void> saveDrink(Drink drink) async {
|
|
if (kIsWeb) {
|
|
final index = _webDrinks.indexWhere((d) => d.id == drink.id);
|
|
if (index >= 0) {
|
|
_webDrinks[index] = drink;
|
|
} else {
|
|
_webDrinks.add(drink);
|
|
}
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
|
|
await db.insert(
|
|
'drinks',
|
|
{
|
|
'id': drink.id,
|
|
'name': drink.name,
|
|
'details': drink.details,
|
|
'image': drink.image,
|
|
'notes': drink.notes,
|
|
'preferred': drink.preferred ? 1 : 0,
|
|
'rating': drink.rating,
|
|
'size': drink.size,
|
|
'bean_id': drink.bean?.id,
|
|
'machine_id': drink.machine?.id,
|
|
'recipe_id': drink.recipe?.id,
|
|
'date_created': drink.dateCreated.toIso8601String(),
|
|
},
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
}
|
|
|
|
Future<List<Drink>> getDrinks() async {
|
|
if (kIsWeb) {
|
|
return _webDrinks;
|
|
}
|
|
|
|
final db = await database;
|
|
final result = await db.query('drinks');
|
|
|
|
// Get reference data from CSV
|
|
final beans = await _csvDataService.getBeans();
|
|
final machines = await _csvDataService.getMachines();
|
|
final recipes = await _csvDataService.getRecipes();
|
|
|
|
List<Drink> drinks = [];
|
|
for (final data in result) {
|
|
Bean? bean;
|
|
if (data['bean_id'] != null) {
|
|
try {
|
|
bean = beans.firstWhere((b) => b.id == data['bean_id']);
|
|
} catch (e) {
|
|
// Bean not found, skip
|
|
}
|
|
}
|
|
|
|
Machine? machine;
|
|
if (data['machine_id'] != null) {
|
|
try {
|
|
machine = machines.firstWhere((m) => m.id == data['machine_id']);
|
|
} catch (e) {
|
|
// Machine not found, skip
|
|
}
|
|
}
|
|
|
|
Recipe? recipe;
|
|
if (data['recipe_id'] != null) {
|
|
try {
|
|
recipe = recipes.firstWhere((r) => r.id == data['recipe_id']);
|
|
} catch (e) {
|
|
// Recipe not found, skip
|
|
}
|
|
}
|
|
|
|
drinks.add(Drink(
|
|
id: data['id'] as String,
|
|
name: data['name'] as String,
|
|
details: data['details'] as String,
|
|
image: data['image'] as String?,
|
|
notes: data['notes'] as String,
|
|
preferred: (data['preferred'] as int) == 1,
|
|
rating: data['rating'] as double,
|
|
size: data['size'] as String,
|
|
bean: bean,
|
|
machine: machine,
|
|
recipe: recipe,
|
|
dateCreated: DateTime.parse(data['date_created'] as String),
|
|
));
|
|
}
|
|
|
|
return drinks;
|
|
}
|
|
|
|
Future<void> deleteDrink(String id) async {
|
|
if (kIsWeb) {
|
|
_webDrinks.removeWhere((drink) => drink.id == id);
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.delete(
|
|
'drinks',
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
|
|
// Journal operations
|
|
Future<void> saveJournalEntry(JournalEntry entry) async {
|
|
if (kIsWeb) {
|
|
// Save the drink first if it's not already saved
|
|
await saveDrink(entry.drink);
|
|
|
|
final index = _webJournalEntries.indexWhere((e) => e.id == entry.id);
|
|
if (index >= 0) {
|
|
_webJournalEntries[index] = entry;
|
|
} else {
|
|
_webJournalEntries.add(entry);
|
|
}
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
|
|
// Save the drink first if it's not already saved
|
|
await saveDrink(entry.drink);
|
|
|
|
await db.insert(
|
|
'journal_entries',
|
|
{
|
|
'id': entry.id,
|
|
'date': entry.date.toIso8601String(),
|
|
'drink_id': entry.drink.id,
|
|
'notes': entry.notes,
|
|
'mood': entry.mood,
|
|
'weather': entry.weather,
|
|
},
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
}
|
|
|
|
Future<List<JournalEntry>> getJournalEntries() async {
|
|
if (kIsWeb) {
|
|
return _webJournalEntries;
|
|
}
|
|
|
|
final db = await database;
|
|
final result = await db.query('journal_entries');
|
|
|
|
final drinks = await getDrinks();
|
|
|
|
List<JournalEntry> entries = [];
|
|
for (final data in result) {
|
|
try {
|
|
final drink = drinks.firstWhere((d) => d.id == data['drink_id']);
|
|
|
|
entries.add(JournalEntry(
|
|
id: data['id'] as String,
|
|
date: DateTime.parse(data['date'] as String),
|
|
drink: drink,
|
|
notes: data['notes'] as String?,
|
|
mood: data['mood'] as String?,
|
|
weather: data['weather'] as String?,
|
|
));
|
|
} catch (e) {
|
|
// Drink not found, skip this entry
|
|
}
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
Future<void> deleteJournalEntry(String id) async {
|
|
if (kIsWeb) {
|
|
_webJournalEntries.removeWhere((entry) => entry.id == id);
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.delete(
|
|
'journal_entries',
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
|
|
// Bean operations - User's personal collection
|
|
Future<List<Bean>> getBeans() async {
|
|
if (kIsWeb) {
|
|
return _webBeans;
|
|
}
|
|
|
|
final db = await database;
|
|
final result = await db.query('user_beans');
|
|
|
|
// Get all available beans from CSV
|
|
final allBeans = await _csvDataService.getBeans();
|
|
|
|
List<Bean> userBeans = [];
|
|
for (final data in result) {
|
|
try {
|
|
final bean = allBeans.firstWhere((b) => b.id == data['id']);
|
|
// You could customize the bean here with user-specific data if needed
|
|
userBeans.add(bean);
|
|
} catch (e) {
|
|
// Bean not found in CSV, skip
|
|
}
|
|
}
|
|
|
|
return userBeans;
|
|
}
|
|
|
|
Future<void> saveBean(Bean bean) async {
|
|
if (kIsWeb) {
|
|
final index = _webBeans.indexWhere((b) => b.id == bean.id);
|
|
if (index >= 0) {
|
|
_webBeans[index] = bean;
|
|
} else {
|
|
_webBeans.add(bean);
|
|
}
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.insert(
|
|
'user_beans',
|
|
{
|
|
'id': bean.id,
|
|
'quantity': bean.quantity,
|
|
'notes': bean.notes,
|
|
'date_added': DateTime.now().toIso8601String(),
|
|
},
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
}
|
|
|
|
Future<void> deleteBean(String id) async {
|
|
if (kIsWeb) {
|
|
_webBeans.removeWhere((bean) => bean.id == id);
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.delete(
|
|
'user_beans',
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
|
|
// Machine operations - User's personal collection
|
|
Future<List<Machine>> getMachines() async {
|
|
if (kIsWeb) {
|
|
return _webMachines;
|
|
}
|
|
|
|
final db = await database;
|
|
final result = await db.query('user_machines');
|
|
|
|
// Get all available machines from CSV
|
|
final allMachines = await _csvDataService.getMachines();
|
|
|
|
List<Machine> userMachines = [];
|
|
for (final data in result) {
|
|
try {
|
|
final machine = allMachines.firstWhere((m) => m.id == data['id']);
|
|
userMachines.add(machine);
|
|
} catch (e) {
|
|
// Machine not found in CSV, skip
|
|
}
|
|
}
|
|
|
|
return userMachines;
|
|
}
|
|
|
|
Future<void> saveMachine(Machine machine) async {
|
|
if (kIsWeb) {
|
|
final index = _webMachines.indexWhere((m) => m.id == machine.id);
|
|
if (index >= 0) {
|
|
_webMachines[index] = machine;
|
|
} else {
|
|
_webMachines.add(machine);
|
|
}
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.insert(
|
|
'user_machines',
|
|
{
|
|
'id': machine.id,
|
|
'notes': '', // You could add notes field to machine model if needed
|
|
'date_added': DateTime.now().toIso8601String(),
|
|
},
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
}
|
|
|
|
Future<void> deleteMachine(String id) async {
|
|
if (kIsWeb) {
|
|
_webMachines.removeWhere((machine) => machine.id == id);
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.delete(
|
|
'user_machines',
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
|
|
// Recipe operations - User's personal collection
|
|
Future<List<Recipe>> getRecipes() async {
|
|
if (kIsWeb) {
|
|
return _webRecipes;
|
|
}
|
|
|
|
final db = await database;
|
|
final result = await db.query('user_recipes');
|
|
|
|
// Get all available recipes from CSV
|
|
final allRecipes = await _csvDataService.getRecipes();
|
|
|
|
List<Recipe> userRecipes = [];
|
|
for (final data in result) {
|
|
try {
|
|
final recipe = allRecipes.firstWhere((r) => r.id == data['id']);
|
|
userRecipes.add(recipe);
|
|
} catch (e) {
|
|
// Recipe not found in CSV, skip
|
|
}
|
|
}
|
|
|
|
return userRecipes;
|
|
}
|
|
|
|
Future<void> saveRecipe(Recipe recipe) async {
|
|
if (kIsWeb) {
|
|
final index = _webRecipes.indexWhere((r) => r.id == recipe.id);
|
|
if (index >= 0) {
|
|
_webRecipes[index] = recipe;
|
|
} else {
|
|
_webRecipes.add(recipe);
|
|
}
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.insert(
|
|
'user_recipes',
|
|
{
|
|
'id': recipe.id,
|
|
'notes': recipe.notes ?? '',
|
|
'date_added': DateTime.now().toIso8601String(),
|
|
},
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
}
|
|
|
|
Future<void> deleteRecipe(String id) async {
|
|
if (kIsWeb) {
|
|
_webRecipes.removeWhere((recipe) => recipe.id == id);
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.delete(
|
|
'user_recipes',
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
|
|
// Utility methods
|
|
Future<void> clearAllData() async {
|
|
if (kIsWeb) {
|
|
_webBeans.clear();
|
|
_webMachines.clear();
|
|
_webRecipes.clear();
|
|
_webDrinks.clear();
|
|
_webJournalEntries.clear();
|
|
return;
|
|
}
|
|
|
|
final db = await database;
|
|
await db.execute('DELETE FROM journal_entries');
|
|
await db.execute('DELETE FROM drinks');
|
|
await db.execute('DELETE FROM user_recipes');
|
|
await db.execute('DELETE FROM user_machines');
|
|
await db.execute('DELETE FROM user_beans');
|
|
}
|
|
|
|
Future<void> close() async {
|
|
if (_database != null) {
|
|
await _database!.close();
|
|
_database = null;
|
|
}
|
|
}
|
|
}
|