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 _webBeans = []; List _webMachines = []; List _webRecipes = []; List _webDrinks = []; List _webJournalEntries = []; Future 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 _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 _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 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> 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 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 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 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> getJournalEntries() async { if (kIsWeb) { return _webJournalEntries; } final db = await database; final result = await db.query('journal_entries'); final drinks = await getDrinks(); List 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 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> 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 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 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 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> 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 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 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 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> 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 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 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 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 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 close() async { if (_database != null) { await _database!.close(); _database = null; } } }