import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import '../models/category_model.dart'; import '../models/transaction_model.dart'; class DatabaseHelper { static final DatabaseHelper instance = DatabaseHelper._init(); static Database? _database; DatabaseHelper._init(); Future get database async { if (_database != null) return _database!; _database = await _initDB('expense_tracker.db'); return _database!; } Future _initDB(String filePath) async { final dbPath = await getDatabasesPath(); final path = join(dbPath, filePath); return await openDatabase( path, version: 2, onCreate: _createDB, onUpgrade: _upgradeDB, ); } Future _createDB(Database db, int version) async { const idType = 'INTEGER PRIMARY KEY AUTOINCREMENT'; const textType = 'TEXT NOT NULL'; const realType = 'REAL NOT NULL'; const intType = 'INTEGER NOT NULL'; const intNullableType = 'INTEGER'; // Create Categories Table await db.execute(''' CREATE TABLE categories ( id $idType, name $textType, type $textType, icon $textType, color $intType, parentId $intNullableType ) '''); // Create Transactions Table await db.execute(''' CREATE TABLE transactions ( id TEXT PRIMARY KEY, amount $realType, type $textType, categoryId $intType, date $intType, note TEXT, createdAt $intType, updatedAt $intType, FOREIGN KEY (categoryId) REFERENCES categories (id) ) '''); // Insert Default Categories await _insertDefaultCategories(db); } Future _upgradeDB(Database db, int oldVersion, int newVersion) async { if (oldVersion < 2) { await db.execute('ALTER TABLE categories ADD COLUMN parentId INTEGER'); } } Future _insertDefaultCategories(Database db) async { // Parent Categories final foodId = await db.insert('categories', Category(name: '餐饮', type: 'expense', icon: 'restaurant', color: 0xFFFF5722).toMap()); final transportId = await db.insert('categories', Category(name: '交通', type: 'expense', icon: 'directions_bus', color: 0xFF2196F3).toMap()); final shoppingId = await db.insert('categories', Category(name: '购物', type: 'expense', icon: 'shopping_cart', color: 0xFFE91E63).toMap()); await db.insert('categories', Category(name: '娱乐', type: 'expense', icon: 'movie', color: 0xFF9C27B0).toMap()); await db.insert('categories', Category(name: '医疗', type: 'expense', icon: 'local_hospital', color: 0xFFF44336).toMap()); // Income await db.insert('categories', Category(name: '工资', type: 'income', icon: 'attach_money', color: 0xFF4CAF50).toMap()); await db.insert('categories', Category(name: '投资', type: 'income', icon: 'trending_up', color: 0xFF009688).toMap()); // Sub Categories await db.insert('categories', Category(name: '早餐', type: 'expense', icon: 'breakfast_dining', color: 0xFFFF5722, parentId: foodId).toMap()); await db.insert('categories', Category(name: '午餐', type: 'expense', icon: 'lunch_dining', color: 0xFFFF5722, parentId: foodId).toMap()); await db.insert('categories', Category(name: '晚餐', type: 'expense', icon: 'dinner_dining', color: 0xFFFF5722, parentId: foodId).toMap()); await db.insert('categories', Category(name: '地铁', type: 'expense', icon: 'subway', color: 0xFF2196F3, parentId: transportId).toMap()); await db.insert('categories', Category(name: '打车', type: 'expense', icon: 'local_taxi', color: 0xFF2196F3, parentId: transportId).toMap()); await db.insert('categories', Category(name: '衣服', type: 'expense', icon: 'checkroom', color: 0xFFE91E63, parentId: shoppingId).toMap()); await db.insert('categories', Category(name: '数码', type: 'expense', icon: 'devices', color: 0xFFE91E63, parentId: shoppingId).toMap()); } // Transactions CRUD Future createTransaction(TransactionModel transaction) async { final db = await instance.database; await db.insert('transactions', transaction.toMap()); } Future readTransaction(String id) async { final db = await instance.database; final maps = await db.query( 'transactions', columns: null, where: 'id = ?', whereArgs: [id], ); if (maps.isNotEmpty) { return TransactionModel.fromMap(maps.first); } else { throw Exception('ID $id not found'); } } Future> readAllTransactions() async { final db = await instance.database; final orderBy = 'date DESC'; final result = await db.query('transactions', orderBy: orderBy); return result.map((json) => TransactionModel.fromMap(json)).toList(); } Future updateTransaction(TransactionModel transaction) async { final db = await instance.database; return db.update( 'transactions', transaction.toMap(), where: 'id = ?', whereArgs: [transaction.id], ); } Future deleteTransaction(String id) async { final db = await instance.database; return await db.delete( 'transactions', where: 'id = ?', whereArgs: [id], ); } // Categories CRUD Future> readAllCategories() async { final db = await instance.database; final result = await db.query('categories'); return result.map((json) => Category.fromMap(json)).toList(); } Future createCategory(Category category) async { final db = await instance.database; return await db.insert('categories', category.toMap()); } Future updateCategory(Category category) async { final db = await instance.database; return await db.update( 'categories', category.toMap(), where: 'id = ?', whereArgs: [category.id], ); } Future deleteCategory(int id) async { final db = await instance.database; // Check if it has children, if so, maybe update them or delete them? // For simplicity, we won't cascade delete here but UI should warn. return await db.delete( 'categories', where: 'id = ?', whereArgs: [id], ); } }