diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..86ebcb7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + library_private_types_in_public_api: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/lib/DOM/exercise_db.dart b/lib/DOM/exercise_db.dart index aa609c0..587e1ff 100644 --- a/lib/DOM/exercise_db.dart +++ b/lib/DOM/exercise_db.dart @@ -5,11 +5,11 @@ import 'package:open_fitness_tracker/utils/utils.dart'; import 'package:shared_preferences/shared_preferences.dart'; class ExDB { - static get exercises => _exercises; - static get categories => _categories; - static get muscles => _muscles; - static get names => _names; - static get equipment => _equipment; + static Exercises get exercises => _exercises; + static List get categories => _categories; + static List get muscles => _muscles; + static List get names => _names; + static List get equipment => _equipment; static final List _names = []; static final List _categories = []; diff --git a/lib/exercises/ex_search_page.dart b/lib/exercises/ex_search_page.dart index a7fde23..040477c 100644 --- a/lib/exercises/ex_search_page.dart +++ b/lib/exercises/ex_search_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'package:open_fitness_tracker/DOM/exercise_metadata.dart'; import 'package:open_fitness_tracker/DOM/training_metadata.dart'; import 'package:open_fitness_tracker/common/common_widgets.dart'; @@ -12,7 +13,22 @@ import 'package:open_fitness_tracker/utils/utils.dart'; class ExerciseSearchPage extends StatefulWidget { final bool useForAddingToTraining; - const ExerciseSearchPage({super.key, this.useForAddingToTraining = false}); + final bool useForMappingForeignExercise; + final Function? setForeignExerciseCallback; + final Exercise? foreignEx; + + ExerciseSearchPage({ + super.key, + this.useForAddingToTraining = false, + this.useForMappingForeignExercise = false, + this.setForeignExerciseCallback, + this.foreignEx, + }) { + if (useForMappingForeignExercise) { + assert(setForeignExerciseCallback != null); + assert(foreignEx != null); + } + } @override State createState() => _ExerciseSearchPageState(); @@ -35,20 +51,40 @@ class _ExerciseSearchPageState extends State { Widget build(BuildContext context) { final scrollController = ScrollController(initialScrollOffset: 0); final state = context.watch().state; - // ignore: avoid_unnecessary_containers - return Container( - // color: Theme.of(context).colorScheme.secondary, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Exercises', style: Theme.of(context).textTheme.displayLarge), - _exercisesListView(scrollController, state), - if (widget.useForAddingToTraining) _addSelectedButton(context), - const SearchBar(), - _muscleAndCategoryFilterButtons(state, context), - _createNewExButton(context), - ], - ), + + List pageChildren = []; + if (!widget.useForMappingForeignExercise) { + pageChildren + .add(Text('Exercises', style: Theme.of(context).textTheme.displayLarge)); + } else { + pageChildren + .add(Text('Exercise Match', style: Theme.of(context).textTheme.displayMedium)); + pageChildren.add(ExerciseTile( + exercise: widget.foreignEx!, + isSelectable: false, + colorDecoration: true, + )); + pageChildren.add(Text('To', style: Theme.of(context).textTheme.bodySmall)); + } + pageChildren.addAll([ + _exercisesListView(scrollController, state), + const SearchBar(), + _muscleAndCategoryFilterButtons(state, context), + ]); + + if (widget.useForAddingToTraining) pageChildren.add(_addSelectedButton(context)); + if (widget.useForMappingForeignExercise) { + pageChildren.addAll([ + _thisIsMyExButton(widget.setForeignExerciseCallback!), + _noExerciseMatchButton(widget.setForeignExerciseCallback!), + ]); + } else { + pageChildren.add(_createNewExButton(context)); + } + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: pageChildren, ); } @@ -70,7 +106,6 @@ class _ExerciseSearchPageState extends State { } Expanded _exercisesListView(ScrollController scrollController, ExSearchState state) { - //todo scrolling on web is not perfect.using slider is a bit janky. return Expanded( child: ScrollConfiguration( behavior: GenericScrollBehavior(), @@ -78,25 +113,30 @@ class _ExerciseSearchPageState extends State { controller: scrollController, thumbVisibility: true, child: ListView.builder( - controller: scrollController, - key: ValueKey(state.filteredExercises.length), - itemCount: state.filteredExercises.length, - itemBuilder: (context, index) { - return ExerciseTile( - exercise: state.filteredExercises[index], - isSelectable: widget.useForAddingToTraining, - isSelected: selectedExercises - .contains(state.filteredExercises[index]) || - newlySelectedExercises.contains(state.filteredExercises[index]), - onSelectionChanged: (bool isSelected) { - if (isSelected) { - newlySelectedExercises.add(state.filteredExercises[index]); - } else { - newlySelectedExercises.remove(state.filteredExercises[index]); - } - setState(() {}); - }); - }), + controller: scrollController, + key: ValueKey(state.filteredExercises.length), + itemCount: state.filteredExercises.length, + itemBuilder: (context, index) { + return ExerciseTile( + exercise: state.filteredExercises[index], + isSelectable: + widget.useForAddingToTraining || widget.useForMappingForeignExercise, + isSelected: selectedExercises.contains(state.filteredExercises[index]) || + newlySelectedExercises.contains(state.filteredExercises[index]), + onSelectionChanged: (bool isSelected) { + if (isSelected) { + if (widget.useForMappingForeignExercise) { + newlySelectedExercises.clear(); + } + newlySelectedExercises.add(state.filteredExercises[index]); + } else { + newlySelectedExercises.remove(state.filteredExercises[index]); + } + setState(() {}); + }, + ); + }, + ), ), ), ); @@ -161,6 +201,35 @@ class _ExerciseSearchPageState extends State { ), ); } + + Widget _thisIsMyExButton(Function setForeignExerciseCallback) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), + child: MyGenericButton( + label: "This Is My Exercise", + color: Theme.of(context).colorScheme.primary, + onPressed: () { + if (newlySelectedExercises.isEmpty) return; + setForeignExerciseCallback(newlySelectedExercises.first); + context.pop(); + }, + ), + ); + } + + Widget _noExerciseMatchButton(Function setForeignExerciseCallback) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), + child: MyGenericButton( + label: "None of these exercises match mine", + color: Theme.of(context).colorScheme.primary, + onPressed: () { + setForeignExerciseCallback(null); // Passing null to indicate no match + context.pop(); + }, + ), + ); + } } class SearchMultiSelectModal extends StatelessWidget { diff --git a/lib/exercises/ex_tile.dart b/lib/exercises/ex_tile.dart index 206b317..e4ca029 100644 --- a/lib/exercises/ex_tile.dart +++ b/lib/exercises/ex_tile.dart @@ -8,6 +8,8 @@ class ExerciseTile extends StatelessWidget { final bool isSelectable; final bool isSelected; final Function? onSelectionChanged; + final bool colorDecoration; + final Color? borderColor; const ExerciseTile({ super.key, @@ -15,36 +17,31 @@ class ExerciseTile extends StatelessWidget { this.isSelectable = false, this.isSelected = false, this.onSelectionChanged, + this.colorDecoration = false, + this.borderColor, }); @override Widget build(BuildContext context) { - String musclesUsed = exercise.primaryMuscles.map((muscle) => muscle.capTheFirstLetter()).join(', '); + String musclesUsed = + exercise.primaryMuscles.map((muscle) => muscle.capTheFirstLetter()).join(', '); if (exercise.secondaryMuscles != null && exercise.secondaryMuscles!.isNotEmpty) { musclesUsed += " + ${exercise.secondaryMuscles!.map((muscle) => muscle.capTheFirstLetter()).join(', ')}"; } - BoxDecoration tileDecoration = const BoxDecoration(); - if (isSelectable) { - tileDecoration = BoxDecoration( - border: Border.all( - color: isSelected ? Colors.blue : Colors.black, - width: 1, - ), - ); - } else { - tileDecoration = BoxDecoration( - border: Border.all( - color: Colors.black, - width: 1, - ), - ); - } + BoxDecoration tileDecoration = BoxDecoration( + border: Border.all( + color: (borderColor != null) + ? borderColor! + : (isSelectable ? (isSelected ? Colors.blue : Colors.black) : Colors.black), + width: (borderColor == null) ? 1 : 2, + ), + color: colorDecoration ? Colors.grey[200] : null, + ); return Container( margin: const EdgeInsets.only(bottom: 5, right: 6, left: 6), - // decoration: BoxDecoration(border: Border.all(color: Colors.black, width: 1)), decoration: tileDecoration, child: ListTile( title: Text(exercise.name, style: Theme.of(context).textTheme.titleLarge), @@ -60,6 +57,7 @@ class ExerciseTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ + //todo..replace w/ history! Text("2 uses ", style: Theme.of(context).textTheme.bodyLarge), Text("60lb (x12) ", style: Theme.of(context).textTheme.bodyLarge), ], @@ -71,8 +69,8 @@ class ExerciseTile extends StatelessWidget { builder: (context) => ExerciseDialog(exercise: exercise), useSafeArea: true, ); - } else if (isSelectable) { - onSelectionChanged!(!isSelected); + } else { + onSelectionChanged?.call(!isSelected); } }, onLongPress: () { diff --git a/lib/importing/ex_import_matching_page.dart b/lib/importing/ex_import_matching_page.dart new file mode 100644 index 0000000..329ce98 --- /dev/null +++ b/lib/importing/ex_import_matching_page.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:fuzzywuzzy/fuzzywuzzy.dart'; +import 'package:fuzzywuzzy/model/extracted_result.dart'; +import 'package:go_router/go_router.dart'; +import 'package:open_fitness_tracker/DOM/exercise_db.dart'; +import 'package:open_fitness_tracker/DOM/exercise_metadata.dart'; +import 'package:open_fitness_tracker/DOM/training_metadata.dart'; +import 'package:open_fitness_tracker/importing/ex_match_listview.dart'; +import 'package:open_fitness_tracker/navigation/routes.dart'; +import 'package:open_fitness_tracker/utils/utils.dart'; + +//TODO make a delete button on the exercisematch + +class ImportInspectionPage extends StatefulWidget { + const ImportInspectionPage({super.key, required this.newTrainingSessions}); + final List newTrainingSessions; + + @override + State createState() => _ImportInspectionPageState(); +} + +class _ImportInspectionPageState extends State { + List matchPairs = []; + Exercises newExs = []; + + @override + void initState() { + super.initState(); + List newExNames = []; + + for (var sesh in widget.newTrainingSessions) { + for (SetsOfAnExercise setsOfAnExercise in sesh.trainingData) { + Exercise ex = setsOfAnExercise.ex; + if (!newExNames.contains(ex.name)) { + newExs.add(ex); + newExNames.add(ex.name); + } + } + } + matchPairs = _exerciseMatcher(newExs, 90); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + "Imported ${widget.newTrainingSessions.length} sessions &\n ${newExs.length} different exercises"), + ), + body: SafeArea( + minimum: const EdgeInsets.symmetric(horizontal: 5), + child: MatchExercisesScrollView( + exerciseMatches: matchPairs, + allImportedExercises: newExs, + confirmSelections: _confirmSelections), + ), + ); + } + + List _exerciseMatcher(Exercises foreignExercises, int similarityCutoff) { + List exerciseMatches = []; + + for (var ex in foreignExercises) { + List exNames = ExDB.names; + List> res = extractTop( + query: ex.name, choices: exNames, cutoff: similarityCutoff, limit: 1); + + if (res.isNotEmpty) { + String matchedExName = res.first.choice; + Exercise? matchedEx; + for (var existingEx in ExDB.exercises) { + if (existingEx.name == matchedExName) { + matchedEx = existingEx; + break; + } + } + if (matchedEx != null) { + bool defaultAdd = false; + if (res.first.score == 100) defaultAdd = true; + exerciseMatches.add(ExerciseMatch( + foreignExercise: ex, + matchedExercise: matchedEx, + isConfirmed: defaultAdd, + preferForeignExerciseName: defaultAdd, + )); + } + } else { + exerciseMatches.add(ExerciseMatch( + foreignExercise: ex, matchedExercise: null, isConfirmed: false)); + } + } + exerciseMatches.sort((a, b) { + //todo would be cute if we prioritized exerises with more history. + int score = 0; + if (a.matchedExercise == null) { + score += 2; + } + if (b.matchedExercise == null) { + score -= 2; + } + if (a.isConfirmed) score--; + if (b.isConfirmed) score++; + return score; + }); + + return exerciseMatches; + } + + void _confirmSelections() { + List confirmedMatches = + matchPairs.where((match) => match.isConfirmed).toList(); + + for (var match in confirmedMatches) { + var matchedEx = match.matchedExercise; + if (matchedEx != null) { + Exercise exToUpdate = ExDB.exercises + .firstWhere((e) => e.name == matchedEx.name, orElse: () => matchedEx); + + exToUpdate.alternateNames ??= []; + + if (match.preferForeignExerciseName) { + exToUpdate.alternateNames!.addIfDNE(exToUpdate.name); + exToUpdate.name = match.foreignExercise.name; + } else { + exToUpdate.alternateNames!.addIfDNE(match.foreignExercise.name); + } + + ExDB.addExercises([exToUpdate]); + } + } + + List unmatchedExercises = []; + + for (var importedEx in newExs) { + bool found = false; + for (var match in confirmedMatches) { + if (match.foreignExercise.name == importedEx.name) { + found = true; + break; + } + } + if (!found) { + unmatchedExercises.add(importedEx); + } + } + + context.push(routeNames.History.text); + } +} diff --git a/lib/importing/ex_match_listview.dart b/lib/importing/ex_match_listview.dart new file mode 100644 index 0000000..8c00865 --- /dev/null +++ b/lib/importing/ex_match_listview.dart @@ -0,0 +1,244 @@ +import 'package:flutter/material.dart'; +import 'package:open_fitness_tracker/DOM/exercise_metadata.dart'; +import 'package:open_fitness_tracker/common/common_widgets.dart'; +import 'package:open_fitness_tracker/exercises/ex_search_page.dart'; +import 'package:open_fitness_tracker/exercises/ex_tile.dart'; + +class ExerciseMatch { + final Exercise foreignExercise; + Exercise? matchedExercise; + bool isConfirmed; + bool preferForeignExerciseName; + + ExerciseMatch({ + required this.foreignExercise, + this.matchedExercise, + this.isConfirmed = false, + this.preferForeignExerciseName = false, + }); +} + +class MatchExercisesScrollView extends StatefulWidget { + final List exerciseMatches; + final List allImportedExercises; + final VoidCallback confirmSelections; + + const MatchExercisesScrollView({ + super.key, + required this.exerciseMatches, + required this.allImportedExercises, + required this.confirmSelections, + }); + + @override + _MatchExercisesScrollViewState createState() => _MatchExercisesScrollViewState(); +} + +class _MatchExercisesScrollViewState extends State { + @override + Widget build(BuildContext context) { + if (widget.exerciseMatches.isEmpty) return Container(); + + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "Match exercises with our database.\n(Top is yours, bottom is ours)", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + const SizedBox(height: 10), + Expanded( + child: Container( + decoration: BoxDecoration(border: Border.all()), + child: ListView.builder( + itemCount: widget.exerciseMatches.length, + itemBuilder: (context, index) { + return _buildExerciseMatchBox(index); + }, + ), + ), + ), + const SizedBox(height: 10), + MyGenericButton( + onPressed: widget.confirmSelections, + label: "Confirm Selections", + ), + ], + ); + } + + Widget _buildExerciseMatchBox(int index) { + ExerciseMatch exerciseMatch = widget.exerciseMatches[index]; + + // Determine background color based on the state + Color matchedExerciseBackgroundColor; + if (exerciseMatch.matchedExercise == null) { + matchedExerciseBackgroundColor = Colors.red.shade300; + } else if (exerciseMatch.isConfirmed) { + matchedExerciseBackgroundColor = Colors.greenAccent.shade400; + } else { + matchedExerciseBackgroundColor = Colors.yellow.shade300; + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), + child: Stack( + children: [ + Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8.0), + boxShadow: [ + BoxShadow( + color: Colors.grey.shade200, + offset: const Offset(0, 2), + blurRadius: 4.0, + ), + ], + ), + child: Row( + children: [ + Expanded( + child: Column( + children: [ + ExerciseTile(exercise: exerciseMatch.foreignExercise), + const SizedBox(height: 8.0), + _selectableExTile( + index, exerciseMatch, matchedExerciseBackgroundColor), + ], + ), + ), + SizedBox( + width: 80, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Accept?'), + Switch( + value: exerciseMatch.isConfirmed, + onChanged: (bool value) { + setState(() { + exerciseMatch.isConfirmed = value; + }); + }, + ), + const Center( + child: Text( + 'Use my name?', + textAlign: TextAlign.center, + ), + ), + Switch( + value: exerciseMatch.preferForeignExerciseName, + onChanged: (bool value) { + setState(() { + exerciseMatch.preferForeignExerciseName = value; + }); + }, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + widget.exerciseMatches.removeAt(index); + }); + }, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _selectableExTile( + int index, ExerciseMatch exerciseMatch, Color matchedExerciseBackgroundColor) { + // return GestureDetector( + // onTap: () { + // _addNewExercise(index, exerciseMatch.foreignExercise, (Exercise? userMatchedEx) { + // if (userMatchedEx != null) { + // setState(() { + // exerciseMatch.matchedExercise = userMatchedEx; + // exerciseMatch.isConfirmed = true; + // }); + // } + // }); + // }, + // child: + return Stack( + children: [ + (exerciseMatch.matchedExercise != null) + ? ExerciseTile( + exercise: exerciseMatch.matchedExercise!, + borderColor: matchedExerciseBackgroundColor, + isSelectable: true, + ) + : Container( + margin: const EdgeInsets.symmetric(horizontal: 6), + height: 60, + width: 9999, + decoration: BoxDecoration( + border: Border.all(color: matchedExerciseBackgroundColor, width: 3)), + child: const Icon( + Icons.add_box_outlined, + size: 32, + ), + ), + (exerciseMatch.matchedExercise != null) + ? const Positioned(top: 10, right: 10, child: Icon(Icons.edit)) + : Container(), + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + _addNewExercise(index, exerciseMatch.foreignExercise, + (Exercise? userMatchedEx) { + if (userMatchedEx != null) { + setState(() { + exerciseMatch.matchedExercise = userMatchedEx; + exerciseMatch.isConfirmed = true; + }); + } + }); + }, + ), + ), + ), + ], + // ), + ); + } + + void _addNewExercise( + int index, Exercise foreignExercise, Function onExMatchFound) async { + Exercise? newExercise = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ExerciseSearchPage( + useForMappingForeignExercise: true, + setForeignExerciseCallback: onExMatchFound, + foreignEx: foreignExercise, + ), + ), + ); + + if (newExercise != null) { + setState(() { + widget.exerciseMatches[index].matchedExercise = newExercise; + widget.exerciseMatches[index].isConfirmed = true; + }); + } + } +} diff --git a/lib/importing/import_inspection_page.dart b/lib/importing/import_inspection_page.dart deleted file mode 100644 index 7543697..0000000 --- a/lib/importing/import_inspection_page.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:fuzzywuzzy/fuzzywuzzy.dart'; -import 'package:fuzzywuzzy/model/extracted_result.dart'; -import 'package:open_fitness_tracker/DOM/exercise_db.dart'; -import 'package:open_fitness_tracker/DOM/exercise_metadata.dart'; -import 'package:open_fitness_tracker/DOM/training_metadata.dart'; -import 'package:open_fitness_tracker/common/common_widgets.dart'; -import 'package:open_fitness_tracker/exercises/ex_tile.dart'; - -class ImportInspectionPage extends StatefulWidget { - const ImportInspectionPage({super.key, required this.newTrainingSessions}); - final List newTrainingSessions; - - @override - State createState() => _ImportInspectionPageState(); -} - -class _ImportInspectionPageState extends State { - List similarMatchExs = []; - - @override - void initState() { - super.initState(); - List newExs = []; - List newExNames = []; - - for (var sesh in widget.newTrainingSessions) { - for (SetsOfAnExercise setsOfAnExercise in sesh.trainingData) { - Exercise ex = setsOfAnExercise.ex; - if (!newExNames.contains(ex.name)) { - newExs.add(ex); - newExNames.add(ex.name); - } - } - } - - similarMatchExs = _exerciseMatcher(newExs, 90); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Imported ${widget.newTrainingSessions.length} sessions."), - ), - body: SafeArea( - minimum: const EdgeInsets.symmetric(horizontal: 5), - child: MatchExercises(exerciseMatches: similarMatchExs), - ), - ); - } - - List _exerciseMatcher( - List foreignExercises, int similarityCutoff) { - List exerciseMatches = []; - - for (var ex in foreignExercises) { - List exNames = ExDB.names; - List> res = extractTop( - query: ex.name, choices: exNames, cutoff: similarityCutoff, limit: 1); - - if (res.isNotEmpty) { - String matchedExName = res.first.choice; - Exercise? matchedEx; - for (var existingEx in ExDB.exercises) { - if (existingEx.name == matchedExName) { - matchedEx = existingEx; - break; - } - } - if (matchedEx != null) { - bool defaultAdd = false; - if (res.first.score == 100) defaultAdd = true; - exerciseMatches.add(ExerciseMatch( - foreignExercise: ex, - matchedExercise: matchedEx, - isConfirmed: defaultAdd, - )); - } - } - } - - return exerciseMatches; - } -} - -// Class to hold foreign and matched exercises along with confirmation status -class ExerciseMatch { - final Exercise foreignExercise; - final Exercise matchedExercise; - bool isConfirmed; - bool preferForeignExerciseName; - - ExerciseMatch({ - required this.foreignExercise, - required this.matchedExercise, - this.isConfirmed = false, - this.preferForeignExerciseName = false, - }); -} - -class MatchExercises extends StatefulWidget { - final List exerciseMatches; - - const MatchExercises({super.key, required this.exerciseMatches}); - - @override - _MatchExercisesState createState() => _MatchExercisesState(); -} - -class _MatchExercisesState extends State { - @override - Widget build(BuildContext context) { - if (widget.exerciseMatches.isEmpty) return Container(); - - return Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "First, confirm these similar exercise matches with our database.\n(Top is yours, bottom is ours):", - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - const SizedBox(height: 10), - Expanded( - child: Container( - decoration: BoxDecoration(border: Border.all()), - child: ListView.builder( - itemCount: widget.exerciseMatches.length, - itemBuilder: (context, index) { - return _buildExerciseMatchBox(index); - }, - ), - ), - ), - const SizedBox(height: 10), - MyGenericButton( - onPressed: _confirmSelections, - label: "Confirm Selections", - ), - ], - ); - } - - Widget _buildExerciseMatchBox(int index) { - ExerciseMatch exerciseMatch = widget.exerciseMatches[index]; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), - child: Container( - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(8.0), - boxShadow: [ - BoxShadow( - color: Colors.grey.shade200, - offset: Offset(0, 2), - blurRadius: 4.0, - ), - ], - ), - child: Row( - children: [ - Expanded( - child: Column( - children: [ - ExerciseTile(exercise: exerciseMatch.foreignExercise), - const SizedBox(height: 8.0), - ExerciseTile(exercise: exerciseMatch.matchedExercise), - ], - ), - ), - SizedBox( - width: 80, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Accept?'), - Switch( - value: exerciseMatch.isConfirmed, - onChanged: (bool value) { - setState(() { - exerciseMatch.isConfirmed = value; - }); - }, - ), - const Center( - child: Text( - 'Use my name?', - textAlign: TextAlign.center, - )), - Switch( - value: exerciseMatch.preferForeignExerciseName, - // activeColor: lightGreen, - onChanged: (bool value) { - setState(() { - exerciseMatch.preferForeignExerciseName = value; - }); - }, - ), - ], - ), - ), - ], - ), - ), - ); - } - - void _confirmSelections() { - // Process the confirmed selections - List confirmedMatches = - widget.exerciseMatches.where((match) => match.isConfirmed).toList(); - // Implement your logic here - print('Confirmed matches: ${confirmedMatches.length}'); - } -} diff --git a/lib/importing/import_training_ui.dart b/lib/importing/import_training_ui.dart index e889f0d..6c476f5 100644 --- a/lib/importing/import_training_ui.dart +++ b/lib/importing/import_training_ui.dart @@ -4,7 +4,7 @@ import 'package:open_fitness_tracker/DOM/basic_user_info.dart'; import 'package:open_fitness_tracker/DOM/history_importing_logic.dart'; import 'package:open_fitness_tracker/DOM/training_metadata.dart'; import 'package:open_fitness_tracker/common/common_widgets.dart'; -import 'package:open_fitness_tracker/importing/import_inspection_page.dart'; +import 'package:open_fitness_tracker/importing/ex_import_matching_page.dart'; import 'package:open_fitness_tracker/styles.dart'; import 'package:open_fitness_tracker/utils/utils.dart'; diff --git a/lib/navigation/routes.dart b/lib/navigation/routes.dart index 680246e..3581842 100644 --- a/lib/navigation/routes.dart +++ b/lib/navigation/routes.dart @@ -70,8 +70,7 @@ final GoRouter routerConfig = GoRouter( ), GoRoute( path: routeNames.Exercises.text, - builder: (BuildContext context, GoRouterState state) => - const ExerciseSearchPage(), + builder: (BuildContext context, GoRouterState state) => ExerciseSearchPage(), ), GoRoute( path: routeNames.SignIn.text, @@ -117,5 +116,6 @@ final GoRouter routerConfig = GoRouter( } // otherwise no need to redirect at all return null; + //todo I should probably check my codebase for using navigator.of ... b/c it probably won't trigger my redirect. }, ); diff --git a/lib/training/training_data_table.dart b/lib/training/training_data_table.dart index 9c0c095..08a4626 100644 --- a/lib/training/training_data_table.dart +++ b/lib/training/training_data_table.dart @@ -137,7 +137,7 @@ class _DisplayTrainingDataState extends State { await Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) => - const ExerciseSearchPage(useForAddingToTraining: true), + ExerciseSearchPage(useForAddingToTraining: true), ), ); setState(() {}); //todo this is weird diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index ad05423..2af1b8e 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -3,6 +3,10 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'dart:async'; +import 'package:open_fitness_tracker/DOM/exercise_metadata.dart'; + +typedef Exercises = List; + class GenericScrollBehavior extends MaterialScrollBehavior { @override Set get dragDevices => {