From 82ef2bf87c63ad4cf6816dd2b0e45a4f7ad91776 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Fri, 7 Apr 2023 15:56:17 +1200 Subject: [PATCH] Add basic CRUD with sqlite --- lib/edit_set.dart | 31 +++-- lib/gym_set.dart | 103 +++++++++++++++ lib/home_page.dart | 124 +++++++----------- lib/main.dart | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 27 +++- pubspec.yaml | 2 + 7 files changed, 206 insertions(+), 84 deletions(-) create mode 100644 lib/gym_set.dart diff --git a/lib/edit_set.dart b/lib/edit_set.dart index d3ab393..5ccbda3 100644 --- a/lib/edit_set.dart +++ b/lib/edit_set.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:fmassive/home_page.dart'; +import 'package:fmassive/gym_set.dart'; class EditGymSetPage extends StatefulWidget { final GymSet gymSet; - EditGymSetPage({required this.gymSet}); + const EditGymSetPage({required this.gymSet, super.key}); @override - _EditGymSetPageState createState() => _EditGymSetPageState(); + createState() => _EditGymSetPageState(); } class _EditGymSetPageState extends State { @@ -16,17 +16,18 @@ class _EditGymSetPageState extends State { final TextEditingController _weightController = TextEditingController(); late GymSet _editedGymSet; + late GymSetDatabaseHelper _db; @override void initState() { super.initState(); - // Initialize the edited GymSet object with the values from the input GymSet object + _db = GymSetDatabaseHelper.instance; _editedGymSet = GymSet( + id: widget.gymSet.id, name: widget.gymSet.name, reps: widget.gymSet.reps, weight: widget.gymSet.weight, created: DateTime.now()); - // Set the text controller values _nameController.text = _editedGymSet.name; _repsController.text = _editedGymSet.reps.toString(); _weightController.text = _editedGymSet.weight.toString(); @@ -35,9 +36,15 @@ class _EditGymSetPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('Edit Gym Set'), - ), + appBar: AppBar(title: const Text('Edit Gym Set'), actions: [ + IconButton( + onPressed: () async { + await _db.delete(_editedGymSet); + if (!mounted) return; + Navigator.pop(context, _editedGymSet); + }, + icon: const Icon(Icons.delete)) + ]), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( @@ -76,7 +83,13 @@ class _EditGymSetPageState extends State { ), ), floatingActionButton: FloatingActionButton( - onPressed: () { + onPressed: () async { + print('edited gym set id: ${_editedGymSet.id}'); + if (_editedGymSet.id != null) + await _db.update(_editedGymSet); + else + await _db.insert(_editedGymSet); + if (!mounted) return; Navigator.pop(context, _editedGymSet); }, child: const Icon(Icons.check), diff --git a/lib/gym_set.dart b/lib/gym_set.dart new file mode 100644 index 0000000..53cb4c2 --- /dev/null +++ b/lib/gym_set.dart @@ -0,0 +1,103 @@ +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; + +class GymSet { + int? id; // added id for SQLite + String name; + int reps; + int weight; + DateTime created; + + GymSet( + {this.id, + required this.name, + required this.reps, + required this.weight, + required this.created}); +} + +class GymSetDatabaseHelper { + static final _databaseName = "gym_set_database.db"; + static final _databaseVersion = 1; + + static final table = 'gym_sets'; + + static final columnId = '_id'; + static final columnName = 'name'; + static final columnReps = 'reps'; + static final columnWeight = 'weight'; + static final columnCreated = 'created'; + + GymSetDatabaseHelper._privateConstructor(); + static final GymSetDatabaseHelper instance = + GymSetDatabaseHelper._privateConstructor(); + + static Database? _database; + Future get database async { + if (_database != null) return _database!; + _database = await _initDatabase(); + return _database!; + } + + _initDatabase() async { + String path = join(await getDatabasesPath(), _databaseName); + return await openDatabase(path, + version: _databaseVersion, onCreate: _onCreate); + } + + Future _onCreate(Database db, int version) async { + await db.execute(''' + CREATE TABLE $table ( + $columnId INTEGER PRIMARY KEY, + $columnName TEXT NOT NULL, + $columnReps INTEGER NOT NULL, + $columnWeight INTEGER NOT NULL, + $columnCreated TEXT NOT NULL + ) + '''); + } + + Future insert(GymSet gymSet) async { + Database db = await instance.database; + return await db.insert(table, { + columnName: gymSet.name, + columnReps: gymSet.reps, + columnWeight: gymSet.weight, + columnCreated: gymSet.created.toIso8601String() + }); + } + + Future> getAll() async { + Database db = await instance.database; + List> result = await db.query(table); + return result + .map((gymSetMap) => GymSet( + id: gymSetMap[columnId], + name: gymSetMap[columnName], + reps: gymSetMap[columnReps], + weight: gymSetMap[columnWeight], + created: DateTime.parse(gymSetMap[columnCreated]), + )) + .toList(); + } + + Future update(GymSet gymSet) async { + Database db = await instance.database; + return await db.update( + table, + { + columnName: gymSet.name, + columnReps: gymSet.reps, + columnWeight: gymSet.weight, + columnCreated: gymSet.created.toIso8601String() + }, + where: '$columnId = ?', + whereArgs: [gymSet.id]); + } + + Future delete(GymSet gymSet) async { + Database db = await instance.database; + return await db + .delete(table, where: '$columnId = ?', whereArgs: [gymSet.id]); + } +} diff --git a/lib/home_page.dart b/lib/home_page.dart index 50ae45f..d9464ae 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:fmassive/edit_set.dart'; +import 'package:fmassive/gym_set.dart'; class HomePage extends StatelessWidget { HomePage({super.key}); - final List> _pageData = [ + final List> routes = [ {'title': 'Home', 'icon': Icons.home}, {'title': 'Plans', 'icon': Icons.calendar_today}, {'title': 'Best', 'icon': Icons.star}, @@ -17,19 +18,19 @@ class HomePage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_pageData[0]['title']), + title: Text(routes[0]['title']), ), drawer: Drawer( child: ListView.builder( - itemCount: _pageData.length, + itemCount: routes.length, itemBuilder: (context, index) { return ListTile( - leading: Icon(_pageData[index]['icon']), - title: Text(_pageData[index]['title']), + leading: Icon(routes[index]['icon']), + title: Text(routes[index]['title']), onTap: () { Navigator.pop(context); Navigator.pushNamed( - context, '/${_pageData[index]['title'].toLowerCase()}'); + context, '/${routes[index]['title'].toLowerCase()}'); }, ); }, @@ -42,130 +43,105 @@ class HomePage extends StatelessWidget { } } -class GymSet { - String name; - int reps; - int weight; - DateTime created; - - GymSet( - {required this.name, - required this.reps, - required this.weight, - required this.created}); -} - class GymSetPage extends StatefulWidget { const GymSetPage({Key? key}) : super(key: key); @override - _GymSetPageState createState() => _GymSetPageState(); + createState() => GymSetPageState(); } -class _GymSetPageState extends State { - final List _gymSets = [ - GymSet( - name: "Bench press", - reps: 10, - weight: 50, - created: DateTime(2022, 1, 1)), - GymSet( - name: "Bench press", - reps: 8, - weight: 60, - created: DateTime(2022, 1, 2)), - GymSet( - name: "Bench press", - reps: 6, - weight: 70, - created: DateTime(2022, 1, 3)), - GymSet( - name: "Shoulder press", - reps: 12, - weight: 40, - created: DateTime(2022, 1, 4)), - GymSet( - name: "Shoulder press", - reps: 15, - weight: 35, - created: DateTime(2022, 1, 5)), - ]; +class GymSetPageState extends State { + late GymSetDatabaseHelper db; + List sets = []; - List _searchResults = []; + List searchResults = []; - final TextEditingController _searchController = TextEditingController(); + final TextEditingController searchController = TextEditingController(); - void _searchGymSets(String searchQuery) { + void search(String searchQuery) { List results = []; if (searchQuery.isEmpty) { - results = _gymSets; + results = sets; } else { - for (int i = 0; i < _gymSets.length; i++) { - if (_gymSets[i].reps.toString().contains(searchQuery) || - _gymSets[i].weight.toString().contains(searchQuery) || - _gymSets[i].created.toString().contains(searchQuery) || - _gymSets[i].name.contains(searchQuery)) { - results.add(_gymSets[i]); + for (int i = 0; i < sets.length; i++) { + if (sets[i].reps.toString().contains(searchQuery) || + sets[i].weight.toString().contains(searchQuery) || + sets[i].created.toString().contains(searchQuery) || + sets[i].name.contains(searchQuery)) { + results.add(sets[i]); } } } setState(() { - _searchResults = results; + searchResults = results; + }); + } + + void reset() async { + print('Resetting...'); + final data = await db.getAll(); + setState(() { + sets = data; + searchResults = data; }); } @override void initState() { super.initState(); - - // Initialize the search results to all the gym sets - _searchResults = _gymSets; + db = GymSetDatabaseHelper.instance; + reset(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - automaticallyImplyLeading: false, title: TextField( - controller: _searchController, + controller: searchController, decoration: const InputDecoration( hintText: 'Search Gym Sets', border: InputBorder.none, ), onChanged: (searchQuery) { - _searchGymSets(searchQuery); + search(searchQuery); }, ), ), body: ListView.builder( - itemCount: _searchResults.length, + itemCount: searchResults.length, itemBuilder: (context, index) { return ListTile( title: Text( - '${_searchResults[index].name}: ${_searchResults[index].reps}x${_searchResults[index].weight}kg'), - onTap: () { - Navigator.push( + '${searchResults[index].name}: ${searchResults[index].reps}x${searchResults[index].weight}kg'), + onTap: () async { + await Navigator.push( context, MaterialPageRoute( builder: (context) => - EditGymSetPage(gymSet: _searchResults[index]), + EditGymSetPage(gymSet: searchResults[index]), ), ); + reset(); }); }, ), floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.push( + onPressed: () async { + await Navigator.push( context, MaterialPageRoute( - builder: (context) => - EditGymSetPage(gymSet: _searchResults[0]), + builder: (context) => EditGymSetPage( + gymSet: GymSet( + name: '', + reps: 0, + weight: 0, + created: DateTime.now())), ), ); + reset(); }, child: const Icon(Icons.add))); } diff --git a/lib/main.dart b/lib/main.dart index 2393e32..c9df73a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fmassive/best_page.dart'; import 'package:fmassive/edit_set.dart'; +import 'package:fmassive/gym_set.dart'; import 'package:fmassive/home_page.dart'; import 'package:fmassive/plans_page.dart'; import 'package:fmassive/settings_page.dart'; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..8370e57 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import sqflite func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 8ba7880..352ed89 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -116,7 +116,7 @@ packages: source: hosted version: "1.8.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b @@ -136,6 +136,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758" + url: "https://pub.dev" + source: hosted + version: "2.2.6" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684" + url: "https://pub.dev" + source: hosted + version: "2.4.3" stack_trace: dependency: transitive description: @@ -160,6 +176,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b" + url: "https://pub.dev" + source: hosted + version: "3.0.1" term_glyph: dependency: transitive description: @@ -186,3 +210,4 @@ packages: version: "2.1.4" sdks: dart: ">=2.19.5 <3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 9e6ee84..3fa9f1d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + path: ^1.8.2 + sqflite: ^2.2.6 dev_dependencies: flutter_test: