From 73bc3787885deec75499017b78aad14daec37637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Pedersen?= Date: Mon, 23 Sep 2024 15:55:32 +0200 Subject: [PATCH] Add ability to search for exercises in records tab (#300) * Implemented ExerciseSearchDelegate class to allow searching data * Updated records tab to allow searching for records based on exercise name * Bump v0.8.3+39 --- lib/core/search_delegate.dart | 74 ++++++++++++++++++++++++ lib/widgets/records.dart | 106 ++++++++++++++++++++++++++++------ pubspec.yaml | 2 +- 3 files changed, 163 insertions(+), 19 deletions(-) create mode 100644 lib/core/search_delegate.dart diff --git a/lib/core/search_delegate.dart b/lib/core/search_delegate.dart new file mode 100644 index 0000000..c479f95 --- /dev/null +++ b/lib/core/search_delegate.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +class ExerciseSearchDelegate extends SearchDelegate { + final List> records; + final bool isKg; + final Function(double) convertWeight; + + ExerciseSearchDelegate({ + required this.records, + required this.isKg, + required this.convertWeight, + }); + + @override + List 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> 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'), + ); + }, + ); + } +} diff --git a/lib/widgets/records.dart b/lib/widgets/records.dart index 190e24c..083f2ff 100644 --- a/lib/widgets/records.dart +++ b/lib/widgets/records.dart @@ -14,10 +14,13 @@ class RecordsTab extends StatefulWidget { class _RecordsTabState extends State { final DatabaseHelper _dbHelper = DatabaseHelper(); List> _maxWeights = []; + List> _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 _fetchAndSortRecords() async { setState(() { @@ -29,6 +32,7 @@ class _RecordsTabState extends State { final data = await _dbHelper.getMaxWeightsForExercises(); setState(() { _maxWeights = data; + _filteredWeights = data; _sortRecords(); _isLoading = false; // Finished loading }); @@ -43,11 +47,11 @@ class _RecordsTabState extends State { } void _sortRecords() { - // Ensure that _maxWeights is a modifiable list - _maxWeights = List>.from(_maxWeights); + // Ensure that _filteredWeights is a modifiable list + _filteredWeights = List>.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 = @@ -64,7 +68,7 @@ class _RecordsTabState extends State { : maxWeightB.compareTo(maxWeightA); }); } else { - _maxWeights.sort((a, b) { + _filteredWeights.sort((a, b) { return _isAscending ? (a['exercise'] ?? '').compareTo(b['exercise'] ?? '') : (b['exercise'] ?? '').compareTo(a['exercise'] ?? ''); @@ -84,6 +88,36 @@ class _RecordsTabState extends State { }); } + 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(); @@ -98,10 +132,45 @@ class _RecordsTabState extends State { 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), @@ -112,7 +181,7 @@ class _RecordsTabState extends State { child: Text(_errorMessage!, style: TextStyle(color: Colors.red)), ) - : _maxWeights.isEmpty + : _filteredWeights.isEmpty ? const Center(child: Text('No data available')) : LayoutBuilder( builder: (context, constraints) { @@ -123,7 +192,7 @@ class _RecordsTabState extends State { minWidth: constraints.maxWidth, ), child: DataTable( - columnSpacing: 32.0, + columnSpacing: 64.0, // Increase column spacing dataRowHeight: 65.0, // Set the height of each row columns: [ @@ -162,8 +231,8 @@ class _RecordsTabState extends State { child: GestureDetector( onTap: () => _toggleSorting(true), child: Row( - mainAxisAlignment: - MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment + .end, // Align to the right children: [ Text( 'Weight [${widget.isKg ? 'kg' : 'lbs'}]', @@ -189,7 +258,7 @@ class _RecordsTabState extends State { ), ), ], - rows: _maxWeights.map((record) { + rows: _filteredWeights.map((record) { final exercise = record['exercise'] as String; final weight = record['weight']; final reps = record['reps']; @@ -201,14 +270,15 @@ class _RecordsTabState extends State { 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', + ), ), ), ]); diff --git a/pubspec.yaml b/pubspec.yaml index 42549f2..e4946f7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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'