Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -515,5 +515,6 @@
"description": "Snackbar message to show on copying data to a new log entry"
},
"appUpdateTitle" : "Update needed",
"appUpdateContent" : "This version of the app is not compatible with the server, please update your application."
"appUpdateContent" : "This version of the app is not compatible with the server, please update your application.",
"add_excercise_image_license": "Images must be compatible with the CC BY SA_license. If in doubt, upload only photos you've taken yourself."
}
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import 'package:wger/screens/workout_plan_screen.dart';
import 'package:wger/screens/workout_plans_screen.dart';
import 'package:wger/theme/theme.dart';

import 'providers/add_excercise_provider.dart';
import 'providers/auth.dart';

void main() {
Expand Down Expand Up @@ -102,6 +103,9 @@ class MyApp extends StatelessWidget {
GalleryProvider(Provider.of<AuthProvider>(context, listen: false), []),
update: (context, auth, previous) => previous ?? GalleryProvider(auth, []),
),
ChangeNotifierProvider(
create: (_) => AddExcerciseProvider(),
)
],
child: Consumer<AuthProvider>(
builder: (ctx, auth, _) => MaterialApp(
Expand Down
19 changes: 19 additions & 0 deletions lib/providers/add_excercise_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flutter/foundation.dart';

import 'dart:io';

class AddExcerciseProvider with ChangeNotifier {
List<File> get excerciseImages => [..._excerciseImages];
final List<File> _excerciseImages = [];

void addExcerciseImages(List<File> excercizes) {
_excerciseImages.addAll(excercizes);
notifyListeners();
}

void removeExcercise(String path) {
final file = _excerciseImages.where((element) => element.path == path).first;
_excerciseImages.remove(file);
notifyListeners();
}
}
58 changes: 40 additions & 18 deletions lib/screens/add_exercise_screen.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:wger/providers/add_excercise_provider.dart';
import 'package:wger/widgets/add_exercise/add_exercise_dropdown_button.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/core/app_bar.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class AddExerciseScreen extends StatefulWidget {
const AddExerciseScreen({Key? key}) : super(key: key);
Expand Down Expand Up @@ -148,28 +156,42 @@ class _DuplicatesAndVariationsStepContent extends StatelessWidget {
}
}

class _ImagesStepContent extends StatelessWidget {
final GlobalKey<FormState> _imagesStepFormKey = GlobalKey<FormState>();
class _ImagesStepContent extends StatefulWidget {
@override
State<_ImagesStepContent> createState() => _ImagesStepContentState();
}

class _ImagesStepContentState extends State<_ImagesStepContent> with ExcerciseImagePickerMixin {
final GlobalKey<FormState> _imagesStepFormKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _imagesStepFormKey,
child: Column(
children: [
AddExerciseTextArea(
onChange: (value) => print(value),
title: 'Name',
isRequired: true,
),
AddExerciseTextArea(
onChange: (value) => print(value),
title: 'Alternative names',
isMultiline: true,
helperText: 'One name per line',
return Column(
children: [
Text(
AppLocalizations.of(context).add_excercise_image_license,
style: Theme.of(context).textTheme.caption,
),
Consumer<AddExcerciseProvider>(
builder: (ctx, provider, __) => provider.excerciseImages.isNotEmpty
? PreviewExcercizeImages(
selectedimages: provider.excerciseImages,
)
: ElevatedButton(
onPressed: () => pickImages(context),
child: const Text('BROWSE FOR FILES'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) => Colors.black)),
),
),
RichText(
text: TextSpan(
style: Theme.of(context).textTheme.caption,
children: const <TextSpan>[
TextSpan(text: 'Only JPEG, PNG and WEBP files below 20 MB are supported'),
],
),
],
),
)
],
);
}
}
Expand Down
51 changes: 51 additions & 0 deletions lib/widgets/add_exercise/mixins/image_picker_mixin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:wger/providers/add_excercise_provider.dart';

const validFileExtensions = ['jpg', 'jpeg', 'png', 'webp'];
const maxFileSize = 20;

mixin ExcerciseImagePickerMixin {
bool _validateFileType(int fileLength) {
final kb = fileLength / 1024;
final mb = kb / 1024;
return mb > maxFileSize;
}

bool _validateFileSize(File file) {
final extension = file.path.split('.').last;
return validFileExtensions.any((element) => extension == element.toLowerCase());
}

void pickImages(BuildContext context) async {
final imagePicker = ImagePicker();
final images = await imagePicker.pickMultiImage();
final selectedImages = <File>[];
if (images != null) {
selectedImages.addAll(images.map((e) => File(e.path)).toList());

for (final image in selectedImages) {
bool isFileValid = true;
String errorMessage = '';

if (!_validateFileSize(image)) {
isFileValid = false;
errorMessage = "Select only 'jpg', 'jpeg', 'png', 'webp' files";
}
if (_validateFileType(image.lengthSync())) {
isFileValid = true;
errorMessage = 'File Size should not be greater than 20 mb';
}

if (!isFileValid) {
showDialog(context: context, builder: (context) => Text(errorMessage));
return;
}
}
context.read<AddExcerciseProvider>().addExcerciseImages(selectedImages);
}
}
}
74 changes: 74 additions & 0 deletions lib/widgets/add_exercise/preview_images.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/widgets/add_exercise/mixins/image_picker_mixin.dart';
import '../../providers/add_excercise_provider.dart';

class PreviewExcercizeImages extends StatelessWidget with ExcerciseImagePickerMixin {
PreviewExcercizeImages({
Key? key,
required this.selectedimages,
}) : super(key: key);

final List<File> selectedimages;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 300,
child: ListView(scrollDirection: Axis.horizontal, children: [
...selectedimages
.map(
(file) => SizedBox(
height: 200,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
children: [
Image.file(file),
Positioned(
bottom: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(3.0),
child: Container(
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.5),
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: IconButton(
iconSize: 20,
onPressed: () =>
context.read<AddExcerciseProvider>().removeExcercise(file.path),
color: Colors.white,
icon: const Icon(Icons.delete),
),
),
),
),
],
),
),
),
)
.toList(),
const SizedBox(
width: 10,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.grey,
height: 200,
width: 100,
child: Center(
child: IconButton(
icon: const Icon(Icons.add),
onPressed: () => pickImages(context),
),
),
),
)
]),
);
}
}