Skip to content

Commit

Permalink
Merge pull request #8 from andreped:more-tabular-data
Browse files Browse the repository at this point in the history
Support deleting rows in DB; improved visualization; refactored
  • Loading branch information
andreped authored Jul 14, 2024
2 parents 7f04af2 + b931bbc commit 3647dcc
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 107 deletions.
2 changes: 1 addition & 1 deletion lib/common/constants.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const List<String> predefinedExercises = [
List<String> predefinedExercises = [
"Benchpress",
"Pull-up",
"Squats",
Expand Down
44 changes: 42 additions & 2 deletions lib/core/database.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../common/constants.dart';

class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
Expand All @@ -20,14 +21,27 @@ class DatabaseHelper {
return await openDatabase(
path,
version: 1,
onCreate: (db, version) {
return db.execute(
onCreate: (db, version) async {
await db.execute(
'CREATE TABLE exercises(id INTEGER PRIMARY KEY AUTOINCREMENT, exercise TEXT, weight TEXT, timestamp TEXT)',
);
await db.execute(
'CREATE TABLE IF NOT EXISTS predefined_exercises(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)',
);
await _initializePredefinedExercises(db);
},
);
}

Future<void> _initializePredefinedExercises(Database db) async {
// Insert predefined exercises into the database if they do not exist
Batch batch = db.batch();
for (String exercise in predefinedExercises) {
batch.insert('predefined_exercises', {'name': exercise});
}
await batch.commit();
}

Future<void> insertExercise(String exercise, String weight) async {
final db = await database;
await db.insert(
Expand All @@ -46,4 +60,30 @@ class DatabaseHelper {
final db = await database;
await db.delete('exercises');
}

Future<List<String>> getPredefinedExercises() async {
final db = await database;
List<Map<String, dynamic>> maps = await db.query('predefined_exercises');
return List.generate(maps.length, (i) {
return maps[i]['name'];
});
}

Future<void> addPredefinedExercise(String exerciseName) async {
final db = await database;
await db.insert(
'predefined_exercises',
{'name': exerciseName},
conflictAlgorithm: ConflictAlgorithm.ignore, // Handle if exercise already exists
);
}

Future<void> deleteExercise(int id) async {
final db = await database;
await db.delete(
'exercises',
where: 'id = ?',
whereArgs: [id],
);
}
}
31 changes: 23 additions & 8 deletions lib/widgets/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import '../core/database.dart';
import 'visualization.dart';
import 'inputs.dart';


class ExerciseStoreApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Expand All @@ -29,6 +30,11 @@ class _ExerciseStoreHomePageState extends State<ExerciseStoreHomePage> {
setState(() {});
}

Future<void> _deleteExercise(int id) async {
await _dbHelper.deleteExercise(id);
setState(() {});
}

Future<List<Map<String, dynamic>>> _getExercises() async {
return await _dbHelper.getExercises();
}
Expand All @@ -39,18 +45,18 @@ class _ExerciseStoreHomePageState extends State<ExerciseStoreHomePage> {
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('IronFlow'),
title: const Text('IronFlow'),
actions: [
IconButton(
icon: Icon(Icons.delete),
icon: const Icon(Icons.delete),
onPressed: () async {
await _clearDatabase();
},
),
],
bottom: TabBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.add), text: 'Log Exercise'),
const Tab(icon: Icon(Icons.add), text: 'Log Exercise'),
Tab(icon: Icon(Icons.table_chart), text: 'View Table'),
Tab(icon: Icon(Icons.show_chart), text: 'Visualize Data'),
],
Expand All @@ -60,7 +66,7 @@ class _ExerciseStoreHomePageState extends State<ExerciseStoreHomePage> {
children: [
// Log Exercise Tab
Padding(
padding: EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16.0),
child: ExerciseSetter(
onExerciseAdded: () {
setState(() {});
Expand All @@ -74,24 +80,33 @@ class _ExerciseStoreHomePageState extends State<ExerciseStoreHomePage> {
future: _getExercises(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
return const Center(child: CircularProgressIndicator());
}
final variables = snapshot.data!;
return SizedBox(
width: MediaQuery.of(context).size.width,
child: DataTable(
columns: [
columns: const [
DataColumn(label: Text('ID')),
DataColumn(label: Text('Exercise')),
DataColumn(label: Text('Weight')),
DataColumn(label: Text('Timestamp')),
DataColumn(label: Text('Actions')),
],
rows: variables.map((variable) {
return DataRow(cells: [
DataCell(Text(variable['id'].toString())),
DataCell(Text(variable['exercise'])),
DataCell(Text(variable['weight'])),
DataCell(Text(variable['timestamp'])),
DataCell(
IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
await _deleteExercise(variable['id']);
},
),
),
]);
}).toList(),
),
Expand All @@ -101,7 +116,7 @@ class _ExerciseStoreHomePageState extends State<ExerciseStoreHomePage> {
),
// Visualize Data Tab
Padding(
padding: EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16.0),
child: VisualizationTab(),
),
],
Expand Down
168 changes: 109 additions & 59 deletions lib/widgets/inputs.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import '../core/database.dart';
import '../common/constants.dart';

class ExerciseSetter extends StatefulWidget {
final Function() onExerciseAdded;
Expand All @@ -17,20 +16,51 @@ class _ExerciseSetterState extends State<ExerciseSetter> {
final _exerciseValueController = TextEditingController();
final DatabaseHelper _dbHelper = DatabaseHelper();

String? _selectedPredefinedExercise;
String? _selectedExercise;
bool _isAddingNewExercise = false;

List<String> _predefinedExercises = [];

@override
void initState() {
super.initState();
_loadPredefinedExercises();
}

Future<void> _loadPredefinedExercises() async {
List<String> exercises = await _dbHelper.getPredefinedExercises();
setState(() {
_predefinedExercises = exercises;
_selectedExercise = _predefinedExercises.isNotEmpty ? _predefinedExercises.first : null;
});
}

Future<void> _addExercise() async {
if (_formKey.currentState!.validate()) {
final exerciseName = _selectedPredefinedExercise ?? _exerciseNameController.text;
await _dbHelper.insertExercise(
exerciseName,
_exerciseValueController.text,
);
final exerciseName = _isAddingNewExercise
? _exerciseNameController.text.trim()
: _selectedExercise!;

await _dbHelper.insertExercise(exerciseName, _exerciseValueController.text);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Exercise added successfully'),
duration: Duration(seconds: 2),
));

if (_isAddingNewExercise) {
await _dbHelper.addPredefinedExercise(exerciseName);
setState(() {
_predefinedExercises.add(exerciseName);
_selectedExercise = exerciseName;
});
}

_exerciseNameController.clear();
_exerciseValueController.clear();
setState(() {
_selectedPredefinedExercise = null;
_isAddingNewExercise = false;
});

widget.onExerciseAdded();
}
}
Expand All @@ -40,59 +70,79 @@ class _ExerciseSetterState extends State<ExerciseSetter> {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: 72, // Adjust the height as needed
child: DropdownButtonFormField<String>(
decoration: InputDecoration(labelText: 'Select Predefined Exercise'),
items: predefinedExercises.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedPredefinedExercise = value;
_exerciseNameController.clear();
});
},
value: _selectedPredefinedExercise,
isExpanded: true,
iconSize: 24.0,
icon: Icon(Icons.arrow_drop_down),
elevation: 16,
style: TextStyle(color: Colors.black),
validator: (value) {
if (_selectedPredefinedExercise == null && (value == null || value.isEmpty)) {
return 'Please enter an exercise name or select one';
}
return null;
},
dropdownColor: Colors.white,
// Limit the number of items shown
selectedItemBuilder: (BuildContext context) {
return predefinedExercises.take(dropdownVisibleItemCount).map<Widget>((String value) {
return Text(value);
}).toList();
},
// Allow scrolling to select items outside the visible range
// The items are already specified above
),
),
TextFormField(
controller: _exerciseNameController,
decoration: InputDecoration(labelText: 'Exercise Name (or enter custom)'),
validator: (value) {
if (_selectedPredefinedExercise == null && (value == null || value.isEmpty)) {
return 'Please enter an exercise name or select one';
}
return null;
},
Row(
children: [
Expanded(
flex: 2,
child: DropdownButtonFormField<String>(
decoration: const InputDecoration(labelText: 'Select Exercise'),
items: [
..._predefinedExercises.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}),
const DropdownMenuItem<String>(
value: 'custom',
child: Text('Add New Exercise'),
),
],
onChanged: (value) {
setState(() {
if (value == 'custom') {
_isAddingNewExercise = true;
_selectedExercise = null;
} else {
_isAddingNewExercise = false;
_selectedExercise = value;
}
_exerciseNameController.clear();
});
},
value: _selectedExercise,
isExpanded: true,
iconSize: 24.0,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
style: const TextStyle(color: Colors.black),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please select or enter an exercise';
}
if (value == 'custom' && !_isAddingNewExercise) {
return 'Please select or enter an exercise';
}
return null;
},
dropdownColor: Colors.white,
),
),
const SizedBox(width: 16.0),
Expanded(
flex: 3,
child: TextFormField(
controller: _exerciseNameController,
enabled: _isAddingNewExercise,
decoration: InputDecoration(
labelText: 'Exercise Name (or enter custom)',
enabled: _isAddingNewExercise,
),
validator: (value) {
if (_isAddingNewExercise && (value == null || value.isEmpty)) {
return 'Please enter an exercise name';
}
return null;
},
),
),
],
),
TextFormField(
controller: _exerciseValueController,
decoration: InputDecoration(labelText: 'Exercise Value'),
decoration: const InputDecoration(labelText: 'Exercise Value'),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
Expand All @@ -104,10 +154,10 @@ class _ExerciseSetterState extends State<ExerciseSetter> {
return null;
},
),
SizedBox(height: 16.0),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: _addExercise,
child: Text('Add Exercise'),
child: const Text('Add Exercise'),
),
],
),
Expand Down
Loading

0 comments on commit 3647dcc

Please sign in to comment.