mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
dcm fix --only-rules=prefer-trailing-comma lib
This commit is contained in:
@@ -44,13 +44,16 @@ void showErrorDialog(dynamic exception, BuildContext context) {
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showHttpExceptionErrorDialog(WgerHttpException exception, BuildContext context) async {
|
||||
void showHttpExceptionErrorDialog(
|
||||
WgerHttpException exception,
|
||||
BuildContext context,
|
||||
) async {
|
||||
log('showHttpExceptionErrorDialog: ');
|
||||
log(exception.toString());
|
||||
log('-------------------');
|
||||
@@ -95,7 +98,7 @@ void showHttpExceptionErrorDialog(WgerHttpException exception, BuildContext cont
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -113,42 +116,43 @@ dynamic showDeleteDialog(
|
||||
Map<Exercise, List<Log>> exerciseData,
|
||||
) async {
|
||||
final res = await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext contextDialog) {
|
||||
return AlertDialog(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).confirmDelete(confirmDeleteName),
|
||||
context: context,
|
||||
builder: (BuildContext contextDialog) {
|
||||
return AlertDialog(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).confirmDelete(confirmDeleteName),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
onPressed: () => Navigator.of(contextDialog).pop(),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
onPressed: () => Navigator.of(contextDialog).pop(),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
onPressed: () {
|
||||
exerciseData[exercise]!.removeWhere((el) => el.id == log.id);
|
||||
Provider.of<WorkoutPlansProvider>(context, listen: false).deleteLog(
|
||||
log,
|
||||
);
|
||||
onPressed: () {
|
||||
exerciseData[exercise]!.removeWhere((el) => el.id == log.id);
|
||||
Provider.of<WorkoutPlansProvider>(context, listen: false).deleteLog(
|
||||
log,
|
||||
);
|
||||
|
||||
Navigator.of(contextDialog).pop();
|
||||
Navigator.of(contextDialog).pop();
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,8 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
ChangeNotifierProxyProvider<AuthProvider, ExercisesProvider>(
|
||||
create: (context) => ExercisesProvider(
|
||||
WgerBaseProvider(Provider.of<AuthProvider>(context, listen: false))),
|
||||
WgerBaseProvider(Provider.of<AuthProvider>(context, listen: false)),
|
||||
),
|
||||
update: (context, base, previous) =>
|
||||
previous ?? ExercisesProvider(WgerBaseProvider(base)),
|
||||
),
|
||||
@@ -120,8 +121,10 @@ class MyApp extends StatelessWidget {
|
||||
previous ?? BodyWeightProvider(WgerBaseProvider(base)),
|
||||
),
|
||||
ChangeNotifierProxyProvider<AuthProvider, GalleryProvider>(
|
||||
create: (context) =>
|
||||
GalleryProvider(Provider.of<AuthProvider>(context, listen: false), []),
|
||||
create: (context) => GalleryProvider(
|
||||
Provider.of<AuthProvider>(context, listen: false),
|
||||
[],
|
||||
),
|
||||
update: (context, auth, previous) => previous ?? GalleryProvider(auth, []),
|
||||
),
|
||||
ChangeNotifierProxyProvider<AuthProvider, AddExerciseProvider>(
|
||||
@@ -130,7 +133,7 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
update: (context, base, previous) =>
|
||||
previous ?? AddExerciseProvider(WgerBaseProvider(base)),
|
||||
)
|
||||
),
|
||||
],
|
||||
child: Consumer<AuthProvider>(
|
||||
builder: (ctx, auth, _) => MaterialApp(
|
||||
|
||||
@@ -41,8 +41,10 @@ class MeasurementCategory extends Equatable {
|
||||
}
|
||||
|
||||
MeasurementEntry findEntryById(entryId) {
|
||||
return entries.firstWhere((entry) => entry.id == entryId,
|
||||
orElse: () => throw NoSuchEntryException());
|
||||
return entries.firstWhere(
|
||||
(entry) => entry.id == entryId,
|
||||
orElse: () => throw NoSuchEntryException(),
|
||||
);
|
||||
}
|
||||
|
||||
// Boilerplate
|
||||
|
||||
@@ -42,22 +42,28 @@ class NutritionalGoals {
|
||||
// infer values where we can
|
||||
if (energy == null) {
|
||||
if (protein != null && carbohydrates != null && fat != null) {
|
||||
energy =
|
||||
protein! * ENERGY_PROTEIN + carbohydrates! * ENERGY_CARBOHYDRATES + fat! * ENERGY_FAT;
|
||||
energy = protein! * ENERGY_PROTEIN +
|
||||
carbohydrates! * ENERGY_CARBOHYDRATES +
|
||||
fat! * ENERGY_FAT;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// TODO: input validation when the user modifies/creates the plan, to assure energy is high enough
|
||||
if (protein == null && carbohydrates != null && fat != null) {
|
||||
protein =
|
||||
(energy! - carbohydrates! * ENERGY_CARBOHYDRATES - fat! * ENERGY_FAT) / ENERGY_PROTEIN;
|
||||
protein = (energy! -
|
||||
carbohydrates! * ENERGY_CARBOHYDRATES -
|
||||
fat! * ENERGY_FAT) /
|
||||
ENERGY_PROTEIN;
|
||||
assert(protein! > 0);
|
||||
} else if (carbohydrates == null && protein != null && fat != null) {
|
||||
carbohydrates =
|
||||
(energy! - protein! * ENERGY_PROTEIN - fat! * ENERGY_FAT) / ENERGY_CARBOHYDRATES;
|
||||
(energy! - protein! * ENERGY_PROTEIN - fat! * ENERGY_FAT) /
|
||||
ENERGY_CARBOHYDRATES;
|
||||
assert(carbohydrates! > 0);
|
||||
} else if (fat == null && protein != null && carbohydrates != null) {
|
||||
fat = (energy! - protein! * ENERGY_PROTEIN - carbohydrates! * ENERGY_CARBOHYDRATES) /
|
||||
fat = (energy! -
|
||||
protein! * ENERGY_PROTEIN -
|
||||
carbohydrates! * ENERGY_CARBOHYDRATES) /
|
||||
ENERGY_FAT;
|
||||
assert(fat! > 0);
|
||||
}
|
||||
@@ -68,7 +74,8 @@ class NutritionalGoals {
|
||||
energy: energy != null ? energy! / v : null,
|
||||
protein: protein != null ? protein! / v : null,
|
||||
carbohydrates: carbohydrates != null ? carbohydrates! / v : null,
|
||||
carbohydratesSugar: carbohydratesSugar != null ? carbohydratesSugar! / v : null,
|
||||
carbohydratesSugar:
|
||||
carbohydratesSugar != null ? carbohydratesSugar! / v : null,
|
||||
fat: fat != null ? fat! / v : null,
|
||||
fatSaturated: fatSaturated != null ? fatSaturated! / v : null,
|
||||
fiber: fiber != null ? fiber! / v : null,
|
||||
@@ -77,7 +84,10 @@ class NutritionalGoals {
|
||||
}
|
||||
|
||||
bool isComplete() {
|
||||
return energy != null && protein != null && carbohydrates != null && fat != null;
|
||||
return energy != null &&
|
||||
protein != null &&
|
||||
carbohydrates != null &&
|
||||
fat != null;
|
||||
}
|
||||
|
||||
/// Convert goals into values.
|
||||
@@ -110,7 +120,8 @@ class NutritionalGoals {
|
||||
goals.protein = (100 * protein! * ENERGY_PROTEIN) / energy!;
|
||||
}
|
||||
if (carbohydrates != null) {
|
||||
goals.carbohydrates = (100 * carbohydrates! * ENERGY_CARBOHYDRATES) / energy!;
|
||||
goals.carbohydrates =
|
||||
(100 * carbohydrates! * ENERGY_CARBOHYDRATES) / energy!;
|
||||
}
|
||||
if (fat != null) {
|
||||
goals.fat = (100 * fat! * ENERGY_FAT) / energy!;
|
||||
@@ -140,7 +151,15 @@ class NutritionalGoals {
|
||||
@override
|
||||
//ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => Object.hash(
|
||||
energy, protein, carbohydrates, carbohydratesSugar, fat, fatSaturated, fiber, sodium);
|
||||
energy,
|
||||
protein,
|
||||
carbohydrates,
|
||||
carbohydratesSugar,
|
||||
fat,
|
||||
fatSaturated,
|
||||
fiber,
|
||||
sodium,
|
||||
);
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
|
||||
@@ -134,7 +134,10 @@ class NutritionalPlan {
|
||||
return NutritionalGoals();
|
||||
}
|
||||
// otherwise, add up all the nutritional values of the meals and use that as goals
|
||||
final sumValues = meals.fold(NutritionalValues(), (a, b) => a + b.plannedNutritionalValues);
|
||||
final sumValues = meals.fold(
|
||||
NutritionalValues(),
|
||||
(a, b) => a + b.plannedNutritionalValues,
|
||||
);
|
||||
return NutritionalGoals(
|
||||
energy: sumValues.energy,
|
||||
fat: sumValues.fat,
|
||||
@@ -209,7 +212,8 @@ class NutritionalPlan {
|
||||
for (final meal in meals) {
|
||||
for (final mealItem in meal.mealItems) {
|
||||
final found = out.firstWhereOrNull(
|
||||
(e) => e.amount == mealItem.amount && e.ingredientId == mealItem.ingredientId);
|
||||
(e) => e.amount == mealItem.amount && e.ingredientId == mealItem.ingredientId,
|
||||
);
|
||||
|
||||
if (found == null) {
|
||||
out.add(mealItem);
|
||||
@@ -224,8 +228,9 @@ class NutritionalPlan {
|
||||
List<Log> get dedupDiaryEntries {
|
||||
final out = <Log>[];
|
||||
for (final log in diaryEntries) {
|
||||
final found =
|
||||
out.firstWhereOrNull((e) => e.amount == log.amount && e.ingredientId == log.ingredientId);
|
||||
final found = out.firstWhereOrNull(
|
||||
(e) => e.amount == log.amount && e.ingredientId == log.ingredientId,
|
||||
);
|
||||
if (found == null) {
|
||||
out.add(log);
|
||||
}
|
||||
|
||||
@@ -117,5 +117,13 @@ class NutritionalValues {
|
||||
@override
|
||||
//ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => Object.hash(
|
||||
energy, protein, carbohydrates, carbohydratesSugar, fat, fatSaturated, fiber, sodium);
|
||||
energy,
|
||||
protein,
|
||||
carbohydrates,
|
||||
carbohydratesSugar,
|
||||
fat,
|
||||
fatSaturated,
|
||||
fiber,
|
||||
sodium,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,11 +64,13 @@ class AuthProvider with ChangeNotifier {
|
||||
} else if (Platform.isLinux || Platform.isMacOS) {
|
||||
metadata = {
|
||||
MANIFEST_KEY_CHECK_UPDATE: Platform.environment[MANIFEST_KEY_CHECK_UPDATE] ?? '',
|
||||
MANIFEST_KEY_API: Platform.environment[MANIFEST_KEY_API] ?? ''
|
||||
MANIFEST_KEY_API: Platform.environment[MANIFEST_KEY_API] ?? '',
|
||||
};
|
||||
}
|
||||
} on PlatformException {
|
||||
throw Exception('An error occurred reading the metadata from AndroidManifest');
|
||||
throw Exception(
|
||||
'An error occurred reading the metadata from AndroidManifest',
|
||||
);
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
@@ -98,7 +100,10 @@ class AuthProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Checking if there is a new version of the application.
|
||||
Future<bool> applicationUpdateRequired([String? version, Map<String, String>? metadata]) async {
|
||||
Future<bool> applicationUpdateRequired([
|
||||
String? version,
|
||||
Map<String, String>? metadata,
|
||||
]) async {
|
||||
metadata ??= this.metadata;
|
||||
if (!metadata.containsKey(MANIFEST_KEY_CHECK_UPDATE) ||
|
||||
metadata[MANIFEST_KEY_CHECK_UPDATE] == 'false') {
|
||||
@@ -122,7 +127,10 @@ class AuthProvider with ChangeNotifier {
|
||||
String locale = 'en',
|
||||
}) async {
|
||||
// Register
|
||||
final Map<String, String> data = {'username': username, 'password': password};
|
||||
final Map<String, String> data = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
};
|
||||
if (email != '') {
|
||||
data['email'] = email;
|
||||
}
|
||||
@@ -132,7 +140,7 @@ class AuthProvider with ChangeNotifier {
|
||||
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
|
||||
HttpHeaders.authorizationHeader: 'Token ${metadata[MANIFEST_KEY_API]}',
|
||||
HttpHeaders.userAgentHeader: getAppNameHeader(),
|
||||
HttpHeaders.acceptLanguageHeader: locale
|
||||
HttpHeaders.acceptLanguageHeader: locale,
|
||||
},
|
||||
body: json.encode(data),
|
||||
);
|
||||
|
||||
@@ -192,7 +192,10 @@ class ExercisesProvider with ChangeNotifier {
|
||||
/// returned exercises. Since this is typically called by one exercise, we are
|
||||
/// not interested in seeing that same exercise returned in the list of variations.
|
||||
/// If this parameter is not passed, all exercises are returned.
|
||||
List<Exercise> findExercisesByVariationId(int id, {int? exerciseBaseIdToExclude}) {
|
||||
List<Exercise> findExercisesByVariationId(
|
||||
int id, {
|
||||
int? exerciseBaseIdToExclude,
|
||||
}) {
|
||||
var out = exercises.where((base) => base.variationId == id).toList();
|
||||
|
||||
if (exerciseBaseIdToExclude != null) {
|
||||
@@ -290,7 +293,10 @@ class ExercisesProvider with ChangeNotifier {
|
||||
/// -> yes: Do we need to re-fetch?
|
||||
/// -> no: just return what we have in the DB
|
||||
/// -> yes: fetch data and update if necessary
|
||||
Future<Exercise> handleUpdateExerciseFromApi(ExerciseDatabase database, int exerciseId) async {
|
||||
Future<Exercise> handleUpdateExerciseFromApi(
|
||||
ExerciseDatabase database,
|
||||
int exerciseId,
|
||||
) async {
|
||||
Exercise exercise;
|
||||
final exerciseDb = await (database.select(database.exercises)
|
||||
..where((e) => e.id.equals(exerciseId)))
|
||||
@@ -336,10 +342,11 @@ class ExercisesProvider with ChangeNotifier {
|
||||
if (exerciseDb == null) {
|
||||
await database.into(database.exercises).insert(
|
||||
ExercisesCompanion.insert(
|
||||
id: exercise.id!,
|
||||
data: jsonEncode(baseData),
|
||||
lastUpdate: exercise.lastUpdateGlobal!,
|
||||
lastFetched: DateTime.now()),
|
||||
id: exercise.id!,
|
||||
data: jsonEncode(baseData),
|
||||
lastUpdate: exercise.lastUpdateGlobal!,
|
||||
lastFetched: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -438,8 +445,10 @@ class ExercisesProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Set the available exercises as available in the db
|
||||
Future<void> setExercisesFromDatabase(ExerciseDatabase database,
|
||||
{bool forceDeleteCache = false}) async {
|
||||
Future<void> setExercisesFromDatabase(
|
||||
ExerciseDatabase database, {
|
||||
bool forceDeleteCache = false,
|
||||
}) async {
|
||||
if (forceDeleteCache) {
|
||||
await database.delete(database.exercises).go();
|
||||
}
|
||||
@@ -469,10 +478,11 @@ class ExercisesProvider with ChangeNotifier {
|
||||
if (exercise == null) {
|
||||
database.into(database.exercises).insert(
|
||||
ExercisesCompanion.insert(
|
||||
id: exerciseData['id'],
|
||||
data: jsonEncode(exerciseData),
|
||||
lastUpdate: DateTime.parse(exerciseData['last_update_global']),
|
||||
lastFetched: DateTime.now()),
|
||||
id: exerciseData['id'],
|
||||
data: jsonEncode(exerciseData),
|
||||
lastUpdate: DateTime.parse(exerciseData['last_update_global']),
|
||||
lastFetched: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -481,7 +491,8 @@ class ExercisesProvider with ChangeNotifier {
|
||||
if (exercise != null && lastUpdateApi.isAfter(exercise.lastUpdate)) {
|
||||
// TODO: timezones 🥳
|
||||
print(
|
||||
'Exercise ${exercise.id}: update API $lastUpdateApi | Update DB: ${exercise.lastUpdate}');
|
||||
'Exercise ${exercise.id}: update API $lastUpdateApi | Update DB: ${exercise.lastUpdate}',
|
||||
);
|
||||
(database.update(database.exercises)..where((e) => e.id.equals(exerciseData['id']))).write(
|
||||
ExercisesCompanion(
|
||||
id: Value(exerciseData['id']),
|
||||
@@ -524,7 +535,10 @@ class ExercisesProvider with ChangeNotifier {
|
||||
);
|
||||
});
|
||||
validTill = DateTime.now().add(const Duration(days: EXERCISE_CACHE_DAYS));
|
||||
await prefs.setString(PREFS_LAST_UPDATED_MUSCLES, validTill.toIso8601String());
|
||||
await prefs.setString(
|
||||
PREFS_LAST_UPDATED_MUSCLES,
|
||||
validTill.toIso8601String(),
|
||||
);
|
||||
log('Wrote ${_muscles.length} muscles from cache. Valid till $validTill');
|
||||
}
|
||||
|
||||
@@ -558,7 +572,10 @@ class ExercisesProvider with ChangeNotifier {
|
||||
);
|
||||
});
|
||||
validTill = DateTime.now().add(const Duration(days: EXERCISE_CACHE_DAYS));
|
||||
await prefs.setString(PREFS_LAST_UPDATED_CATEGORIES, validTill.toIso8601String());
|
||||
await prefs.setString(
|
||||
PREFS_LAST_UPDATED_CATEGORIES,
|
||||
validTill.toIso8601String(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Fetches and sets the available languages
|
||||
@@ -590,7 +607,10 @@ class ExercisesProvider with ChangeNotifier {
|
||||
);
|
||||
});
|
||||
validTill = DateTime.now().add(const Duration(days: EXERCISE_CACHE_DAYS));
|
||||
await prefs.setString(PREFS_LAST_UPDATED_LANGUAGES, validTill.toIso8601String());
|
||||
await prefs.setString(
|
||||
PREFS_LAST_UPDATED_LANGUAGES,
|
||||
validTill.toIso8601String(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Fetches and sets the available equipment
|
||||
@@ -623,15 +643,21 @@ class ExercisesProvider with ChangeNotifier {
|
||||
);
|
||||
});
|
||||
validTill = DateTime.now().add(const Duration(days: EXERCISE_CACHE_DAYS));
|
||||
await prefs.setString(PREFS_LAST_UPDATED_EQUIPMENT, validTill.toIso8601String());
|
||||
await prefs.setString(
|
||||
PREFS_LAST_UPDATED_EQUIPMENT,
|
||||
validTill.toIso8601String(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Searches for an exercise
|
||||
///
|
||||
/// We could do this locally, but the server has better text searching capabilities
|
||||
/// with postgresql.
|
||||
Future<List<Exercise>> searchExercise(String name,
|
||||
{String languageCode = LANGUAGE_SHORT_ENGLISH, bool searchEnglish = false}) async {
|
||||
Future<List<Exercise>> searchExercise(
|
||||
String name, {
|
||||
String languageCode = LANGUAGE_SHORT_ENGLISH,
|
||||
bool searchEnglish = false,
|
||||
}) async {
|
||||
if (name.length <= 1) {
|
||||
return [];
|
||||
}
|
||||
@@ -651,7 +677,9 @@ class ExercisesProvider with ChangeNotifier {
|
||||
|
||||
// Load the ingredients
|
||||
final results = ExerciseApiSearch.fromJson(result);
|
||||
return Future.wait(results.suggestions.map((e) => fetchAndSetExercise(e.data.exerciseId)));
|
||||
return Future.wait(
|
||||
results.suggestions.map((e) => fetchAndSetExercise(e.data.exerciseId)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,8 +92,9 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
/// Fetches and sets all plans sparsely, i.e. only with the data on the plan
|
||||
/// object itself and no child attributes
|
||||
Future<void> fetchAndSetAllPlansSparse() async {
|
||||
final data = await baseProvider
|
||||
.fetchPaginated(baseProvider.makeUrl(_nutritionalPlansPath, query: {'limit': '1000'}));
|
||||
final data = await baseProvider.fetchPaginated(
|
||||
baseProvider.makeUrl(_nutritionalPlansPath, query: {'limit': '1000'}),
|
||||
);
|
||||
_plans = [];
|
||||
for (final planData in data) {
|
||||
final plan = NutritionalPlan.fromJson(planData);
|
||||
@@ -253,7 +254,10 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
|
||||
/// Adds a meal item to a meal
|
||||
Future<MealItem> addMealItem(MealItem mealItem, Meal meal) async {
|
||||
final data = await baseProvider.post(mealItem.toJson(), baseProvider.makeUrl(_mealItemPath));
|
||||
final data = await baseProvider.post(
|
||||
mealItem.toJson(),
|
||||
baseProvider.makeUrl(_mealItemPath),
|
||||
);
|
||||
|
||||
mealItem = MealItem.fromJson(data);
|
||||
mealItem.ingredient = await fetchIngredient(mealItem.ingredientId);
|
||||
@@ -326,7 +330,7 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
final ingredientData = {
|
||||
'date': DateTime.now().toIso8601String(),
|
||||
'expiresIn': DateTime.now().add(const Duration(days: DAYS_TO_CACHE)).toIso8601String(),
|
||||
'ingredients': []
|
||||
'ingredients': [],
|
||||
};
|
||||
prefs.setString(PREFS_INGREDIENTS, json.encode(ingredientData));
|
||||
return;
|
||||
@@ -349,8 +353,10 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
|
||||
// Send the request
|
||||
final response = await baseProvider.fetch(
|
||||
baseProvider
|
||||
.makeUrl(_ingredientSearchPath, query: {'term': name, 'language': languages.join(',')}),
|
||||
baseProvider.makeUrl(
|
||||
_ingredientSearchPath,
|
||||
query: {'term': name, 'language': languages.join(',')},
|
||||
),
|
||||
);
|
||||
|
||||
// Process the response
|
||||
@@ -392,12 +398,19 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Log custom ingredient to nutrition diary
|
||||
Future<void> logIngredientToDiary(MealItem mealItem, int planId, [DateTime? dateTime]) async {
|
||||
Future<void> logIngredientToDiary(
|
||||
MealItem mealItem,
|
||||
int planId, [
|
||||
DateTime? dateTime,
|
||||
]) async {
|
||||
final plan = findById(planId);
|
||||
mealItem.ingredient = await fetchIngredient(mealItem.ingredientId);
|
||||
final Log log = Log.fromMealItem(mealItem, plan.id!, null, dateTime);
|
||||
|
||||
final data = await baseProvider.post(log.toJson(), baseProvider.makeUrl(_nutritionDiaryPath));
|
||||
final data = await baseProvider.post(
|
||||
log.toJson(),
|
||||
baseProvider.makeUrl(_nutritionDiaryPath),
|
||||
);
|
||||
log.id = data['id'];
|
||||
plan.diaryEntries.add(log);
|
||||
notifyListeners();
|
||||
@@ -417,7 +430,11 @@ class NutritionPlansProvider with ChangeNotifier {
|
||||
final data = await baseProvider.fetchPaginated(
|
||||
baseProvider.makeUrl(
|
||||
_nutritionDiaryPath,
|
||||
query: {'plan': plan.id.toString(), 'limit': '999', 'ordering': 'datetime'},
|
||||
query: {
|
||||
'plan': plan.id.toString(),
|
||||
'limit': '999',
|
||||
'ordering': 'datetime',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -53,8 +53,11 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
List<WeightUnit> _weightUnits = [];
|
||||
List<RepetitionUnit> _repetitionUnit = [];
|
||||
|
||||
WorkoutPlansProvider(this.baseProvider, ExercisesProvider exercises, List<WorkoutPlan> entries)
|
||||
: _exercises = exercises,
|
||||
WorkoutPlansProvider(
|
||||
this.baseProvider,
|
||||
ExercisesProvider exercises,
|
||||
List<WorkoutPlan> entries,
|
||||
) : _exercises = exercises,
|
||||
_workoutPlans = entries;
|
||||
|
||||
List<WorkoutPlan> get items {
|
||||
@@ -207,7 +210,6 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
|
||||
fetchComputedSettings(workoutSet); // request!
|
||||
|
||||
// Settings
|
||||
final List<Setting> settings = [];
|
||||
final settingData = allSettingsData['results'].where((s) => s['set'] == workoutSet.id);
|
||||
|
||||
@@ -261,8 +263,10 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<WorkoutPlan> addWorkout(WorkoutPlan workout) async {
|
||||
final data =
|
||||
await baseProvider.post(workout.toJson(), baseProvider.makeUrl(_workoutPlansUrlPath));
|
||||
final data = await baseProvider.post(
|
||||
workout.toJson(),
|
||||
baseProvider.makeUrl(_workoutPlansUrlPath),
|
||||
);
|
||||
final plan = WorkoutPlan.fromJson(data);
|
||||
_workoutPlans.insert(0, plan);
|
||||
notifyListeners();
|
||||
@@ -271,7 +275,9 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
|
||||
Future<void> editWorkout(WorkoutPlan workout) async {
|
||||
await baseProvider.patch(
|
||||
workout.toJson(), baseProvider.makeUrl(_workoutPlansUrlPath, id: workout.id));
|
||||
workout.toJson(),
|
||||
baseProvider.makeUrl(_workoutPlansUrlPath, id: workout.id),
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -290,7 +296,10 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> fetchLogData(WorkoutPlan workout, Exercise base) async {
|
||||
Future<Map<String, dynamic>> fetchLogData(
|
||||
WorkoutPlan workout,
|
||||
Exercise base,
|
||||
) async {
|
||||
final data = await baseProvider.fetch(
|
||||
baseProvider.makeUrl(
|
||||
_workoutPlansUrlPath,
|
||||
@@ -331,7 +340,9 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
unitData['weightUnit'].forEach(
|
||||
(e) => _weightUnits.add(WeightUnit.fromJson(e)),
|
||||
);
|
||||
dev.log("Read workout units data from cache. Valid till ${unitData['expiresIn']}");
|
||||
dev.log(
|
||||
"Read workout units data from cache. Valid till ${unitData['expiresIn']}",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -359,7 +370,10 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
* Saves a new day instance to the DB and adds it to the given workout
|
||||
*/
|
||||
day.workoutId = workout.id!;
|
||||
final data = await baseProvider.post(day.toJson(), baseProvider.makeUrl(_daysUrlPath));
|
||||
final data = await baseProvider.post(
|
||||
day.toJson(),
|
||||
baseProvider.makeUrl(_daysUrlPath),
|
||||
);
|
||||
day = Day.fromJson(data);
|
||||
day.sets = [];
|
||||
workout.days.insert(0, day);
|
||||
@@ -368,7 +382,10 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> editDay(Day day) async {
|
||||
await baseProvider.patch(day.toJson(), baseProvider.makeUrl(_daysUrlPath, id: day.id));
|
||||
await baseProvider.patch(
|
||||
day.toJson(),
|
||||
baseProvider.makeUrl(_daysUrlPath, id: day.id),
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -487,7 +504,10 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<WorkoutSession> addSession(WorkoutSession session) async {
|
||||
final data = await baseProvider.post(session.toJson(), baseProvider.makeUrl(_sessionUrlPath));
|
||||
final data = await baseProvider.post(
|
||||
session.toJson(),
|
||||
baseProvider.makeUrl(_sessionUrlPath),
|
||||
);
|
||||
final newSession = WorkoutSession.fromJson(data);
|
||||
notifyListeners();
|
||||
return newSession;
|
||||
@@ -497,7 +517,10 @@ class WorkoutPlansProvider with ChangeNotifier {
|
||||
* Logs
|
||||
*/
|
||||
Future<Log> addLog(Log log) async {
|
||||
final data = await baseProvider.post(log.toJson(), baseProvider.makeUrl(_logsUrlPath));
|
||||
final data = await baseProvider.post(
|
||||
log.toJson(),
|
||||
baseProvider.makeUrl(_logsUrlPath),
|
||||
);
|
||||
final newLog = Log.fromJson(data);
|
||||
|
||||
log.id = newLog.id;
|
||||
|
||||
@@ -48,7 +48,7 @@ class _AddExerciseStepperState extends State<AddExerciseStepper> {
|
||||
GlobalKey<FormState>(),
|
||||
GlobalKey<FormState>(),
|
||||
GlobalKey<FormState>(),
|
||||
GlobalKey<FormState>()
|
||||
GlobalKey<FormState>(),
|
||||
];
|
||||
|
||||
Widget _controlsBuilder(BuildContext context, ControlsDetails details) {
|
||||
@@ -75,8 +75,11 @@ class _AddExerciseStepperState extends State<AddExerciseStepper> {
|
||||
|
||||
final baseId = await addExerciseProvider.addExercise();
|
||||
final base = await exerciseProvider.fetchAndSetExercise(baseId);
|
||||
final name =
|
||||
base.getExercise(Localizations.localeOf(context).languageCode).name;
|
||||
final name = base
|
||||
.getExercise(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
)
|
||||
.name;
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
@@ -88,15 +91,19 @@ class _AddExerciseStepperState extends State<AddExerciseStepper> {
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(AppLocalizations.of(context).success),
|
||||
content: Text(AppLocalizations.of(context).cacheWarning),
|
||||
content: Text(
|
||||
AppLocalizations.of(context).cacheWarning,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(name),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.pushReplacementNamed(
|
||||
context, ExerciseDetailScreen.routeName,
|
||||
arguments: base);
|
||||
context,
|
||||
ExerciseDetailScreen.routeName,
|
||||
arguments: base,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -153,8 +160,13 @@ class _AddExerciseStepperState extends State<AddExerciseStepper> {
|
||||
],
|
||||
currentStep: _currentStep,
|
||||
onStepContinue: () {
|
||||
if (_keys[_currentStep].currentState?.validate() ?? false) {
|
||||
_keys[_currentStep].currentState?.save();
|
||||
if (_keys[_currentStep]
|
||||
.currentState
|
||||
?.validate() ??
|
||||
false) {
|
||||
_keys[_currentStep]
|
||||
.currentState
|
||||
?.save();
|
||||
|
||||
if (_currentStep != lastStepIndex) {
|
||||
setState(() {
|
||||
@@ -203,7 +215,7 @@ class EmailNotVerified extends StatelessWidget {
|
||||
leading: const Icon(Icons.warning),
|
||||
title: Text(AppLocalizations.of(context).unVerifiedEmail),
|
||||
subtitle: Text(AppLocalizations.of(context)
|
||||
.contributeExerciseWarning(MIN_ACCOUNT_AGE.toString())),
|
||||
.contributeExerciseWarning(MIN_ACCOUNT_AGE.toString())),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
|
||||
@@ -69,7 +69,10 @@ class AuthScreen extends StatelessWidget {
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 20.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 94.0),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 94.0,
|
||||
),
|
||||
child: const Text(
|
||||
'wger',
|
||||
style: TextStyle(
|
||||
@@ -125,8 +128,9 @@ class _AuthCardState extends State<AuthCard> {
|
||||
final _passwordController = TextEditingController();
|
||||
final _password2Controller = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _serverUrlController =
|
||||
TextEditingController(text: kDebugMode ? DEFAULT_SERVER_TEST : DEFAULT_SERVER_PROD);
|
||||
final _serverUrlController = TextEditingController(
|
||||
text: kDebugMode ? DEFAULT_SERVER_TEST : DEFAULT_SERVER_PROD,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -253,7 +257,10 @@ class _AuthCardState extends State<AuthCard> {
|
||||
elevation: 8.0,
|
||||
child: Container(
|
||||
width: deviceSize.width * 0.9,
|
||||
padding: EdgeInsets.symmetric(horizontal: 15.0, vertical: 0.025 * deviceSize.height),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15.0,
|
||||
vertical: 0.025 * deviceSize.height,
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: SingleChildScrollView(
|
||||
@@ -263,9 +270,10 @@ class _AuthCardState extends State<AuthCard> {
|
||||
TextFormField(
|
||||
key: const Key('inputUsername'),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).username,
|
||||
errorMaxLines: 2,
|
||||
prefixIcon: const Icon(Icons.account_circle)),
|
||||
labelText: AppLocalizations.of(context).username,
|
||||
errorMaxLines: 2,
|
||||
prefixIcon: const Icon(Icons.account_circle),
|
||||
),
|
||||
autofillHints: const [AutofillHints.username],
|
||||
controller: _usernameController,
|
||||
textInputAction: TextInputAction.next,
|
||||
@@ -280,7 +288,9 @@ class _AuthCardState extends State<AuthCard> {
|
||||
|
||||
return null;
|
||||
},
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s\b|\b\s'))],
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp(r'\s\b|\b\s')),
|
||||
],
|
||||
onSaved: (value) {
|
||||
_authData['username'] = value!;
|
||||
},
|
||||
@@ -376,9 +386,10 @@ class _AuthCardState extends State<AuthCard> {
|
||||
child: TextFormField(
|
||||
key: const Key('inputServer'),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).customServerUrl,
|
||||
helperText: AppLocalizations.of(context).customServerHint,
|
||||
helperMaxLines: 4),
|
||||
labelText: AppLocalizations.of(context).customServerUrl,
|
||||
helperText: AppLocalizations.of(context).customServerHint,
|
||||
helperMaxLines: 4,
|
||||
),
|
||||
controller: _serverUrlController,
|
||||
validator: (value) {
|
||||
if (Uri.tryParse(value!) == null) {
|
||||
@@ -412,7 +423,7 @@ class _AuthCardState extends State<AuthCard> {
|
||||
kDebugMode ? DEFAULT_SERVER_TEST : DEFAULT_SERVER_PROD;
|
||||
},
|
||||
),
|
||||
Text(AppLocalizations.of(context).reset)
|
||||
Text(AppLocalizations.of(context).reset),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -473,12 +484,15 @@ class _AuthCardState extends State<AuthCard> {
|
||||
text.substring(0, text.lastIndexOf('?') + 1),
|
||||
),
|
||||
Text(
|
||||
text.substring(text.lastIndexOf('?') + 1, text.length),
|
||||
text.substring(
|
||||
text.lastIndexOf('?') + 1,
|
||||
text.length,
|
||||
),
|
||||
style: const TextStyle(
|
||||
//color: wgerPrimaryColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -45,9 +45,10 @@ class _LogMealScreenState extends State<LogMealScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
final args = ModalRoute.of(context)!.settings.arguments as LogMealArguments;
|
||||
final meal = args.meal.copyWith(
|
||||
mealItems: args.meal.mealItems
|
||||
.map((mealItem) => mealItem.copyWith(amount: mealItem.amount * portionPct / 100))
|
||||
.toList());
|
||||
mealItems: args.meal.mealItems
|
||||
.map((mealItem) => mealItem.copyWith(amount: mealItem.amount * portionPct / 100))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -62,7 +63,10 @@ class _LogMealScreenState extends State<LogMealScreen> {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(meal.name, style: Theme.of(context).textTheme.headlineSmall),
|
||||
Text(
|
||||
meal.name,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
if (meal.mealItems.isEmpty)
|
||||
const ListTile(title: Text('No ingredients defined yet'))
|
||||
else
|
||||
@@ -70,7 +74,7 @@ class _LogMealScreenState extends State<LogMealScreen> {
|
||||
children: [
|
||||
const NutritionDiaryheader(),
|
||||
...meal.mealItems
|
||||
.map((item) => MealItemWidget(item, viewMode.withAllDetails, false)),
|
||||
.map((item) => MealItemWidget(item, viewMode.withAllDetails, false)),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
'Portion: ${portionPct.round()} %',
|
||||
@@ -92,8 +96,10 @@ class _LogMealScreenState extends State<LogMealScreen> {
|
||||
TextButton(
|
||||
child: const Text('Log'),
|
||||
onPressed: () async {
|
||||
await Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.logMealToDiary(meal);
|
||||
await Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).logMealToDiary(meal);
|
||||
// ignore: use_build_context_synchronously
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -111,7 +117,9 @@ class _LogMealScreenState extends State<LogMealScreen> {
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).cancelButtonLabel,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -33,20 +33,22 @@ class _LogMealsScreenState extends State<LogMealsScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final nutritionalPlan = ModalRoute.of(context)!.settings.arguments as NutritionalPlan;
|
||||
final nutritionalPlan =
|
||||
ModalRoute.of(context)!.settings.arguments as NutritionalPlan;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context).selectMealToLog),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: nutritionalPlan.meals.length,
|
||||
itemBuilder: (context, index) => MealWidget(
|
||||
nutritionalPlan.meals[index],
|
||||
nutritionalPlan.dedupMealItems,
|
||||
true,
|
||||
true,
|
||||
)),
|
||||
itemCount: nutritionalPlan.meals.length,
|
||||
itemBuilder: (context, index) => MealWidget(
|
||||
nutritionalPlan.meals[index],
|
||||
nutritionalPlan.dedupMealItems,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,44 +58,49 @@ class MeasurementEntriesScreen extends StatelessWidget {
|
||||
|
||||
case MeasurementOptions.delete:
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext contextDialog) {
|
||||
return AlertDialog(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).confirmDelete(category.name),
|
||||
context: context,
|
||||
builder: (BuildContext contextDialog) {
|
||||
return AlertDialog(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).confirmDelete(category.name),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
onPressed: () => Navigator.of(contextDialog).pop(),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
onPressed: () => Navigator.of(contextDialog).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
onPressed: () {
|
||||
// Confirmed, delete the workout
|
||||
Provider.of<MeasurementProvider>(context, listen: false)
|
||||
.deleteCategory(category.id!);
|
||||
|
||||
// Close the popup
|
||||
Navigator.of(contextDialog).pop();
|
||||
|
||||
// and inform the user
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
onPressed: () {
|
||||
// Confirmed, delete the workout
|
||||
Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).deleteCategory(category.id!);
|
||||
|
||||
// Close the popup
|
||||
Navigator.of(contextDialog).pop();
|
||||
|
||||
// and inform the user
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -75,7 +75,10 @@ class NutritionalPlanScreen extends StatelessWidget {
|
||||
heroTag: null,
|
||||
tooltip: AppLocalizations.of(context).logMeal,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(LogMealsScreen.routeName, arguments: nutritionalPlan);
|
||||
Navigator.of(context).pushNamed(
|
||||
LogMealsScreen.routeName,
|
||||
arguments: nutritionalPlan,
|
||||
);
|
||||
},
|
||||
child: const SvgIcon(
|
||||
icon: SvgIconData('assets/icons/meal-diary.svg'),
|
||||
@@ -122,7 +125,7 @@ class NutritionalPlanScreen extends StatelessWidget {
|
||||
);
|
||||
} else if (value == NutritionalPlanOptions.delete) {
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.deletePlan(nutritionalPlan.id!);
|
||||
.deletePlan(nutritionalPlan.id!);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
@@ -131,15 +134,17 @@ class NutritionalPlanScreen extends StatelessWidget {
|
||||
PopupMenuItem<NutritionalPlanOptions>(
|
||||
value: NutritionalPlanOptions.edit,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.edit),
|
||||
title: Text(AppLocalizations.of(context).edit)),
|
||||
leading: const Icon(Icons.edit),
|
||||
title: Text(AppLocalizations.of(context).edit),
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem<NutritionalPlanOptions>(
|
||||
value: NutritionalPlanOptions.delete,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.delete),
|
||||
title: Text(AppLocalizations.of(context).delete)),
|
||||
leading: const Icon(Icons.delete),
|
||||
title: Text(AppLocalizations.of(context).delete),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
@@ -149,7 +154,10 @@ class NutritionalPlanScreen extends StatelessWidget {
|
||||
titlePadding: const EdgeInsets.fromLTRB(56, 0, 56, 16),
|
||||
title: Text(
|
||||
nutritionalPlan.getLabel(context),
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(color: appBarForeground),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(color: appBarForeground),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -165,13 +173,14 @@ class NutritionalPlanScreen extends StatelessWidget {
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Consumer<NutritionPlansProvider>(
|
||||
builder: (context, value, child) =>
|
||||
NutritionalPlanDetailWidget(nutritionalPlan)),
|
||||
NutritionalPlanDetailWidget(nutritionalPlan),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -71,7 +71,8 @@ class _WorkoutPlanScreenState extends State<WorkoutPlanScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const appBarForeground = Colors.white;
|
||||
final workoutPlan = ModalRoute.of(context)!.settings.arguments as WorkoutPlan;
|
||||
final workoutPlan =
|
||||
ModalRoute.of(context)!.settings.arguments as WorkoutPlan;
|
||||
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
@@ -84,7 +85,10 @@ class _WorkoutPlanScreenState extends State<WorkoutPlanScreen> {
|
||||
titlePadding: const EdgeInsets.fromLTRB(56, 0, 56, 16),
|
||||
title: Text(
|
||||
workoutPlan.name,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(color: appBarForeground),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(color: appBarForeground),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@@ -129,7 +133,8 @@ class _WorkoutPlanScreenState extends State<WorkoutPlanScreen> {
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _loadFullWorkout(context, workoutPlan.id!),
|
||||
builder: (context, AsyncSnapshot<WorkoutPlan> snapshot) => SliverList(
|
||||
builder: (context, AsyncSnapshot<WorkoutPlan> snapshot) =>
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
if (snapshot.connectionState == ConnectionState.waiting)
|
||||
@@ -146,7 +151,7 @@ class _WorkoutPlanScreenState extends State<WorkoutPlanScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -9,20 +9,23 @@ class AddExerciseMultiselectButton<T> extends StatefulWidget {
|
||||
final FormFieldSetter<List<T?>?>? onSaved;
|
||||
final Function displayName;
|
||||
|
||||
const AddExerciseMultiselectButton(
|
||||
{super.key,
|
||||
required this.items,
|
||||
required this.title,
|
||||
required this.onChange,
|
||||
this.initialItems = const [],
|
||||
this.onSaved,
|
||||
required this.displayName});
|
||||
const AddExerciseMultiselectButton({
|
||||
super.key,
|
||||
required this.items,
|
||||
required this.title,
|
||||
required this.onChange,
|
||||
this.initialItems = const [],
|
||||
this.onSaved,
|
||||
required this.displayName,
|
||||
});
|
||||
|
||||
@override
|
||||
_AddExerciseMultiselectButtonState createState() => _AddExerciseMultiselectButtonState<T>();
|
||||
_AddExerciseMultiselectButtonState createState() =>
|
||||
_AddExerciseMultiselectButtonState<T>();
|
||||
}
|
||||
|
||||
class _AddExerciseMultiselectButtonState<T> extends State<AddExerciseMultiselectButton> {
|
||||
class _AddExerciseMultiselectButtonState<T>
|
||||
extends State<AddExerciseMultiselectButton> {
|
||||
List<T> _selectedItems = [];
|
||||
|
||||
@override
|
||||
@@ -32,8 +35,9 @@ class _AddExerciseMultiselectButtonState<T> extends State<AddExerciseMultiselect
|
||||
child: MultiSelectDialogField(
|
||||
initialValue: widget.initialItems,
|
||||
onSaved: widget.onSaved,
|
||||
items:
|
||||
widget.items.map((item) => MultiSelectItem<T>(item, widget.displayName(item))).toList(),
|
||||
items: widget.items
|
||||
.map((item) => MultiSelectItem<T>(item, widget.displayName(item)))
|
||||
.toList(),
|
||||
onConfirm: (value) {
|
||||
setState(() {
|
||||
_selectedItems = value.cast<T>();
|
||||
|
||||
@@ -6,7 +6,8 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wger/providers/add_exercise.dart';
|
||||
import 'mixins/image_picker_mixin.dart';
|
||||
|
||||
class PreviewExerciseImages extends StatelessWidget with ExerciseImagePickerMixin {
|
||||
class PreviewExerciseImages extends StatelessWidget
|
||||
with ExerciseImagePickerMixin {
|
||||
PreviewExerciseImages({
|
||||
super.key,
|
||||
required this.selectedImages,
|
||||
@@ -34,12 +35,14 @@ class PreviewExerciseImages extends StatelessWidget with ExerciseImagePickerMixi
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: IconButton(
|
||||
iconSize: 20,
|
||||
onPressed: () =>
|
||||
context.read<AddExerciseProvider>().removeExercise(file.path),
|
||||
onPressed: () => context
|
||||
.read<AddExerciseProvider>()
|
||||
.removeExercise(file.path),
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
@@ -67,7 +70,7 @@ class PreviewExerciseImages extends StatelessWidget with ExerciseImagePickerMixi
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ class Step2Variations extends StatelessWidget {
|
||||
...exerciseProvider.exerciseBasesByVariation[key]!.map(
|
||||
(base) => Text(
|
||||
base
|
||||
.getExercise(Localizations.localeOf(context).languageCode)
|
||||
.getExercise(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -51,41 +53,46 @@ class Step2Variations extends StatelessWidget {
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => Switch(
|
||||
value: provider.variationId == key,
|
||||
onChanged: (state) => provider.variationId = key),
|
||||
value: provider.variationId == key,
|
||||
onChanged: (state) => provider.variationId = key,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Exercise bases without variations
|
||||
...exerciseProvider.exercises.where((b) => b.variationId == null).map(
|
||||
(base) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
base
|
||||
.getExercise(Localizations.localeOf(context).languageCode)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
...exerciseProvider.exercises
|
||||
.where((b) => b.variationId == null)
|
||||
.map(
|
||||
(base) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
base
|
||||
.getExercise(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => Switch(
|
||||
value: provider.newVariationForExercise == base.id,
|
||||
onChanged: (state) => provider.newVariationForExercise = base.id,
|
||||
),
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => Switch(
|
||||
value: provider.newVariationForExercise == base.id,
|
||||
onChanged: (state) => provider.newVariationForExercise = base.id,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,7 +13,8 @@ class Step5Images extends StatefulWidget {
|
||||
State<Step5Images> createState() => _Step5ImagesState();
|
||||
}
|
||||
|
||||
class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin {
|
||||
class _Step5ImagesState extends State<Step5Images>
|
||||
with ExerciseImagePickerMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
@@ -33,7 +34,8 @@ class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => pickImages(context, pickFromCamera: true),
|
||||
onPressed: () =>
|
||||
pickImages(context, pickFromCamera: true),
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
),
|
||||
IconButton(
|
||||
@@ -46,7 +48,7 @@ class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin
|
||||
Text(
|
||||
'Only JPEG, PNG and WEBP files below 20 MB are supported',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -44,64 +44,81 @@ class MainAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(AppLocalizations.of(context).optionsLabel),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(AppLocalizations.of(context).optionsLabel),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).closeButtonLabel,
|
||||
),
|
||||
],
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
//dense: true,
|
||||
leading: const Icon(Icons.person),
|
||||
title: Text(AppLocalizations.of(context).userProfile),
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).userProfile,
|
||||
UserProfileForm(context.read<UserProvider>().profile!),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
//dense: true,
|
||||
leading: const Icon(Icons.person),
|
||||
title: Text(AppLocalizations.of(context).userProfile),
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).userProfile,
|
||||
UserProfileForm(
|
||||
context.read<UserProvider>().profile!,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
onTap: () => Navigator.of(context).pushNamed(SettingsPage.routeName),
|
||||
title: Text(AppLocalizations.of(context).settingsTitle),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
onTap: () => Navigator.of(context).pushNamed(AboutPage.routeName),
|
||||
title: Text(AppLocalizations.of(context).aboutPageTitle),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
//dense: true,
|
||||
leading: const Icon(Icons.exit_to_app),
|
||||
title: Text(AppLocalizations.of(context).logout),
|
||||
onTap: () {
|
||||
context.read<AuthProvider>().logout();
|
||||
context.read<WorkoutPlansProvider>().clear();
|
||||
context.read<NutritionPlansProvider>().clear();
|
||||
context.read<BodyWeightProvider>().clear();
|
||||
context.read<GalleryProvider>().clear();
|
||||
context.read<UserProvider>().clear();
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
onTap: () => Navigator.of(context).pushNamed(SettingsPage.routeName),
|
||||
title: Text(AppLocalizations.of(context).settingsTitle),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
onTap: () => Navigator.of(context).pushNamed(AboutPage.routeName),
|
||||
title: Text(AppLocalizations.of(context).aboutPageTitle),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
//dense: true,
|
||||
leading: const Icon(Icons.exit_to_app),
|
||||
title: Text(AppLocalizations.of(context).logout),
|
||||
onTap: () {
|
||||
context
|
||||
.read<AuthProvider>()
|
||||
.logout();
|
||||
context
|
||||
.read<WorkoutPlansProvider>()
|
||||
.clear();
|
||||
context
|
||||
.read<NutritionPlansProvider>()
|
||||
.clear();
|
||||
context
|
||||
.read<BodyWeightProvider>()
|
||||
.clear();
|
||||
context
|
||||
.read<GalleryProvider>()
|
||||
.clear();
|
||||
context
|
||||
.read<UserProvider>()
|
||||
.clear();
|
||||
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushReplacementNamed('/');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushReplacementNamed('/');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -34,37 +34,41 @@ class SettingsPage extends StatefulWidget {
|
||||
class _SettingsPageState extends State<SettingsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final exerciseProvider = Provider.of<ExercisesProvider>(context, listen: false);
|
||||
final exerciseProvider =
|
||||
Provider.of<ExercisesProvider>(context, listen: false);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context).settingsTitle),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
// leading: const Icon(Icons.cached),
|
||||
title: Text(AppLocalizations.of(context).settingsCacheTitle),
|
||||
subtitle: Text(AppLocalizations.of(context).settingsCacheDescription),
|
||||
trailing: IconButton(
|
||||
key: const ValueKey('cacheIcon'),
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
await exerciseProvider.clearAllCachesAndPrefs();
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context).settingsTitle),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
// leading: const Icon(Icons.cached),
|
||||
title: Text(AppLocalizations.of(context).settingsCacheTitle),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context).settingsCacheDescription),
|
||||
trailing: IconButton(
|
||||
key: const ValueKey('cacheIcon'),
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
await exerciseProvider.clearAllCachesAndPrefs();
|
||||
|
||||
if (context.mounted) {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(context).settingsCacheDeletedSnackbar),
|
||||
);
|
||||
if (context.mounted) {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(context)
|
||||
.settingsCacheDeletedSnackbar),
|
||||
);
|
||||
|
||||
// Find the ScaffoldMessenger in the widget tree
|
||||
// and use it to show a SnackBar.
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
// Find the ScaffoldMessenger in the widget tree
|
||||
// and use it to show a SnackBar.
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,8 +108,10 @@ class _DashboardCalendarWidgetState extends State<DashboardCalendarWidget>
|
||||
_events[date] = [];
|
||||
}
|
||||
|
||||
_events[date]!
|
||||
.add(Event(EventType.measurement, '${category.name}: ${entry.value} ${category.unit}'));
|
||||
_events[date]!.add(Event(
|
||||
EventType.measurement,
|
||||
'${category.name}: ${entry.value} ${category.unit}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +270,7 @@ class _DashboardCalendarWidgetState extends State<DashboardCalendarWidget>
|
||||
})()),
|
||||
subtitle: Text(event.description),
|
||||
//onTap: () => print('$event tapped!'),
|
||||
))
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -87,7 +87,10 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
//textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
MutedText(getShortNutritionValues(meal.plannedNutritionalValues, context)),
|
||||
MutedText(getShortNutritionValues(
|
||||
meal.plannedNutritionalValues,
|
||||
context,
|
||||
)),
|
||||
IconButton(
|
||||
icon: const SvgIcon(
|
||||
icon: SvgIconData('assets/icons/meal-diary.svg'),
|
||||
@@ -99,7 +102,7 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
arguments: LogMealArguments(meal, false),
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -120,7 +123,9 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text('${item.amount.toStringAsFixed(0)} ${AppLocalizations.of(context).g}'),
|
||||
Text(
|
||||
'${item.amount.toStringAsFixed(0)} ${AppLocalizations.of(context).g}',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -149,7 +154,7 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
subtitle: Text(
|
||||
_hasContent
|
||||
? DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(_plan!.creationDate)
|
||||
.format(_plan!.creationDate)
|
||||
: '',
|
||||
),
|
||||
leading: Icon(
|
||||
@@ -163,7 +168,8 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
viewMode.base => const Icon(Icons.info_outline),
|
||||
viewMode.withMeals => const Icon(Icons.info),
|
||||
viewMode.withMealsDetails => const Icon(Icons.info),
|
||||
})
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
@@ -177,16 +183,17 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
),
|
||||
if (_hasContent)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
...getContent(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 15),
|
||||
child: FlNutritionalPlanGoalWidget(nutritionalPlan: _plan!),
|
||||
),
|
||||
],
|
||||
))
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
...getContent(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 15),
|
||||
child: FlNutritionalPlanGoalWidget(nutritionalPlan: _plan!),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
NothingFound(
|
||||
AppLocalizations.of(context).noNutritionalPlans,
|
||||
@@ -200,13 +207,17 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context).goToDetailPage),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pushNamed(NutritionalPlanScreen.routeName, arguments: _plan);
|
||||
Navigator.of(context).pushNamed(
|
||||
NutritionalPlanScreen.routeName,
|
||||
arguments: _plan,
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
IconButton(
|
||||
icon: const SvgIcon(icon: SvgIconData('assets/icons/ingredient-diary.svg')),
|
||||
icon: const SvgIcon(
|
||||
icon: SvgIconData('assets/icons/ingredient-diary.svg'),
|
||||
),
|
||||
tooltip: AppLocalizations.of(context).logIngredient,
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
@@ -221,7 +232,9 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const SvgIcon(icon: SvgIconData('assets/icons/meal-diary.svg')),
|
||||
icon: const SvgIcon(
|
||||
icon: SvgIconData('assets/icons/meal-diary.svg'),
|
||||
),
|
||||
tooltip: AppLocalizations.of(context).logMeal,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(LogMealsScreen.routeName, arguments: _plan);
|
||||
@@ -269,21 +282,25 @@ class _DashboardWeightWidgetState extends State<DashboardWeightWidget> {
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: MeasurementChartWidgetFl(
|
||||
weightProvider.items
|
||||
.map((e) => MeasurementChartEntry(e.weight, e.date))
|
||||
.toList(),
|
||||
unit: profile!.isMetric
|
||||
? AppLocalizations.of(context).kg
|
||||
: AppLocalizations.of(context).lb),
|
||||
weightProvider.items
|
||||
.map((e) => MeasurementChartEntry(e.weight, e.date))
|
||||
.toList(),
|
||||
unit: profile!.isMetric
|
||||
? AppLocalizations.of(context).kg
|
||||
: AppLocalizations.of(context).lb,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context).goToDetailPage),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(WeightScreen.routeName);
|
||||
}),
|
||||
child: Text(
|
||||
AppLocalizations.of(context).goToDetailPage,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(WeightScreen.routeName);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
@@ -292,7 +309,9 @@ class _DashboardWeightWidgetState extends State<DashboardWeightWidget> {
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).newEntry,
|
||||
WeightForm(weightProvider.getNewestEntry()?.copyWith(id: null)),
|
||||
WeightForm(weightProvider
|
||||
.getNewestEntry()
|
||||
?.copyWith(id: null)),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -330,13 +349,13 @@ class _DashboardMeasurementWidgetState extends State<DashboardMeasurementWidget>
|
||||
final provider = Provider.of<MeasurementProvider>(context, listen: false);
|
||||
|
||||
final items = provider.categories
|
||||
.map<Widget>(
|
||||
(item) => CategoriesCard(
|
||||
item,
|
||||
elevation: 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
.map<Widget>(
|
||||
(item) => CategoriesCard(
|
||||
item,
|
||||
elevation: 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
if (items.isNotEmpty) {
|
||||
items.add(
|
||||
NothingFound(
|
||||
@@ -393,20 +412,28 @@ class _DashboardMeasurementWidgetState extends State<DashboardMeasurementWidget>
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: items.asMap().entries.map((entry) {
|
||||
children: items
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) {
|
||||
return GestureDetector(
|
||||
onTap: () => _controller.animateToPage(entry.key),
|
||||
child: Container(
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 4.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall!
|
||||
.color!
|
||||
.withOpacity(_current == entry.key ? 0.9 : 0.4),
|
||||
.textTheme
|
||||
.headlineSmall!
|
||||
.color!
|
||||
.withOpacity(
|
||||
_current == entry.key ? 0.9 : 0.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -501,7 +528,9 @@ class _DashboardWorkoutWidgetState extends State<DashboardWorkoutWidget> {
|
||||
.getExercise(Localizations.localeOf(context).languageCode)
|
||||
.name),
|
||||
const SizedBox(width: 10),
|
||||
MutedText(set.getSmartRepr(s.exerciseObj).join('\n')),
|
||||
MutedText(
|
||||
set.getSmartRepr(s.exerciseObj).join('\n'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
@@ -532,7 +561,7 @@ class _DashboardWorkoutWidgetState extends State<DashboardWorkoutWidget> {
|
||||
subtitle: Text(
|
||||
_hasContent
|
||||
? DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(_workoutPlan!.creationDate)
|
||||
.format(_workoutPlan!.creationDate)
|
||||
: '',
|
||||
),
|
||||
leading: Icon(
|
||||
@@ -546,7 +575,8 @@ class _DashboardWorkoutWidgetState extends State<DashboardWorkoutWidget> {
|
||||
? const Icon(
|
||||
Icons.info,
|
||||
)
|
||||
: const Icon(Icons.info_outline))
|
||||
: const Icon(Icons.info_outline),
|
||||
)
|
||||
: const SizedBox(),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
@@ -576,12 +606,14 @@ class _DashboardWorkoutWidgetState extends State<DashboardWorkoutWidget> {
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context).goToDetailPage),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pushNamed(WorkoutPlanScreen.routeName, arguments: _workoutPlan);
|
||||
Navigator.of(context).pushNamed(
|
||||
WorkoutPlanScreen.routeName,
|
||||
arguments: _workoutPlan,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ class ExerciseDetail extends StatelessWidget {
|
||||
...getMuscles(context),
|
||||
|
||||
// Variants
|
||||
...getVariations(context)
|
||||
...getVariations(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -217,9 +217,10 @@ class ExerciseDetail extends StatelessWidget {
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Chip(
|
||||
label: Text(getTranslation(e.name, context)),
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor: theme.splashColor),
|
||||
label: Text(getTranslation(e.name, context)),
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor: theme.splashColor,
|
||||
),
|
||||
))
|
||||
.forEach((element) => out.add(element));
|
||||
}
|
||||
@@ -244,8 +245,9 @@ class ExerciseDetail extends StatelessWidget {
|
||||
final List<Widget> out = [];
|
||||
if (_exercise.aliases.isNotEmpty) {
|
||||
out.add(MutedText(
|
||||
AppLocalizations.of(context)
|
||||
.alsoKnownAs(_exercise.aliases.map((e) => e.alias).toList().join(', ')),
|
||||
AppLocalizations.of(context).alsoKnownAs(
|
||||
_exercise.aliases.map((e) => e.alias).toList().join(', '),
|
||||
),
|
||||
));
|
||||
out.add(const SizedBox(height: PADDING));
|
||||
}
|
||||
@@ -287,7 +289,10 @@ class MuscleRowWidget extends StatelessWidget {
|
||||
final List<Muscle> muscles;
|
||||
final List<Muscle> musclesSecondary;
|
||||
|
||||
const MuscleRowWidget({required this.muscles, required this.musclesSecondary});
|
||||
const MuscleRowWidget({
|
||||
required this.muscles,
|
||||
required this.musclesSecondary,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -342,8 +347,9 @@ class MuscleWidget extends StatelessWidget {
|
||||
children: [
|
||||
SvgPicture.asset('assets/images/muscles/$background.svg'),
|
||||
...muscles.map((m) => SvgPicture.asset('assets/images/muscles/main/muscle-${m.id}.svg')),
|
||||
...musclesSecondary
|
||||
.map((m) => SvgPicture.asset('assets/images/muscles/secondary/muscle-${m.id}.svg')),
|
||||
...musclesSecondary.map((m) => SvgPicture.asset(
|
||||
'assets/images/muscles/secondary/muscle-${m.id}.svg',
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,10 +41,11 @@ class _FilterRowState extends State<FilterRow> {
|
||||
_exerciseNameController = TextEditingController()
|
||||
..addListener(
|
||||
() {
|
||||
final provider = Provider.of<ExercisesProvider>(context, listen: false);
|
||||
final provider =
|
||||
Provider.of<ExercisesProvider>(context, listen: false);
|
||||
if (provider.filters!.searchTerm != _exerciseNameController.text) {
|
||||
provider
|
||||
.setFilters(provider.filters!.copyWith(searchTerm: _exerciseNameController.text));
|
||||
provider.setFilters(provider.filters!
|
||||
.copyWith(searchTerm: _exerciseNameController.text));
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -90,8 +91,9 @@ class _FilterRowState extends State<FilterRow> {
|
||||
return [
|
||||
PopupMenuItem<ExerciseMoreOption>(
|
||||
value: ExerciseMoreOption.ADD_EXERCISE,
|
||||
child: Text(AppLocalizations.of(context).contributeExercise),
|
||||
)
|
||||
child:
|
||||
Text(AppLocalizations.of(context).contributeExercise),
|
||||
),
|
||||
];
|
||||
},
|
||||
shape: const RoundedRectangleBorder(
|
||||
@@ -100,14 +102,15 @@ class _FilterRowState extends State<FilterRow> {
|
||||
onSelected: (ExerciseMoreOption selectedOption) {
|
||||
switch (selectedOption) {
|
||||
case ExerciseMoreOption.ADD_EXERCISE:
|
||||
Navigator.of(context).pushNamed(AddExerciseScreen.routeName);
|
||||
Navigator.of(context)
|
||||
.pushNamed(AddExerciseScreen.routeName);
|
||||
break;
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.more_vert),
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -37,6 +37,7 @@ class ExerciseImageWidget extends StatelessWidget {
|
||||
: const Image(
|
||||
image: AssetImage('assets/images/placeholder.png'),
|
||||
color: Color.fromRGBO(255, 255, 255, 0.3),
|
||||
colorBlendMode: BlendMode.modulate);
|
||||
colorBlendMode: BlendMode.modulate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ class _ExerciseVideoWidgetState extends State<ExerciseVideoWidget> {
|
||||
VideoPlayer(_controller),
|
||||
_ControlsOverlay(controller: _controller),
|
||||
VideoProgressIndicator(_controller, allowScrubbing: true),
|
||||
]))
|
||||
]),
|
||||
)
|
||||
: Container();
|
||||
}
|
||||
}
|
||||
@@ -118,7 +119,7 @@ class _ControlsOverlay extends StatelessWidget {
|
||||
PopupMenuItem(
|
||||
value: speed,
|
||||
child: Text('${speed}x'),
|
||||
)
|
||||
),
|
||||
];
|
||||
},
|
||||
child: Padding(
|
||||
|
||||
@@ -116,12 +116,15 @@ class _ImageFormState extends State<ImageForm> {
|
||||
title: Text(AppLocalizations.of(context).takePicture),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_showPicker(ImageSource.gallery);
|
||||
},
|
||||
leading: const Icon(Icons.photo_library),
|
||||
title: Text(AppLocalizations.of(context).chooseFromLibrary))
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_showPicker(ImageSource.gallery);
|
||||
},
|
||||
leading: const Icon(Icons.photo_library),
|
||||
title: Text(
|
||||
AppLocalizations.of(context).chooseFromLibrary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -166,7 +169,9 @@ class _ImageFormState extends State<ImageForm> {
|
||||
),
|
||||
TextFormField(
|
||||
key: const Key('field-description'),
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).description),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).description,
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: 10,
|
||||
controller: descriptionController,
|
||||
@@ -187,11 +192,11 @@ class _ImageFormState extends State<ImageForm> {
|
||||
|
||||
if (widget._image.id == null) {
|
||||
Provider.of<GalleryProvider>(context, listen: false)
|
||||
.addImage(widget._image, _file!);
|
||||
.addImage(widget._image, _file!);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
Provider.of<GalleryProvider>(context, listen: false)
|
||||
.editImage(widget._image, _file);
|
||||
.editImage(widget._image, _file);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -59,7 +59,7 @@ class Gallery extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(currentImage.date),
|
||||
.format(currentImage.date),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
Expanded(
|
||||
@@ -73,12 +73,15 @@ class Gallery extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
Provider.of<GalleryProvider>(context, listen: false)
|
||||
.deleteImage(currentImage);
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
Provider.of<GalleryProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).deleteImage(currentImage);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
if (!isDesktop)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
@@ -95,7 +98,7 @@ class Gallery extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -31,7 +31,9 @@ class CategoriesCard extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(10),
|
||||
height: 220,
|
||||
child: MeasurementChartWidgetFl(
|
||||
currentCategory.entries.map((e) => MeasurementChartEntry(e.value, e.date)).toList(),
|
||||
currentCategory.entries
|
||||
.map((e) => MeasurementChartEntry(e.value, e.date))
|
||||
.toList(),
|
||||
unit: currentCategory.unit,
|
||||
),
|
||||
),
|
||||
@@ -40,14 +42,15 @@ class CategoriesCard extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context).goToDetailPage),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
MeasurementEntriesScreen.routeName,
|
||||
arguments: currentCategory.id,
|
||||
);
|
||||
}),
|
||||
child: Text(AppLocalizations.of(context).goToDetailPage),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
MeasurementEntriesScreen.routeName,
|
||||
arguments: currentCategory.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await Navigator.pushNamed(
|
||||
|
||||
@@ -51,14 +51,16 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
|
||||
}
|
||||
|
||||
LineTouchData tooltipData() {
|
||||
return LineTouchData(touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) {
|
||||
return touchedSpots.map((touchedSpot) {
|
||||
return LineTooltipItem(
|
||||
'${touchedSpot.y} kg',
|
||||
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
||||
);
|
||||
}).toList();
|
||||
}));
|
||||
return LineTouchData(
|
||||
touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) {
|
||||
return touchedSpots.map((touchedSpot) {
|
||||
return LineTooltipItem(
|
||||
'${touchedSpot.y} kg',
|
||||
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
||||
);
|
||||
}).toList();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
LineChartData mainData() {
|
||||
@@ -105,7 +107,10 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
|
||||
);
|
||||
},
|
||||
interval: widget._entries.isNotEmpty
|
||||
? chartGetInterval(widget._entries.last.date, widget._entries.first.date)
|
||||
? chartGetInterval(
|
||||
widget._entries.last.date,
|
||||
widget._entries.first.date,
|
||||
)
|
||||
: 1000,
|
||||
),
|
||||
),
|
||||
@@ -128,8 +133,10 @@ class _MeasurementChartWidgetFlState extends State<MeasurementChartWidgetFl> {
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: [
|
||||
...widget._entries
|
||||
.map((e) => FlSpot(e.date.millisecondsSinceEpoch.toDouble(), e.value.toDouble()))
|
||||
...widget._entries.map((e) => FlSpot(
|
||||
e.date.millisecondsSinceEpoch.toDouble(),
|
||||
e.value.toDouble(),
|
||||
)),
|
||||
],
|
||||
isCurved: false,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
|
||||
@@ -39,7 +39,9 @@ class EntriesList extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(10),
|
||||
height: 220,
|
||||
child: MeasurementChartWidgetFl(
|
||||
_category.entries.map((e) => MeasurementChartEntry(e.value, e.date)).toList(),
|
||||
_category.entries
|
||||
.map((e) => MeasurementChartEntry(e.value, e.date))
|
||||
.toList(),
|
||||
unit: _category.unit,
|
||||
),
|
||||
),
|
||||
@@ -56,39 +58,47 @@ class EntriesList extends StatelessWidget {
|
||||
title: Text('${currentEntry.value} ${_category.unit}'),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(currentEntry.date),
|
||||
.format(currentEntry.date),
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
child: Text(AppLocalizations.of(context).edit),
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).edit,
|
||||
MeasurementEntryForm(currentEntry.category, currentEntry),
|
||||
),
|
||||
)),
|
||||
child: Text(AppLocalizations.of(context).edit),
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).edit,
|
||||
MeasurementEntryForm(
|
||||
currentEntry.category,
|
||||
currentEntry,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Text(AppLocalizations.of(context).delete),
|
||||
onTap: () async {
|
||||
// Delete entry from DB
|
||||
await provider.deleteEntry(currentEntry.id!, currentEntry.category);
|
||||
child: Text(AppLocalizations.of(context).delete),
|
||||
onTap: () async {
|
||||
// Delete entry from DB
|
||||
await provider.deleteEntry(
|
||||
currentEntry.id!,
|
||||
currentEntry.category,
|
||||
);
|
||||
|
||||
// and inform the user
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
// and inform the user
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
})
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
|
||||
@@ -31,7 +31,11 @@ class MeasurementCategoryForm extends StatelessWidget {
|
||||
final nameController = TextEditingController();
|
||||
final unitController = TextEditingController();
|
||||
|
||||
final Map<String, dynamic> categoryData = {'id': null, 'name': '', 'unit': ''};
|
||||
final Map<String, dynamic> categoryData = {
|
||||
'id': null,
|
||||
'name': '',
|
||||
'unit': '',
|
||||
};
|
||||
|
||||
MeasurementCategoryForm([MeasurementCategory? category]) {
|
||||
//this._category = category ?? MeasurementCategory();
|
||||
@@ -99,15 +103,24 @@ class MeasurementCategoryForm extends StatelessWidget {
|
||||
// Save the entry on the server
|
||||
try {
|
||||
categoryData['id'] == null
|
||||
? await Provider.of<MeasurementProvider>(context, listen: false).addCategory(
|
||||
? await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addCategory(
|
||||
MeasurementCategory(
|
||||
id: categoryData['id'],
|
||||
name: categoryData['name'],
|
||||
unit: categoryData['unit'],
|
||||
),
|
||||
)
|
||||
: await Provider.of<MeasurementProvider>(context, listen: false).editCategory(
|
||||
categoryData['id'], categoryData['name'], categoryData['unit']);
|
||||
: await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editCategory(
|
||||
categoryData['id'],
|
||||
categoryData['name'],
|
||||
categoryData['unit'],
|
||||
);
|
||||
} on WgerHttpException catch (error) {
|
||||
if (context.mounted) {
|
||||
showHttpExceptionErrorDialog(error, context);
|
||||
@@ -261,15 +274,20 @@ class MeasurementEntryForm extends StatelessWidget {
|
||||
// Save the entry on the server
|
||||
try {
|
||||
_entryData['id'] == null
|
||||
? await Provider.of<MeasurementProvider>(context, listen: false)
|
||||
.addEntry(MeasurementEntry(
|
||||
? await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addEntry(MeasurementEntry(
|
||||
id: _entryData['id'],
|
||||
category: _entryData['category'],
|
||||
date: _entryData['date'],
|
||||
value: _entryData['value'],
|
||||
notes: _entryData['notes'],
|
||||
))
|
||||
: await Provider.of<MeasurementProvider>(context, listen: false).editEntry(
|
||||
: await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editEntry(
|
||||
_entryData['id'],
|
||||
_entryData['category'],
|
||||
_entryData['value'],
|
||||
|
||||
@@ -52,12 +52,20 @@ class FlNutritionalPlanGoalWidgetState extends State<FlNutritionalPlanGoalWidget
|
||||
// why don't we just handle this inside this function? because it might be
|
||||
// *another* gauge that's in surplus and we want to have consistent widths
|
||||
// between all gauges
|
||||
Widget _diyGauge(BuildContext context, double normWidth, double? plan, double val) {
|
||||
Widget _diyGauge(
|
||||
BuildContext context,
|
||||
double normWidth,
|
||||
double? plan,
|
||||
double val,
|
||||
) {
|
||||
Container segment(double width, Color color) {
|
||||
return Container(
|
||||
height: 16,
|
||||
width: width,
|
||||
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(15.0)),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,31 +120,56 @@ class FlNutritionalPlanGoalWidgetState extends State<FlNutritionalPlanGoalWidget
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(fmtMacro(AppLocalizations.of(context).energy, today.energy, goals.energy,
|
||||
AppLocalizations.of(context).kcal)),
|
||||
Text(fmtMacro(
|
||||
AppLocalizations.of(context).energy,
|
||||
today.energy,
|
||||
goals.energy,
|
||||
AppLocalizations.of(context).kcal,
|
||||
)),
|
||||
const SizedBox(height: 2),
|
||||
_diyGauge(context, normWidth, goals.energy, today.energy),
|
||||
const SizedBox(height: 8),
|
||||
Text(fmtMacro(AppLocalizations.of(context).protein, today.protein, goals.protein,
|
||||
AppLocalizations.of(context).g)),
|
||||
Text(fmtMacro(
|
||||
AppLocalizations.of(context).protein,
|
||||
today.protein,
|
||||
goals.protein,
|
||||
AppLocalizations.of(context).g,
|
||||
)),
|
||||
const SizedBox(height: 2),
|
||||
_diyGauge(context, normWidth, goals.protein, today.protein),
|
||||
const SizedBox(height: 8),
|
||||
Text(fmtMacro(AppLocalizations.of(context).carbohydrates, today.carbohydrates,
|
||||
goals.carbohydrates, AppLocalizations.of(context).g)),
|
||||
Text(fmtMacro(
|
||||
AppLocalizations.of(context).carbohydrates,
|
||||
today.carbohydrates,
|
||||
goals.carbohydrates,
|
||||
AppLocalizations.of(context).g,
|
||||
)),
|
||||
const SizedBox(height: 2),
|
||||
_diyGauge(context, normWidth, goals.carbohydrates, today.carbohydrates),
|
||||
_diyGauge(
|
||||
context,
|
||||
normWidth,
|
||||
goals.carbohydrates,
|
||||
today.carbohydrates,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(fmtMacro(AppLocalizations.of(context).fat, today.fat, goals.fat,
|
||||
AppLocalizations.of(context).g)),
|
||||
Text(fmtMacro(
|
||||
AppLocalizations.of(context).fat,
|
||||
today.fat,
|
||||
goals.fat,
|
||||
AppLocalizations.of(context).g,
|
||||
)),
|
||||
const SizedBox(height: 2),
|
||||
_diyGauge(context, normWidth, goals.fat, today.fat),
|
||||
// optionally display the advanced macro goals:
|
||||
if (goals.fiber != null)
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
const SizedBox(height: 8),
|
||||
Text(fmtMacro(AppLocalizations.of(context).fiber, today.fiber, goals.fiber,
|
||||
AppLocalizations.of(context).g)),
|
||||
Text(fmtMacro(
|
||||
AppLocalizations.of(context).fiber,
|
||||
today.fiber,
|
||||
goals.fiber,
|
||||
AppLocalizations.of(context).g,
|
||||
)),
|
||||
const SizedBox(height: 2),
|
||||
_diyGauge(context, normWidth, goals.fiber, today.fiber),
|
||||
]),
|
||||
@@ -206,17 +239,17 @@ class FlNutritionalPlanPieChartState extends State<FlNutritionalPlanPieChartWidg
|
||||
children: [
|
||||
(AppLocalizations.of(context).protein, LIST_OF_COLORS3[1]),
|
||||
(AppLocalizations.of(context).carbohydrates, LIST_OF_COLORS3[0]),
|
||||
(AppLocalizations.of(context).fat, LIST_OF_COLORS3[2])
|
||||
(AppLocalizations.of(context).fat, LIST_OF_COLORS3[2]),
|
||||
]
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Indicator(
|
||||
color: e.$2,
|
||||
text: e.$1,
|
||||
isSquare: true,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Indicator(
|
||||
color: e.$2,
|
||||
text: e.$1,
|
||||
isSquare: true,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 28,
|
||||
@@ -229,7 +262,7 @@ class FlNutritionalPlanPieChartState extends State<FlNutritionalPlanPieChartWidg
|
||||
return [
|
||||
(0, LIST_OF_COLORS3[1], widget.nutritionalValues.protein),
|
||||
(1, LIST_OF_COLORS3[0], widget.nutritionalValues.carbohydrates),
|
||||
(2, LIST_OF_COLORS3[2], widget.nutritionalValues.fat)
|
||||
(2, LIST_OF_COLORS3[2], widget.nutritionalValues.fat),
|
||||
].map((e) {
|
||||
final isTouched = e.$1 == touchedIndex;
|
||||
final radius = isTouched ? 92.0 : 80.0;
|
||||
@@ -297,7 +330,12 @@ class NutritionalDiaryChartWidgetFlState extends State<NutritionalDiaryChartWidg
|
||||
|
||||
final [colorPlanned, colorLoggedToday, colorLogged7Day] = LIST_OF_COLORS3;
|
||||
|
||||
BarChartGroupData barchartGroup(int x, double barsSpace, double barsWidth, String prop) {
|
||||
BarChartGroupData barchartGroup(
|
||||
int x,
|
||||
double barsSpace,
|
||||
double barsWidth,
|
||||
String prop,
|
||||
) {
|
||||
final plan = planned.prop(prop);
|
||||
|
||||
BarChartRodData barChartRodData(double? plan, double val, Color color) {
|
||||
@@ -397,7 +435,12 @@ class NutritionalDiaryChartWidgetFlState extends State<NutritionalDiaryChartWidg
|
||||
barGroups: [
|
||||
barchartGroup(0, barsSpace, barsWidth, 'protein'),
|
||||
barchartGroup(1, barsSpace, barsWidth, 'carbohydrates'),
|
||||
barchartGroup(2, barsSpace, barsWidth, 'carbohydratesSugar'),
|
||||
barchartGroup(
|
||||
2,
|
||||
barsSpace,
|
||||
barsWidth,
|
||||
'carbohydratesSugar',
|
||||
),
|
||||
barchartGroup(3, barsSpace, barsWidth, 'fat'),
|
||||
barchartGroup(4, barsSpace, barsWidth, 'fatSaturated'),
|
||||
if (widget._nutritionalPlan.nutritionalGoals.fiber != null)
|
||||
@@ -414,17 +457,17 @@ class NutritionalDiaryChartWidgetFlState extends State<NutritionalDiaryChartWidg
|
||||
(AppLocalizations.of(context).deficit, colorPlanned),
|
||||
(AppLocalizations.of(context).surplus, COLOR_SURPLUS),
|
||||
(AppLocalizations.of(context).today, colorLoggedToday),
|
||||
(AppLocalizations.of(context).weekAverage, colorLogged7Day)
|
||||
(AppLocalizations.of(context).weekAverage, colorLogged7Day),
|
||||
]
|
||||
.map(
|
||||
(e) => Indicator(
|
||||
color: e.$2,
|
||||
text: e.$1,
|
||||
isSquare: true,
|
||||
marginRight: 0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
.map(
|
||||
(e) => Indicator(
|
||||
color: e.$2,
|
||||
text: e.$1,
|
||||
isSquare: true,
|
||||
marginRight: 0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -98,9 +98,14 @@ class MealForm extends StatelessWidget {
|
||||
|
||||
try {
|
||||
_meal.id == null
|
||||
? Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.addMeal(_meal, _planId)
|
||||
: Provider.of<NutritionPlansProvider>(context, listen: false).editMeal(_meal);
|
||||
? Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addMeal(_meal, _planId)
|
||||
: Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editMeal(_meal);
|
||||
} on WgerHttpException catch (error) {
|
||||
showHttpExceptionErrorDialog(error, context);
|
||||
} catch (error) {
|
||||
@@ -116,35 +121,42 @@ class MealForm extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget MealItemForm(Meal meal, List<MealItem> recent, [String? barcode, bool? test]) {
|
||||
Widget MealItemForm(
|
||||
Meal meal,
|
||||
List<MealItem> recent, [
|
||||
String? barcode,
|
||||
bool? test,
|
||||
]) {
|
||||
return IngredientForm(
|
||||
// TODO we use planId 0 here cause we don't have one and we don't need it I think?
|
||||
recent: recent.map((e) => Log.fromMealItem(e, 0, e.mealId)).toList(),
|
||||
onSave: (BuildContext context, MealItem mealItem, DateTime? dt) {
|
||||
mealItem.mealId = meal.id!;
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false).addMealItem(mealItem, meal);
|
||||
},
|
||||
barcode: barcode ?? '',
|
||||
test: test ?? false,
|
||||
withDate: false);
|
||||
// TODO we use planId 0 here cause we don't have one and we don't need it I think?
|
||||
recent: recent.map((e) => Log.fromMealItem(e, 0, e.mealId)).toList(),
|
||||
onSave: (BuildContext context, MealItem mealItem, DateTime? dt) {
|
||||
mealItem.mealId = meal.id!;
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false).addMealItem(mealItem, meal);
|
||||
},
|
||||
barcode: barcode ?? '',
|
||||
test: test ?? false,
|
||||
withDate: false,
|
||||
);
|
||||
}
|
||||
|
||||
Widget IngredientLogForm(NutritionalPlan plan) {
|
||||
return IngredientForm(
|
||||
recent: plan.dedupDiaryEntries,
|
||||
onSave: (BuildContext context, MealItem mealItem, DateTime? dt) {
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.logIngredientToDiary(mealItem, plan.id!, dt);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).ingredientLogged,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
recent: plan.dedupDiaryEntries,
|
||||
onSave: (BuildContext context, MealItem mealItem, DateTime? dt) {
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.logIngredientToDiary(mealItem, plan.id!, dt);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).ingredientLogged,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
},
|
||||
withDate: true);
|
||||
),
|
||||
);
|
||||
},
|
||||
withDate: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// IngredientForm is a form that lets the user pick an ingredient (and amount) to
|
||||
@@ -202,7 +214,7 @@ class IngredientFormState extends State<IngredientForm> {
|
||||
});
|
||||
}
|
||||
|
||||
// note: does not reset text search and amount inputs
|
||||
// note: does not reset text search and amount inputs
|
||||
void unSelectIngredient() {
|
||||
setState(() {
|
||||
_mealItem.ingredientId = 0;
|
||||
@@ -220,8 +232,9 @@ class IngredientFormState extends State<IngredientForm> {
|
||||
Widget build(BuildContext context) {
|
||||
final String unit = AppLocalizations.of(context).g;
|
||||
final queryLower = _searchQuery.toLowerCase();
|
||||
final suggestions =
|
||||
widget.recent.where((e) => e.ingredient.name.toLowerCase().contains(queryLower)).toList();
|
||||
final suggestions = widget.recent
|
||||
.where((e) => e.ingredient.name.toLowerCase().contains(queryLower))
|
||||
.toList();
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(20),
|
||||
child: Form(
|
||||
@@ -242,7 +255,9 @@ class IngredientFormState extends State<IngredientForm> {
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
key: const Key('field-weight'), // needed ?
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).weight),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).weight,
|
||||
),
|
||||
controller: _amountController,
|
||||
keyboardType: TextInputType.number,
|
||||
onFieldSubmitted: (_) {},
|
||||
@@ -336,9 +351,14 @@ class IngredientFormState extends State<IngredientForm> {
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
FutureBuilder<Ingredient>(
|
||||
future: Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.fetchIngredient(_mealItem.ingredientId),
|
||||
builder: (BuildContext context, AsyncSnapshot<Ingredient> snapshot) {
|
||||
future: Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).fetchIngredient(_mealItem.ingredientId),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<Ingredient> snapshot,
|
||||
) {
|
||||
if (snapshot.hasData) {
|
||||
_mealItem.ingredient = snapshot.data!;
|
||||
return MealItemTile(
|
||||
@@ -378,7 +398,13 @@ class IngredientFormState extends State<IngredientForm> {
|
||||
try {
|
||||
var date = DateTime.parse(_dateController.text);
|
||||
final tod = stringToTime(_timeController.text);
|
||||
date = DateTime(date.year, date.month, date.day, tod.hour, tod.minute);
|
||||
date = DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
tod.hour,
|
||||
tod.minute,
|
||||
);
|
||||
widget.onSave(context, _mealItem, date);
|
||||
} on WgerHttpException catch (error) {
|
||||
showHttpExceptionErrorDialog(error, context);
|
||||
@@ -402,18 +428,25 @@ class IngredientFormState extends State<IngredientForm> {
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
final ingredient = suggestions[index].ingredient;
|
||||
selectIngredient(ingredient.id, ingredient.name, suggestions[index].amount);
|
||||
selectIngredient(
|
||||
ingredient.id,
|
||||
ingredient.name,
|
||||
suggestions[index].amount,
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
'${suggestions[index].ingredient.name} (${suggestions[index].amount.toStringAsFixed(0)}$unit)'),
|
||||
'${suggestions[index].ingredient.name} (${suggestions[index].amount.toStringAsFixed(0)}$unit)',
|
||||
),
|
||||
subtitle: Text(getShortNutritionValues(
|
||||
suggestions[index].ingredient.nutritionalValues, context)),
|
||||
suggestions[index].ingredient.nutritionalValues,
|
||||
context,
|
||||
)),
|
||||
trailing: const Icon(Icons.copy),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -476,7 +509,9 @@ class _PlanFormState extends State<PlanForm> {
|
||||
// Description
|
||||
TextFormField(
|
||||
key: const Key('field-description'),
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).description),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).description,
|
||||
),
|
||||
controller: _descriptionController,
|
||||
onFieldSubmitted: (_) {},
|
||||
onSaved: (newValue) {
|
||||
@@ -505,10 +540,13 @@ class _PlanFormState extends State<PlanForm> {
|
||||
child: DropdownButtonFormField<GoalType>(
|
||||
value: _goalType,
|
||||
items: GoalType.values
|
||||
.map(
|
||||
(e) => DropdownMenuItem<GoalType>(value: e, child: Text(e.label)),
|
||||
)
|
||||
.toList(),
|
||||
.map(
|
||||
(e) => DropdownMenuItem<GoalType>(
|
||||
value: e,
|
||||
child: Text(e.label),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (GoalType? g) {
|
||||
setState(() {
|
||||
if (g == null) {
|
||||
@@ -590,14 +628,18 @@ class _PlanFormState extends State<PlanForm> {
|
||||
// Save to DB
|
||||
try {
|
||||
if (widget._plan.id != null) {
|
||||
await Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.editPlan(widget._plan);
|
||||
await Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editPlan(widget._plan);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
widget._plan = await Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.addPlan(widget._plan);
|
||||
widget._plan = await Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addPlan(widget._plan);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pushReplacementNamed(
|
||||
NutritionalPlanScreen.routeName,
|
||||
|
||||
@@ -102,59 +102,63 @@ class _MealWidgetState extends State<MealWidget> {
|
||||
),
|
||||
if (_editing)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context).addIngredient),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).addIngredient,
|
||||
MealItemForm(widget._meal, widget._recentMealItems),
|
||||
hasListView: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton.icon(
|
||||
label: Text(AppLocalizations.of(context).edit),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).edit,
|
||||
MealForm(widget._meal.planId, widget._meal),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.timer),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Delete the meal
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.deleteMeal(widget._meal);
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context).addIngredient),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).addIngredient,
|
||||
MealItemForm(widget._meal, widget._recentMealItems),
|
||||
hasListView: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton.icon(
|
||||
label: Text(AppLocalizations.of(context).edit),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).edit,
|
||||
MealForm(widget._meal.planId, widget._meal),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.timer),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Delete the meal
|
||||
Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).deleteMeal(widget._meal);
|
||||
|
||||
// and inform the user
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
label: Text(AppLocalizations.of(context).delete),
|
||||
icon: const Icon(Icons.delete)),
|
||||
],
|
||||
)),
|
||||
// and inform the user
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
label: Text(AppLocalizations.of(context).delete),
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_viewMode == viewMode.withIngredients || _viewMode == viewMode.withAllDetails)
|
||||
const Divider(),
|
||||
if (_viewMode == viewMode.withAllDetails) const NutritionDiaryheader(),
|
||||
@@ -223,7 +227,7 @@ class MealItemWidget extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (_viewMode == viewMode.withAllDetails) ...getMutedNutritionalValues(values, context)
|
||||
if (_viewMode == viewMode.withAllDetails) ...getMutedNutritionalValues(values, context),
|
||||
],
|
||||
),
|
||||
trailing: _editing
|
||||
@@ -238,10 +242,11 @@ class MealItemWidget extends StatelessWidget {
|
||||
// and inform the user
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
@@ -314,20 +319,24 @@ class MealHeader extends StatelessWidget {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
title: Row(children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
((_meal.time != null) ? '${_meal.time!.format(context)} ' : '') + _meal.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
if (_meal.isRealMeal)
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
getShortNutritionValues(_meal.plannedNutritionalValues, context),
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
((_meal.time != null) ? '${_meal.time!.format(context)} ' : '') + _meal.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
)),
|
||||
if (_meal.isRealMeal)
|
||||
Text(
|
||||
getShortNutritionValues(
|
||||
_meal.plannedNutritionalValues,
|
||||
context,
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -65,7 +65,10 @@ class NutritionalDiaryDetailWidget extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
NutritionDiaryEntry(diaryEntry: e, nutritionalPlan: _nutritionalPlan),
|
||||
NutritionDiaryEntry(
|
||||
diaryEntry: e,
|
||||
nutritionalPlan: _nutritionalPlan,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
@@ -153,11 +156,15 @@ class NutritionDiaryTable extends StatelessWidget {
|
||||
TableRow(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding, horizontal: 12),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: tablePadding,
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Text(AppLocalizations.of(context).sugars),
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).gValue(planned.carbohydratesSugar.toStringAsFixed(0))),
|
||||
AppLocalizations.of(context).gValue(planned.carbohydratesSugar.toStringAsFixed(0)),
|
||||
),
|
||||
Text(AppLocalizations.of(context).gValue(logged.carbohydratesSugar.toStringAsFixed(0))),
|
||||
Text((logged.carbohydratesSugar - planned.carbohydratesSugar).toStringAsFixed(0)),
|
||||
],
|
||||
@@ -176,7 +183,10 @@ class NutritionDiaryTable extends StatelessWidget {
|
||||
TableRow(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: tablePadding, horizontal: 12),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: tablePadding,
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Text(AppLocalizations.of(context).saturatedFat),
|
||||
),
|
||||
Text(AppLocalizations.of(context).gValue(planned.fatSaturated.toStringAsFixed(0))),
|
||||
|
||||
@@ -43,67 +43,85 @@ class NutritionalDiaryTable extends StatelessWidget {
|
||||
children: [
|
||||
Text(style: Theme.of(context).textTheme.titleMedium, 'Date'),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
'${AppLocalizations.of(context).energyShort} (${AppLocalizations.of(context).kcal})'),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
'${AppLocalizations.of(context).energyShort} (${AppLocalizations.of(context).kcal})',
|
||||
),
|
||||
if (goals.energy != null)
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
AppLocalizations.of(context).difference),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
'${AppLocalizations.of(context).proteinShort} (${AppLocalizations.of(context).g})'),
|
||||
AppLocalizations.of(context).difference,
|
||||
),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
'${AppLocalizations.of(context).carbohydratesShort} (${AppLocalizations.of(context).g})'),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
'${AppLocalizations.of(context).proteinShort} (${AppLocalizations.of(context).g})',
|
||||
),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
'${AppLocalizations.of(context).fatShort} (${AppLocalizations.of(context).g})'),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
'${AppLocalizations.of(context).carbohydratesShort} (${AppLocalizations.of(context).g})',
|
||||
),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
'${AppLocalizations.of(context).fatShort} (${AppLocalizations.of(context).g})',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
TableRow nutritionDiaryEntry(final BuildContext context, NutritionalGoals goals, DateTime date,
|
||||
final NutritionalValues values) {
|
||||
TableRow nutritionDiaryEntry(
|
||||
final BuildContext context,
|
||||
NutritionalGoals goals,
|
||||
DateTime date,
|
||||
final NutritionalValues values,
|
||||
) {
|
||||
return TableRow(
|
||||
decoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[300]!))),
|
||||
children: [
|
||||
decoration: BoxDecoration(
|
||||
border: Border(top: BorderSide(color: Colors.grey[300]!)),
|
||||
),
|
||||
children: [
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: LIST_OF_COLORS3.first),
|
||||
DateFormat.Md(Localizations.localeOf(context).languageCode).format(date),
|
||||
),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
values.energy.toStringAsFixed(0),
|
||||
),
|
||||
if (goals.energy != null)
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: LIST_OF_COLORS3.first),
|
||||
DateFormat.Md(Localizations.localeOf(context).languageCode).format(date),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
(values.energy - goals.energy!).toStringAsFixed(0),
|
||||
),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
values.energy.toStringAsFixed(0)),
|
||||
if (goals.energy != null)
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
(values.energy - goals.energy!).toStringAsFixed(0)),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
values.protein.toStringAsFixed(0)),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
values.carbohydrates.toStringAsFixed(0)),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
values.fat.toStringAsFixed(0)),
|
||||
].map((element) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
NutritionalDiaryScreen.routeName,
|
||||
arguments: NutritionalDiaryArguments(plan, date),
|
||||
),
|
||||
child: element);
|
||||
}).toList());
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
values.protein.toStringAsFixed(0),
|
||||
),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
values.carbohydrates.toStringAsFixed(0),
|
||||
),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.end,
|
||||
values.fat.toStringAsFixed(0),
|
||||
),
|
||||
].map((element) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
NutritionalDiaryScreen.routeName,
|
||||
arguments: NutritionalDiaryArguments(plan, date),
|
||||
),
|
||||
child: element,
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,74 +40,80 @@ class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
lastWeightEntry != null ? nutritionalGoals / lastWeightEntry.weight.toDouble() : null;
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FlNutritionalPlanGoalWidget(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FlNutritionalPlanGoalWidget(
|
||||
nutritionalPlan: _nutritionalPlan,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
..._nutritionalPlan.meals.map((meal) => MealWidget(
|
||||
meal,
|
||||
_nutritionalPlan.dedupMealItems,
|
||||
false,
|
||||
false,
|
||||
)),
|
||||
MealWidget(
|
||||
_nutritionalPlan.pseudoMealOthers('Other logs'),
|
||||
_nutritionalPlan.dedupMealItems,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
if (nutritionalGoals.isComplete())
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: FlNutritionalPlanPieChartWidget(nutritionalGoals.toValues()),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: MacronutrientsTable(
|
||||
nutritionalGoals: nutritionalGoals,
|
||||
plannedValuesPercentage: nutritionalGoals.energyPercentage(),
|
||||
nutritionalGoalsGperKg: nutritionalGoalsGperKg,
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8.0)),
|
||||
Text(
|
||||
AppLocalizations.of(context).logged,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 16, left: 8, right: 8),
|
||||
height: 300,
|
||||
child: NutritionalDiaryChartWidgetFl(
|
||||
nutritionalPlan: _nutritionalPlan,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
..._nutritionalPlan.meals.map((meal) => MealWidget(
|
||||
meal,
|
||||
_nutritionalPlan.dedupMealItems,
|
||||
false,
|
||||
false,
|
||||
)),
|
||||
MealWidget(
|
||||
_nutritionalPlan.pseudoMealOthers('Other logs'),
|
||||
_nutritionalPlan.dedupMealItems,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
if (nutritionalGoals.isComplete())
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: FlNutritionalPlanPieChartWidget(nutritionalGoals.toValues()),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: MacronutrientsTable(
|
||||
nutritionalGoals: nutritionalGoals,
|
||||
plannedValuesPercentage: nutritionalGoals.energyPercentage(),
|
||||
nutritionalGoalsGperKg: nutritionalGoalsGperKg,
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8.0)),
|
||||
Text(
|
||||
AppLocalizations.of(context).logged,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 16, left: 8, right: 8),
|
||||
height: 300,
|
||||
child: NutritionalDiaryChartWidgetFl(nutritionalPlan: _nutritionalPlan),
|
||||
),
|
||||
if (_nutritionalPlan.logEntriesValues.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 15, left: 15, right: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).nutritionalDiary,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
SizedBox(
|
||||
if (_nutritionalPlan.logEntriesValues.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 15, left: 15, right: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).nutritionalDiary,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: SingleChildScrollView(
|
||||
child: NutritionalDiaryTable(nutritionalPlan: _nutritionalPlan),
|
||||
)),
|
||||
],
|
||||
child: NutritionalDiaryTable(
|
||||
nutritionalPlan: _nutritionalPlan,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,9 @@ class NutritionalPlansList extends StatelessWidget {
|
||||
},
|
||||
title: Text(currentPlan.getLabel(context)),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(currentPlan.creationDate),
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(currentPlan.creationDate),
|
||||
),
|
||||
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const VerticalDivider(),
|
||||
@@ -60,48 +61,51 @@ class NutritionalPlansList extends StatelessWidget {
|
||||
onPressed: () async {
|
||||
// Delete the plan from DB
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext contextDialog) {
|
||||
return AlertDialog(
|
||||
content: Text(
|
||||
AppLocalizations.of(context)
|
||||
.confirmDelete(currentPlan.description),
|
||||
context: context,
|
||||
builder: (BuildContext contextDialog) {
|
||||
return AlertDialog(
|
||||
content: Text(
|
||||
AppLocalizations.of(context)
|
||||
.confirmDelete(currentPlan.description),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).cancelButtonLabel,
|
||||
),
|
||||
onPressed: () => Navigator.of(contextDialog).pop(),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child:
|
||||
Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
onPressed: () => Navigator.of(contextDialog).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
onPressed: () {
|
||||
// Confirmed, delete the plan
|
||||
_nutritionProvider.deletePlan(currentPlan.id!);
|
||||
|
||||
// Close the popup
|
||||
Navigator.of(contextDialog).pop();
|
||||
|
||||
// and inform the user
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
onPressed: () {
|
||||
// Confirmed, delete the plan
|
||||
_nutritionProvider.deletePlan(currentPlan.id!);
|
||||
|
||||
// Close the popup
|
||||
Navigator.of(contextDialog).pop();
|
||||
|
||||
// and inform the user
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).successfullyDeleted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -154,8 +154,12 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
|
||||
final url = context.read<NutritionPlansProvider>().baseProvider.auth.serverUrl;
|
||||
return ListTile(
|
||||
leading: suggestion.data.image != null
|
||||
? CircleAvatar(backgroundImage: NetworkImage(url! + suggestion.data.image!))
|
||||
: const CircleIconAvatar(Icon(Icons.image, color: Colors.grey)),
|
||||
? CircleAvatar(
|
||||
backgroundImage: NetworkImage(url! + suggestion.data.image!),
|
||||
)
|
||||
: const CircleIconAvatar(
|
||||
Icon(Icons.image, color: Colors.grey),
|
||||
),
|
||||
title: Text(suggestion.value),
|
||||
// subtitle: Text(suggestion.data.id.toString()),
|
||||
);
|
||||
@@ -197,8 +201,10 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final result = await Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.searchIngredientWithCode(barcode);
|
||||
final result = await Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).searchIngredientWithCode(barcode);
|
||||
// TODO: show spinner...
|
||||
if (!mounted) {
|
||||
return;
|
||||
@@ -231,11 +237,13 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
|
||||
),
|
||||
TextButton(
|
||||
key: const Key('found-dialog-close-button'),
|
||||
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).closeButtonLabel,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -252,11 +260,13 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
|
||||
actions: [
|
||||
TextButton(
|
||||
key: const Key('notFound-dialog-close-button'),
|
||||
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).closeButtonLabel,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -292,13 +302,13 @@ class NutritionDiaryheader extends StatelessWidget {
|
||||
AppLocalizations.of(context).energy,
|
||||
AppLocalizations.of(context).protein,
|
||||
AppLocalizations.of(context).carbohydrates,
|
||||
AppLocalizations.of(context).fat
|
||||
AppLocalizations.of(context).fat,
|
||||
]
|
||||
.map((e) => MutedText(
|
||||
e,
|
||||
textAlign: TextAlign.right,
|
||||
))
|
||||
.toList(),
|
||||
.map((e) => MutedText(
|
||||
e,
|
||||
textAlign: TextAlign.right,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -338,7 +348,10 @@ class NutritionDiaryEntry extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
...getMutedNutritionalValues(diaryEntry.nutritionalValues, context),
|
||||
...getMutedNutritionalValues(
|
||||
diaryEntry.nutritionalValues,
|
||||
context,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -347,12 +360,13 @@ class NutritionDiaryEntry extends StatelessWidget {
|
||||
),
|
||||
if (nutritionalPlan != null)
|
||||
IconButton(
|
||||
tooltip: AppLocalizations.of(context).delete,
|
||||
onPressed: () {
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.deleteLog(diaryEntry.id!, nutritionalPlan!.id!);
|
||||
},
|
||||
icon: const Icon(Icons.delete_outline)),
|
||||
tooltip: AppLocalizations.of(context).delete,
|
||||
onPressed: () {
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.deleteLog(diaryEntry.id!, nutritionalPlan!.id!);
|
||||
},
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -367,7 +381,9 @@ class IngredientAvatar extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ingredient.image != null
|
||||
? GestureDetector(
|
||||
child: CircleAvatar(backgroundImage: NetworkImage(ingredient.image!.image)),
|
||||
child: CircleAvatar(
|
||||
backgroundImage: NetworkImage(ingredient.image!.image),
|
||||
),
|
||||
onTap: () async {
|
||||
if (ingredient.image!.objectUrl != '') {
|
||||
return launchURL(ingredient.image!.objectUrl, context);
|
||||
|
||||
@@ -73,15 +73,16 @@ class _UserProfileFormState extends State<UserProfileForm> {
|
||||
leading: const Icon(Icons.email_rounded, color: wgerPrimaryColor),
|
||||
title: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: widget._profile.emailVerified
|
||||
? AppLocalizations.of(context).verifiedEmail
|
||||
: AppLocalizations.of(context).unVerifiedEmail,
|
||||
suffixIcon: widget._profile.emailVerified
|
||||
? const Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green,
|
||||
)
|
||||
: null),
|
||||
labelText: widget._profile.emailVerified
|
||||
? AppLocalizations.of(context).verifiedEmail
|
||||
: AppLocalizations.of(context).unVerifiedEmail,
|
||||
suffixIcon: widget._profile.emailVerified
|
||||
? const Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
controller: emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
onSaved: (newValue) {
|
||||
@@ -104,7 +105,9 @@ class _UserProfileFormState extends State<UserProfileForm> {
|
||||
}
|
||||
|
||||
// Verify
|
||||
await context.read<UserProvider>().verifyEmail();
|
||||
await context
|
||||
.read<UserProvider>()
|
||||
.verifyEmail();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
@@ -125,10 +128,14 @@ class _UserProfileFormState extends State<UserProfileForm> {
|
||||
_form.currentState!.save();
|
||||
|
||||
// Update profile
|
||||
context.read<UserProvider>().saveProfile();
|
||||
context
|
||||
.read<UserProvider>()
|
||||
.saveProfile();
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(AppLocalizations.of(context).successfullySaved)),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context).successfullySaved),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context).save),
|
||||
|
||||
@@ -39,7 +39,9 @@ class WeightEntriesList extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
child: MeasurementChartWidgetFl(
|
||||
weightProvider.items.map((e) => MeasurementChartEntry(e.weight, e.date)).toList(),
|
||||
weightProvider.items
|
||||
.map((e) => MeasurementChartEntry(e.weight, e.date))
|
||||
.toList(),
|
||||
unit: profile!.isMetric
|
||||
? AppLocalizations.of(context).kg
|
||||
: AppLocalizations.of(context).lb,
|
||||
@@ -54,7 +56,7 @@ class WeightEntriesList extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(AppLocalizations.of(context).measurements),
|
||||
const Icon(Icons.chevron_right)
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -70,22 +72,24 @@ class WeightEntriesList extends StatelessWidget {
|
||||
child: ListTile(
|
||||
title: Text('${currentEntry.weight} kg'),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(currentEntry.date),
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(currentEntry.date),
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
child: Text(AppLocalizations.of(context).edit),
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).edit,
|
||||
WeightForm(currentEntry),
|
||||
),
|
||||
)),
|
||||
child: Text(AppLocalizations.of(context).edit),
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).edit,
|
||||
WeightForm(currentEntry),
|
||||
),
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Text(AppLocalizations.of(context).delete),
|
||||
onTap: () async {
|
||||
@@ -104,7 +108,7 @@ class WeightEntriesList extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
|
||||
@@ -26,33 +26,37 @@ enum _WorkoutAppBarOptions {
|
||||
contribute,
|
||||
}
|
||||
|
||||
class WorkoutOverviewAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
class WorkoutOverviewAppBar extends StatelessWidget
|
||||
implements PreferredSizeWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
title: Text(AppLocalizations.of(context).labelWorkoutPlans),
|
||||
actions: [
|
||||
PopupMenuButton(itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem<_WorkoutAppBarOptions>(
|
||||
value: _WorkoutAppBarOptions.list,
|
||||
child: Text(AppLocalizations.of(context).exerciseList),
|
||||
),
|
||||
PopupMenuItem<_WorkoutAppBarOptions>(
|
||||
value: _WorkoutAppBarOptions.contribute,
|
||||
child: Text(AppLocalizations.of(context).contributeExercise),
|
||||
),
|
||||
];
|
||||
}, onSelected: (value) {
|
||||
switch (value) {
|
||||
case _WorkoutAppBarOptions.contribute:
|
||||
Navigator.of(context).pushNamed(AddExerciseScreen.routeName);
|
||||
break;
|
||||
case _WorkoutAppBarOptions.list:
|
||||
Navigator.of(context).pushNamed(ExercisesScreen.routeName);
|
||||
break;
|
||||
}
|
||||
}),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem<_WorkoutAppBarOptions>(
|
||||
value: _WorkoutAppBarOptions.list,
|
||||
child: Text(AppLocalizations.of(context).exerciseList),
|
||||
),
|
||||
PopupMenuItem<_WorkoutAppBarOptions>(
|
||||
value: _WorkoutAppBarOptions.contribute,
|
||||
child: Text(AppLocalizations.of(context).contributeExercise),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (value) {
|
||||
switch (value) {
|
||||
case _WorkoutAppBarOptions.contribute:
|
||||
Navigator.of(context).pushNamed(AddExerciseScreen.routeName);
|
||||
break;
|
||||
case _WorkoutAppBarOptions.list:
|
||||
Navigator.of(context).pushNamed(ExercisesScreen.routeName);
|
||||
break;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ class _LogChartWidgetFlState extends State<LogChartWidgetFl> {
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipItems: (touchedSpots) {
|
||||
return touchedSpots.map((touchedSpot) {
|
||||
final reps = widget._data['chart_data'][touchedSpot.barIndex].first['reps'];
|
||||
final reps =
|
||||
widget._data['chart_data'][touchedSpot.barIndex].first['reps'];
|
||||
|
||||
return LineTooltipItem(
|
||||
'$reps × ${touchedSpot.y} kg',
|
||||
@@ -68,7 +69,8 @@ class _LogChartWidgetFlState extends State<LogChartWidgetFl> {
|
||||
}
|
||||
|
||||
LineChartData mainData() {
|
||||
final colors = generateChartColors(widget._data['chart_data'].length).iterator;
|
||||
final colors =
|
||||
generateChartColors(widget._data['chart_data'].length).iterator;
|
||||
|
||||
return LineChartData(
|
||||
lineTouchData: tooltipData(),
|
||||
@@ -106,9 +108,11 @@ class _LogChartWidgetFlState extends State<LogChartWidgetFl> {
|
||||
return const Text('');
|
||||
}
|
||||
|
||||
final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt());
|
||||
final DateTime date =
|
||||
DateTime.fromMillisecondsSinceEpoch(value.toInt());
|
||||
return Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode).format(date),
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(date),
|
||||
);
|
||||
},
|
||||
interval: chartGetInterval(
|
||||
@@ -138,10 +142,12 @@ class _LogChartWidgetFlState extends State<LogChartWidgetFl> {
|
||||
spots: [
|
||||
...e.map(
|
||||
(entry) => FlSpot(
|
||||
DateTime.parse(entry['date']).millisecondsSinceEpoch.toDouble(),
|
||||
DateTime.parse(entry['date'])
|
||||
.millisecondsSinceEpoch
|
||||
.toDouble(),
|
||||
double.parse(entry['weight']),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
isCurved: true,
|
||||
color: colors.current,
|
||||
@@ -156,7 +162,7 @@ class _LogChartWidgetFlState extends State<LogChartWidgetFl> {
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,9 @@ class SettingWidget extends StatelessWidget {
|
||||
content: ExerciseDetail(setting.exerciseObj),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).closeButtonLabel,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
@@ -80,7 +82,9 @@ class SettingWidget extends StatelessWidget {
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...set.getSmartRepr(setting.exerciseObj).map((e) => Text(e)),
|
||||
...set
|
||||
.getSmartRepr(setting.exerciseObj)
|
||||
.map((e) => Text(e)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -172,55 +176,62 @@ class _WorkoutDayWidgetState extends State<WorkoutDayWidget> {
|
||||
),
|
||||
if (_editing)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
label: Text(AppLocalizations.of(context).addSet),
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).newSet,
|
||||
SetFormWidget(widget._day),
|
||||
hasListView: true,
|
||||
padding: EdgeInsets.zero,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
label: Text(AppLocalizations.of(context).addSet),
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).newSet,
|
||||
SetFormWidget(widget._day),
|
||||
hasListView: true,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.calendar_month),
|
||||
label: Text(AppLocalizations.of(context).edit),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).edit,
|
||||
DayFormWidget(
|
||||
Provider.of<WorkoutPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).findById(widget._day.workoutId),
|
||||
widget._day,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.calendar_month),
|
||||
label: Text(AppLocalizations.of(context).edit),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).edit,
|
||||
DayFormWidget(
|
||||
Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.findById(widget._day.workoutId),
|
||||
widget._day),
|
||||
hasListView: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.delete),
|
||||
label: Text(AppLocalizations.of(context).delete),
|
||||
onPressed: () {
|
||||
Provider.of<WorkoutPlansProvider>(context, listen: false).deleteDay(
|
||||
widget._day,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
hasListView: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.delete),
|
||||
label: Text(AppLocalizations.of(context).delete),
|
||||
onPressed: () {
|
||||
Provider.of<WorkoutPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).deleteDay(
|
||||
widget._day,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
ReorderableListView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
@@ -237,8 +248,10 @@ class _WorkoutDayWidgetState extends State<WorkoutDayWidget> {
|
||||
setState(() {
|
||||
_sets.insert(newIndex, _sets.removeAt(oldIndex));
|
||||
});
|
||||
_sets = await Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.reorderSets(_sets, startIndex);
|
||||
_sets = await Provider.of<WorkoutPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).reorderSets(_sets, startIndex);
|
||||
},
|
||||
children: [
|
||||
for (var i = 0; i < widget._day.sets.length; i++) getSetRow(widget._day.sets[i], i),
|
||||
@@ -285,7 +298,7 @@ class DayHeader extends StatelessWidget {
|
||||
onPressed: () {
|
||||
_toggle();
|
||||
},
|
||||
)
|
||||
),
|
||||
]),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
||||
@@ -73,7 +73,9 @@ class WorkoutForm extends StatelessWidget {
|
||||
),
|
||||
TextFormField(
|
||||
key: const Key('field-description'),
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).description),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).description,
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: 10,
|
||||
controller: workoutDescriptionController,
|
||||
@@ -108,9 +110,10 @@ class WorkoutForm extends StatelessWidget {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
final WorkoutPlan newPlan =
|
||||
await Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.addWorkout(_plan);
|
||||
final WorkoutPlan newPlan = await Provider.of<WorkoutPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addWorkout(_plan);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pushReplacementNamed(
|
||||
WorkoutPlanScreen.routeName,
|
||||
@@ -246,7 +249,7 @@ class _DayFormWidgetState extends State<DayFormWidget> {
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -333,7 +336,9 @@ class _SetFormWidgetState extends State<SetFormWidget> {
|
||||
min: 1,
|
||||
max: 10,
|
||||
divisions: 10,
|
||||
label: _currentSetSliderValue.round().toString(),
|
||||
label: _currentSetSliderValue
|
||||
.round()
|
||||
.toString(),
|
||||
onChanged: (double value) {
|
||||
setState(() {
|
||||
widget._set.sets = value.round();
|
||||
@@ -393,13 +398,14 @@ class _SetFormWidgetState extends State<SetFormWidget> {
|
||||
children: [
|
||||
Text(AppLocalizations.of(context).selectExercises),
|
||||
const SizedBox(height: 10),
|
||||
Text(AppLocalizations.of(context).sameRepetitions)
|
||||
Text(AppLocalizations.of(context).sameRepetitions),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).closeButtonLabel),
|
||||
MaterialLocalizations.of(context).closeButtonLabel,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
@@ -433,18 +439,25 @@ class _SetFormWidgetState extends State<SetFormWidget> {
|
||||
if (pattern == '') {
|
||||
return null;
|
||||
}
|
||||
return context.read<ExercisesProvider>().searchExercise(
|
||||
pattern,
|
||||
languageCode: Localizations.localeOf(context).languageCode,
|
||||
searchEnglish: _searchEnglish,
|
||||
);
|
||||
return context
|
||||
.read<ExercisesProvider>()
|
||||
.searchExercise(
|
||||
pattern,
|
||||
languageCode: Localizations.localeOf(context).languageCode,
|
||||
searchEnglish: _searchEnglish,
|
||||
);
|
||||
},
|
||||
itemBuilder: (BuildContext context, Exercise exerciseSuggestion) =>
|
||||
itemBuilder: (
|
||||
BuildContext context,
|
||||
Exercise exerciseSuggestion,
|
||||
) =>
|
||||
ListTile(
|
||||
key: Key('exercise-${exerciseSuggestion.id}'),
|
||||
leading: SizedBox(
|
||||
width: 45,
|
||||
child: ExerciseImageWidget(image: exerciseSuggestion.getMainImage),
|
||||
child: ExerciseImageWidget(
|
||||
image: exerciseSuggestion.getMainImage,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
exerciseSuggestion
|
||||
@@ -474,7 +487,10 @@ class _SetFormWidgetState extends State<SetFormWidget> {
|
||||
);
|
||||
},
|
||||
transitionBuilder: (context, animation, child) => FadeTransition(
|
||||
opacity: CurvedAnimation(parent: animation, curve: Curves.fastOutSlowIn),
|
||||
opacity: CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
onSelected: (Exercise exerciseSuggestion) {
|
||||
@@ -494,7 +510,7 @@ class _SetFormWidgetState extends State<SetFormWidget> {
|
||||
});
|
||||
},
|
||||
dense: true,
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -558,8 +574,10 @@ class _SetFormWidgetState extends State<SetFormWidget> {
|
||||
}
|
||||
_formKey.currentState!.save();
|
||||
|
||||
final workoutProvider =
|
||||
Provider.of<WorkoutPlansProvider>(context, listen: false);
|
||||
final workoutProvider = Provider.of<WorkoutPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
|
||||
// Save set
|
||||
final Set setDb = await workoutProvider.addSet(widget._set);
|
||||
@@ -704,10 +722,11 @@ class ExerciseSetting extends StatelessWidget {
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: ExerciseImageWidget(image: _exerciseBase.getMainImage),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
removeExercise(_exerciseBase);
|
||||
}),
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
removeExercise(_exerciseBase);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
|
||||
@@ -906,8 +925,8 @@ class _WeightUnitInputWidgetState extends State<WeightUnitInputWidget> {
|
||||
});
|
||||
},
|
||||
items: Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.weightUnits
|
||||
.map<DropdownMenuItem<WeightUnit>>((WeightUnit value) {
|
||||
.weightUnits
|
||||
.map<DropdownMenuItem<WeightUnit>>((WeightUnit value) {
|
||||
return DropdownMenuItem<WeightUnit>(
|
||||
key: Key(value.id.toString()),
|
||||
value: value,
|
||||
@@ -937,7 +956,9 @@ class _RepetitionUnitInputWidgetState extends State<RepetitionUnitInputWidget> {
|
||||
|
||||
return DropdownButtonFormField(
|
||||
value: selectedWeightUnit,
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).repetitionUnit),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).repetitionUnit,
|
||||
),
|
||||
isDense: true,
|
||||
onChanged: (RepetitionUnit? newValue) {
|
||||
setState(() {
|
||||
@@ -946,8 +967,8 @@ class _RepetitionUnitInputWidgetState extends State<RepetitionUnitInputWidget> {
|
||||
});
|
||||
},
|
||||
items: Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.repetitionUnits
|
||||
.map<DropdownMenuItem<RepetitionUnit>>((RepetitionUnit value) {
|
||||
.repetitionUnits
|
||||
.map<DropdownMenuItem<RepetitionUnit>>((RepetitionUnit value) {
|
||||
return DropdownMenuItem<RepetitionUnit>(
|
||||
key: Key(value.id.toString()),
|
||||
value: value,
|
||||
|
||||
@@ -156,7 +156,7 @@ class _GymModeState extends State<GymMode> {
|
||||
...getContent(),
|
||||
SessionPage(
|
||||
Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.findById(widget._workoutDay.workoutId),
|
||||
.findById(widget._workoutDay.workoutId),
|
||||
_controller,
|
||||
widget._start,
|
||||
_exercisePages,
|
||||
@@ -199,7 +199,9 @@ class StartPage extends StatelessWidget {
|
||||
.name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
...set.getSmartRepr(s.exerciseObj).map((e) => Text(e)),
|
||||
...set
|
||||
.getSmartRepr(s.exerciseObj)
|
||||
.map((e) => Text(e)),
|
||||
const SizedBox(height: 15),
|
||||
],
|
||||
);
|
||||
@@ -215,7 +217,9 @@ class StartPage extends StatelessWidget {
|
||||
child: Text(AppLocalizations.of(context).start),
|
||||
onPressed: () {
|
||||
_controller.nextPage(
|
||||
duration: const Duration(milliseconds: 200), curve: Curves.bounceIn);
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.bounceIn,
|
||||
);
|
||||
},
|
||||
),
|
||||
NavigationFooter(
|
||||
@@ -452,7 +456,7 @@ class _LogPageState extends State<LogPage> {
|
||||
children: [
|
||||
Flexible(child: getWeightWidget()),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(child: WeightUnitInputWidget(widget._log))
|
||||
Flexible(child: WeightUnitInputWidget(widget._log)),
|
||||
],
|
||||
),
|
||||
if (_detailed) RiRInputWidget(widget._log),
|
||||
@@ -479,8 +483,10 @@ class _LogPageState extends State<LogPage> {
|
||||
|
||||
// Save the entry on the server
|
||||
try {
|
||||
await Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.addLog(widget._log);
|
||||
await Provider.of<WorkoutPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addLog(widget._log);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
duration: const Duration(seconds: 2), // default is 4
|
||||
@@ -535,8 +541,9 @@ class _LogPageState extends State<LogPage> {
|
||||
.map((log) {
|
||||
return ListTile(
|
||||
title: Text(log.singleLogRepTextNoNl),
|
||||
subtitle:
|
||||
Text(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(log.date)),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode).format(log.date),
|
||||
),
|
||||
trailing: const Icon(Icons.copy),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
@@ -550,8 +557,9 @@ class _LogPageState extends State<LogPage> {
|
||||
widget._log.weightUnit = log.weightUnitObj;
|
||||
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).dataCopied)));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(AppLocalizations.of(context).dataCopied),
|
||||
));
|
||||
});
|
||||
},
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
@@ -600,7 +608,9 @@ class _LogPageState extends State<LogPage> {
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
key.toString(),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -609,10 +619,12 @@ class _LogPageState extends State<LogPage> {
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
: MutedText(AppLocalizations.of(context).plateCalculatorNotDivisible),
|
||||
: MutedText(
|
||||
AppLocalizations.of(context).plateCalculatorNotDivisible,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
],
|
||||
@@ -642,9 +654,10 @@ class _LogPageState extends State<LogPage> {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: (widget._workoutPlan.filterLogsByExerciseBase(widget._exerciseBase).isNotEmpty)
|
||||
? getPastLogs()
|
||||
: Container()),
|
||||
child: (widget._workoutPlan.filterLogsByExerciseBase(widget._exerciseBase).isNotEmpty)
|
||||
? getPastLogs()
|
||||
: Container(),
|
||||
),
|
||||
// Only show calculator for barbell
|
||||
if (widget._log.exerciseBaseObj.equipment.map((e) => e.id).contains(ID_EQUIPMENT_BARBELL))
|
||||
getPlates(),
|
||||
@@ -710,9 +723,10 @@ class ExerciseOverview extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Html(
|
||||
data: _exerciseBase
|
||||
.getExercise(Localizations.localeOf(context).languageCode)
|
||||
.description),
|
||||
data: _exerciseBase
|
||||
.getExercise(Localizations.localeOf(context).languageCode)
|
||||
.description,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -826,44 +840,46 @@ class _SessionPageState extends State<SessionPage> {
|
||||
children: [
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).timeStart,
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
controller: timeStartController,
|
||||
onFieldSubmitted: (_) {},
|
||||
onTap: () async {
|
||||
// Stop keyboard from appearing
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).timeStart,
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
controller: timeStartController,
|
||||
onFieldSubmitted: (_) {},
|
||||
onTap: () async {
|
||||
// Stop keyboard from appearing
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
|
||||
// Open time picker
|
||||
final pickedTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: _session.timeStart,
|
||||
);
|
||||
// Open time picker
|
||||
final pickedTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: _session.timeStart,
|
||||
);
|
||||
|
||||
if (pickedTime != null) {
|
||||
timeStartController.text = timeToString(pickedTime)!;
|
||||
_session.timeStart = pickedTime;
|
||||
}
|
||||
},
|
||||
onSaved: (newValue) {
|
||||
_session.timeStart = stringToTime(newValue);
|
||||
},
|
||||
validator: (_) {
|
||||
final TimeOfDay startTime = stringToTime(timeStartController.text);
|
||||
final TimeOfDay endTime = stringToTime(timeEndController.text);
|
||||
if (startTime.isAfter(endTime)) {
|
||||
return AppLocalizations.of(context).timeStartAhead;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
if (pickedTime != null) {
|
||||
timeStartController.text = timeToString(pickedTime)!;
|
||||
_session.timeStart = pickedTime;
|
||||
}
|
||||
},
|
||||
onSaved: (newValue) {
|
||||
_session.timeStart = stringToTime(newValue);
|
||||
},
|
||||
validator: (_) {
|
||||
final TimeOfDay startTime = stringToTime(timeStartController.text);
|
||||
final TimeOfDay endTime = stringToTime(timeEndController.text);
|
||||
if (startTime.isAfter(endTime)) {
|
||||
return AppLocalizations.of(context).timeStartAhead;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
decoration:
|
||||
InputDecoration(labelText: AppLocalizations.of(context).timeEnd),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).timeEnd,
|
||||
),
|
||||
controller: timeEndController,
|
||||
onFieldSubmitted: (_) {},
|
||||
onTap: () async {
|
||||
@@ -900,8 +916,10 @@ class _SessionPageState extends State<SessionPage> {
|
||||
|
||||
// Save the entry on the server
|
||||
try {
|
||||
await Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.addSession(_session);
|
||||
await Provider.of<WorkoutPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addSession(_session);
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
@@ -1002,7 +1020,10 @@ class _TimerWidgetState extends State<TimerWidget> {
|
||||
child: Center(
|
||||
child: Text(
|
||||
DateFormat('m:ss').format(today.add(Duration(seconds: _seconds))),
|
||||
style: Theme.of(context).textTheme.displayLarge!.copyWith(color: wgerPrimaryColor),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.displayLarge!
|
||||
.copyWith(color: wgerPrimaryColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1018,8 +1039,12 @@ class NavigationFooter extends StatelessWidget {
|
||||
final bool showPrevious;
|
||||
final bool showNext;
|
||||
|
||||
const NavigationFooter(this._controller, this._ratioCompleted,
|
||||
{this.showPrevious = true, this.showNext = true});
|
||||
const NavigationFooter(
|
||||
this._controller,
|
||||
this._ratioCompleted, {
|
||||
this.showPrevious = true,
|
||||
this.showNext = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -1105,7 +1130,7 @@ class NavigationHeader extends StatelessWidget {
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ class ExerciseLogChart extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final workoutPlansData = Provider.of<WorkoutPlansProvider>(context, listen: false);
|
||||
final workoutPlansData =
|
||||
Provider.of<WorkoutPlansProvider>(context, listen: false);
|
||||
final workout = workoutPlansData.currentPlan;
|
||||
var colors = generateChartColors(1).iterator;
|
||||
|
||||
@@ -45,44 +46,46 @@ class ExerciseLogChart extends StatelessWidget {
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
future: getChartEntries(context),
|
||||
builder: (context, AsyncSnapshot<Map<String, dynamic>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
colors = generateChartColors(snapshot.data!['chart_data'].length).iterator;
|
||||
}
|
||||
future: getChartEntries(context),
|
||||
builder: (context, AsyncSnapshot<Map<String, dynamic>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
colors =
|
||||
generateChartColors(snapshot.data!['chart_data'].length).iterator;
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: 260,
|
||||
child: snapshot.connectionState == ConnectionState.waiting
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
LogChartWidgetFl(snapshot.data!, _currentDate),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
...snapshot.data!['chart_data'].map((e) {
|
||||
// e is the list of logs with the same reps, so we can just take the
|
||||
// first entry and read the reps from it. Yes, this is an amazingly ugly hack
|
||||
final reps = e.first['reps'];
|
||||
return SizedBox(
|
||||
height: 260,
|
||||
child: snapshot.connectionState == ConnectionState.waiting
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
LogChartWidgetFl(snapshot.data!, _currentDate),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
...snapshot.data!['chart_data'].map((e) {
|
||||
// e is the list of logs with the same reps, so we can just take the
|
||||
// first entry and read the reps from it. Yes, this is an amazingly ugly hack
|
||||
final reps = e.first['reps'];
|
||||
|
||||
colors.moveNext();
|
||||
return Indicator(
|
||||
color: colors.current,
|
||||
text: reps.toString(),
|
||||
isSquare: false,
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
colors.moveNext();
|
||||
return Indicator(
|
||||
color: colors.current,
|
||||
text: reps.toString(),
|
||||
isSquare: false,
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,12 +112,14 @@ class _DayLogWidgetState extends State<DayLogWidget> {
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode).format(widget._date),
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(widget._date),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
if (widget._session != null) const Text('Session data here'),
|
||||
...widget._exerciseData.keys.map((base) {
|
||||
final exercise = base.getExercise(Localizations.localeOf(context).languageCode);
|
||||
final exercise =
|
||||
base.getExercise(Localizations.localeOf(context).languageCode);
|
||||
return Column(
|
||||
children: [
|
||||
if (widget._exerciseData[base]!.isNotEmpty)
|
||||
@@ -133,7 +138,12 @@ class _DayLogWidgetState extends State<DayLogWidget> {
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
showDeleteDialog(
|
||||
context, exercise.name, log, exercise, widget._exerciseData);
|
||||
context,
|
||||
exercise.name,
|
||||
log,
|
||||
exercise,
|
||||
widget._exerciseData,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -142,10 +152,10 @@ class _DayLogWidgetState extends State<DayLogWidget> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: ExerciseLogChart(base, widget._date),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
})
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -140,7 +140,7 @@ class _WorkoutLogCalendarState extends State<WorkoutLogCalendar> {
|
||||
date,
|
||||
entry['session'],
|
||||
entry['exercises'],
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -190,17 +190,18 @@ class _WorkoutLogCalendarState extends State<WorkoutLogCalendar> {
|
||||
const SizedBox(height: 8.0),
|
||||
SizedBox(
|
||||
child: ValueListenableBuilder<List<WorkoutLogEvent>>(
|
||||
valueListenable: _selectedEvents,
|
||||
builder: (context, logEvents, _) {
|
||||
// At the moment there is only one "event" per day
|
||||
return logEvents.isNotEmpty
|
||||
? DayLogWidget(
|
||||
logEvents.first.dateTime,
|
||||
logEvents.first.exercises,
|
||||
logEvents.first.session,
|
||||
)
|
||||
: Container();
|
||||
}),
|
||||
valueListenable: _selectedEvents,
|
||||
builder: (context, logEvents, _) {
|
||||
// At the moment there is only one "event" per day
|
||||
return logEvents.isNotEmpty
|
||||
? DayLogWidget(
|
||||
logEvents.first.dateTime,
|
||||
logEvents.first.exercises,
|
||||
logEvents.first.session,
|
||||
)
|
||||
: Container();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -41,26 +41,28 @@ class _WorkoutPlanDetailState extends State<WorkoutPlanDetail> {
|
||||
children: [
|
||||
if (widget._workoutPlan.days.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: ToggleButtons(
|
||||
renderBorder: false,
|
||||
onPressed: (int index) {
|
||||
if (index == 1) {
|
||||
widget._changeMode(WorkoutScreenMode.log);
|
||||
}
|
||||
},
|
||||
isSelected: const [true, false],
|
||||
children: const <Widget>[
|
||||
Icon(Icons.table_chart),
|
||||
Icon(Icons.show_chart),
|
||||
],
|
||||
)),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: ToggleButtons(
|
||||
renderBorder: false,
|
||||
onPressed: (int index) {
|
||||
if (index == 1) {
|
||||
widget._changeMode(WorkoutScreenMode.log);
|
||||
}
|
||||
},
|
||||
isSelected: const [true, false],
|
||||
children: const <Widget>[
|
||||
Icon(Icons.table_chart),
|
||||
Icon(Icons.show_chart),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget._workoutPlan.description != '')
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Text(widget._workoutPlan.description),
|
||||
),
|
||||
...widget._workoutPlan.days.map((workoutDay) => WorkoutDayWidget(workoutDay)),
|
||||
...widget._workoutPlan.days
|
||||
.map((workoutDay) => WorkoutDayWidget(workoutDay)),
|
||||
Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
|
||||
@@ -42,26 +42,29 @@ class WorkoutPlansList extends StatelessWidget {
|
||||
final currentWorkout = _workoutProvider.items[index];
|
||||
|
||||
return Card(
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
_workoutProvider.setCurrentPlan(currentWorkout.id!);
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
_workoutProvider.setCurrentPlan(currentWorkout.id!);
|
||||
|
||||
Navigator.of(context)
|
||||
.pushNamed(WorkoutPlanScreen.routeName, arguments: currentWorkout);
|
||||
},
|
||||
title: Text(currentWorkout.name),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode)
|
||||
.format(currentWorkout.creationDate),
|
||||
),
|
||||
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const VerticalDivider(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
tooltip: AppLocalizations.of(context).delete,
|
||||
onPressed: () async {
|
||||
// Delete workout from DB
|
||||
await showDialog(
|
||||
Navigator.of(context).pushNamed(
|
||||
WorkoutPlanScreen.routeName,
|
||||
arguments: currentWorkout,
|
||||
);
|
||||
},
|
||||
title: Text(currentWorkout.name),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(currentWorkout.creationDate),
|
||||
),
|
||||
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const VerticalDivider(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
tooltip: AppLocalizations.of(context).delete,
|
||||
onPressed: () async {
|
||||
// Delete workout from DB
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext contextDialog) {
|
||||
return AlertDialog(
|
||||
@@ -70,19 +73,24 @@ class WorkoutPlansList extends StatelessWidget {
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child:
|
||||
Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).cancelButtonLabel,
|
||||
),
|
||||
onPressed: () => Navigator.of(contextDialog).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
// Confirmed, delete the workout
|
||||
Provider.of<WorkoutPlansProvider>(context, listen: false)
|
||||
.deleteWorkout(currentWorkout.id!);
|
||||
Provider.of<WorkoutPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).deleteWorkout(currentWorkout.id!);
|
||||
|
||||
// Close the popup
|
||||
Navigator.of(contextDialog).pop();
|
||||
@@ -100,11 +108,13 @@ class WorkoutPlansList extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
)
|
||||
]),
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user