mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Split up the widgets used, this was getting too cluttered
This commit is contained in:
33
lib/helpers/exercises/forms.dart
Normal file
33
lib/helpers/exercises/forms.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
/// The amount of characters an exercise description needs to have
|
||||
const MIN_CHARS_DESCRIPTION = 40;
|
||||
|
||||
/// The amount of characters an exercise name needs to have
|
||||
const MIN_CHARS_NAME = 5;
|
||||
const MAX_CHARS_NAME = 40;
|
||||
|
||||
String? validateName(String? name, BuildContext context) {
|
||||
if (name!.isEmpty) {
|
||||
return AppLocalizations.of(context).enterValue;
|
||||
}
|
||||
|
||||
if (name.length < MIN_CHARS_NAME || name.length > MAX_CHARS_NAME) {
|
||||
return AppLocalizations.of(context).enterCharacters(MIN_CHARS_NAME, MAX_CHARS_NAME);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateDescription(String? name, BuildContext context) {
|
||||
if (name!.isEmpty) {
|
||||
return AppLocalizations.of(context).enterValue;
|
||||
}
|
||||
|
||||
if (name.length < MIN_CHARS_DESCRIPTION) {
|
||||
return AppLocalizations.of(context).enterMinCharacters(MIN_CHARS_DESCRIPTION);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -3,19 +3,13 @@ import 'dart:developer';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/models/exercises/category.dart';
|
||||
import 'package:wger/models/exercises/equipment.dart';
|
||||
import 'package:wger/models/exercises/language.dart';
|
||||
import 'package:wger/models/exercises/muscle.dart';
|
||||
import 'package:wger/providers/add_exercise_provider.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:wger/widgets/add_exercise/add_exercise_multiselect_button.dart';
|
||||
import 'package:wger/widgets/add_exercise/add_exercise_text_area.dart';
|
||||
import 'package:wger/widgets/add_exercise/mixins/image_picker_mixin.dart';
|
||||
import 'package:wger/widgets/add_exercise/preview_images.dart';
|
||||
import 'package:wger/widgets/add_exercise/steps/basics.dart';
|
||||
import 'package:wger/widgets/add_exercise/steps/description.dart';
|
||||
import 'package:wger/widgets/add_exercise/steps/images.dart';
|
||||
import 'package:wger/widgets/add_exercise/steps/translations.dart';
|
||||
import 'package:wger/widgets/add_exercise/steps/variations.dart';
|
||||
import 'package:wger/widgets/core/app_bar.dart';
|
||||
import 'package:wger/widgets/exercises/exercises.dart';
|
||||
import 'package:wger/widgets/exercises/forms.dart';
|
||||
|
||||
class AddExerciseScreen extends StatefulWidget {
|
||||
const AddExerciseScreen({Key? key}) : super(key: key);
|
||||
@@ -31,37 +25,6 @@ abstract class ValidateStep {
|
||||
abstract VoidCallback _submit;
|
||||
}
|
||||
|
||||
/// The amount of characters an exercise description needs to have
|
||||
const MIN_CHARS_DESCRIPTION = 40;
|
||||
|
||||
/// The amount of characters an exercise name needs to have
|
||||
const MIN_CHARS_NAME = 5;
|
||||
const MAX_CHARS_NAME = 40;
|
||||
|
||||
String? validateName(String? name, BuildContext context) {
|
||||
if (name!.isEmpty) {
|
||||
return AppLocalizations.of(context).enterValue;
|
||||
}
|
||||
|
||||
if (name.length < MIN_CHARS_NAME || name.length > MAX_CHARS_NAME) {
|
||||
return AppLocalizations.of(context).enterCharacters(MIN_CHARS_NAME, MAX_CHARS_NAME);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateDescription(String? name, BuildContext context) {
|
||||
if (name!.isEmpty) {
|
||||
return AppLocalizations.of(context).enterValue;
|
||||
}
|
||||
|
||||
if (name.length < MIN_CHARS_DESCRIPTION) {
|
||||
return AppLocalizations.of(context).enterMinCharacters(MIN_CHARS_DESCRIPTION);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class _AddExerciseScreenState extends State<AddExerciseScreen> {
|
||||
int _currentStep = 0;
|
||||
int lastStepIndex = AddExerciseScreen.STEPS_IN_FORM - 1;
|
||||
@@ -115,23 +78,23 @@ class _AddExerciseScreenState extends State<AddExerciseScreen> {
|
||||
steps: [
|
||||
Step(
|
||||
title: Text(AppLocalizations.of(context).baseData),
|
||||
content: _BasicStepContent(formkey: _keys[0]),
|
||||
content: BasicStepContent(formkey: _keys[0]),
|
||||
),
|
||||
Step(
|
||||
title: Text(AppLocalizations.of(context).variations),
|
||||
content: _DuplicatesAndVariationsStepContent(formkey: _keys[1]),
|
||||
content: DuplicatesAndVariationsStepContent(formkey: _keys[1]),
|
||||
),
|
||||
Step(
|
||||
title: Text(AppLocalizations.of(context).description),
|
||||
content: _DescriptionStepContent(formkey: _keys[2]),
|
||||
content: DescriptionStepContent(formkey: _keys[2]),
|
||||
),
|
||||
Step(
|
||||
title: Text(AppLocalizations.of(context).translation),
|
||||
content: _DescriptionTranslationStepContent(formkey: _keys[3]),
|
||||
content: DescriptionTranslationStepContent(formkey: _keys[3]),
|
||||
),
|
||||
Step(
|
||||
title: Text(AppLocalizations.of(context).images),
|
||||
content: _ImagesStepContent(formkey: _keys[4]),
|
||||
content: ImagesStepContent(formkey: _keys[4]),
|
||||
),
|
||||
],
|
||||
currentStep: _currentStep,
|
||||
@@ -167,360 +130,3 @@ class _AddExerciseScreenState extends State<AddExerciseScreen> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BasicStepContent extends StatelessWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
const _BasicStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
final exerciseProvider = context.read<ExercisesProvider>();
|
||||
final categories = exerciseProvider.categories;
|
||||
final muscles = exerciseProvider.muscles;
|
||||
final equipment = exerciseProvider.equipment;
|
||||
final languages = exerciseProvider.languages;
|
||||
|
||||
// Initialize some values
|
||||
addExerciseProvider.category = categories.first;
|
||||
addExerciseProvider.language = languages.first;
|
||||
|
||||
return Form(
|
||||
key: formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${AppLocalizations.of(context).name}*',
|
||||
helperText: AppLocalizations.of(context).baseNameEnglish,
|
||||
isRequired: true,
|
||||
validator: (name) => validateName(name, context),
|
||||
onSaved: (String? name) => addExerciseProvider.exerciseNameEn = name!,
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: AppLocalizations.of(context).alternativeNames,
|
||||
isMultiline: true,
|
||||
helperText: AppLocalizations.of(context).oneNamePerLine,
|
||||
onSaved: (String? alternateName) =>
|
||||
addExerciseProvider.alternateNamesEn = alternateName!.split('\n'),
|
||||
),
|
||||
ExerciseCategoryInputWidget<ExerciseCategory>(
|
||||
categories: categories,
|
||||
title: AppLocalizations.of(context).category,
|
||||
callback: (ExerciseCategory newValue) {
|
||||
addExerciseProvider.category = newValue;
|
||||
},
|
||||
displayName: (ExerciseCategory c) => c.name,
|
||||
),
|
||||
AddExerciseMultiselectButton<Equipment>(
|
||||
title: AppLocalizations.of(context).equipment,
|
||||
items: equipment,
|
||||
initialItems: addExerciseProvider.equipment,
|
||||
onChange: (dynamic entries) {
|
||||
addExerciseProvider.equipment = entries.cast<Equipment>();
|
||||
},
|
||||
onSaved: (dynamic entries) {
|
||||
addExerciseProvider.equipment = entries.cast<Equipment>();
|
||||
},
|
||||
),
|
||||
AddExerciseMultiselectButton<Muscle>(
|
||||
title: AppLocalizations.of(context).muscles,
|
||||
items: muscles,
|
||||
initialItems: addExerciseProvider.primaryMuscles,
|
||||
onChange: (dynamic muscles) {
|
||||
addExerciseProvider.primaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
onSaved: (dynamic muscles) {
|
||||
addExerciseProvider.primaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
),
|
||||
AddExerciseMultiselectButton<Muscle>(
|
||||
title: AppLocalizations.of(context).musclesSecondary,
|
||||
items: muscles,
|
||||
initialItems: addExerciseProvider.secondaryMuscles,
|
||||
onChange: (dynamic muscles) {
|
||||
addExerciseProvider.secondaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
onSaved: (dynamic muscles) {
|
||||
addExerciseProvider.secondaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (context, value, child) => MuscleRowWidget(
|
||||
muscles: value.primaryMuscles,
|
||||
musclesSecondary: value.secondaryMuscles,
|
||||
),
|
||||
),
|
||||
const MuscleColorHelper(main: true),
|
||||
const SizedBox(height: 5),
|
||||
const MuscleColorHelper(main: false),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DuplicatesAndVariationsStepContent extends StatelessWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
|
||||
const _DuplicatesAndVariationsStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
final exerciseProvider = context.read<ExercisesProvider>();
|
||||
|
||||
return Form(
|
||||
key: formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).whatVariationsExist,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
SizedBox(
|
||||
height: 400,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Exercise bases with variations
|
||||
...exerciseProvider.exerciseBasesByVariation.keys
|
||||
.map(
|
||||
(key) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
//mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
...exerciseProvider.exerciseBasesByVariation[key]!
|
||||
.map(
|
||||
(base) => Text(
|
||||
base
|
||||
.getExercises(
|
||||
Localizations.localeOf(context).languageCode)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => Switch(
|
||||
value: provider.variationId == key,
|
||||
onChanged: (state) => provider.variationId = key),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
// Exercise bases without variations
|
||||
...exerciseProvider.bases
|
||||
.where((b) => b.variationId == null)
|
||||
.map(
|
||||
(base) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
base
|
||||
.getExercises(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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ImagesStepContent extends StatefulWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
const _ImagesStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
State<_ImagesStepContent> createState() => _ImagesStepContentState();
|
||||
}
|
||||
|
||||
class _ImagesStepContentState extends State<_ImagesStepContent> with ExerciseImagePickerMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
key: widget.formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).add_exercise_image_license,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => provider.exerciseImages.isNotEmpty
|
||||
? PreviewExerciseImages(
|
||||
selectedImages: provider.exerciseImages,
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => pickImages(context, pickFromCamera: true),
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => pickImages(context),
|
||||
icon: const Icon(Icons.collections),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Only JPEG, PNG and WEBP files below 20 MB are supported',
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DescriptionStepContent extends StatelessWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
const _DescriptionStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
|
||||
return Form(
|
||||
key: formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${AppLocalizations.of(context).description}*',
|
||||
isRequired: true,
|
||||
isMultiline: true,
|
||||
validator: (name) => validateDescription(name, context),
|
||||
onSaved: (String? description) => addExerciseProvider.descriptionEn = description!,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DescriptionTranslationStepContent extends StatefulWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
const _DescriptionTranslationStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
State<_DescriptionTranslationStepContent> createState() =>
|
||||
_DescriptionTranslationStepContentState();
|
||||
}
|
||||
|
||||
class _DescriptionTranslationStepContentState extends State<_DescriptionTranslationStepContent> {
|
||||
bool translate = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
final exerciseProvider = context.read<ExercisesProvider>();
|
||||
final languages = exerciseProvider.languages;
|
||||
|
||||
return Form(
|
||||
key: widget.formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context).translation),
|
||||
subtitle: Text(AppLocalizations.of(context).translateExercise),
|
||||
value: translate,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
translate = !translate;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (translate)
|
||||
Column(
|
||||
children: [
|
||||
ExerciseCategoryInputWidget<Language>(
|
||||
categories: languages,
|
||||
title: AppLocalizations.of(context).language,
|
||||
displayName: (Language l) => l.fullName,
|
||||
callback: (Language newValue) {
|
||||
addExerciseProvider.language = newValue;
|
||||
},
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${AppLocalizations.of(context).name}*',
|
||||
isRequired: true,
|
||||
validator: (name) => validateName(name, context),
|
||||
onSaved: (String? name) => addExerciseProvider.exerciseNameTrans = name!,
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: AppLocalizations.of(context).alternativeNames,
|
||||
isMultiline: true,
|
||||
helperText: AppLocalizations.of(context).oneNamePerLine,
|
||||
validator: (alternateNames) {
|
||||
// check that each line (name) is at least MIN_CHARACTERS_NAME long
|
||||
if (alternateNames?.isNotEmpty == true) {
|
||||
final names = alternateNames!.split('\n');
|
||||
for (final name in names) {
|
||||
if (name.length < MIN_CHARS_NAME || name.length > MAX_CHARS_NAME) {
|
||||
return AppLocalizations.of(context).enterCharacters(
|
||||
MIN_CHARS_NAME,
|
||||
MAX_CHARS_NAME,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onSaved: (String? alternateName) =>
|
||||
addExerciseProvider.alternateNamesTrans = alternateName!.split('\n'),
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${AppLocalizations.of(context).description}*',
|
||||
isRequired: true,
|
||||
isMultiline: true,
|
||||
validator: (name) => validateDescription(name, context),
|
||||
onSaved: (String? description) =>
|
||||
addExerciseProvider.descriptionTrans = description!,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
106
lib/widgets/add_exercise/steps/basics.dart
Normal file
106
lib/widgets/add_exercise/steps/basics.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/exercises/forms.dart';
|
||||
import 'package:wger/models/exercises/category.dart';
|
||||
import 'package:wger/models/exercises/equipment.dart';
|
||||
import 'package:wger/models/exercises/muscle.dart';
|
||||
import 'package:wger/providers/add_exercise_provider.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:wger/widgets/add_exercise/add_exercise_multiselect_button.dart';
|
||||
import 'package:wger/widgets/add_exercise/add_exercise_text_area.dart';
|
||||
import 'package:wger/widgets/exercises/exercises.dart';
|
||||
import 'package:wger/widgets/exercises/forms.dart';
|
||||
|
||||
class BasicStepContent extends StatelessWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
const BasicStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
final exerciseProvider = context.read<ExercisesProvider>();
|
||||
final categories = exerciseProvider.categories;
|
||||
final muscles = exerciseProvider.muscles;
|
||||
final equipment = exerciseProvider.equipment;
|
||||
final languages = exerciseProvider.languages;
|
||||
|
||||
// Initialize some values
|
||||
addExerciseProvider.category = categories.first;
|
||||
addExerciseProvider.language = languages.first;
|
||||
|
||||
return Form(
|
||||
key: formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${AppLocalizations.of(context).name}*',
|
||||
helperText: AppLocalizations.of(context).baseNameEnglish,
|
||||
isRequired: true,
|
||||
validator: (name) => validateName(name, context),
|
||||
onSaved: (String? name) => addExerciseProvider.exerciseNameEn = name!,
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: AppLocalizations.of(context).alternativeNames,
|
||||
isMultiline: true,
|
||||
helperText: AppLocalizations.of(context).oneNamePerLine,
|
||||
onSaved: (String? alternateName) =>
|
||||
addExerciseProvider.alternateNamesEn = alternateName!.split('\n'),
|
||||
),
|
||||
ExerciseCategoryInputWidget<ExerciseCategory>(
|
||||
categories: categories,
|
||||
title: AppLocalizations.of(context).category,
|
||||
callback: (ExerciseCategory newValue) {
|
||||
addExerciseProvider.category = newValue;
|
||||
},
|
||||
displayName: (ExerciseCategory c) => c.name,
|
||||
),
|
||||
AddExerciseMultiselectButton<Equipment>(
|
||||
title: AppLocalizations.of(context).equipment,
|
||||
items: equipment,
|
||||
initialItems: addExerciseProvider.equipment,
|
||||
onChange: (dynamic entries) {
|
||||
addExerciseProvider.equipment = entries.cast<Equipment>();
|
||||
},
|
||||
onSaved: (dynamic entries) {
|
||||
addExerciseProvider.equipment = entries.cast<Equipment>();
|
||||
},
|
||||
),
|
||||
AddExerciseMultiselectButton<Muscle>(
|
||||
title: AppLocalizations.of(context).muscles,
|
||||
items: muscles,
|
||||
initialItems: addExerciseProvider.primaryMuscles,
|
||||
onChange: (dynamic muscles) {
|
||||
addExerciseProvider.primaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
onSaved: (dynamic muscles) {
|
||||
addExerciseProvider.primaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
),
|
||||
AddExerciseMultiselectButton<Muscle>(
|
||||
title: AppLocalizations.of(context).musclesSecondary,
|
||||
items: muscles,
|
||||
initialItems: addExerciseProvider.secondaryMuscles,
|
||||
onChange: (dynamic muscles) {
|
||||
addExerciseProvider.secondaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
onSaved: (dynamic muscles) {
|
||||
addExerciseProvider.secondaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (context, value, child) => MuscleRowWidget(
|
||||
muscles: value.primaryMuscles,
|
||||
musclesSecondary: value.secondaryMuscles,
|
||||
),
|
||||
),
|
||||
const MuscleColorHelper(main: true),
|
||||
const SizedBox(height: 5),
|
||||
const MuscleColorHelper(main: false),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
32
lib/widgets/add_exercise/steps/description.dart
Normal file
32
lib/widgets/add_exercise/steps/description.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/exercises/forms.dart';
|
||||
import 'package:wger/providers/add_exercise_provider.dart';
|
||||
import 'package:wger/widgets/add_exercise/add_exercise_text_area.dart';
|
||||
|
||||
class DescriptionStepContent extends StatelessWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
const DescriptionStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
|
||||
return Form(
|
||||
key: formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${AppLocalizations.of(context).description}*',
|
||||
isRequired: true,
|
||||
isMultiline: true,
|
||||
validator: (name) => validateDescription(name, context),
|
||||
onSaved: (String? description) => addExerciseProvider.descriptionEn = description!,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
54
lib/widgets/add_exercise/steps/images.dart
Normal file
54
lib/widgets/add_exercise/steps/images.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/providers/add_exercise_provider.dart';
|
||||
import 'package:wger/widgets/add_exercise/mixins/image_picker_mixin.dart';
|
||||
import 'package:wger/widgets/add_exercise/preview_images.dart';
|
||||
|
||||
class ImagesStepContent extends StatefulWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
const ImagesStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
State<ImagesStepContent> createState() => _ImagesStepContentState();
|
||||
}
|
||||
|
||||
class _ImagesStepContentState extends State<ImagesStepContent> with ExerciseImagePickerMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
key: widget.formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).add_exercise_image_license,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => provider.exerciseImages.isNotEmpty
|
||||
? PreviewExerciseImages(
|
||||
selectedImages: provider.exerciseImages,
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => pickImages(context, pickFromCamera: true),
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => pickImages(context),
|
||||
icon: const Icon(Icons.collections),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Only JPEG, PNG and WEBP files below 20 MB are supported',
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
99
lib/widgets/add_exercise/steps/translations.dart
Normal file
99
lib/widgets/add_exercise/steps/translations.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/exercises/forms.dart';
|
||||
import 'package:wger/models/exercises/language.dart';
|
||||
import 'package:wger/providers/add_exercise_provider.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:wger/widgets/add_exercise/add_exercise_text_area.dart';
|
||||
import 'package:wger/widgets/exercises/forms.dart';
|
||||
|
||||
class DescriptionTranslationStepContent extends StatefulWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
const DescriptionTranslationStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
State<DescriptionTranslationStepContent> createState() =>
|
||||
_DescriptionTranslationStepContentState();
|
||||
}
|
||||
|
||||
class _DescriptionTranslationStepContentState extends State<DescriptionTranslationStepContent> {
|
||||
bool translate = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
final exerciseProvider = context.read<ExercisesProvider>();
|
||||
final languages = exerciseProvider.languages;
|
||||
|
||||
return Form(
|
||||
key: widget.formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context).translation),
|
||||
subtitle: Text(AppLocalizations.of(context).translateExercise),
|
||||
value: translate,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
translate = !translate;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (translate)
|
||||
Column(
|
||||
children: [
|
||||
ExerciseCategoryInputWidget<Language>(
|
||||
categories: languages,
|
||||
title: AppLocalizations.of(context).language,
|
||||
displayName: (Language l) => l.fullName,
|
||||
callback: (Language newValue) {
|
||||
addExerciseProvider.language = newValue;
|
||||
},
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${AppLocalizations.of(context).name}*',
|
||||
isRequired: true,
|
||||
validator: (name) => validateName(name, context),
|
||||
onSaved: (String? name) => addExerciseProvider.exerciseNameTrans = name!,
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: AppLocalizations.of(context).alternativeNames,
|
||||
isMultiline: true,
|
||||
helperText: AppLocalizations.of(context).oneNamePerLine,
|
||||
validator: (alternateNames) {
|
||||
// check that each line (name) is at least MIN_CHARACTERS_NAME long
|
||||
if (alternateNames?.isNotEmpty == true) {
|
||||
final names = alternateNames!.split('\n');
|
||||
for (final name in names) {
|
||||
if (name.length < MIN_CHARS_NAME || name.length > MAX_CHARS_NAME) {
|
||||
return AppLocalizations.of(context).enterCharacters(
|
||||
MIN_CHARS_NAME,
|
||||
MAX_CHARS_NAME,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onSaved: (String? alternateName) =>
|
||||
addExerciseProvider.alternateNamesTrans = alternateName!.split('\n'),
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${AppLocalizations.of(context).description}*',
|
||||
isRequired: true,
|
||||
isMultiline: true,
|
||||
validator: (name) => validateDescription(name, context),
|
||||
onSaved: (String? description) =>
|
||||
addExerciseProvider.descriptionTrans = description!,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
105
lib/widgets/add_exercise/steps/variations.dart
Normal file
105
lib/widgets/add_exercise/steps/variations.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/providers/add_exercise_provider.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
|
||||
class DuplicatesAndVariationsStepContent extends StatelessWidget {
|
||||
final GlobalKey<FormState> formkey;
|
||||
|
||||
const DuplicatesAndVariationsStepContent({required this.formkey});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
final exerciseProvider = context.read<ExercisesProvider>();
|
||||
|
||||
return Form(
|
||||
key: formkey,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).whatVariationsExist,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
SizedBox(
|
||||
height: 400,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Exercise bases with variations
|
||||
...exerciseProvider.exerciseBasesByVariation.keys
|
||||
.map(
|
||||
(key) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
//mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
...exerciseProvider.exerciseBasesByVariation[key]!
|
||||
.map(
|
||||
(base) => Text(
|
||||
base
|
||||
.getExercises(
|
||||
Localizations.localeOf(context).languageCode)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => Switch(
|
||||
value: provider.variationId == key,
|
||||
onChanged: (state) => provider.variationId = key),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
// Exercise bases without variations
|
||||
...exerciseProvider.bases
|
||||
.where((b) => b.variationId == null)
|
||||
.map(
|
||||
(base) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
base
|
||||
.getExercises(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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user