mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Reuse AddExerciseTextArea in the image form
This commit is contained in:
@@ -145,4 +145,4 @@ const String API_RESULTS_PAGE_SIZE = '100';
|
||||
const INTERPOLATION_MARKER = 123;
|
||||
|
||||
/// Creative Commons license IDs
|
||||
const CC_BY_SA_4_ID = 4;
|
||||
const CC_BY_SA_4_ID = 2;
|
||||
|
||||
43
lib/helpers/exercises/validators.dart
Normal file
43
lib/helpers/exercises/validators.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wger/l10n/generated/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.toString(), MAX_CHARS_NAME.toString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateAuthorName(String? name, BuildContext context) {
|
||||
if (name!.isEmpty) {
|
||||
return AppLocalizations.of(context).enterValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateExerciseDescription(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.toString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -741,19 +741,19 @@
|
||||
"@imageDetailsLicenseTitleHint": {
|
||||
"description": "Hint text for image title field"
|
||||
},
|
||||
"imageDetailsSourceLink": "Link to the source website, if available",
|
||||
"imageDetailsSourceLink": "Link to the source website",
|
||||
"@imageDetailsSourceLink": {
|
||||
"description": "Label for source link field"
|
||||
},
|
||||
"imageDetailsAuthor": "Author(s)",
|
||||
"@imageDetailsAuthor": {
|
||||
"author": "Author(s)",
|
||||
"@Author": {
|
||||
"description": "Label for author field"
|
||||
},
|
||||
"imageDetailsAuthorHint": "Enter author name",
|
||||
"@imageDetailsAuthorHint": {
|
||||
"authorHint": "Enter author name",
|
||||
"@authorHint": {
|
||||
"description": "Hint text for author field"
|
||||
},
|
||||
"imageDetailsAuthorLink": "Link to author website or profile, if available",
|
||||
"imageDetailsAuthorLink": "Link to author website or profile",
|
||||
"@imageDetailsAuthorLink": {
|
||||
"description": "Label for author link field"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ ExerciseCategory _$ExerciseCategoryFromJson(Map<String, dynamic> json) {
|
||||
return ExerciseCategory(id: (json['id'] as num).toInt(), name: json['name'] as String);
|
||||
}
|
||||
|
||||
ap<String, dynamic> _$ExerciseCategoryToJson(ExerciseCategory instance) => <String, dynamic>{
|
||||
Map<String, dynamic> _$ExerciseCategoryToJson(ExerciseCategory instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ class AddExerciseProvider with ChangeNotifier {
|
||||
|
||||
List<ExerciseSubmissionImage> get exerciseImages => [..._exerciseImages];
|
||||
|
||||
String author = '';
|
||||
String? exerciseNameEn;
|
||||
String? exerciseNameTrans;
|
||||
String? descriptionEn;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AddExerciseTextArea extends StatelessWidget {
|
||||
const AddExerciseTextArea({
|
||||
AddExerciseTextArea({
|
||||
super.key,
|
||||
required this.onChange,
|
||||
required this.title,
|
||||
ValueChanged<String>? onChange,
|
||||
this.helperText = '',
|
||||
this.isRequired = true,
|
||||
this.isMultiline = false,
|
||||
this.initialValue = '',
|
||||
this.validator,
|
||||
this.onSaved,
|
||||
});
|
||||
}) : onChange = onChange ?? ((String value) {});
|
||||
|
||||
final ValueChanged<String> onChange;
|
||||
final bool isRequired;
|
||||
final bool isMultiline;
|
||||
final String title;
|
||||
final String helperText;
|
||||
final String? initialValue;
|
||||
final FormFieldValidator<String?>? validator;
|
||||
final FormFieldSetter<String?>? onSaved;
|
||||
|
||||
@@ -28,6 +28,7 @@ class AddExerciseTextArea extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextFormField(
|
||||
initialValue: initialValue,
|
||||
keyboardType: isMultiline ? TextInputType.multiline : TextInputType.text,
|
||||
maxLines: isMultiline ? null : DEFAULT_LINES,
|
||||
minLines: isMultiline ? MULTILINE_MIN_LINES : DEFAULT_LINES,
|
||||
@@ -35,12 +36,11 @@ class AddExerciseTextArea extends StatelessWidget {
|
||||
onSaved: onSaved,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
labelText: title,
|
||||
alignLabelWithHint: true,
|
||||
helperText: helperText,
|
||||
helperMaxLines: 3,
|
||||
),
|
||||
onChanged: onChange,
|
||||
),
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wger/core/validators.dart';
|
||||
import 'package:wger/helpers/exercises/validators.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise_submission_images.dart';
|
||||
import 'package:wger/widgets/add_exercise/license_info_widget.dart';
|
||||
|
||||
import 'add_exercise_text_area.dart';
|
||||
|
||||
/// Form for collecting CC BY-SA 4.0 license metadata for exercise images
|
||||
///
|
||||
/// This form is displayed after image selection in Step 5 of exercise creation.
|
||||
@@ -50,6 +54,11 @@ class _ImageDetailsFormState extends State<ImageDetailsForm> {
|
||||
/// Currently selected image type
|
||||
ImageType _selectedImageType = ImageType.photo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_titleController.dispose();
|
||||
@@ -60,137 +69,76 @@ class _ImageDetailsFormState extends State<ImageDetailsForm> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Validates URL format
|
||||
///
|
||||
/// Returns error message if URL is invalid, null if valid or empty
|
||||
String? _validateUrl(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null; // Empty is OK (optional field)
|
||||
}
|
||||
|
||||
final trimmedValue = value.trim();
|
||||
|
||||
// Check if starts with http:// or https://
|
||||
if (!trimmedValue.startsWith('http://') && !trimmedValue.startsWith('https://')) {
|
||||
return AppLocalizations.of(context).invalidUrl;
|
||||
}
|
||||
|
||||
// Try to parse as URI
|
||||
try {
|
||||
final uri = Uri.parse(trimmedValue);
|
||||
if (!uri.hasScheme || !uri.hasAuthority) {
|
||||
return AppLocalizations.of(context).invalidUrl;
|
||||
}
|
||||
} catch (e) {
|
||||
return AppLocalizations.of(context).invalidUrl;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Maps UI image type selection to API 'style' field value
|
||||
///
|
||||
/// API expects numeric string:
|
||||
/// - PHOTO = '1'
|
||||
/// - 3D = '2'
|
||||
/// - LINE = '3'
|
||||
/// - LOW-POLY = '4'
|
||||
/// - OTHER = '5'
|
||||
String _getStyleValue() {
|
||||
switch (_selectedImageType) {
|
||||
case 'PHOTO':
|
||||
return '1';
|
||||
case '3D':
|
||||
return '2';
|
||||
case 'LINE':
|
||||
return '3';
|
||||
case 'LOW-POLY':
|
||||
return '4';
|
||||
case 'OTHER':
|
||||
return '5';
|
||||
default:
|
||||
return '1'; // Default to PHOTO if unknown
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).imageDetailsTitle,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).imageDetailsTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildImagePreview(),
|
||||
const SizedBox(height: 24),
|
||||
_buildImagePreview(),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// License title field - helps identify the image
|
||||
_buildTextField(
|
||||
controller: _titleController,
|
||||
label: AppLocalizations.of(context).imageDetailsLicenseTitle,
|
||||
hint: AppLocalizations.of(context).imageDetailsLicenseTitleHint,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Author name - required for proper CC BY-SA attribution
|
||||
AddExerciseTextArea(
|
||||
title: '${AppLocalizations.of(context).author}*',
|
||||
initialValue: widget.submissionImage.author,
|
||||
onSaved: (value) => widget.submissionImage.author = value!,
|
||||
validator: (name) => validateAuthorName(name, context),
|
||||
),
|
||||
|
||||
// Source URL - where the image was found (license_object_url in API)
|
||||
_buildTextField(
|
||||
controller: _sourceLinkController,
|
||||
label: AppLocalizations.of(context).imageDetailsSourceLink,
|
||||
hint: 'https://example.com',
|
||||
keyboardType: TextInputType.url,
|
||||
validator: _validateUrl,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// License title field - helps identify the image
|
||||
AddExerciseTextArea(
|
||||
title: AppLocalizations.of(context).imageDetailsLicenseTitle,
|
||||
helperText: AppLocalizations.of(context).imageDetailsLicenseTitleHint,
|
||||
initialValue: widget.submissionImage.title,
|
||||
onSaved: (value) => widget.submissionImage.title = value,
|
||||
),
|
||||
|
||||
// Author name - required for proper CC BY-SA attribution
|
||||
_buildTextField(
|
||||
controller: _authorController,
|
||||
label: AppLocalizations.of(context).imageDetailsAuthor,
|
||||
hint: AppLocalizations.of(context).imageDetailsAuthorHint,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Source URL - where the image was found (license_object_url in API)
|
||||
AddExerciseTextArea(
|
||||
title: AppLocalizations.of(context).imageDetailsSourceLink,
|
||||
initialValue: widget.submissionImage.sourceUrl,
|
||||
onSaved: (value) => widget.submissionImage.sourceUrl = value,
|
||||
validator: (value) => validateUrl(value, i18n, required: false),
|
||||
),
|
||||
|
||||
// Author's website/profile URL
|
||||
_buildTextField(
|
||||
controller: _authorLinkController,
|
||||
label: AppLocalizations.of(context).imageDetailsAuthorLink,
|
||||
hint: 'https://example.com/author',
|
||||
keyboardType: TextInputType.url,
|
||||
validator: _validateUrl,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Author's website/profile URL
|
||||
AddExerciseTextArea(
|
||||
title: AppLocalizations.of(context).imageDetailsAuthorLink,
|
||||
initialValue: widget.submissionImage.authorUrl,
|
||||
onSaved: (value) => widget.submissionImage.authorUrl = value,
|
||||
validator: (value) => validateUrl(value, i18n, required: false),
|
||||
),
|
||||
|
||||
// Original source if this is a derivative work (modified from another image)
|
||||
_buildTextField(
|
||||
controller: _originalSourceController,
|
||||
label: AppLocalizations.of(context).imageDetailsDerivativeSource,
|
||||
hint: 'https://example.com/original',
|
||||
keyboardType: TextInputType.url,
|
||||
helperText: AppLocalizations.of(context).imageDetailsDerivativeHelp,
|
||||
validator: _validateUrl,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Original source if this is a derivative work (modified from another image)
|
||||
AddExerciseTextArea(
|
||||
title: AppLocalizations.of(context).imageDetailsDerivativeSource,
|
||||
helperText: AppLocalizations.of(context).imageDetailsDerivativeHelp,
|
||||
initialValue: widget.submissionImage.derivativeSourceUrl,
|
||||
onSaved: (value) => widget.submissionImage.derivativeSourceUrl = value,
|
||||
validator: (value) => validateUrl(value, i18n, required: false),
|
||||
),
|
||||
|
||||
_buildImageTypeSelector(),
|
||||
const SizedBox(height: 24),
|
||||
_buildImageTypeSelector(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// License info as separate widget for better optimization
|
||||
const LicenseInfoWidget(),
|
||||
const SizedBox(height: 24),
|
||||
// License info as separate widget for better optimization
|
||||
const LicenseInfoWidget(),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildButtons(),
|
||||
],
|
||||
),
|
||||
_buildButtons(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -292,7 +240,9 @@ class _ImageDetailsFormState extends State<ImageDetailsForm> {
|
||||
type.label,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: isSelected ? Colors.blue : Colors.grey.shade700,
|
||||
color: isSelected
|
||||
? theme.buttonTheme.colorScheme!.onPrimary
|
||||
: theme.buttonTheme.colorScheme!.primary,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
@@ -322,10 +272,6 @@ class _ImageDetailsFormState extends State<ImageDetailsForm> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build details map with API field names
|
||||
// Style is always included, other fields only if non-empty
|
||||
final details = <String, String>{'style': _getStyleValue()};
|
||||
|
||||
// Add optional fields only if user provided values
|
||||
final title = _titleController.text.trim();
|
||||
if (title.isNotEmpty) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/exercises/forms.dart';
|
||||
import 'package:wger/helpers/exercises/validators.dart';
|
||||
import 'package:wger/helpers/i18n.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/category.dart';
|
||||
@@ -9,6 +9,7 @@ import 'package:wger/models/exercises/equipment.dart';
|
||||
import 'package:wger/models/exercises/muscle.dart';
|
||||
import 'package:wger/providers/add_exercise.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:wger/providers/user.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';
|
||||
@@ -21,6 +22,7 @@ class Step1Basics extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userProvider = context.read<UserProvider>();
|
||||
final addExerciseProvider = context.read<AddExerciseProvider>();
|
||||
final exerciseProvider = context.read<ExercisesProvider>();
|
||||
final categories = exerciseProvider.categories;
|
||||
@@ -38,21 +40,25 @@ class Step1Basics extends StatelessWidget {
|
||||
builder: (context, provider, 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!,
|
||||
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'),
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
title: '${AppLocalizations.of(context).author}*',
|
||||
isMultiline: false,
|
||||
validator: (name) => validateAuthorName(name, context),
|
||||
initialValue: userProvider.profile!.username,
|
||||
onSaved: (String? author) => addExerciseProvider.author = author!,
|
||||
),
|
||||
ExerciseCategoryInputWidget<ExerciseCategory>(
|
||||
key: const Key('category-dropdown'),
|
||||
entries: categories,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/exercises/forms.dart';
|
||||
import 'package:wger/helpers/exercises/validators.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/add_exercise.dart';
|
||||
import 'package:wger/widgets/add_exercise/add_exercise_text_area.dart';
|
||||
@@ -23,10 +23,9 @@ class Step3Description extends StatelessWidget {
|
||||
onChange: (value) => {},
|
||||
title: '${i18n.description}*',
|
||||
helperText: i18n.enterTextInLanguage,
|
||||
isRequired: true,
|
||||
isMultiline: true,
|
||||
validator: (name) => validateExerciseDescription(name, context),
|
||||
onSaved: (String? description) => addExerciseProvider.descriptionEn = description!,
|
||||
onSaved: (String? description) => addExerciseProvider.descriptionEn = description,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/exercises/forms.dart';
|
||||
import 'package:wger/helpers/exercises/validators.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/language.dart';
|
||||
import 'package:wger/providers/add_exercise.dart';
|
||||
@@ -59,14 +59,11 @@ class _Step4TranslationState extends State<Step4Translation> {
|
||||
},
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: '${i18n.name}*',
|
||||
isRequired: true,
|
||||
validator: (name) => validateName(name, context),
|
||||
onSaved: (String? name) => addExerciseProvider.exerciseNameTrans = name!,
|
||||
),
|
||||
AddExerciseTextArea(
|
||||
onChange: (value) => {},
|
||||
title: i18n.alternativeNames,
|
||||
isMultiline: true,
|
||||
helperText: i18n.oneNamePerLine,
|
||||
@@ -93,7 +90,6 @@ class _Step4TranslationState extends State<Step4Translation> {
|
||||
onChange: (value) => {},
|
||||
title: '${i18n.description}*',
|
||||
helperText: i18n.enterTextInLanguage,
|
||||
isRequired: true,
|
||||
isMultiline: true,
|
||||
validator: (name) => validateExerciseDescription(name, context),
|
||||
onSaved: (String? description) =>
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise_submission_images.dart';
|
||||
import 'package:wger/providers/add_exercise.dart';
|
||||
import 'package:wger/providers/user.dart';
|
||||
import 'package:wger/widgets/add_exercise/image_details_form.dart';
|
||||
import 'package:wger/widgets/add_exercise/mixins/image_picker_mixin.dart';
|
||||
import 'package:wger/widgets/add_exercise/preview_images.dart';
|
||||
@@ -33,7 +34,7 @@ class Step5Images extends StatefulWidget {
|
||||
class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin {
|
||||
/// Currently selected image waiting for metadata input
|
||||
/// When non-null, ImageDetailsForm is displayed instead of image picker
|
||||
ExerciseSubmissionImage? _currentImageToAddNew;
|
||||
ExerciseSubmissionImage? _currentImageToAdd;
|
||||
|
||||
/// Show dialog to choose between Camera and Gallery
|
||||
Future<void> _showImageSourceDialog(BuildContext context) async {
|
||||
@@ -75,6 +76,7 @@ class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin
|
||||
///
|
||||
/// [pickFromCamera] - If true, opens camera; otherwise opens gallery
|
||||
void _pickAndShowImageDetails(BuildContext context, {bool pickFromCamera = false}) async {
|
||||
final userProvider = context.read<UserProvider>();
|
||||
final imagePicker = ImagePicker();
|
||||
|
||||
XFile? selectedImage;
|
||||
@@ -114,7 +116,10 @@ class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin
|
||||
|
||||
// Show metadata collection form for valid image
|
||||
setState(() {
|
||||
_currentImageToAddNew = ExerciseSubmissionImage(imageFile: imageFile);
|
||||
_currentImageToAdd = ExerciseSubmissionImage(
|
||||
imageFile: imageFile,
|
||||
author: userProvider.profile?.username ?? '',
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -135,14 +140,14 @@ class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin
|
||||
|
||||
// Reset form state - image is now visible in preview list
|
||||
setState(() {
|
||||
_currentImageToAddNew = null;
|
||||
_currentImageToAdd = null;
|
||||
});
|
||||
}
|
||||
|
||||
/// Cancel metadata input and return to image picker
|
||||
void _cancelImageAdd() {
|
||||
setState(() {
|
||||
_currentImageToAddNew = null;
|
||||
_currentImageToAdd = null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,7 +158,7 @@ class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin
|
||||
child: Column(
|
||||
children: [
|
||||
// License notice - shown when not entering metadata
|
||||
if (_currentImageToAddNew == null)
|
||||
if (_currentImageToAdd == null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Text(
|
||||
@@ -164,15 +169,15 @@ class _Step5ImagesState extends State<Step5Images> with ExerciseImagePickerMixin
|
||||
),
|
||||
|
||||
// Metadata collection form - shown when image is selected
|
||||
if (_currentImageToAddNew != null)
|
||||
if (_currentImageToAdd != null)
|
||||
ImageDetailsForm(
|
||||
submissionImage: _currentImageToAddNew!,
|
||||
submissionImage: _currentImageToAdd!,
|
||||
onAdd: _addImageWithDetails,
|
||||
onCancel: _cancelImageAdd,
|
||||
),
|
||||
|
||||
// Image picker or preview - shown when not entering metadata
|
||||
if (_currentImageToAddNew == null)
|
||||
if (_currentImageToAdd == null)
|
||||
Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) {
|
||||
if (provider.exerciseImages.isNotEmpty) {
|
||||
|
||||
@@ -11,13 +11,14 @@ class Step6Overview extends StatelessWidget {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
return Consumer<AddExerciseProvider>(
|
||||
builder: (ctx, provider, __) => Column(
|
||||
builder: (ctx, provider, _) => Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(i18n.baseData, style: Theme.of(context).textTheme.headlineSmall),
|
||||
Table(
|
||||
columnWidths: const {0: FlexColumnWidth(2), 1: FlexColumnWidth(3)},
|
||||
children: [
|
||||
TableRow(children: [Text(i18n.author), Text(provider.author)]),
|
||||
TableRow(children: [Text(i18n.name), Text(provider.exerciseNameEn ?? '...')]),
|
||||
TableRow(
|
||||
children: [
|
||||
|
||||
@@ -8,13 +8,13 @@ import 'dart:ui' as _i15;
|
||||
|
||||
import 'package:http/http.dart' as _i5;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:mockito/src/dummies.dart' as _i11;
|
||||
import 'package:mockito/src/dummies.dart' as _i8;
|
||||
import 'package:wger/models/exercises/category.dart' as _i13;
|
||||
import 'package:wger/models/exercises/equipment.dart' as _i8;
|
||||
import 'package:wger/models/exercises/exercise_submission.dart' as _i10;
|
||||
import 'package:wger/models/exercises/equipment.dart' as _i9;
|
||||
import 'package:wger/models/exercises/exercise_submission.dart' as _i11;
|
||||
import 'package:wger/models/exercises/exercise_submission_images.dart' as _i7;
|
||||
import 'package:wger/models/exercises/language.dart' as _i12;
|
||||
import 'package:wger/models/exercises/muscle.dart' as _i9;
|
||||
import 'package:wger/models/exercises/muscle.dart' as _i10;
|
||||
import 'package:wger/models/exercises/variation.dart' as _i3;
|
||||
import 'package:wger/providers/add_exercise.dart' as _i6;
|
||||
import 'package:wger/providers/auth.dart' as _i4;
|
||||
@@ -84,6 +84,14 @@ class MockAddExerciseProvider extends _i1.Mock implements _i6.AddExerciseProvide
|
||||
)
|
||||
as List<_i7.ExerciseSubmissionImage>);
|
||||
|
||||
@override
|
||||
String get author =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#author),
|
||||
returnValue: _i8.dummyValue<String>(this, Invocation.getter(#author)),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
List<String> get alternateNamesEn =>
|
||||
(super.noSuchMethod(Invocation.getter(#alternateNamesEn), returnValue: <String>[])
|
||||
@@ -95,9 +103,9 @@ class MockAddExerciseProvider extends _i1.Mock implements _i6.AddExerciseProvide
|
||||
as List<String>);
|
||||
|
||||
@override
|
||||
List<_i8.Equipment> get equipment =>
|
||||
(super.noSuchMethod(Invocation.getter(#equipment), returnValue: <_i8.Equipment>[])
|
||||
as List<_i8.Equipment>);
|
||||
List<_i9.Equipment> get equipment =>
|
||||
(super.noSuchMethod(Invocation.getter(#equipment), returnValue: <_i9.Equipment>[])
|
||||
as List<_i9.Equipment>);
|
||||
|
||||
@override
|
||||
bool get newVariation =>
|
||||
@@ -112,25 +120,29 @@ class MockAddExerciseProvider extends _i1.Mock implements _i6.AddExerciseProvide
|
||||
as _i3.Variation);
|
||||
|
||||
@override
|
||||
List<_i9.Muscle> get primaryMuscles =>
|
||||
(super.noSuchMethod(Invocation.getter(#primaryMuscles), returnValue: <_i9.Muscle>[])
|
||||
as List<_i9.Muscle>);
|
||||
List<_i10.Muscle> get primaryMuscles =>
|
||||
(super.noSuchMethod(Invocation.getter(#primaryMuscles), returnValue: <_i10.Muscle>[])
|
||||
as List<_i10.Muscle>);
|
||||
|
||||
@override
|
||||
List<_i9.Muscle> get secondaryMuscles =>
|
||||
(super.noSuchMethod(Invocation.getter(#secondaryMuscles), returnValue: <_i9.Muscle>[])
|
||||
as List<_i9.Muscle>);
|
||||
List<_i10.Muscle> get secondaryMuscles =>
|
||||
(super.noSuchMethod(Invocation.getter(#secondaryMuscles), returnValue: <_i10.Muscle>[])
|
||||
as List<_i10.Muscle>);
|
||||
|
||||
@override
|
||||
_i10.ExerciseSubmissionApi get exerciseApiObject =>
|
||||
_i11.ExerciseSubmissionApi get exerciseApiObject =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#exerciseApiObject),
|
||||
returnValue: _i11.dummyValue<_i10.ExerciseSubmissionApi>(
|
||||
returnValue: _i8.dummyValue<_i11.ExerciseSubmissionApi>(
|
||||
this,
|
||||
Invocation.getter(#exerciseApiObject),
|
||||
),
|
||||
)
|
||||
as _i10.ExerciseSubmissionApi);
|
||||
as _i11.ExerciseSubmissionApi);
|
||||
|
||||
@override
|
||||
set author(String? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#author, value), returnValueForMissingStub: null);
|
||||
|
||||
@override
|
||||
set exerciseNameEn(String? value) => super.noSuchMethod(
|
||||
@@ -181,7 +193,7 @@ class MockAddExerciseProvider extends _i1.Mock implements _i6.AddExerciseProvide
|
||||
super.noSuchMethod(Invocation.setter(#category, value), returnValueForMissingStub: null);
|
||||
|
||||
@override
|
||||
set equipment(List<_i8.Equipment>? equipment) =>
|
||||
set equipment(List<_i9.Equipment>? equipment) =>
|
||||
super.noSuchMethod(Invocation.setter(#equipment, equipment), returnValueForMissingStub: null);
|
||||
|
||||
@override
|
||||
@@ -197,13 +209,13 @@ class MockAddExerciseProvider extends _i1.Mock implements _i6.AddExerciseProvide
|
||||
);
|
||||
|
||||
@override
|
||||
set primaryMuscles(List<_i9.Muscle>? muscles) => super.noSuchMethod(
|
||||
set primaryMuscles(List<_i10.Muscle>? muscles) => super.noSuchMethod(
|
||||
Invocation.setter(#primaryMuscles, muscles),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set secondaryMuscles(List<_i9.Muscle>? muscles) => super.noSuchMethod(
|
||||
set secondaryMuscles(List<_i10.Muscle>? muscles) => super.noSuchMethod(
|
||||
Invocation.setter(#secondaryMuscles, muscles),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@@ -8,13 +8,13 @@ import 'dart:ui' as _i16;
|
||||
|
||||
import 'package:flutter/material.dart' as _i18;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:mockito/src/dummies.dart' as _i14;
|
||||
import 'package:mockito/src/dummies.dart' as _i13;
|
||||
import 'package:shared_preferences/shared_preferences.dart' as _i4;
|
||||
import 'package:wger/database/exercises/exercise_database.dart' as _i5;
|
||||
import 'package:wger/models/exercises/category.dart' as _i7;
|
||||
import 'package:wger/models/exercises/equipment.dart' as _i8;
|
||||
import 'package:wger/models/exercises/exercise.dart' as _i6;
|
||||
import 'package:wger/models/exercises/exercise_submission.dart' as _i13;
|
||||
import 'package:wger/models/exercises/exercise_submission.dart' as _i14;
|
||||
import 'package:wger/models/exercises/exercise_submission_images.dart' as _i12;
|
||||
import 'package:wger/models/exercises/language.dart' as _i10;
|
||||
import 'package:wger/models/exercises/muscle.dart' as _i9;
|
||||
@@ -104,6 +104,14 @@ class MockAddExerciseProvider extends _i1.Mock implements _i11.AddExerciseProvid
|
||||
)
|
||||
as List<_i12.ExerciseSubmissionImage>);
|
||||
|
||||
@override
|
||||
String get author =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#author),
|
||||
returnValue: _i13.dummyValue<String>(this, Invocation.getter(#author)),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
List<String> get alternateNamesEn =>
|
||||
(super.noSuchMethod(Invocation.getter(#alternateNamesEn), returnValue: <String>[])
|
||||
@@ -142,15 +150,19 @@ class MockAddExerciseProvider extends _i1.Mock implements _i11.AddExerciseProvid
|
||||
as List<_i9.Muscle>);
|
||||
|
||||
@override
|
||||
_i13.ExerciseSubmissionApi get exerciseApiObject =>
|
||||
_i14.ExerciseSubmissionApi get exerciseApiObject =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#exerciseApiObject),
|
||||
returnValue: _i14.dummyValue<_i13.ExerciseSubmissionApi>(
|
||||
returnValue: _i13.dummyValue<_i14.ExerciseSubmissionApi>(
|
||||
this,
|
||||
Invocation.getter(#exerciseApiObject),
|
||||
),
|
||||
)
|
||||
as _i13.ExerciseSubmissionApi);
|
||||
as _i14.ExerciseSubmissionApi);
|
||||
|
||||
@override
|
||||
set author(String? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#author, value), returnValueForMissingStub: null);
|
||||
|
||||
@override
|
||||
set exerciseNameEn(String? value) => super.noSuchMethod(
|
||||
|
||||
Reference in New Issue
Block a user