From ef58a06dfa59229d1baf6967171b3b81f8357d41 Mon Sep 17 00:00:00 2001 From: Hyperling Date: Fri, 7 Feb 2025 15:29:20 -0700 Subject: [PATCH] Successfully migrate the application to SQLIte! --- lib/db.dart | 112 +++++-- lib/main.dart | 143 ++------ lib/models/expense.dart | 36 +- lib/pages/expense.dart | 314 ++++++++++-------- lib/pages/home.dart | 119 +++++++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 98 +++++- pubspec.yaml | 3 + 8 files changed, 535 insertions(+), 292 deletions(-) create mode 100644 lib/pages/home.dart diff --git a/lib/db.dart b/lib/db.dart index 469479a..645b648 100644 --- a/lib/db.dart +++ b/lib/db.dart @@ -1,45 +1,101 @@ // https://docs.flutter.dev/cookbook/persistence/sqlite // SQLite +import 'dart:io'; import 'dart:async'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_expense_tracker/models/frequency.dart'; import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; +import 'package:flutter/material.dart'; + // Local +import '/models/frequency.dart'; import '/models/expense.dart'; -void loadDB() async { - // Avoid errors caused by flutter upgrade. - WidgetsFlutterBinding.ensureInitialized(); +// Leaned on this example: +// https://learnflutterwithme.com/sqlite +class DatabaseHelper { + DatabaseHelper._privateConstructor(); + static final DatabaseHelper instance = DatabaseHelper._privateConstructor(); - final String frequencies = - "'${Frequency.values.map((freq) => freq.title).join("','")}'"; - print(frequencies); + static Database? _db; + Future get db async => _db ??= await _initDatabase(); - // Open the database and store the reference. - final database = openDatabase( - // Set the path to the database. Note: Using the `join` function from the - // `path` package is best practice to ensure the path is correctly - // constructed for each platform. - join(await getDatabasesPath(), 'expense_tracker.db'), + Future _initDatabase() async { + Directory documentsDirectory = await getApplicationDocumentsDirectory(); + String path = join(documentsDirectory.path, "com_hyperling_expense.db"); + return await openDatabase( + path, + version: 1, + onCreate: _onCreate, + ); + } - onCreate: (db, version) { - // Run the CREATE TABLE statement on the database. - return db.execute( - """ + Future _onCreate(Database db, int version) async { + await db.execute(""" CREATE TABLE expense ( id INTEGER PRIMARY KEY - , name TEXT - , cost DOUBLE - , frequency TEXT CHECK(frequency IN ($frequencies) ) + , name TEXT NOT NULL UNIQUE + , cost DOUBLE NOT NULL + , frequency TEXT NOT NULL , description TEXT - )""", - ); - }, - // Set the version. This executes the onCreate function and provides a - // path to perform database upgrades and downgrades. - version: 1, - ); + ) + """); + } + + /// Expense Section + /// + Future> getExpenses() async { + Database db = await instance.db; + var expenses = await db.query("expense", orderBy: "name"); + List expenseList = expenses.isNotEmpty + ? expenses.map((c) => Expense.fromMap(c)).toList() + : []; + return expenseList; + } + + Future addExpense(Expense expense) async { + Database db = await instance.db; + return await db.insert( + "expense", + expense.toMap(), + ); + } + + Future removeExpense(int id) async { + Database db = await instance.db; + return await db.delete( + "expense", + where: "id = ?", + whereArgs: [id], + ); + } + + Future updateExpense(Expense expense) async { + Database db = await instance.db; + return await db.update( + "expense", + expense.toMap(), + where: "id = ?", + whereArgs: [expense.id], + ); + } + + Future checkExpenseNameExists(String name) async { + Database db = await instance.db; + var expenses = await db.query("expense", + where: "name = ?", whereArgs: [name],); + return expenses.isNotEmpty; + } + + /// + + /// Income Section + /// + + /// + /// Liquid Asset Section + + /// } diff --git a/lib/main.dart b/lib/main.dart index 6fd4b8a..27cec85 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,16 +2,36 @@ import 'package:flutter/material.dart'; // Local -import '/pages/expense.dart'; -import '/pages/income.dart'; -import '/pages/asset.dart'; -import '/pages/report.dart'; -import '/pages/settings.dart'; -import '/pages/help.dart'; -import '/db.dart'; +import '/pages/home.dart'; + +// SQLite +import 'dart:io'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; + +const bool testing = true; void main() { - loadDB(); + // I see no good explanations of why to use this other package yet, but + // trying this to see if it fixes the DB factory errors. + // "Unhandled Exception: Bad state: databaseFactory not initialized databaseFactory is only initialized when using sqflite. When using `sqflite_common_ffi`You must call `databaseFactory = databaseFactoryFfi;` before using global openDatabase API + // https://stackoverflow.com/questions/76158800/databasefactory-not-initialized-when-using-sqflite-in-flutter + if (Platform.isWindows || Platform.isLinux) { + // Initialize FFI + sqfliteFfiInit(); + databaseFactory = databaseFactoryFfi; + } + WidgetsFlutterBinding.ensureInitialized(); + + if (testing) { + () async { + Directory documentsDirectory = await getApplicationDocumentsDirectory(); + String path = join(documentsDirectory.path, 'com_hyperling_expense.db'); + await deleteDatabase(path); + }(); + } + runApp(const MainApp()); } @@ -36,110 +56,3 @@ class MainApp extends StatelessWidget { ); } } - -class HomePage extends StatefulWidget { - const HomePage({ - super.key, - }); - - @override - State createState() => _HomePageState(); -} - -class _HomePageState extends State { - var pageSelected = 0; - - refresh() { - setState(() {}); - } - - @override - Widget build(BuildContext context) { - Widget page; - Widget? dialog; - switch (pageSelected) { - case 0: - page = ExpensePage(); - dialog = ExpenseInputDialog( - notifyParent: refresh, - ); - case 1: - page = IncomePage(); - case 2: - page = AssetPage(); - case 3: - page = ProjectionPage(); - case 4: - page = SettingsPage(); - case 5: - page = HelpPage(); - default: - throw UnimplementedError('No widget for page $pageSelected yet!'); - } - - Future addNewValue(BuildContext context) { - return showDialog( - context: context, - builder: (_) => AlertDialog(content: dialog), - ); - } - - Widget? floatingButton; - if (dialog != null) { - floatingButton = IconButton( - onPressed: () { - addNewValue(context); - }, - icon: Icon(Icons.add), - color: Theme.of(context).colorScheme.onSurface, - ); - } - - return LayoutBuilder(builder: (context, constraints) { - return Scaffold( - appBar: AppBar(title: Text("Expense Tracker")), - drawer: NavigationRail( - extended: true, - destinations: [ - NavigationRailDestination( - icon: Icon(Icons.payment), - label: Text('Expenses'), - ), - NavigationRailDestination( - icon: Icon(Icons.account_balance), - label: Text('Income'), - ), - NavigationRailDestination( - icon: Icon(Icons.attach_money), - label: Text('Liquid Assets'), - ), - NavigationRailDestination( - icon: Icon(Icons.bar_chart), - label: Text('Reports'), - ), - NavigationRailDestination( - icon: Icon(Icons.settings), - label: Text('Settings'), - ), - NavigationRailDestination( - icon: Icon(Icons.help), - label: Text('Help'), - ), - ], - selectedIndex: pageSelected, - onDestinationSelected: (value) { - setState(() { - pageSelected = value; - Navigator.pop(context); - }); - }, - ), - body: Container( - color: Theme.of(context).colorScheme.primaryContainer, - child: Center(child: page), - ), - floatingActionButton: floatingButton, - ); - }); - } -} diff --git a/lib/models/expense.dart b/lib/models/expense.dart index 0cc39d2..1cec144 100644 --- a/lib/models/expense.dart +++ b/lib/models/expense.dart @@ -1,20 +1,24 @@ import '/models/frequency.dart'; class Expense { + final int? id; final String name; final double cost; final Frequency frequency; final String description; - const Expense( - {required this.name, - required this.cost, - required this.frequency, - required this.description}); + const Expense({ + this.id, + required this.name, + required this.cost, + required this.frequency, + required this.description, + }); @override String toString() { - return "$name, $cost, ${frequency.title}, $description"; + //return "$name, $cost, ${frequency.title}, $description"; + return toMap().toString(); } double calcComparableCost() { @@ -24,4 +28,24 @@ class Expense { double calcComparableCostDaily() { return cost / frequency.numDays; } + + factory Expense.fromMap(Map json) => Expense( + id: json['id'], + name: json['name'], + cost: json['cost'], + frequency: Frequency.values + .where((expense) => expense.title == json['frequency']) + .first, + description: json['description'], + ); + + Map toMap() { + return { + 'id': id, + 'name': name, + 'cost': cost, + 'frequency': frequency.title, + 'description': description, + }; + } } diff --git a/lib/pages/expense.dart b/lib/pages/expense.dart index e78f0b0..bbf7dab 100644 --- a/lib/pages/expense.dart +++ b/lib/pages/expense.dart @@ -1,12 +1,13 @@ // Flutter +import 'dart:nativewrappers/_internal/vm/lib/ffi_allocation_patch.dart'; + import 'package:flutter/material.dart'; +import 'package:flutter_expense_tracker/db.dart'; // Local import '/models/expense.dart'; import '/models/frequency.dart'; -List expenses = []; - class ExpensePage extends StatefulWidget { const ExpensePage({ super.key, @@ -25,139 +26,154 @@ class _ExpensePageState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); - expenses.sort( - (a, b) => (b.calcComparableCost() - a.calcComparableCost()).toInt(), - ); - - return expenses.isEmpty - ? Text("Add expenses to get started!") - : ListView.builder( - itemCount: expenses.length, - itemBuilder: (_, index) { - final Expense curr = expenses[index]; - final String estimateSymbolYearly = curr.frequency.timesPerYear - .toStringAsFixed(2) - .endsWith(".00") && - curr.calcComparableCost().toStringAsFixed(3).endsWith("0") - ? "" - : "~"; - final String estimateSymbolDaily = - curr.frequency.numDays.toStringAsFixed(2).endsWith(".00") && - curr - .calcComparableCostDaily() - .toStringAsFixed(3) - .endsWith("0") - ? "" - : "~"; - return Padding( - padding: const EdgeInsets.all(4.0), - child: Dismissible( - key: Key(curr.toString()), - background: Container( - color: Colors.red, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon(Icons.delete), - Text("Delete"), - ], - ), - ), - secondaryBackground: Container( - color: Colors.orange, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text("Edit"), - Icon(Icons.edit), - ], - ), - ), - onDismissed: (direction) { - setState(() { - expenses.remove(curr); - }); - switch (direction) { - case DismissDirection.startToEnd: - // Only remove the item from the list. - break; - case DismissDirection.endToStart: - // Open an edit dialog, then remove the item from the list. - showDialog( - context: context, - builder: (_) => AlertDialog( - content: ExpenseInputDialog( - notifyParent: refresh, - expense: curr, - ), - ), - ); - break; - default: - UnimplementedError( - "Direction ${direction.toString()} not recognized.", - ); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: theme.colorScheme.onPrimary, - ), - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - curr.name, - style: TextStyle(fontSize: 20.0), - ), - Text( - "${curr.cost.toStringAsFixed(2)} ${curr.frequency.title}", - style: TextStyle(fontSize: 12.0), - ), - ], - ), - Expanded( - child: Center( - child: Text( - curr.description, - style: TextStyle( - fontSize: 12.0, - ), - softWrap: true, - textAlign: TextAlign.center, - ), - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - //if (curr.frequency != Frequency.daily) - Text( - "$estimateSymbolDaily${curr.calcComparableCostDaily().toStringAsFixed(2)} ${Frequency.daily.title}", - style: TextStyle(fontSize: 12.0), - ), - //if (curr.frequency != Frequency.yearly) - Text( - "$estimateSymbolYearly${curr.calcComparableCost().toStringAsFixed(2)} ${Frequency.yearly.title}", - style: TextStyle(fontSize: 12.0), - ), - ], - ), - ], - ), - ), - ), - ), - ); - }, + return FutureBuilder>( + future: DatabaseHelper.instance.getExpenses(), + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (!snapshot.hasData) { + return Center(child: Text('Loading...')); + } + snapshot.data!.sort( + (a, b) => (b.calcComparableCost() - a.calcComparableCost()).toInt(), ); + return snapshot.data!.isEmpty + ? Text( + "Add expenses to get started.", + softWrap: true, + ) + : ListView.builder( + itemCount: snapshot.data!.length, + itemBuilder: (_, index) { + List expenses = snapshot.data!; + final Expense curr = expenses[index]; + final String estimateSymbolYearly = curr + .frequency.timesPerYear + .toStringAsFixed(2) + .endsWith(".00") && + curr + .calcComparableCost() + .toStringAsFixed(3) + .endsWith("0") + ? "" + : "~"; + final String estimateSymbolDaily = curr.frequency.numDays + .toStringAsFixed(2) + .endsWith(".00") && + curr + .calcComparableCostDaily() + .toStringAsFixed(3) + .endsWith("0") + ? "" + : "~"; + return Padding( + padding: const EdgeInsets.all(4.0), + child: Dismissible( + key: Key(curr.id!.toString()), + background: Container( + color: Colors.red, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.delete), + Text("Delete"), + ], + ), + ), + secondaryBackground: Container( + color: Colors.orange, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text("Edit"), + Icon(Icons.edit), + ], + ), + ), + onDismissed: (direction) { + setState(() { + expenses.remove(curr); + switch (direction) { + case DismissDirection.startToEnd: + DatabaseHelper.instance.removeExpense(curr.id!); + break; + case DismissDirection.endToStart: + // Open an edit dialog, then remove the item from the list. + showDialog( + context: context, + builder: (_) => AlertDialog( + content: ExpenseInputDialog( + notifyParent: refresh, + expense: curr, + ), + ), + ); + break; + default: + UnimplementedError( + "Direction ${direction.toString()} not recognized.", + ); + } + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: theme.colorScheme.onPrimary, + ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + curr.name, + style: TextStyle(fontSize: 20.0), + ), + Text( + "${curr.cost.toStringAsFixed(2)} ${curr.frequency.title}", + style: TextStyle(fontSize: 12.0), + ), + ], + ), + Expanded( + child: Center( + child: Text( + curr.description, + style: TextStyle( + fontSize: 12.0, + ), + softWrap: true, + textAlign: TextAlign.center, + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + //if (curr.frequency != Frequency.daily) + Text( + "$estimateSymbolDaily${curr.calcComparableCostDaily().toStringAsFixed(2)} ${Frequency.daily.title}", + style: TextStyle(fontSize: 12.0), + ), + //if (curr.frequency != Frequency.yearly) + Text( + "$estimateSymbolYearly${curr.calcComparableCost().toStringAsFixed(2)} ${Frequency.yearly.title}", + style: TextStyle(fontSize: 12.0), + ), + ], + ), + ], + ), + ), + ), + ), + ); + }, + ); + }); } } @@ -178,6 +194,7 @@ class ExpenseInputDialog extends StatefulWidget { class _ExpenseInputDialogState extends State { final _expenseFormKey = GlobalKey(); + int? _id; String _name = ""; double _cost = 0; Frequency _freq = Frequency.monthly; @@ -186,6 +203,7 @@ class _ExpenseInputDialogState extends State { @override Widget build(BuildContext context) { if (widget.expense != null) { + _id = widget.expense!.id; _name = widget.expense!.name; _cost = widget.expense!.cost; _freq = widget.expense!.frequency; @@ -202,7 +220,7 @@ class _ExpenseInputDialogState extends State { onPressed: () { if (widget.expense != null) { setState(() { - expenses.add(widget.expense!); + DatabaseHelper.instance.addExpense(widget.expense!); widget.notifyParent(); }); } @@ -237,9 +255,12 @@ class _ExpenseInputDialogState extends State { if (value!.isEmpty) { return "Name must be provided."; } - if (!expenses.every((expense) => expense.name != value)) { + // TODO: Figure out how to check the DB if a name already exists. + /* + if (DatabaseHelper.instance.checkExpenseNameExists(value)) { return "Name must be unique, already in use."; } + */ return null; }, onSaved: (value) { @@ -341,13 +362,22 @@ class _ExpenseInputDialogState extends State { if (_expenseFormKey.currentState!.validate()) { _expenseFormKey.currentState!.save(); setState(() { - expenses.add( - Expense( - name: _name, - cost: _cost, - frequency: _freq, - description: _desc), + Expense expense = Expense( + id: _id, + name: _name, + cost: _cost, + frequency: _freq, + description: _desc, ); + if (_id != null) { + DatabaseHelper.instance.updateExpense( + expense, + ); + } else { + DatabaseHelper.instance.addExpense( + expense, + ); + } }); widget.notifyParent(); Navigator.of(context).pop(); diff --git a/lib/pages/home.dart b/lib/pages/home.dart new file mode 100644 index 0000000..08b77bf --- /dev/null +++ b/lib/pages/home.dart @@ -0,0 +1,119 @@ +// Flutter +import 'package:flutter/material.dart'; + +// Local +import '/pages/expense.dart'; +import '/pages/income.dart'; +import '/pages/asset.dart'; +import '/pages/report.dart'; +import '/pages/settings.dart'; +import '/pages/help.dart'; + +class HomePage extends StatefulWidget { + const HomePage({ + super.key, + }); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + var pageSelected = 0; + + refresh() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + Widget page; + Widget? dialog; + switch (pageSelected) { + case 0: + page = ExpensePage(); + dialog = ExpenseInputDialog( + notifyParent: refresh, + ); + case 1: + page = IncomePage(); + case 2: + page = AssetPage(); + case 3: + page = ProjectionPage(); + case 4: + page = SettingsPage(); + case 5: + page = HelpPage(); + default: + throw UnimplementedError('No widget for page $pageSelected yet!'); + } + + Future addNewValue(BuildContext context) { + return showDialog( + context: context, + builder: (_) => AlertDialog(content: dialog), + ); + } + + Widget? floatingButton; + if (dialog != null) { + floatingButton = IconButton( + onPressed: () { + addNewValue(context); + }, + icon: Icon(Icons.add), + color: Theme.of(context).colorScheme.onSurface, + ); + } + + return LayoutBuilder(builder: (context, constraints) { + return Scaffold( + appBar: AppBar( + title: Text("Expense Tracker"), + ), + drawer: NavigationRail( + extended: true, + destinations: [ + NavigationRailDestination( + icon: Icon(Icons.payment), + label: Text('Expenses'), + ), + NavigationRailDestination( + icon: Icon(Icons.account_balance), + label: Text('Income'), + ), + NavigationRailDestination( + icon: Icon(Icons.attach_money), + label: Text('Liquid Assets'), + ), + NavigationRailDestination( + icon: Icon(Icons.bar_chart), + label: Text('Reports'), + ), + NavigationRailDestination( + icon: Icon(Icons.settings), + label: Text('Settings'), + ), + NavigationRailDestination( + icon: Icon(Icons.help), + label: Text('Help'), + ), + ], + selectedIndex: pageSelected, + onDestinationSelected: (value) { + setState(() { + pageSelected = value; + Navigator.pop(context); + }); + }, + ), + body: Container( + color: Theme.of(context).colorScheme.primaryContainer, + child: Center(child: page), + ), + floatingActionButton: floatingButton, + ); + }); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 29d4e7f..252c004 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import path_provider_foundation import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index a98f24f..8c6b778 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" flutter: dependency: "direct main" description: flutter @@ -124,13 +132,61 @@ packages: source: hosted version: "1.15.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" platform: dependency: transitive description: @@ -184,6 +240,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.4+6" + sqflite_common_ffi: + dependency: "direct main" + description: + name: sqflite_common_ffi + sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" + url: "https://pub.dev" + source: hosted + version: "2.3.4+4" sqflite_darwin: dependency: transitive description: @@ -200,6 +264,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "35d3726fe18ab1463403a5cc8d97dbc81f2a0b08082e8173851363fcc97b6627" + url: "https://pub.dev" + source: hosted + version: "2.7.2" stack_trace: dependency: transitive description: @@ -248,6 +320,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.3" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -264,6 +344,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: dart: ">=3.6.1 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1f83416..67bedb2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,10 @@ environment: dependencies: flutter: sdk: flutter + path: ^1.9.0 + path_provider: ^2.1.5 sqflite: ^2.4.1 + sqflite_common_ffi: ^2.3.4+4 dev_dependencies: flutter_test: