Skip to content

Commit

Permalink
Add ability to search for exercises in records tab (#300)
Browse files Browse the repository at this point in the history
* Implemented ExerciseSearchDelegate class to allow searching data

* Updated records tab to allow searching for records based on exercise name

* Bump v0.8.3+39
  • Loading branch information
andreped authored Sep 23, 2024
1 parent bf57e01 commit 73bc378
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 19 deletions.
74 changes: 74 additions & 0 deletions lib/core/search_delegate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';

class ExerciseSearchDelegate extends SearchDelegate {
final List<Map<String, dynamic>> records;
final bool isKg;
final Function(double) convertWeight;

ExerciseSearchDelegate({
required this.records,
required this.isKg,
required this.convertWeight,
});

@override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}

@override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
close(context, null);
},
);
}

@override
Widget buildResults(BuildContext context) {
final filteredRecords = records.where((record) {
final exercise = record['exercise'] as String;
return exercise.toLowerCase().contains(query.toLowerCase());
}).toList();

return _buildRecordList(filteredRecords);
}

@override
Widget buildSuggestions(BuildContext context) {
final filteredRecords = records.where((record) {
final exercise = record['exercise'] as String;
return exercise.toLowerCase().contains(query.toLowerCase());
}).toList();

return _buildRecordList(filteredRecords);
}

Widget _buildRecordList(List<Map<String, dynamic>> records) {
return ListView.builder(
itemCount: records.length,
itemBuilder: (context, index) {
final record = records[index];
final exercise = record['exercise'] as String;
final weight = record['weight'];
final reps = record['reps'];
final displayWeight =
convertWeight(weight is String ? double.parse(weight) : weight);

return ListTile(
title: Text(exercise),
subtitle: Text('${displayWeight.toStringAsFixed(1)} x $reps reps'),
);
},
);
}
}
106 changes: 88 additions & 18 deletions lib/widgets/records.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ class RecordsTab extends StatefulWidget {
class _RecordsTabState extends State<RecordsTab> {
final DatabaseHelper _dbHelper = DatabaseHelper();
List<Map<String, dynamic>> _maxWeights = [];
List<Map<String, dynamic>> _filteredWeights = [];
bool _isSortedByWeight = true;
bool _isAscending = false;
bool _isLoading = true; // Track loading state
String? _errorMessage; // Track error message
bool _isSearching = false;
final TextEditingController _searchController = TextEditingController();

Future<void> _fetchAndSortRecords() async {
setState(() {
Expand All @@ -29,6 +32,7 @@ class _RecordsTabState extends State<RecordsTab> {
final data = await _dbHelper.getMaxWeightsForExercises();
setState(() {
_maxWeights = data;
_filteredWeights = data;
_sortRecords();
_isLoading = false; // Finished loading
});
Expand All @@ -43,11 +47,11 @@ class _RecordsTabState extends State<RecordsTab> {
}

void _sortRecords() {
// Ensure that _maxWeights is a modifiable list
_maxWeights = List<Map<String, dynamic>>.from(_maxWeights);
// Ensure that _filteredWeights is a modifiable list
_filteredWeights = List<Map<String, dynamic>>.from(_filteredWeights);

if (_isSortedByWeight) {
_maxWeights.sort((a, b) {
_filteredWeights.sort((a, b) {
final maxWeightA =
a['weight'] != null ? double.parse(a['weight'].toString()) : 0.0;
final maxWeightB =
Expand All @@ -64,7 +68,7 @@ class _RecordsTabState extends State<RecordsTab> {
: maxWeightB.compareTo(maxWeightA);
});
} else {
_maxWeights.sort((a, b) {
_filteredWeights.sort((a, b) {
return _isAscending
? (a['exercise'] ?? '').compareTo(b['exercise'] ?? '')
: (b['exercise'] ?? '').compareTo(a['exercise'] ?? '');
Expand All @@ -84,6 +88,36 @@ class _RecordsTabState extends State<RecordsTab> {
});
}

void _startSearch() {
setState(() {
_isSearching = true;
});
}

void _stopSearch() {
setState(() {
_isSearching = false;
_searchController.clear();
_filteredWeights = _maxWeights;
});
}

void _filterRecords(String query) {
setState(() {
if (query.isEmpty) {
_filteredWeights = _maxWeights;
} else {
_filteredWeights = _maxWeights
.where((record) => record['exercise']
.toString()
.toLowerCase()
.contains(query.toLowerCase()))
.toList();
}
_sortRecords();
});
}

@override
void initState() {
super.initState();
Expand All @@ -98,10 +132,45 @@ class _RecordsTabState extends State<RecordsTab> {
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final arrowColor = isDarkMode ? Colors.purple : null;
final textColor = isDarkMode ? Colors.white : Colors.black;

return Scaffold(
appBar: AppBar(
title: Text('High Scores'),
title: _isSearching
? TextField(
controller: _searchController,
autofocus: true,
decoration: InputDecoration(
hintText: 'Search for exercises...',
hintStyle: TextStyle(color: textColor.withOpacity(0.6)),
border: InputBorder.none,
),
style: TextStyle(color: textColor),
onChanged: _filterRecords,
)
: Text('Records'),
leading: _isSearching
? IconButton(
icon: Icon(Icons.arrow_back),
onPressed: _stopSearch,
)
: null,
actions: _isSearching
? [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
_searchController.clear();
_filterRecords('');
},
),
]
: [
IconButton(
icon: Icon(Icons.search),
onPressed: _startSearch,
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
Expand All @@ -112,7 +181,7 @@ class _RecordsTabState extends State<RecordsTab> {
child: Text(_errorMessage!,
style: TextStyle(color: Colors.red)),
)
: _maxWeights.isEmpty
: _filteredWeights.isEmpty
? const Center(child: Text('No data available'))
: LayoutBuilder(
builder: (context, constraints) {
Expand All @@ -123,7 +192,7 @@ class _RecordsTabState extends State<RecordsTab> {
minWidth: constraints.maxWidth,
),
child: DataTable(
columnSpacing: 32.0,
columnSpacing: 64.0, // Increase column spacing
dataRowHeight:
65.0, // Set the height of each row
columns: [
Expand Down Expand Up @@ -162,8 +231,8 @@ class _RecordsTabState extends State<RecordsTab> {
child: GestureDetector(
onTap: () => _toggleSorting(true),
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment
.end, // Align to the right
children: [
Text(
'Weight [${widget.isKg ? 'kg' : 'lbs'}]',
Expand All @@ -189,7 +258,7 @@ class _RecordsTabState extends State<RecordsTab> {
),
),
],
rows: _maxWeights.map((record) {
rows: _filteredWeights.map((record) {
final exercise = record['exercise'] as String;
final weight = record['weight'];
final reps = record['reps'];
Expand All @@ -201,14 +270,15 @@ class _RecordsTabState extends State<RecordsTab> {
return DataRow(cells: [
DataCell(Text(exercise)),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'${displayWeight.toStringAsFixed(1)} x $reps reps',
),
],
Container(
alignment: Alignment
.centerRight, // Align to the right
padding: EdgeInsets.only(
right:
16.0), // Adjust padding as needed
child: Text(
'${displayWeight.toStringAsFixed(1)} x $reps reps',
),
),
),
]);
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 0.8.2+38
version: 0.8.3+39

environment:
sdk: '>=3.4.3 <4.0.0'
Expand Down

0 comments on commit 73bc378

Please sign in to comment.