CoffeeAtHome/lib/services/user_data_service.dart
2026-03-29 08:13:38 -07:00

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;
}
}
}