diff --git a/lib/DOM/history_importing_logic.dart b/lib/DOM/history_importing_logic.dart index d4c41a0..3f33470 100644 --- a/lib/DOM/history_importing_logic.dart +++ b/lib/DOM/history_importing_logic.dart @@ -41,7 +41,7 @@ List importStrongCsv(String filepathORfileStr, Units units) { final workoutName = rowList[0][1]; final duration = parseStrongWorkoutDuration(rowList[0][2]); final exerciseName = rowList[0][3]; - // final _setOrder = int.parse(rowList[0][4]); // unused..why this here + // final _setOrder = int.parse(rowList[0][4]); // todo unused..why this here final weightRaw = double.tryParse(rowList[0][5]) ?? 0; final weight = double.parse(weightRaw.toStringAsFixed(2)); final reps = int.tryParse(rowList[0][6]) ?? 0; diff --git a/lib/cloud_io/firestore_sync.dart b/lib/cloud_io/firestore_sync.dart index 876f17f..8ad32d1 100644 --- a/lib/cloud_io/firestore_sync.dart +++ b/lib/cloud_io/firestore_sync.dart @@ -39,8 +39,6 @@ class CloudStorage { }); if (!isUserEmailVerified()) return; - - // TrainHistoryDB.loadUserTrainingHistory(useCache: false); // refreshCacheIfItsBeenXHours(12); } @@ -79,17 +77,22 @@ class CloudStorage { "Sign in. Make sure to verify your email if not signing in with Google Sign In, etc..."); } return await _retryWithExponentialBackoff(() async { - final docSnapshot = await firestore - .collection('users') - .doc(firebaseAuth.currentUser!.uid) - .get(const GetOptions(source: Source.server)); - final data = docSnapshot.data(); // as Map?; - - if (data != null && data.containsKey(_basicUserInfoKey)) { - final basicUserInfoJson = data[_basicUserInfoKey] as Map; - return BasicUserInfo.fromJson(basicUserInfoJson); - } else { - return BasicUserInfo(); + try { + final docSnapshot = await firestore + .collection('users') + .doc(firebaseAuth.currentUser!.uid) + .get(const GetOptions(source: Source.server)); + final data = docSnapshot.data(); // as Map?; + + if (data != null && data.containsKey(_basicUserInfoKey)) { + final basicUserInfoJson = data[_basicUserInfoKey] as Map; + return BasicUserInfo.fromJson(basicUserInfoJson); + } else { + return BasicUserInfo(); + } + } catch (e) { + print("test"); //todo rm + rethrow; } }); } @@ -132,6 +135,7 @@ class CloudStorage { await firebaseAuth.signOut(); // rethrow; } else { + //todo just sign out & log??? // Handle other Firebase exceptions rethrow; } @@ -179,20 +183,26 @@ class TrainingHistoryCubit extends Cubit { //todo re enable me // try { await CloudStorage._retryWithExponentialBackoff(() async { - QuerySnapshot cloudTrainingHistory = await CloudStorage.firestore - .collection('users') - .doc(CloudStorage.firebaseAuth.currentUser!.uid) - .collection(CloudStorage._historyKey) - .get(GetOptions(source: useCache ? Source.cache : Source.server)); - - List sessions = []; - for (var doc in cloudTrainingHistory.docs) { - sessions.add( - TrainingSession.fromJson(doc.data() as Map)..id = doc.id, - ); + try { + QuerySnapshot cloudTrainingHistory = await CloudStorage.firestore + .collection('users') + .doc(CloudStorage.firebaseAuth.currentUser!.uid) + .collection(CloudStorage._historyKey) + .get(GetOptions(source: useCache ? Source.cache : Source.server)); + + List sessions = []; + for (var doc in cloudTrainingHistory.docs) { + sessions.add( + TrainingSession.fromJson(doc.data() as Map)..id = doc.id, + ); + } + sessions.sort((a, b) => b.date.compareTo(a.date)); + emit(TrainingHistoryLoaded(sessions)); + } catch (e) { + print('gotcha'); + rethrow; //todo rm me } - sessions.sort((a, b) => b.date.compareTo(a.date)); - emit(TrainingHistoryLoaded(sessions)); + //todo whats up with trying to load history on startup for the first time? }); // } catch (e) { diff --git a/lib/exercises/create_new_exercise/muscle_selector.dart b/lib/exercises/create_new_exercise/muscle_selector.dart index f8f3aaf..3ceaabf 100644 --- a/lib/exercises/create_new_exercise/muscle_selector.dart +++ b/lib/exercises/create_new_exercise/muscle_selector.dart @@ -289,7 +289,6 @@ class MusclesDropdown extends StatelessWidget { itemBuilder: (context, index) => ListTile( title: Text(filteredMuscles[index]), onTap: () { - // onMuscleAdded(filteredMuscles[index]); if (filteredMuscles.contains(filteredMuscles[index])) { onMuscleRemoved(filteredMuscles[index]); } else { diff --git a/lib/history/history_page.dart b/lib/history/history_page.dart index d7d266b..978626d 100644 --- a/lib/history/history_page.dart +++ b/lib/history/history_page.dart @@ -3,7 +3,8 @@ import 'package:intl/intl.dart'; import 'package:open_fitness_tracker/DOM/training_metadata.dart'; import 'package:open_fitness_tracker/cloud_io/firestore_sync.dart'; import 'package:open_fitness_tracker/common/common_widgets.dart'; -import 'package:open_fitness_tracker/importing/import_training_ui.dart'; +import 'package:open_fitness_tracker/importing/import_training_first_page.dart'; +import 'package:open_fitness_tracker/navigation/routes.dart'; import 'package:open_fitness_tracker/utils/utils.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -75,16 +76,9 @@ class HistoryPage extends StatelessWidget { onSelected: (String result) { final cubit = context.read(); if (result == 'import') { - showDialog( - context: context, - builder: (BuildContext context) { - return const ExternalAppImportSelectionDialog(); - }, - ); + routerConfig.push(routeNames.ImportExternalAppHistory.text); } else if (result == 'refresh training history') { cubit.loadUserTrainingHistory(useCache: false); - } else if (result == 'delete ENTIRE history') { - cubit.deleteEntireTrainingHistory(); } }, itemBuilder: (BuildContext context) => >[ @@ -96,10 +90,6 @@ class HistoryPage extends StatelessWidget { value: 'refresh training history', child: Text('Refresh training history cache'), ), - const PopupMenuItem( - value: 'delete history', - child: Text('Delete entire history'), - ), ], ); } diff --git a/lib/importing/ex_match_listview.dart b/lib/importing/ex_match_listview.dart index 6afa2e2..13b08c3 100644 --- a/lib/importing/ex_match_listview.dart +++ b/lib/importing/ex_match_listview.dart @@ -3,6 +3,7 @@ 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'; +import 'package:open_fitness_tracker/navigation/routes.dart'; class ExerciseMatch { final Exercise foreignExercise; @@ -220,6 +221,7 @@ class _MatchExercisesScrollViewState extends State { void _addNewExercise( int index, Exercise foreignExercise, Function onExMatchFound) async { + // Exercise? newExercise = await routerConfig.push(routeNames.Exercises.text); Exercise? newExercise = await Navigator.push( context, MaterialPageRoute( diff --git a/lib/importing/import_matching_page.dart b/lib/importing/import_inspection_page.dart similarity index 100% rename from lib/importing/import_matching_page.dart rename to lib/importing/import_inspection_page.dart diff --git a/lib/importing/import_training_first_page.dart b/lib/importing/import_training_first_page.dart new file mode 100644 index 0000000..908e9e6 --- /dev/null +++ b/lib/importing/import_training_first_page.dart @@ -0,0 +1,202 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +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/styles.dart'; +import 'package:open_fitness_tracker/utils/utils.dart'; + +class ImportTrainingDataPage extends StatefulWidget { + const ImportTrainingDataPage({ + super.key, + }); + + @override + State createState() => _ImportTrainingDataPageState(); +} + +class _ImportTrainingDataPageState extends State { + final Units units = Units(); + late MassUnits selectedMassUnit; + late DistanceUnits selectedDistanceUnit; + bool setAsStandard = true; + String? filepathORfileStr; + OtherTrainingApps originApp = OtherTrainingApps.strong; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Import Your Training History"), + ), + body: SafeArea( + minimum: const EdgeInsets.all(40), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + originAppSelector(), + const SizedBox(height: 16), + fileSelectButton(context), + const SizedBox(height: 16), + massSelectDropdown(), + const SizedBox(height: 16), + distanceSelectDropdown(), + const SizedBox(height: 16), + setAsDefaultUnitsSwitch(), + BottomCancelOrCompleteButtons( + cancelLabel: "Cancel", + completeLabel: "Import", + onCancel: () { + Navigator.of(context).pop(); + }, + onComplete: runImport, + ), + ], + ), + ), + ), + ); + + /* + + for (var session in sessions) { + myStorage.addTrainingSessionToHistory(session); + } + + */ + } + + Row setAsDefaultUnitsSwitch() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Set as Default Units for Me'), + Switch( + activeTrackColor: mediumGreen, + value: setAsStandard, + onChanged: (bool value) { + setState(() { + setAsStandard = value; + }); + }, + ), + ], + ); + } + + DropdownButtonFormField distanceSelectDropdown() { + return DropdownButtonFormField( + decoration: const InputDecoration(labelText: 'Distance Unit'), + value: units.preferredDistanceUnit, + onChanged: (DistanceUnits? newValue) { + setState(() { + units.preferredDistanceUnit = newValue!; + }); + }, + items: DistanceUnits.values.map((DistanceUnits unit) { + return DropdownMenuItem( + value: unit, + child: Text(unit.text), + ); + }).toList(), + ); + } + + Widget massSelectDropdown() { + return DropdownButtonFormField( + decoration: const InputDecoration(labelText: 'Mass Unit'), + value: units.preferredMassUnit, + onChanged: (MassUnits? newValue) { + setState(() { + units.preferredMassUnit = newValue!; + }); + }, + items: MassUnits.values.map((MassUnits unit) { + return DropdownMenuItem( + value: unit, + child: Text(unit.text), + ); + }).toList(), + ); + } + + MyGenericButton fileSelectButton(BuildContext context) { + return MyGenericButton( + label: (filepathORfileStr == null) ? "Select file to Import" : "File Selected.", + onPressed: () async { + filepathORfileStr = + await getFileWithSnackbarErrors(context, ['csv', 'txt', 'json']); + setState(() {}); + }, + color: (filepathORfileStr == null) ? darkTan : mediumGreen, + ); + } + + DropdownMenu originAppSelector() { + return DropdownMenu( + hintText: "Pick an app to import from", + initialSelection: OtherTrainingApps.strong.text, + width: double.infinity, + dropdownMenuEntries: >[ + DropdownMenuEntry( + value: OtherTrainingApps.strong.text, + label: "Strong App", + ), + const DropdownMenuEntry( + value: "Submit a github ticket w/ an example file to import.", + label: "Submit a github ticket w/ an example file to import.", + ), + ], + ); + } + + void runImport() { + if (filepathORfileStr == null) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar(content: Text('No file selected'))); + return; + } + + List sessions; + if (originApp == OtherTrainingApps.strong) { + try { + sessions = importStrongCsv(filepathORfileStr!, units); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text( + 'Failed to import. Is the file malformed? Did you select the correct App'))); + return; + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Select a valid App to import from.'))); + return; + } + + if (setAsStandard) { + var userInfoCubit = context.read(); + BasicUserInfo userInfo = userInfoCubit.state; + userInfo.preferredDistanceUnit = units.preferredDistanceUnit; + userInfo.preferredMassUnit = units.preferredMassUnit; + userInfoCubit.set(userInfo); + } + Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) => + ImportInspectionPage(newTrainingSessions: sessions))); + } +} + +enum OtherTrainingApps { + strong('Strong'); + + const OtherTrainingApps(this.text); + final String text; +} diff --git a/lib/importing/import_training_ui.dart b/lib/importing/import_training_ui.dart deleted file mode 100644 index 9bcaba2..0000000 --- a/lib/importing/import_training_ui.dart +++ /dev/null @@ -1,190 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -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_matching_page.dart'; -import 'package:open_fitness_tracker/styles.dart'; -import 'package:open_fitness_tracker/utils/utils.dart'; - -class ExternalAppImportSelectionDialog extends StatelessWidget { - const ExternalAppImportSelectionDialog({super.key}); - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: const Text('Choose Import Source'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: const Text('Strong App'), - onTap: () async { - Navigator.of(context).pop(); - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) => - const ImportTrainingDataPage(OtherTrainingApps.strong), - ), - ); - }, - ), - ListTile( - title: const Text('more to come..submit ticket on github'), - onTap: () {}, - ), - ], - ), - ); - } -} - -class ImportTrainingDataPage extends StatefulWidget { - const ImportTrainingDataPage( - this.originApp, { - super.key, - }); - final OtherTrainingApps originApp; - - @override - State createState() => _ImportTrainingDataPageState(); -} - -class _ImportTrainingDataPageState extends State { - final Units units = Units(); - late MassUnits selectedMassUnit; - late DistanceUnits selectedDistanceUnit; - bool setAsStandard = true; - String? filepathORfileStr; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text("Import Your Training History"), - ), - body: SafeArea( - minimum: const EdgeInsets.all(40), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - MyGenericButton( - label: (filepathORfileStr == null) - ? "Select file to Import" - : "File Selected.", - onPressed: () async { - //todo for web? - // https://github.com/miguelpruivo/flutter_file_picker/wiki/FAQ - filepathORfileStr = - await getFileWithSnackbarErrors(context, ['csv', 'txt', 'json']); - setState(() {}); - }, - color: (filepathORfileStr == null) ? darkTan : mediumGreen, - ), - const SizedBox(height: 16), - DropdownButtonFormField( - decoration: const InputDecoration(labelText: 'Mass Unit'), - value: units.preferredMassUnit, - onChanged: (MassUnits? newValue) { - setState(() { - units.preferredMassUnit = newValue!; - }); - }, - items: MassUnits.values.map((MassUnits unit) { - return DropdownMenuItem( - value: unit, - child: Text(unit.text), - ); - }).toList(), - ), - const SizedBox(height: 16), - DropdownButtonFormField( - decoration: const InputDecoration(labelText: 'Distance Unit'), - value: units.preferredDistanceUnit, - onChanged: (DistanceUnits? newValue) { - setState(() { - units.preferredDistanceUnit = newValue!; - }); - }, - items: DistanceUnits.values.map((DistanceUnits unit) { - return DropdownMenuItem( - value: unit, - child: Text(unit.text), - ); - }).toList(), - ), - const SizedBox(height: 16), - // Toggle Switch - - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('Set as Default Units for Me'), - Switch( - activeTrackColor: mediumGreen, - value: setAsStandard, - onChanged: (bool value) { - setState(() { - setAsStandard = value; - }); - }, - ), - ], - ), - BottomCancelOrCompleteButtons( - cancelLabel: "Cancel", - completeLabel: "Import", - onCancel: () { - Navigator.of(context).pop(); - }, - onComplete: () { - if (filepathORfileStr == null) { - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar(content: Text('No file selected'))); - return; - } - - List sessions = - importStrongCsv(filepathORfileStr!, units); - - if (setAsStandard) { - var userInfoCubit = context.read(); - BasicUserInfo userInfo = userInfoCubit.state; - userInfo.preferredDistanceUnit = units.preferredDistanceUnit; - userInfo.preferredMassUnit = units.preferredMassUnit; - userInfoCubit.set(userInfo); - } - Navigator.of(context).push(MaterialPageRoute( - builder: (BuildContext context) => - ImportInspectionPage(newTrainingSessions: sessions))); - }, - ), - ], - ), - ), - ), - ); - - /* - - for (var session in sessions) { - myStorage.addTrainingSessionToHistory(session); - } - - */ - } -} - -enum OtherTrainingApps { - strong('Strong'); - - const OtherTrainingApps(this.text); - final String text; -} diff --git a/lib/navigation/routes.dart b/lib/navigation/routes.dart index 38f3646..5295eb6 100644 --- a/lib/navigation/routes.dart +++ b/lib/navigation/routes.dart @@ -8,7 +8,7 @@ import 'package:open_fitness_tracker/community/profile_page.dart'; import 'package:open_fitness_tracker/community/settings_page.dart'; import 'package:open_fitness_tracker/exercises/ex_search_page.dart'; import 'package:open_fitness_tracker/history/history_page.dart'; -import 'package:open_fitness_tracker/importing/import_training_ui.dart'; +import 'package:open_fitness_tracker/importing/import_training_first_page.dart'; import 'package:open_fitness_tracker/navigation/page_not_found_page.dart'; import 'package:open_fitness_tracker/navigation/scaffold_with_nav_bar.dart'; import 'package:open_fitness_tracker/training/start_training_page.dart'; @@ -27,6 +27,7 @@ enum routeNames { Profile("/Profile"), UserSettings("/UserSettings"), VerifyEmail("/VerifyEmail"), + ImportExternalAppHistory("/ImportExternalAppHistory"), None("/None"), Temp("/Temp"), @@ -91,10 +92,15 @@ final GoRouter routerConfig = GoRouter( builder: (BuildContext context, GoRouterState state) => const EmailVerificationScreenWrapper(), ), + GoRoute( + path: routeNames.ImportExternalAppHistory.text, + builder: (BuildContext context, GoRouterState state) => + const ImportTrainingDataPage(), + ), GoRoute( path: routeNames.Temp.text, builder: (BuildContext context, GoRouterState state) => - const ImportTrainingDataPage(OtherTrainingApps.strong), + const ImportTrainingDataPage(), ), ], ), diff --git a/lib/training/training_page.dart b/lib/training/training_page.dart index a14b126..fcadd6f 100644 --- a/lib/training/training_page.dart +++ b/lib/training/training_page.dart @@ -62,17 +62,20 @@ class TrainingTitle extends StatelessWidget { @override Widget build(BuildContext context) { var state = context.read().state; + var textController = TextEditingController( + text: state.name.isEmpty ? 'New Training Session' : state.name); return Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 10.0), child: TextField( style: Theme.of(context).textTheme.headlineSmall, - controller: TextEditingController( - text: state.name.isEmpty ? 'New Training Session' : state.name), + controller: textController, decoration: const InputDecoration( icon: Icon(Icons.edit), border: OutlineInputBorder(borderSide: BorderSide())), onChanged: (value) { state.name = value; }, + onTap: () => textController.selection = + TextSelection(baseOffset: 0, extentOffset: textController.text.length), ), ); }