Skip to content

Add simple WYSIWYG editor for exercise descriptions #189 #283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions lib/helpers/i18n.dart
Original file line number Diff line number Diff line change
@@ -99,6 +99,7 @@ String getTranslation(String value, BuildContext context) {

case 'Legs':
return AppLocalizations.of(context).legs;

default:
return 'NOT TRANSLATED';
}
14 changes: 8 additions & 6 deletions lib/providers/exercises.dart
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/main.dart';
import 'package:wger/models/exercises/alias.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/exercises/category.dart';
@@ -37,6 +38,7 @@ import 'package:wger/models/exercises/muscle.dart';
import 'package:wger/models/exercises/variation.dart';
import 'package:wger/models/exercises/video.dart';
import 'package:wger/providers/base_provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class ExercisesProvider with ChangeNotifier {
final WgerBaseProvider baseProvider;
@@ -104,23 +106,23 @@ class ExercisesProvider with ChangeNotifier {
}

// Initialize filters for exercises search in exercises list
void _initFilters() {
void _initFilters(BuildContext context) {
if (_muscles.isEmpty || _equipment.isEmpty || _filters != null) {
return;
}

setFilters(
Filters(
exerciseCategories: FilterCategory<ExerciseCategory>(
title: 'Category',
title: AppLocalizations.of(context).category,
items: Map.fromEntries(
_categories.map(
(category) => MapEntry<ExerciseCategory, bool>(category, false),
),
),
),
equipment: FilterCategory<Equipment>(
title: 'Equipment',
title: AppLocalizations.of(context).equipment,
items: Map.fromEntries(
_equipment.map(
(singleEquipment) => MapEntry<Equipment, bool>(singleEquipment, false),
@@ -367,7 +369,7 @@ class ExercisesProvider with ChangeNotifier {
}
}

Future<void> fetchAndSetExercises() async {
Future<void> fetchAndSetExercises(BuildContext context) async {
clear();

// Load exercises from cache, if available
@@ -384,7 +386,7 @@ class ExercisesProvider with ChangeNotifier {
cacheData['variations'].forEach((e) => _variations.add(Variation.fromJson(e)));
cacheData['bases'].forEach((e) => _exerciseBases.add(readExerciseBaseFromBaseInfo(e)));

_initFilters();
_initFilters(context);
log("Read ${_exerciseBases.length} exercises from cache. Valid till ${cacheData['expiresIn']}");
return;
}
@@ -420,7 +422,7 @@ class ExercisesProvider with ChangeNotifier {
log("Saved ${_exerciseBases.length} exercises to cache. Valid till ${cacheData['expiresIn']}");

await prefs.setString(PREFS_EXERCISES, json.encode(cacheData));
_initFilters();
_initFilters(context);
notifyListeners();
} on MissingRequiredKeysException catch (error) {
log(error.missingKeys.toString());
2 changes: 1 addition & 1 deletion lib/screens/home_tabs_screen.dart
Original file line number Diff line number Diff line change
@@ -89,7 +89,7 @@ class _HomeTabsScreenState extends State<HomeTabsScreen> with SingleTickerProvid
userProvider.fetchAndSetProfile(),
workoutPlansProvider.fetchAndSetUnits(),
nutritionPlansProvider.fetchIngredientsFromCache(),
exercisesProvider.fetchAndSetExercises(),
exercisesProvider.fetchAndSetExercises(context),
]);

// Plans, weight and gallery
5 changes: 3 additions & 2 deletions lib/screens/splash_screen.dart
Original file line number Diff line number Diff line change
@@ -17,13 +17,14 @@
*/

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class SplashScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Scaffold(
return Scaffold(
body: Center(
child: Text('Loading...'),
child: Text(AppLocalizations.of(context).loadingText),
),
);
}
77 changes: 77 additions & 0 deletions lib/widgets/add_exercise/add_exercise_html_editor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'dart:async';
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:provider/provider.dart';
import 'package:wger/providers/add_exercise.dart';

class AddExerciseHtmlEditor extends StatefulWidget {
const AddExerciseHtmlEditor({
Key? key,
this.helperText = '',
}) : super(key: key);

final String helperText;

@override
_AddExerciseHtmlEditorState createState() => _AddExerciseHtmlEditorState();
}

class _AddExerciseHtmlEditorState extends State<AddExerciseHtmlEditor> {
@override
Widget build(BuildContext context) {
final addExerciseProvider = context.read<AddExerciseProvider>();
final HtmlEditorController editorController = HtmlEditorController(
processInputHtml: true,
processNewLineAsBr: false,
processOutputHtml: true
);
return Padding(
padding: const EdgeInsets.all(8.0),
child: HtmlEditor(
controller: editorController,
htmlToolbarOptions: const HtmlToolbarOptions(
toolbarPosition: ToolbarPosition.belowEditor,
toolbarType: ToolbarType.nativeScrollable,
defaultToolbarButtons: [
FontButtons(bold: true,
underline: true,
italic: true,
strikethrough: false,
superscript: false,
subscript: false,
clearAll: false
),
ListButtons(
ol: true,
ul: true,
listStyles: false
),
ParagraphButtons(
textDirection: false,
lineHeight: false,
caseConverter: false,
increaseIndent: false,
decreaseIndent: false,
alignLeft: false,
alignCenter: false,
alignRight: false,
alignJustify: false
),
]
),
htmlEditorOptions: HtmlEditorOptions(
hint: widget.helperText,
shouldEnsureVisible: true,
),
otherOptions: const OtherOptions(
height: 200,
),
callbacks: Callbacks(onChangeContent: (String? currentHtml) {
addExerciseProvider.descriptionEn = currentHtml!;
},
),
),
);
}
}
12 changes: 3 additions & 9 deletions lib/widgets/add_exercise/steps/step3description.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
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.dart';
import 'package:wger/widgets/add_exercise/add_exercise_text_area.dart';
import 'package:wger/widgets/add_exercise/add_exercise_html_editor.dart';

class Step3Description extends StatelessWidget {
final GlobalKey<FormState> formkey;
@@ -17,13 +16,8 @@ class Step3Description extends StatelessWidget {
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!,
AddExerciseHtmlEditor(
helperText: AppLocalizations.of(context).description,
),
],
),
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ dependencies:
carousel_slider: ^4.1.1
multi_select_flutter: ^4.1.2
flutter_svg: ^0.23.0+1
html_editor_enhanced: ^2.5.0

dev_dependencies:
flutter_test:
2 changes: 1 addition & 1 deletion test/workout/gym_mode_screen_test.mocks.dart
Original file line number Diff line number Diff line change
@@ -358,7 +358,7 @@ class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
returnValueForMissingStub: _i9.Future<void>.value(),
) as _i9.Future<void>);
@override
_i9.Future<void> fetchAndSetExercises() => (super.noSuchMethod(
_i9.Future<void> fetchAndSetExercises(context) => (super.noSuchMethod(
Invocation.method(
#fetchAndSetExercises,
[],
2 changes: 1 addition & 1 deletion test/workout/workout_set_form_test.mocks.dart
Original file line number Diff line number Diff line change
@@ -358,7 +358,7 @@ class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
returnValueForMissingStub: _i9.Future<void>.value(),
) as _i9.Future<void>);
@override
_i9.Future<void> fetchAndSetExercises() => (super.noSuchMethod(
_i9.Future<void> fetchAndSetExercises(context) => (super.noSuchMethod(
Invocation.method(
#fetchAndSetExercises,
[],