mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Merge pull request #989 from wger-project/feature/gym-mode
Gym mode improvements
This commit is contained in:
@@ -17,19 +17,19 @@ subprojects {
|
||||
afterEvaluate { project ->
|
||||
if (project.extensions.findByName("android") != null) {
|
||||
Integer pluginCompileSdk = project.android.compileSdk
|
||||
if (pluginCompileSdk != null && pluginCompileSdk < 31) {
|
||||
if (pluginCompileSdk != null && pluginCompileSdk < 34) {
|
||||
project.logger.error(
|
||||
"Warning: Overriding compileSdk version in Flutter plugin: "
|
||||
+ project.name
|
||||
+ " from "
|
||||
+ pluginCompileSdk
|
||||
+ " to 31 (to work around https://issuetracker.google.com/issues/199180389)."
|
||||
+ " to 36 (to work around https://issuetracker.google.com/issues/199180389)."
|
||||
+ "\nIf there is not a new version of " + project.name + ", consider filing an issue against "
|
||||
+ project.name
|
||||
+ " to increase their compileSdk to the latest (otherwise try updating to the latest version)."
|
||||
)
|
||||
project.android {
|
||||
compileSdk 34
|
||||
compileSdk 36
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx2048M
|
||||
android.enableR8=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip
|
||||
@@ -18,8 +18,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.6.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.20" apply false
|
||||
id "com.android.application" version "8.13.1" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.2.21" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/screens/gym_mode.dart';
|
||||
import 'package:wger/screens/routine_screen.dart';
|
||||
import 'package:wger/theme/theme.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/summary.dart';
|
||||
|
||||
import '../test/routine/gym_mode_screen_test.mocks.dart';
|
||||
import '../test/routine/gym_mode/gym_mode_test.mocks.dart';
|
||||
import '../test_data/exercises.dart';
|
||||
import '../test_data/routines.dart';
|
||||
|
||||
@@ -26,8 +29,6 @@ Widget createGymModeScreen({Locale? locale}) {
|
||||
|
||||
when(mockExerciseProvider.findExerciseById(1)).thenReturn(exercises[0]); // bench press
|
||||
when(mockExerciseProvider.findExerciseById(6)).thenReturn(exercises[5]); // side raises
|
||||
//when(mockExerciseProvider.findExerciseBaseById(2)).thenReturn(bases[1]); // crunches
|
||||
//when(mockExerciseProvider.findExerciseBaseById(3)).thenReturn(bases[2]); // dead lift
|
||||
|
||||
return MediaQuery(
|
||||
data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first).copyWith(
|
||||
@@ -71,3 +72,59 @@ Widget createGymModeScreen({Locale? locale}) {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget createGymModeResultsScreen({String locale = 'en'}) {
|
||||
final controller = PageController(initialPage: 0);
|
||||
|
||||
final key = GlobalKey<NavigatorState>();
|
||||
final routine = getTestRoutine(exercises: getScreenshotExercises());
|
||||
routine.sessions.first.session.date = clock.now();
|
||||
|
||||
final mockRoutinesProvider = MockRoutinesProvider();
|
||||
final mockExerciseProvider = MockExercisesProvider();
|
||||
|
||||
when(mockRoutinesProvider.fetchAndSetRoutineFull(1)).thenAnswer((_) async => routine);
|
||||
when(mockRoutinesProvider.findById(1)).thenAnswer((_) => routine);
|
||||
|
||||
return riverpod.UncontrolledProviderScope(
|
||||
container: riverpod.ProviderContainer.test(
|
||||
overrides: [
|
||||
gymStateProvider.overrideWithValue(
|
||||
GymModeState(
|
||||
routine: routine,
|
||||
dayId: routine.days.first.id!,
|
||||
iteration: 1,
|
||||
showExercisePages: true,
|
||||
showTimerPages: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<RoutinesProvider>(
|
||||
create: (context) => mockRoutinesProvider,
|
||||
),
|
||||
ChangeNotifierProvider<ExercisesProvider>(
|
||||
create: (context) => mockExerciseProvider,
|
||||
),
|
||||
],
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
navigatorKey: key,
|
||||
theme: wgerLightTheme,
|
||||
home: Scaffold(
|
||||
body: PageView(
|
||||
controller: controller,
|
||||
children: [
|
||||
WorkoutSummary(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,14 +78,16 @@ const languages = [
|
||||
'en-US',
|
||||
'es-ES',
|
||||
|
||||
'fa-IR',
|
||||
'fr-FR',
|
||||
'hi-IN',
|
||||
'hr',
|
||||
'it-IT',
|
||||
'iw-IL',
|
||||
'ko-KR',
|
||||
'nb-NO',
|
||||
'pl-PL',
|
||||
|
||||
'pl-PL',
|
||||
'pt-BR',
|
||||
'pt-PT',
|
||||
'ru-RU',
|
||||
@@ -128,9 +130,19 @@ void main() {
|
||||
await takeScreenshot(tester, binding, language, '02 - workout detail');
|
||||
});
|
||||
|
||||
testWidgets('gym mode screen - $language', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(createGymModeScreen(locale: locale));
|
||||
await tester.tap(find.byType(TextButton));
|
||||
// testWidgets('gym mode screen - $language', (WidgetTester tester) async {
|
||||
// await tester.pumpWidget(createGymModeScreen(locale: locale));
|
||||
// await tester.tap(find.byType(TextButton));
|
||||
// await tester.pumpAndSettle();
|
||||
// await tester.tap(find.byKey(const ValueKey('gym-mode-options-tile')));
|
||||
// await tester.pumpAndSettle();
|
||||
// await tester.tap(find.byKey(const ValueKey('gym-mode-option-show-exercises')));
|
||||
// await tester.pumpAndSettle();
|
||||
// await takeScreenshot(tester, binding, language, '03 - gym mode');
|
||||
// });
|
||||
|
||||
testWidgets('gym mode stats screen - $language', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(createGymModeResultsScreen(locale: languageCode));
|
||||
await tester.pumpAndSettle();
|
||||
await takeScreenshot(tester, binding, language, '03 - gym mode');
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,7 @@ class $IngredientsTable extends Ingredients with TableInfo<$IngredientsTable, In
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
|
||||
$IngredientsTable(this.attachedDatabase, [this._alias]);
|
||||
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
@@ -28,7 +26,9 @@ class $IngredientsTable extends Ingredients with TableInfo<$IngredientsTable, In
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const VerificationMeta _lastFetchedMeta = const VerificationMeta('lastFetched');
|
||||
static const VerificationMeta _lastFetchedMeta = const VerificationMeta(
|
||||
'lastFetched',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<DateTime> lastFetched = GeneratedColumn<DateTime>(
|
||||
'last_fetched',
|
||||
@@ -37,17 +37,13 @@ class $IngredientsTable extends Ingredients with TableInfo<$IngredientsTable, In
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, data, lastFetched];
|
||||
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'ingredients';
|
||||
|
||||
@override
|
||||
VerificationContext validateIntegrity(
|
||||
Insertable<IngredientTable> instance, {
|
||||
@@ -61,14 +57,20 @@ class $IngredientsTable extends Ingredients with TableInfo<$IngredientsTable, In
|
||||
context.missing(_idMeta);
|
||||
}
|
||||
if (data.containsKey('data')) {
|
||||
context.handle(_dataMeta, this.data.isAcceptableOrUnknown(data['data']!, _dataMeta));
|
||||
context.handle(
|
||||
_dataMeta,
|
||||
this.data.isAcceptableOrUnknown(data['data']!, _dataMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_dataMeta);
|
||||
}
|
||||
if (data.containsKey('last_fetched')) {
|
||||
context.handle(
|
||||
_lastFetchedMeta,
|
||||
lastFetched.isAcceptableOrUnknown(data['last_fetched']!, _lastFetchedMeta),
|
||||
lastFetched.isAcceptableOrUnknown(
|
||||
data['last_fetched']!,
|
||||
_lastFetchedMeta,
|
||||
),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_lastFetchedMeta);
|
||||
@@ -78,13 +80,18 @@ class $IngredientsTable extends Ingredients with TableInfo<$IngredientsTable, In
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => const {};
|
||||
|
||||
@override
|
||||
IngredientTable map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return IngredientTable(
|
||||
id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||
data: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}data'])!,
|
||||
id: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}id'],
|
||||
)!,
|
||||
data: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}data'],
|
||||
)!,
|
||||
lastFetched: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}last_fetched'],
|
||||
@@ -104,9 +111,11 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
|
||||
|
||||
/// The date when the ingredient was last fetched from the server
|
||||
final DateTime lastFetched;
|
||||
|
||||
const IngredientTable({required this.id, required this.data, required this.lastFetched});
|
||||
|
||||
const IngredientTable({
|
||||
required this.id,
|
||||
required this.data,
|
||||
required this.lastFetched,
|
||||
});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
@@ -117,10 +126,17 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
|
||||
}
|
||||
|
||||
IngredientsCompanion toCompanion(bool nullToAbsent) {
|
||||
return IngredientsCompanion(id: Value(id), data: Value(data), lastFetched: Value(lastFetched));
|
||||
return IngredientsCompanion(
|
||||
id: Value(id),
|
||||
data: Value(data),
|
||||
lastFetched: Value(lastFetched),
|
||||
);
|
||||
}
|
||||
|
||||
factory IngredientTable.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) {
|
||||
factory IngredientTable.fromJson(
|
||||
Map<String, dynamic> json, {
|
||||
ValueSerializer? serializer,
|
||||
}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return IngredientTable(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
@@ -128,7 +144,6 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
|
||||
lastFetched: serializer.fromJson<DateTime>(json['lastFetched']),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
@@ -144,7 +159,6 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
|
||||
data: data ?? this.data,
|
||||
lastFetched: lastFetched ?? this.lastFetched,
|
||||
);
|
||||
|
||||
IngredientTable copyWithCompanion(IngredientsCompanion data) {
|
||||
return IngredientTable(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
@@ -165,7 +179,6 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, data, lastFetched);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
@@ -180,14 +193,12 @@ class IngredientsCompanion extends UpdateCompanion<IngredientTable> {
|
||||
final Value<String> data;
|
||||
final Value<DateTime> lastFetched;
|
||||
final Value<int> rowid;
|
||||
|
||||
const IngredientsCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.data = const Value.absent(),
|
||||
this.lastFetched = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
|
||||
IngredientsCompanion.insert({
|
||||
required int id,
|
||||
required String data,
|
||||
@@ -196,7 +207,6 @@ class IngredientsCompanion extends UpdateCompanion<IngredientTable> {
|
||||
}) : id = Value(id),
|
||||
data = Value(data),
|
||||
lastFetched = Value(lastFetched);
|
||||
|
||||
static Insertable<IngredientTable> custom({
|
||||
Expression<int>? id,
|
||||
Expression<String>? data,
|
||||
@@ -257,14 +267,11 @@ class IngredientsCompanion extends UpdateCompanion<IngredientTable> {
|
||||
|
||||
abstract class _$IngredientDatabase extends GeneratedDatabase {
|
||||
_$IngredientDatabase(QueryExecutor e) : super(e);
|
||||
|
||||
$IngredientDatabaseManager get managers => $IngredientDatabaseManager(this);
|
||||
late final $IngredientsTable ingredients = $IngredientsTable(this);
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [ingredients];
|
||||
}
|
||||
@@ -292,15 +299,20 @@ class $$IngredientsTableFilterComposer extends Composer<_$IngredientDatabase, $I
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<int> get id => $composableBuilder(
|
||||
column: $table.id,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<int> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
ColumnFilters<String> get data => $composableBuilder(
|
||||
column: $table.data,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get data =>
|
||||
$composableBuilder(column: $table.data, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<DateTime> get lastFetched =>
|
||||
$composableBuilder(column: $table.lastFetched, builder: (column) => ColumnFilters(column));
|
||||
ColumnFilters<DateTime> get lastFetched => $composableBuilder(
|
||||
column: $table.lastFetched,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$IngredientsTableOrderingComposer extends Composer<_$IngredientDatabase, $IngredientsTable> {
|
||||
@@ -311,15 +323,20 @@ class $$IngredientsTableOrderingComposer extends Composer<_$IngredientDatabase,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<int> get id => $composableBuilder(
|
||||
column: $table.id,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<int> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
ColumnOrderings<String> get data => $composableBuilder(
|
||||
column: $table.data,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get data =>
|
||||
$composableBuilder(column: $table.data, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DateTime> get lastFetched =>
|
||||
$composableBuilder(column: $table.lastFetched, builder: (column) => ColumnOrderings(column));
|
||||
ColumnOrderings<DateTime> get lastFetched => $composableBuilder(
|
||||
column: $table.lastFetched,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$IngredientsTableAnnotationComposer
|
||||
@@ -331,14 +348,15 @@ class $$IngredientsTableAnnotationComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
|
||||
GeneratedColumn<int> get id => $composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get data =>
|
||||
$composableBuilder(column: $table.data, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get lastFetched =>
|
||||
$composableBuilder(column: $table.lastFetched, builder: (column) => column);
|
||||
GeneratedColumn<DateTime> get lastFetched => $composableBuilder(
|
||||
column: $table.lastFetched,
|
||||
builder: (column) => column,
|
||||
);
|
||||
}
|
||||
|
||||
class $$IngredientsTableTableManager
|
||||
@@ -359,8 +377,10 @@ class $$IngredientsTableTableManager
|
||||
IngredientTable,
|
||||
PrefetchHooks Function()
|
||||
> {
|
||||
$$IngredientsTableTableManager(_$IngredientDatabase db, $IngredientsTable table)
|
||||
: super(
|
||||
$$IngredientsTableTableManager(
|
||||
_$IngredientDatabase db,
|
||||
$IngredientsTable table,
|
||||
) : super(
|
||||
TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
@@ -374,8 +394,12 @@ class $$IngredientsTableTableManager
|
||||
Value<String> data = const Value.absent(),
|
||||
Value<DateTime> lastFetched = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
IngredientsCompanion(id: id, data: data, lastFetched: lastFetched, rowid: rowid),
|
||||
}) => IngredientsCompanion(
|
||||
id: id,
|
||||
data: data,
|
||||
lastFetched: lastFetched,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
required int id,
|
||||
@@ -412,9 +436,7 @@ typedef $$IngredientsTableProcessedTableManager =
|
||||
|
||||
class $IngredientDatabaseManager {
|
||||
final _$IngredientDatabase _db;
|
||||
|
||||
$IngredientDatabaseManager(this._db);
|
||||
|
||||
$$IngredientsTableTableManager get ingredients =>
|
||||
$$IngredientsTableTableManager(_db, _db.ingredients);
|
||||
}
|
||||
|
||||
@@ -9,126 +9,127 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
String getTranslation(String value, BuildContext context) {
|
||||
final logger = Logger('getTranslation');
|
||||
String getServerStringTranslation(String value, BuildContext context) {
|
||||
final logger = Logger('getServerStringTranslation');
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
switch (value) {
|
||||
case 'Abs':
|
||||
return AppLocalizations.of(context).abs;
|
||||
return i18n.abs;
|
||||
|
||||
case 'Arms':
|
||||
return AppLocalizations.of(context).arms;
|
||||
return i18n.arms;
|
||||
|
||||
case 'Back':
|
||||
return AppLocalizations.of(context).back;
|
||||
return i18n.back;
|
||||
|
||||
case 'Barbell':
|
||||
return AppLocalizations.of(context).barbell;
|
||||
return i18n.barbell;
|
||||
|
||||
case 'Bench':
|
||||
return AppLocalizations.of(context).bench;
|
||||
return i18n.bench;
|
||||
|
||||
case 'Biceps':
|
||||
return AppLocalizations.of(context).biceps;
|
||||
return i18n.biceps;
|
||||
|
||||
case 'Body Weight':
|
||||
return AppLocalizations.of(context).body_weight;
|
||||
return i18n.body_weight;
|
||||
|
||||
case 'Calves':
|
||||
return AppLocalizations.of(context).calves;
|
||||
return i18n.calves;
|
||||
|
||||
case 'Cardio':
|
||||
return AppLocalizations.of(context).cardio;
|
||||
return i18n.cardio;
|
||||
|
||||
case 'Chest':
|
||||
return AppLocalizations.of(context).chest;
|
||||
return i18n.chest;
|
||||
|
||||
case 'Dumbbell':
|
||||
return AppLocalizations.of(context).dumbbell;
|
||||
return i18n.dumbbell;
|
||||
|
||||
case 'Glutes':
|
||||
return AppLocalizations.of(context).glutes;
|
||||
return i18n.glutes;
|
||||
|
||||
case 'Gym mat':
|
||||
return AppLocalizations.of(context).gym_mat;
|
||||
return i18n.gym_mat;
|
||||
|
||||
case 'Hamstrings':
|
||||
return AppLocalizations.of(context).hamstrings;
|
||||
return i18n.hamstrings;
|
||||
|
||||
case 'Incline bench':
|
||||
return AppLocalizations.of(context).incline_bench;
|
||||
return i18n.incline_bench;
|
||||
|
||||
case 'Kettlebell':
|
||||
return AppLocalizations.of(context).kettlebell;
|
||||
return i18n.kettlebell;
|
||||
|
||||
case 'Kilometers':
|
||||
return AppLocalizations.of(context).kilometers;
|
||||
return i18n.kilometers;
|
||||
|
||||
case 'Kilometers Per Hour':
|
||||
return AppLocalizations.of(context).kilometers_per_hour;
|
||||
return i18n.kilometers_per_hour;
|
||||
|
||||
case 'Lats':
|
||||
return AppLocalizations.of(context).lats;
|
||||
return i18n.lats;
|
||||
|
||||
case 'Legs':
|
||||
return AppLocalizations.of(context).legs;
|
||||
return i18n.legs;
|
||||
|
||||
case 'Lower back':
|
||||
return AppLocalizations.of(context).lower_back;
|
||||
return i18n.lower_back;
|
||||
|
||||
case 'Max Reps':
|
||||
return AppLocalizations.of(context).max_reps;
|
||||
return i18n.max_reps;
|
||||
|
||||
case 'Miles':
|
||||
return AppLocalizations.of(context).miles;
|
||||
return i18n.miles;
|
||||
|
||||
case 'Miles Per Hour':
|
||||
return AppLocalizations.of(context).miles_per_hour;
|
||||
return i18n.miles_per_hour;
|
||||
|
||||
case 'Minutes':
|
||||
return AppLocalizations.of(context).minutes;
|
||||
return i18n.minutes;
|
||||
|
||||
case 'Plates':
|
||||
return AppLocalizations.of(context).plates;
|
||||
return i18n.plates;
|
||||
|
||||
case 'Pull-up bar':
|
||||
return AppLocalizations.of(context).pull_up_bar;
|
||||
return i18n.pull_up_bar;
|
||||
|
||||
case 'Quads':
|
||||
return AppLocalizations.of(context).quads;
|
||||
return i18n.quads;
|
||||
|
||||
case 'Repetitions':
|
||||
return AppLocalizations.of(context).repetitions;
|
||||
return i18n.repetitions;
|
||||
|
||||
case 'Resistance band':
|
||||
return AppLocalizations.of(context).resistance_band;
|
||||
return i18n.resistance_band;
|
||||
|
||||
case 'SZ-Bar':
|
||||
return AppLocalizations.of(context).sz_bar;
|
||||
return i18n.sz_bar;
|
||||
|
||||
case 'Seconds':
|
||||
return AppLocalizations.of(context).seconds;
|
||||
return i18n.seconds;
|
||||
|
||||
case 'Shoulders':
|
||||
return AppLocalizations.of(context).shoulders;
|
||||
return i18n.shoulders;
|
||||
|
||||
case 'Swiss Ball':
|
||||
return AppLocalizations.of(context).swiss_ball;
|
||||
return i18n.swiss_ball;
|
||||
|
||||
case 'Triceps':
|
||||
return AppLocalizations.of(context).triceps;
|
||||
return i18n.triceps;
|
||||
|
||||
case 'Until Failure':
|
||||
return AppLocalizations.of(context).until_failure;
|
||||
return i18n.until_failure;
|
||||
|
||||
case 'kg':
|
||||
return AppLocalizations.of(context).kg;
|
||||
return i18n.kg;
|
||||
|
||||
case 'lb':
|
||||
return AppLocalizations.of(context).lb;
|
||||
return i18n.lb;
|
||||
|
||||
case 'none (bodyweight exercise)':
|
||||
return AppLocalizations.of(context).none__bodyweight_exercise_;
|
||||
return i18n.none__bodyweight_exercise_;
|
||||
|
||||
default:
|
||||
logger.warning('Could not translate the server string $value');
|
||||
|
||||
@@ -23,6 +23,10 @@ num stringToNum(String? e) {
|
||||
return e == null ? 0 : num.parse(e);
|
||||
}
|
||||
|
||||
num? stringToNumNull(String? e) {
|
||||
return e == null ? null : num.parse(e);
|
||||
}
|
||||
|
||||
num stringOrIntToNum(dynamic e) {
|
||||
if (e is int) {
|
||||
return e.toDouble(); // Convert int to double (a type of num)
|
||||
@@ -30,10 +34,6 @@ num stringOrIntToNum(dynamic e) {
|
||||
return num.tryParse(e) ?? 0;
|
||||
}
|
||||
|
||||
num? stringToNumNull(String? e) {
|
||||
return e == null ? null : num.parse(e);
|
||||
}
|
||||
|
||||
String? numToString(num? e) {
|
||||
if (e == null) {
|
||||
return null;
|
||||
|
||||
@@ -18,50 +18,6 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/models/workouts/repetition_unit.dart';
|
||||
import 'package:wger/models/workouts/weight_unit.dart';
|
||||
|
||||
/// Returns the text representation for a single setting, used in the gym mode
|
||||
String repText(
|
||||
num? repetitions,
|
||||
RepetitionUnit? repetitionUnitObj,
|
||||
num? weight,
|
||||
WeightUnit? weightUnitObj,
|
||||
num? rir,
|
||||
) {
|
||||
// TODO(x): how to (easily?) translate strings like the units or 'RiR'
|
||||
|
||||
final List<String> out = [];
|
||||
|
||||
if (repetitions != null) {
|
||||
out.add(formatNum(repetitions).toString());
|
||||
|
||||
// The default repetition unit is 'reps', which we don't show unless there
|
||||
// is no weight defined so that we don't just output something like "8" but
|
||||
// rather "8 repetitions". If there is weight we want to output "8 x 50kg",
|
||||
// since the repetitions are implied. If other units are used, we always
|
||||
// print them
|
||||
if (repetitionUnitObj != null && repetitionUnitObj.id != REP_UNIT_REPETITIONS_ID ||
|
||||
weight == 0 ||
|
||||
weight == null) {
|
||||
out.add(repetitionUnitObj!.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (weight != null && weight != 0) {
|
||||
out.add('×');
|
||||
out.add(formatNum(weight).toString());
|
||||
out.add(weightUnitObj!.name);
|
||||
}
|
||||
|
||||
if (rir != null && rir != '') {
|
||||
out.add('\n');
|
||||
out.add('($rir RiR)');
|
||||
}
|
||||
|
||||
return out.join(' ');
|
||||
}
|
||||
|
||||
void launchURL(String url, BuildContext context) async {
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
|
||||
45
lib/helpers/uuid.dart
Normal file
45
lib/helpers/uuid.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
String uuidV4() {
|
||||
final rnd = Random.secure();
|
||||
final bytes = Uint8List(16);
|
||||
for (var i = 0; i < 16; i++) {
|
||||
bytes[i] = rnd.nextInt(256);
|
||||
}
|
||||
|
||||
// Set version to 4 -> xxxx0100
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||
|
||||
// Set variant to RFC4122 -> 10xxxxxx
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
|
||||
return _bytesToUuid(bytes);
|
||||
}
|
||||
|
||||
String _bytesToUuid(Uint8List bytes) {
|
||||
final sb = StringBuffer();
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
sb.write(bytes[i].toRadixString(16).padLeft(2, '0'));
|
||||
if (i == 3 || i == 5 || i == 7 || i == 9) sb.write('-');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -232,6 +232,9 @@
|
||||
"@comment": {
|
||||
"description": "Comment, additional information"
|
||||
},
|
||||
"impressionGood": "Good",
|
||||
"impressionNeutral": "Neutral",
|
||||
"impressionBad": "Bad",
|
||||
"impression": "Impression",
|
||||
"@impression": {
|
||||
"description": "General impression (e.g. for a workout session) such as good, bad, etc."
|
||||
@@ -263,6 +266,33 @@
|
||||
"@gymMode": {
|
||||
"description": "Label when starting the gym mode"
|
||||
},
|
||||
"gymModeShowExercises": "Show exercise overview pages",
|
||||
"gymModeShowTimer": "Show timer between sets",
|
||||
"gymModeTimerType": "Timer type",
|
||||
"gymModeTimerTypeHelText": "If a set has pause time, a countdown is always used.",
|
||||
"countdown": "Countdown",
|
||||
"stopwatch": "Stopwatch",
|
||||
"gymModeDefaultCountdownTime": "Default countdown time, in seconds",
|
||||
"gymModeNotifyOnCountdownFinish": "Notify on countdown end",
|
||||
"duration": "Duration",
|
||||
"durationHoursMinutes": "{hours}h {minutes}m",
|
||||
"@durationHoursMinutes": {
|
||||
"description": "A duration, in hours and minutes",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"type": "int"
|
||||
},
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"volume": "Volume",
|
||||
"@volume": {
|
||||
"description": "The volume of a workout or set, i.e. weight x reps"
|
||||
},
|
||||
"workoutCompleted": "Workout completed",
|
||||
"plateCalculator": "Plates",
|
||||
"@plateCalculator": {
|
||||
"description": "Label used for the plate calculator in the gym mode"
|
||||
@@ -643,6 +673,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"formMinMaxValues": "Please enter a value between {min} and {max}",
|
||||
"@formMinMaxValues": {
|
||||
"description": "Error message when the user needs to enter a value between min and max",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"min": {
|
||||
"type": "int"
|
||||
},
|
||||
"max": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enterMinCharacters": "Please enter at least {min} characters",
|
||||
"@enterMinCharacters": {
|
||||
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
|
||||
@@ -845,6 +888,7 @@
|
||||
"fitInWeek": "Fit in week",
|
||||
"fitInWeekHelp": "If enabled, the days will repeat in a weekly cycle, otherwise the days will follow sequentially without regards to the start of a new week.",
|
||||
"addSuperset": "Add superset",
|
||||
"superset": "Superset",
|
||||
"setHasProgression": "Set has progression",
|
||||
"setHasProgressionWarning": "Please note that at the moment it is not possible to edit all settings for a set on the mobile application or configure automatic progression. For now, please use the web application.",
|
||||
"setHasNoExercises": "This set has no exercises yet!",
|
||||
@@ -1063,7 +1107,10 @@
|
||||
"@indicatorAvg": {
|
||||
"description": "added for localization of Class Indicator's field text"
|
||||
},
|
||||
"endWorkout": "End Workout",
|
||||
"endWorkout": "End workout",
|
||||
"@endWorkout": {
|
||||
"description": "Use the imperative, label on button to finish the current workout in gym mode"
|
||||
},
|
||||
"themeMode": "Theme mode",
|
||||
"darkMode": "Always dark mode",
|
||||
"lightMode": "Always light mode",
|
||||
|
||||
@@ -8,7 +8,10 @@ part of 'category.dart';
|
||||
|
||||
ExerciseCategory _$ExerciseCategoryFromJson(Map<String, dynamic> json) {
|
||||
$checkKeys(json, requiredKeys: const ['id', 'name']);
|
||||
return ExerciseCategory(id: (json['id'] as num).toInt(), name: json['name'] as String);
|
||||
return ExerciseCategory(
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ExerciseCategoryToJson(ExerciseCategory instance) => <String, dynamic>{
|
||||
|
||||
@@ -38,7 +38,9 @@ Exercise _$ExerciseFromJson(Map<String, dynamic> json) {
|
||||
.toList(),
|
||||
category: json['categories'] == null
|
||||
? null
|
||||
: ExerciseCategory.fromJson(json['categories'] as Map<String, dynamic>),
|
||||
: ExerciseCategory.fromJson(
|
||||
json['categories'] as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
..categoryId = (json['category'] as num).toInt()
|
||||
..musclesIds = (json['muscles'] as List<dynamic>).map((e) => (e as num).toInt()).toList()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,9 @@ _ExerciseBaseData _$ExerciseBaseDataFromJson(Map<String, dynamic> json) => _Exer
|
||||
created: DateTime.parse(json['created'] as String),
|
||||
lastUpdate: DateTime.parse(json['last_update'] as String),
|
||||
lastUpdateGlobal: DateTime.parse(json['last_update_global'] as String),
|
||||
category: ExerciseCategory.fromJson(json['category'] as Map<String, dynamic>),
|
||||
category: ExerciseCategory.fromJson(
|
||||
json['category'] as Map<String, dynamic>,
|
||||
),
|
||||
muscles: (json['muscles'] as List<dynamic>)
|
||||
.map((e) => Muscle.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
@@ -56,34 +58,39 @@ Map<String, dynamic> _$ExerciseBaseDataToJson(_ExerciseBaseData instance) => <St
|
||||
'total_authors_history': instance.authorsGlobal,
|
||||
};
|
||||
|
||||
_ExerciseSearchDetails _$ExerciseSearchDetailsFromJson(Map<String, dynamic> json) =>
|
||||
_ExerciseSearchDetails(
|
||||
translationId: (json['id'] as num).toInt(),
|
||||
exerciseId: (json['base_id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
category: json['category'] as String,
|
||||
image: json['image'] as String?,
|
||||
imageThumbnail: json['image_thumbnail'] as String?,
|
||||
);
|
||||
_ExerciseSearchDetails _$ExerciseSearchDetailsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _ExerciseSearchDetails(
|
||||
translationId: (json['id'] as num).toInt(),
|
||||
exerciseId: (json['base_id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
category: json['category'] as String,
|
||||
image: json['image'] as String?,
|
||||
imageThumbnail: json['image_thumbnail'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ExerciseSearchDetailsToJson(_ExerciseSearchDetails instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.translationId,
|
||||
'base_id': instance.exerciseId,
|
||||
'name': instance.name,
|
||||
'category': instance.category,
|
||||
'image': instance.image,
|
||||
'image_thumbnail': instance.imageThumbnail,
|
||||
};
|
||||
Map<String, dynamic> _$ExerciseSearchDetailsToJson(
|
||||
_ExerciseSearchDetails instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.translationId,
|
||||
'base_id': instance.exerciseId,
|
||||
'name': instance.name,
|
||||
'category': instance.category,
|
||||
'image': instance.image,
|
||||
'image_thumbnail': instance.imageThumbnail,
|
||||
};
|
||||
|
||||
_ExerciseSearchEntry _$ExerciseSearchEntryFromJson(Map<String, dynamic> json) =>
|
||||
_ExerciseSearchEntry(
|
||||
value: json['value'] as String,
|
||||
data: ExerciseSearchDetails.fromJson(json['data'] as Map<String, dynamic>),
|
||||
data: ExerciseSearchDetails.fromJson(
|
||||
json['data'] as Map<String, dynamic>,
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ExerciseSearchEntryToJson(_ExerciseSearchEntry instance) =>
|
||||
<String, dynamic>{'value': instance.value, 'data': instance.data};
|
||||
Map<String, dynamic> _$ExerciseSearchEntryToJson(
|
||||
_ExerciseSearchEntry instance,
|
||||
) => <String, dynamic>{'value': instance.value, 'data': instance.data};
|
||||
|
||||
_ExerciseApiSearch _$ExerciseApiSearchFromJson(Map<String, dynamic> json) => _ExerciseApiSearch(
|
||||
suggestions: (json['suggestions'] as List<dynamic>)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,17 +6,21 @@ part of 'exercise_submission.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_ExerciseAliasSubmissionApi _$ExerciseAliasSubmissionApiFromJson(Map<String, dynamic> json) =>
|
||||
_ExerciseAliasSubmissionApi(alias: json['alias'] as String);
|
||||
_ExerciseAliasSubmissionApi _$ExerciseAliasSubmissionApiFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _ExerciseAliasSubmissionApi(alias: json['alias'] as String);
|
||||
|
||||
Map<String, dynamic> _$ExerciseAliasSubmissionApiToJson(_ExerciseAliasSubmissionApi instance) =>
|
||||
<String, dynamic>{'alias': instance.alias};
|
||||
Map<String, dynamic> _$ExerciseAliasSubmissionApiToJson(
|
||||
_ExerciseAliasSubmissionApi instance,
|
||||
) => <String, dynamic>{'alias': instance.alias};
|
||||
|
||||
_ExerciseCommentSubmissionApi _$ExerciseCommentSubmissionApiFromJson(Map<String, dynamic> json) =>
|
||||
_ExerciseCommentSubmissionApi(alias: json['alias'] as String);
|
||||
_ExerciseCommentSubmissionApi _$ExerciseCommentSubmissionApiFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _ExerciseCommentSubmissionApi(alias: json['alias'] as String);
|
||||
|
||||
Map<String, dynamic> _$ExerciseCommentSubmissionApiToJson(_ExerciseCommentSubmissionApi instance) =>
|
||||
<String, dynamic>{'alias': instance.alias};
|
||||
Map<String, dynamic> _$ExerciseCommentSubmissionApiToJson(
|
||||
_ExerciseCommentSubmissionApi instance,
|
||||
) => <String, dynamic>{'alias': instance.alias};
|
||||
|
||||
_ExerciseTranslationSubmissionApi _$ExerciseTranslationSubmissionApiFromJson(
|
||||
Map<String, dynamic> json,
|
||||
@@ -27,12 +31,18 @@ _ExerciseTranslationSubmissionApi _$ExerciseTranslationSubmissionApiFromJson(
|
||||
author: json['license_author'] as String,
|
||||
aliases:
|
||||
(json['aliases'] as List<dynamic>?)
|
||||
?.map((e) => ExerciseAliasSubmissionApi.fromJson(e as Map<String, dynamic>))
|
||||
?.map(
|
||||
(e) => ExerciseAliasSubmissionApi.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList() ??
|
||||
const [],
|
||||
comments:
|
||||
(json['comments'] as List<dynamic>?)
|
||||
?.map((e) => ExerciseCommentSubmissionApi.fromJson(e as Map<String, dynamic>))
|
||||
?.map(
|
||||
(e) => ExerciseCommentSubmissionApi.fromJson(
|
||||
e as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
@@ -48,30 +58,36 @@ Map<String, dynamic> _$ExerciseTranslationSubmissionApiToJson(
|
||||
'comments': instance.comments,
|
||||
};
|
||||
|
||||
_ExerciseSubmissionApi _$ExerciseSubmissionApiFromJson(Map<String, dynamic> json) =>
|
||||
_ExerciseSubmissionApi(
|
||||
category: (json['category'] as num).toInt(),
|
||||
muscles: (json['muscles'] as List<dynamic>).map((e) => (e as num).toInt()).toList(),
|
||||
musclesSecondary: (json['muscles_secondary'] as List<dynamic>)
|
||||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
equipment: (json['equipment'] as List<dynamic>).map((e) => (e as num).toInt()).toList(),
|
||||
author: json['license_author'] as String,
|
||||
variation: (json['variation'] as num?)?.toInt(),
|
||||
variationConnectTo: (json['variations_connect_to'] as num?)?.toInt(),
|
||||
translations: (json['translations'] as List<dynamic>)
|
||||
.map((e) => ExerciseTranslationSubmissionApi.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
_ExerciseSubmissionApi _$ExerciseSubmissionApiFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _ExerciseSubmissionApi(
|
||||
category: (json['category'] as num).toInt(),
|
||||
muscles: (json['muscles'] as List<dynamic>).map((e) => (e as num).toInt()).toList(),
|
||||
musclesSecondary: (json['muscles_secondary'] as List<dynamic>)
|
||||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
equipment: (json['equipment'] as List<dynamic>).map((e) => (e as num).toInt()).toList(),
|
||||
author: json['license_author'] as String,
|
||||
variation: (json['variation'] as num?)?.toInt(),
|
||||
variationConnectTo: (json['variations_connect_to'] as num?)?.toInt(),
|
||||
translations: (json['translations'] as List<dynamic>)
|
||||
.map(
|
||||
(e) => ExerciseTranslationSubmissionApi.fromJson(
|
||||
e as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ExerciseSubmissionApiToJson(_ExerciseSubmissionApi instance) =>
|
||||
<String, dynamic>{
|
||||
'category': instance.category,
|
||||
'muscles': instance.muscles,
|
||||
'muscles_secondary': instance.musclesSecondary,
|
||||
'equipment': instance.equipment,
|
||||
'license_author': instance.author,
|
||||
'variation': instance.variation,
|
||||
'variations_connect_to': instance.variationConnectTo,
|
||||
'translations': instance.translations,
|
||||
};
|
||||
Map<String, dynamic> _$ExerciseSubmissionApiToJson(
|
||||
_ExerciseSubmissionApi instance,
|
||||
) => <String, dynamic>{
|
||||
'category': instance.category,
|
||||
'muscles': instance.muscles,
|
||||
'muscles_secondary': instance.musclesSecondary,
|
||||
'equipment': instance.equipment,
|
||||
'license_author': instance.author,
|
||||
'variation': instance.variation,
|
||||
'variations_connect_to': instance.variationConnectTo,
|
||||
'translations': instance.translations,
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ class Muscle extends Equatable {
|
||||
List<Object?> get props => [id, name, isFront];
|
||||
|
||||
String nameTranslated(BuildContext context) {
|
||||
return name + (nameEn.isNotEmpty ? ' (${getTranslation(nameEn, context)})' : '');
|
||||
return name + (nameEn.isNotEmpty ? ' (${getServerStringTranslation(nameEn, context)})' : '');
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -9,7 +9,15 @@ part of 'translation.dart';
|
||||
Translation _$TranslationFromJson(Map<String, dynamic> json) {
|
||||
$checkKeys(
|
||||
json,
|
||||
requiredKeys: const ['id', 'uuid', 'language', 'created', 'exercise', 'name', 'description'],
|
||||
requiredKeys: const [
|
||||
'id',
|
||||
'uuid',
|
||||
'language',
|
||||
'created',
|
||||
'exercise',
|
||||
'name',
|
||||
'description',
|
||||
],
|
||||
);
|
||||
return Translation(
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
|
||||
@@ -20,7 +20,9 @@ MeasurementCategory _$MeasurementCategoryFromJson(Map<String, dynamic> json) {
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$MeasurementCategoryToJson(MeasurementCategory instance) => <String, dynamic>{
|
||||
Map<String, dynamic> _$MeasurementCategoryToJson(
|
||||
MeasurementCategory instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'unit': instance.unit,
|
||||
|
||||
@@ -50,7 +50,9 @@ Ingredient _$IngredientFromJson(Map<String, dynamic> json) {
|
||||
: IngredientImage.fromJson(json['image'] as Map<String, dynamic>),
|
||||
thumbnails: json['thumbnails'] == null
|
||||
? null
|
||||
: IngredientImageThumbnails.fromJson(json['thumbnails'] as Map<String, dynamic>),
|
||||
: IngredientImageThumbnails.fromJson(
|
||||
json['thumbnails'] as Map<String, dynamic>,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ part of 'ingredient_image_thumbnails.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
IngredientImageThumbnails _$IngredientImageThumbnailsFromJson(Map<String, dynamic> json) {
|
||||
IngredientImageThumbnails _$IngredientImageThumbnailsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) {
|
||||
$checkKeys(
|
||||
json,
|
||||
requiredKeys: const [
|
||||
@@ -28,12 +30,13 @@ IngredientImageThumbnails _$IngredientImageThumbnailsFromJson(Map<String, dynami
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$IngredientImageThumbnailsToJson(IngredientImageThumbnails instance) =>
|
||||
<String, dynamic>{
|
||||
'small': instance.small,
|
||||
'small_cropped': instance.smallCropped,
|
||||
'medium': instance.medium,
|
||||
'medium_cropped': instance.mediumCropped,
|
||||
'large': instance.large,
|
||||
'large_cropped': instance.largeCropped,
|
||||
};
|
||||
Map<String, dynamic> _$IngredientImageThumbnailsToJson(
|
||||
IngredientImageThumbnails instance,
|
||||
) => <String, dynamic>{
|
||||
'small': instance.small,
|
||||
'small_cropped': instance.smallCropped,
|
||||
'medium': instance.medium,
|
||||
'medium_cropped': instance.mediumCropped,
|
||||
'large': instance.large,
|
||||
'large_cropped': instance.largeCropped,
|
||||
};
|
||||
|
||||
@@ -7,21 +7,27 @@ part of 'ingredient_weight_unit.dart';
|
||||
// **************************************************************************
|
||||
|
||||
IngredientWeightUnit _$IngredientWeightUnitFromJson(Map<String, dynamic> json) {
|
||||
$checkKeys(json, requiredKeys: const ['id', 'weight_unit', 'ingredient', 'grams', 'amount']);
|
||||
$checkKeys(
|
||||
json,
|
||||
requiredKeys: const ['id', 'weight_unit', 'ingredient', 'grams', 'amount'],
|
||||
);
|
||||
return IngredientWeightUnit(
|
||||
id: (json['id'] as num).toInt(),
|
||||
weightUnit: WeightUnit.fromJson(json['weight_unit'] as Map<String, dynamic>),
|
||||
weightUnit: WeightUnit.fromJson(
|
||||
json['weight_unit'] as Map<String, dynamic>,
|
||||
),
|
||||
ingredient: Ingredient.fromJson(json['ingredient'] as Map<String, dynamic>),
|
||||
grams: (json['grams'] as num).toInt(),
|
||||
amount: (json['amount'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$IngredientWeightUnitToJson(IngredientWeightUnit instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'weight_unit': instance.weightUnit,
|
||||
'ingredient': instance.ingredient,
|
||||
'grams': instance.grams,
|
||||
'amount': instance.amount,
|
||||
};
|
||||
Map<String, dynamic> _$IngredientWeightUnitToJson(
|
||||
IngredientWeightUnit instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'weight_unit': instance.weightUnit,
|
||||
'ingredient': instance.ingredient,
|
||||
'grams': instance.grams,
|
||||
'amount': instance.amount,
|
||||
};
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/i18n.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/helpers/misc.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
@@ -27,6 +29,13 @@ import 'package:wger/models/workouts/weight_unit.dart';
|
||||
|
||||
part 'log.g.dart';
|
||||
|
||||
enum LogTargetStatus {
|
||||
lessThanTarget,
|
||||
atTarget,
|
||||
moreThanTarget,
|
||||
notSet,
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Log {
|
||||
@JsonKey(required: true)
|
||||
@@ -50,16 +59,16 @@ class Log {
|
||||
@JsonKey(required: true, name: 'slot_entry')
|
||||
int? slotEntryId;
|
||||
|
||||
@JsonKey(required: false, fromJson: stringToNum)
|
||||
@JsonKey(required: false, fromJson: stringToNumNull)
|
||||
num? rir;
|
||||
|
||||
@JsonKey(required: false, fromJson: stringToNum, name: 'rir_target')
|
||||
@JsonKey(required: false, fromJson: stringToNumNull, name: 'rir_target')
|
||||
num? rirTarget;
|
||||
|
||||
@JsonKey(required: true, fromJson: stringToNum, name: 'repetitions')
|
||||
@JsonKey(required: true, fromJson: stringToNumNull, name: 'repetitions')
|
||||
num? repetitions;
|
||||
|
||||
@JsonKey(required: true, fromJson: stringToNum, name: 'repetitions_target')
|
||||
@JsonKey(required: true, fromJson: stringToNumNull, name: 'repetitions_target')
|
||||
num? repetitionsTarget;
|
||||
|
||||
@JsonKey(required: true, name: 'repetitions_unit')
|
||||
@@ -68,10 +77,10 @@ class Log {
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
late RepetitionUnit? repetitionsUnitObj;
|
||||
|
||||
@JsonKey(required: true, fromJson: stringToNum, toJson: numToString)
|
||||
@JsonKey(required: true, fromJson: stringToNumNull, toJson: numToString)
|
||||
late num? weight;
|
||||
|
||||
@JsonKey(required: true, fromJson: stringToNum, toJson: numToString, name: 'weight_target')
|
||||
@JsonKey(required: true, fromJson: stringToNumNull, toJson: numToString, name: 'weight_target')
|
||||
num? weightTarget;
|
||||
|
||||
@JsonKey(required: true, name: 'weight_unit')
|
||||
@@ -105,16 +114,25 @@ class Log {
|
||||
Log.fromSetConfigData(SetConfigData data) {
|
||||
date = DateTime.now();
|
||||
sessionId = null;
|
||||
|
||||
slotEntryId = data.slotEntryId;
|
||||
exerciseBase = data.exercise;
|
||||
|
||||
weight = data.weight;
|
||||
weightTarget = data.weight;
|
||||
weightUnit = data.weightUnit;
|
||||
if (data.weight != null) {
|
||||
weight = data.weight;
|
||||
weightTarget = data.weight;
|
||||
}
|
||||
if (data.weightUnit != null) {
|
||||
weightUnit = data.weightUnit;
|
||||
}
|
||||
|
||||
repetitions = data.repetitions;
|
||||
repetitionsTarget = data.repetitions;
|
||||
repetitionUnit = data.repetitionsUnit;
|
||||
if (data.repetitions != null) {
|
||||
repetitions = data.repetitions;
|
||||
repetitionsTarget = data.repetitions;
|
||||
}
|
||||
if (data.repetitionsUnit != null) {
|
||||
repetitionUnit = data.repetitionsUnit;
|
||||
}
|
||||
|
||||
rir = data.rir;
|
||||
rirTarget = data.rir;
|
||||
@@ -140,15 +158,80 @@ class Log {
|
||||
repetitionsUnitId = repetitionUnit?.id;
|
||||
}
|
||||
|
||||
/// Returns the text representation for a single setting, used in the gym mode
|
||||
String get singleLogRepTextNoNl {
|
||||
return repText(
|
||||
repetitions,
|
||||
repetitionsUnitObj,
|
||||
weight,
|
||||
weightUnitObj,
|
||||
rir,
|
||||
).replaceAll('\n', '');
|
||||
/// Returns the text representation for a single setting, removes new lines
|
||||
String repTextNoNl(BuildContext context) {
|
||||
return repText(context).replaceAll('\n', '');
|
||||
}
|
||||
|
||||
/// Returns the text representation for a single setting
|
||||
String repText(BuildContext context) {
|
||||
final List<String> out = [];
|
||||
|
||||
if (repetitions != null) {
|
||||
out.add(formatNum(repetitions!).toString());
|
||||
|
||||
// The default repetition unit is 'reps', which we don't show unless there
|
||||
// is no weight defined so that we don't just output something like "8" but
|
||||
// rather "8 repetitions". If there is weight we want to output "8 x 50kg",
|
||||
// since the repetitions are implied. If other units are used, we always
|
||||
// print them
|
||||
if (repetitionsUnitObj != null && repetitionsUnitObj!.id != REP_UNIT_REPETITIONS_ID ||
|
||||
weight == 0 ||
|
||||
weight == null) {
|
||||
out.add(getServerStringTranslation(repetitionsUnitObj!.name, context));
|
||||
}
|
||||
}
|
||||
|
||||
if (weight != null && weight != 0) {
|
||||
out.add('×');
|
||||
out.add(formatNum(weight!).toString());
|
||||
out.add(weightUnitObj!.name);
|
||||
}
|
||||
|
||||
if (rir != null) {
|
||||
out.add('\n($rir RiR)');
|
||||
}
|
||||
|
||||
return out.join(' ');
|
||||
}
|
||||
|
||||
/// Calculates the volume for this log entry
|
||||
num volume({bool metric = true}) {
|
||||
final unitId = metric ? WEIGHT_UNIT_KG : WEIGHT_UNIT_LB;
|
||||
|
||||
if (weight != null &&
|
||||
weightUnitId == unitId &&
|
||||
repetitions != null &&
|
||||
repetitionsUnitId == REP_UNIT_REPETITIONS_ID) {
|
||||
return weight! * repetitions!;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
LogTargetStatus get targetStatus {
|
||||
if (weightTarget == null && repetitionsTarget == null && rirTarget == null) {
|
||||
return LogTargetStatus.notSet;
|
||||
}
|
||||
|
||||
final weightOk = weightTarget == null || (weight != null && weight! >= weightTarget!);
|
||||
final repsOk =
|
||||
repetitionsTarget == null || (repetitions != null && repetitions! >= repetitionsTarget!);
|
||||
final rirOk = rirTarget == null || (rir != null && rir! <= rirTarget!);
|
||||
|
||||
if (weightOk && repsOk && rirOk) {
|
||||
return LogTargetStatus.atTarget;
|
||||
}
|
||||
|
||||
final weightMore = weightTarget != null && weight != null && weight! > weightTarget!;
|
||||
final repsMore =
|
||||
repetitionsTarget != null && repetitions != null && repetitions! > repetitionsTarget!;
|
||||
final rirLess = rirTarget != null && rir != null && rir! < rirTarget!;
|
||||
|
||||
if (weightMore || repsMore || rirLess) {
|
||||
return LogTargetStatus.moreThanTarget;
|
||||
}
|
||||
|
||||
return LogTargetStatus.lessThanTarget;
|
||||
}
|
||||
|
||||
/// Override the equals operator
|
||||
|
||||
@@ -31,13 +31,13 @@ Log _$LogFromJson(Map<String, dynamic> json) {
|
||||
iteration: (json['iteration'] as num?)?.toInt(),
|
||||
slotEntryId: (json['slot_entry'] as num?)?.toInt(),
|
||||
routineId: (json['routine'] as num).toInt(),
|
||||
repetitions: stringToNum(json['repetitions'] as String?),
|
||||
repetitionsTarget: stringToNum(json['repetitions_target'] as String?),
|
||||
repetitions: stringToNumNull(json['repetitions'] as String?),
|
||||
repetitionsTarget: stringToNumNull(json['repetitions_target'] as String?),
|
||||
repetitionsUnitId: (json['repetitions_unit'] as num?)?.toInt() ?? REP_UNIT_REPETITIONS_ID,
|
||||
rir: stringToNum(json['rir'] as String?),
|
||||
rirTarget: stringToNum(json['rir_target'] as String?),
|
||||
weight: stringToNum(json['weight'] as String?),
|
||||
weightTarget: stringToNum(json['weight_target'] as String?),
|
||||
rir: stringToNumNull(json['rir'] as String?),
|
||||
rirTarget: stringToNumNull(json['rir_target'] as String?),
|
||||
weight: stringToNumNull(json['weight'] as String?),
|
||||
weightTarget: stringToNumNull(json['weight_target'] as String?),
|
||||
weightUnitId: (json['weight_unit'] as num?)?.toInt() ?? WEIGHT_UNIT_KG,
|
||||
date: utcIso8601ToLocalDate(json['date'] as String),
|
||||
)..sessionId = (json['session'] as num?)?.toInt();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:wger/helpers/date.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/day.dart';
|
||||
import 'package:wger/models/workouts/day_data.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
@@ -176,4 +177,37 @@ class Routine {
|
||||
|
||||
return groupedLogs;
|
||||
}
|
||||
|
||||
void replaceExercise(int oldExerciseId, Exercise newExercise) {
|
||||
for (final session in sessions) {
|
||||
for (final log in session.logs) {
|
||||
if (log.exerciseId == oldExerciseId) {
|
||||
log.exerciseId = newExercise.id!;
|
||||
log.exercise = newExercise;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final day in dayData) {
|
||||
for (final slot in day.slots) {
|
||||
for (final config in slot.setConfigs) {
|
||||
if (config.exerciseId == oldExerciseId) {
|
||||
config.exerciseId = newExercise.id!;
|
||||
config.exercise = newExercise;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final day in dayDataGym) {
|
||||
for (final slot in day.slots) {
|
||||
for (final config in slot.setConfigs) {
|
||||
if (config.exerciseId == oldExerciseId) {
|
||||
config.exerciseId = newExercise.id!;
|
||||
config.exercise = newExercise;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
|
||||
part 'session.g.dart';
|
||||
@@ -27,6 +29,8 @@ const IMPRESSION_MAP = {1: 'bad', 2: 'neutral', 3: 'good'};
|
||||
|
||||
@JsonSerializable()
|
||||
class WorkoutSession {
|
||||
final _logger = Logger('WorkoutSession');
|
||||
|
||||
@JsonKey(required: true)
|
||||
int? id;
|
||||
|
||||
@@ -68,12 +72,63 @@ class WorkoutSession {
|
||||
this.date = date ?? DateTime.now();
|
||||
}
|
||||
|
||||
Duration? get duration {
|
||||
if (timeStart == null || timeEnd == null) {
|
||||
return null;
|
||||
}
|
||||
final now = DateTime.now();
|
||||
final startDate = DateTime(now.year, now.month, now.day, timeStart!.hour, timeStart!.minute);
|
||||
final endDate = DateTime(now.year, now.month, now.day, timeEnd!.hour, timeEnd!.minute);
|
||||
return endDate.difference(startDate);
|
||||
}
|
||||
|
||||
String durationTxt(BuildContext context) {
|
||||
final duration = this.duration;
|
||||
if (duration == null) {
|
||||
return '-/-';
|
||||
}
|
||||
return AppLocalizations.of(
|
||||
context,
|
||||
).durationHoursMinutes(duration.inHours, duration.inMinutes.remainder(60));
|
||||
}
|
||||
|
||||
String durationTxtWithStartEnd(BuildContext context) {
|
||||
final duration = this.duration;
|
||||
if (duration == null) {
|
||||
return '-/-';
|
||||
}
|
||||
|
||||
final startTime = MaterialLocalizations.of(context).formatTimeOfDay(timeStart!);
|
||||
final endTime = MaterialLocalizations.of(context).formatTimeOfDay(timeEnd!);
|
||||
|
||||
return '${durationTxt(context)} ($startTime - $endTime)';
|
||||
}
|
||||
|
||||
/// Get total volume of the session for metric and imperial units
|
||||
/// (i.e. sets that have "repetitions" as units and weight in kg or lbs).
|
||||
/// Other combinations such as "seconds" are ignored.
|
||||
Map<String, Object> get volume {
|
||||
final volumeMetric = logs.fold<double>(0, (sum, log) => sum + log.volume(metric: true));
|
||||
final volumeImperial = logs.fold<double>(0, (sum, log) => sum + log.volume(metric: false));
|
||||
|
||||
return {'metric': volumeMetric, 'imperial': volumeImperial};
|
||||
}
|
||||
|
||||
// Boilerplate
|
||||
factory WorkoutSession.fromJson(Map<String, dynamic> json) => _$WorkoutSessionFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$WorkoutSessionToJson(this);
|
||||
|
||||
String get impressionAsString {
|
||||
return IMPRESSION_MAP[impression]!;
|
||||
String impressionAsString(BuildContext context) {
|
||||
if (impression == 1) {
|
||||
return AppLocalizations.of(context).impressionBad;
|
||||
} else if (impression == 2) {
|
||||
return AppLocalizations.of(context).impressionNeutral;
|
||||
} else if (impression == 3) {
|
||||
return AppLocalizations.of(context).impressionGood;
|
||||
}
|
||||
|
||||
_logger.warning('Unknown impression value: $impression');
|
||||
return AppLocalizations.of(context).impressionGood;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,16 @@ class WorkoutSessionApi {
|
||||
return exerciseSet.toList();
|
||||
}
|
||||
|
||||
/// Get total volume of the session for metric and imperial units
|
||||
/// (i.e. sets that have "repetitions" as units and weight in kg or lbs).
|
||||
/// Other combinations such as "seconds" are ignored.
|
||||
Map<String, num> get volume {
|
||||
final volumeMetric = logs.fold<double>(0, (sum, log) => sum + log.volume(metric: true));
|
||||
final volumeImperial = logs.fold<double>(0, (sum, log) => sum + log.volume(metric: false));
|
||||
|
||||
return {'metric': volumeMetric, 'imperial': volumeImperial};
|
||||
}
|
||||
|
||||
// Boilerplate
|
||||
factory WorkoutSessionApi.fromJson(Map<String, dynamic> json) =>
|
||||
_$WorkoutSessionApiFromJson(json);
|
||||
|
||||
@@ -46,55 +46,55 @@ class SetConfigData {
|
||||
String get textReprWithType => '$textRepr${type.typeLabel}';
|
||||
|
||||
@JsonKey(required: true, name: 'sets')
|
||||
late num? nrOfSets;
|
||||
num? nrOfSets;
|
||||
|
||||
@JsonKey(required: true, name: 'max_sets')
|
||||
late num? maxNrOfSets;
|
||||
num? maxNrOfSets;
|
||||
|
||||
@JsonKey(required: true, fromJson: stringToNumNull)
|
||||
late num? weight;
|
||||
num? weight;
|
||||
|
||||
@JsonKey(required: true, name: 'max_weight', fromJson: stringToNumNull)
|
||||
late num? maxWeight;
|
||||
num? maxWeight;
|
||||
|
||||
@JsonKey(required: true, name: 'weight_unit')
|
||||
late int? weightUnitId;
|
||||
int? weightUnitId;
|
||||
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
late WeightUnit? weightUnit;
|
||||
WeightUnit? weightUnit;
|
||||
|
||||
@JsonKey(required: true, name: 'weight_rounding', fromJson: stringToNumNull)
|
||||
late num? weightRounding;
|
||||
num? weightRounding;
|
||||
|
||||
@JsonKey(required: true, name: 'repetitions', fromJson: stringToNumNull)
|
||||
late num? repetitions;
|
||||
num? repetitions;
|
||||
|
||||
@JsonKey(required: true, name: 'max_repetitions', fromJson: stringToNumNull)
|
||||
late num? maxRepetitions;
|
||||
num? maxRepetitions;
|
||||
|
||||
@JsonKey(required: true, name: 'repetitions_unit')
|
||||
late int? repetitionsUnitId;
|
||||
int? repetitionsUnitId;
|
||||
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
late RepetitionUnit? repetitionsUnit;
|
||||
RepetitionUnit? repetitionsUnit;
|
||||
|
||||
@JsonKey(required: true, name: 'repetitions_rounding', fromJson: stringToNumNull)
|
||||
late num? repetitionsRounding;
|
||||
num? repetitionsRounding;
|
||||
|
||||
@JsonKey(required: true, fromJson: stringToNumNull)
|
||||
late num? rir;
|
||||
num? rir;
|
||||
|
||||
@JsonKey(required: true, name: 'max_rir', fromJson: stringToNumNull)
|
||||
late num? maxRir;
|
||||
num? maxRir;
|
||||
|
||||
@JsonKey(required: true, fromJson: stringToNumNull)
|
||||
late num? rpe;
|
||||
num? rpe;
|
||||
|
||||
@JsonKey(required: true, name: 'rest', fromJson: stringToNumNull)
|
||||
late num? restTime;
|
||||
num? restTime;
|
||||
|
||||
@JsonKey(required: true, name: 'max_rest', fromJson: stringToNumNull)
|
||||
late num? maxRestTime;
|
||||
num? maxRestTime;
|
||||
|
||||
@JsonKey(required: true)
|
||||
late String comment;
|
||||
@@ -103,20 +103,20 @@ class SetConfigData {
|
||||
required this.exerciseId,
|
||||
required this.slotEntryId,
|
||||
this.type = SlotEntryType.normal,
|
||||
required this.nrOfSets,
|
||||
this.nrOfSets,
|
||||
this.maxNrOfSets,
|
||||
required this.weight,
|
||||
this.weight,
|
||||
this.maxWeight,
|
||||
this.weightUnitId = WEIGHT_UNIT_KG,
|
||||
this.weightRounding,
|
||||
required this.repetitions,
|
||||
this.repetitions,
|
||||
this.maxRepetitions,
|
||||
this.repetitionsUnitId = REP_UNIT_REPETITIONS_ID,
|
||||
this.repetitionsRounding,
|
||||
required this.rir,
|
||||
this.rir,
|
||||
this.maxRir,
|
||||
required this.rpe,
|
||||
required this.restTime,
|
||||
this.rpe,
|
||||
this.restTime,
|
||||
this.maxRestTime,
|
||||
this.comment = '',
|
||||
this.textRepr = '',
|
||||
@@ -135,6 +135,58 @@ class SetConfigData {
|
||||
}
|
||||
}
|
||||
|
||||
SetConfigData copyWith({
|
||||
int? exerciseId,
|
||||
int? slotEntryId,
|
||||
SlotEntryType? type,
|
||||
String? textRepr,
|
||||
num? nrOfSets,
|
||||
num? maxNrOfSets,
|
||||
num? weight,
|
||||
num? maxWeight,
|
||||
int? weightUnitId,
|
||||
num? weightRounding,
|
||||
num? repetitions,
|
||||
num? maxRepetitions,
|
||||
int? repetitionsUnitId,
|
||||
num? repetitionsRounding,
|
||||
num? rir,
|
||||
num? maxRir,
|
||||
num? rpe,
|
||||
num? restTime,
|
||||
num? maxRestTime,
|
||||
String? comment,
|
||||
Exercise? exercise,
|
||||
WeightUnit? weightUnit,
|
||||
RepetitionUnit? repetitionsUnit,
|
||||
}) {
|
||||
return SetConfigData(
|
||||
exerciseId: exerciseId ?? this.exerciseId,
|
||||
slotEntryId: slotEntryId ?? this.slotEntryId,
|
||||
type: type ?? this.type,
|
||||
textRepr: textRepr ?? this.textRepr,
|
||||
nrOfSets: nrOfSets ?? this.nrOfSets,
|
||||
maxNrOfSets: maxNrOfSets ?? this.maxNrOfSets,
|
||||
weight: weight ?? this.weight,
|
||||
maxWeight: maxWeight ?? this.maxWeight,
|
||||
weightUnitId: weightUnitId ?? this.weightUnitId,
|
||||
weightRounding: weightRounding ?? this.weightRounding,
|
||||
repetitions: repetitions ?? this.repetitions,
|
||||
maxRepetitions: maxRepetitions ?? this.maxRepetitions,
|
||||
repetitionsUnitId: repetitionsUnitId ?? this.repetitionsUnitId,
|
||||
repetitionsRounding: repetitionsRounding ?? this.repetitionsRounding,
|
||||
rir: rir ?? this.rir,
|
||||
maxRir: maxRir ?? this.maxRir,
|
||||
rpe: rpe ?? this.rpe,
|
||||
restTime: restTime ?? this.restTime,
|
||||
maxRestTime: maxRestTime ?? this.maxRestTime,
|
||||
comment: comment ?? this.comment,
|
||||
exercise: exercise ?? this.exercise,
|
||||
weightUnit: weightUnit ?? this.weightUnit,
|
||||
repetitionsUnit: repetitionsUnit ?? this.repetitionsUnit,
|
||||
);
|
||||
}
|
||||
|
||||
// Boilerplate
|
||||
factory SetConfigData.fromJson(Map<String, dynamic> json) => _$SetConfigDataFromJson(json);
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ part of 'weight_unit.dart';
|
||||
|
||||
WeightUnit _$WeightUnitFromJson(Map<String, dynamic> json) {
|
||||
$checkKeys(json, requiredKeys: const ['id', 'name']);
|
||||
return WeightUnit(id: (json['id'] as num).toInt(), name: json['name'] as String);
|
||||
return WeightUnit(
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$WeightUnitToJson(WeightUnit instance) => <String, dynamic>{
|
||||
|
||||
@@ -1,101 +1,704 @@
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:wger/helpers/shared_preferences.dart';
|
||||
import 'package:wger/helpers/uuid.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/day_data.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/models/workouts/set_config_data.dart';
|
||||
|
||||
part 'gym_state.g.dart';
|
||||
|
||||
const DEFAULT_DURATION = Duration(hours: 5);
|
||||
|
||||
final StateNotifierProvider<GymStateNotifier, GymState> gymStateProvider =
|
||||
StateNotifierProvider<GymStateNotifier, GymState>((ref) {
|
||||
return GymStateNotifier();
|
||||
});
|
||||
const PREFS_SHOW_EXERCISES = 'showExercisePrefs';
|
||||
const PREFS_SHOW_TIMER = 'showTimerPrefs';
|
||||
const PREFS_ALERT_COUNTDOWN = 'alertCountdownPrefs';
|
||||
const PREFS_USE_COUNTDOWN_BETWEEN_SETS = 'useCountdownBetweenSetsPrefs';
|
||||
const PREFS_COUNTDOWN_DURATION = 'countdownDurationSecondsPrefs';
|
||||
|
||||
class GymState {
|
||||
final Map<Exercise, int> exercisePages;
|
||||
final bool showExercisePages;
|
||||
final int currentPage;
|
||||
final int? dayId;
|
||||
late TimeOfDay startTime;
|
||||
late DateTime validUntil;
|
||||
/// In seconds
|
||||
const DEFAULT_COUNTDOWN_DURATION = 180;
|
||||
const MIN_COUNTDOWN_DURATION = 10;
|
||||
const MAX_COUNTDOWN_DURATION = 1800;
|
||||
|
||||
GymState({
|
||||
this.exercisePages = const {},
|
||||
this.showExercisePages = true,
|
||||
this.currentPage = 0,
|
||||
this.dayId,
|
||||
DateTime? validUntil,
|
||||
TimeOfDay? startTime,
|
||||
enum PageType {
|
||||
start,
|
||||
set,
|
||||
session,
|
||||
workoutSummary,
|
||||
}
|
||||
|
||||
enum SlotPageType {
|
||||
exerciseOverview,
|
||||
log,
|
||||
timer,
|
||||
}
|
||||
|
||||
class PageEntry {
|
||||
final String uuid;
|
||||
|
||||
final PageType type;
|
||||
|
||||
/// Absolute page index
|
||||
final int pageIndex;
|
||||
|
||||
final List<SlotPageEntry> slotPages;
|
||||
|
||||
PageEntry({
|
||||
required this.type,
|
||||
required this.pageIndex,
|
||||
this.slotPages = const [],
|
||||
String? uuid,
|
||||
}) : uuid = uuid ?? uuidV4(),
|
||||
assert(
|
||||
slotPages.isEmpty || type == PageType.set,
|
||||
'SlotEntries can only be set for set pages',
|
||||
);
|
||||
|
||||
PageEntry copyWith({
|
||||
String? uuid,
|
||||
PageType? type,
|
||||
int? pageIndex,
|
||||
List<SlotPageEntry>? slotPages,
|
||||
}) {
|
||||
this.validUntil = validUntil ?? DateTime.now().add(DEFAULT_DURATION);
|
||||
this.startTime = startTime ?? TimeOfDay.fromDateTime(clock.now());
|
||||
return PageEntry(
|
||||
uuid: uuid ?? this.uuid,
|
||||
type: type ?? this.type,
|
||||
pageIndex: pageIndex ?? this.pageIndex,
|
||||
slotPages: slotPages ?? this.slotPages,
|
||||
);
|
||||
}
|
||||
|
||||
GymState copyWith({
|
||||
Map<Exercise, int>? exercisePages,
|
||||
bool? showExercisePages,
|
||||
int? currentPage,
|
||||
List<Exercise> get exercises {
|
||||
final exerciseSet = <Exercise>{};
|
||||
for (final entry in slotPages) {
|
||||
exerciseSet.add(entry.setConfigData!.exercise);
|
||||
}
|
||||
return exerciseSet.toList();
|
||||
}
|
||||
|
||||
// Whether all sub-pages (e.g. log pages) are marked as done.
|
||||
bool get allLogsDone =>
|
||||
slotPages.where((entry) => entry.type == SlotPageType.log).every((entry) => entry.logDone);
|
||||
|
||||
@override
|
||||
String toString() => 'PageEntry(type: $type, pageIndex: $pageIndex)';
|
||||
}
|
||||
|
||||
class SlotPageEntry {
|
||||
final String uuid;
|
||||
|
||||
final SlotPageType type;
|
||||
|
||||
/// index within a set for overview (e.g. "1 of 5 sets")
|
||||
final int setIndex;
|
||||
|
||||
/// Absolute page index
|
||||
final int pageIndex;
|
||||
|
||||
/// Whether the log page has been marked as done
|
||||
final bool logDone;
|
||||
|
||||
/// The associated SetConfigData
|
||||
final SetConfigData? setConfigData;
|
||||
|
||||
SlotPageEntry({
|
||||
required this.type,
|
||||
required this.pageIndex,
|
||||
required this.setIndex,
|
||||
this.setConfigData,
|
||||
this.logDone = false,
|
||||
String? uuid,
|
||||
}) : uuid = uuid ?? uuidV4();
|
||||
|
||||
SlotPageEntry copyWith({
|
||||
String? uuid,
|
||||
SlotPageType? type,
|
||||
int? exerciseId,
|
||||
int? setIndex,
|
||||
int? pageIndex,
|
||||
SetConfigData? setConfigData,
|
||||
bool? logDone,
|
||||
}) {
|
||||
return SlotPageEntry(
|
||||
uuid: uuid ?? this.uuid,
|
||||
type: type ?? this.type,
|
||||
setIndex: setIndex ?? this.setIndex,
|
||||
pageIndex: pageIndex ?? this.pageIndex,
|
||||
setConfigData: setConfigData ?? this.setConfigData,
|
||||
logDone: logDone ?? this.logDone,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'SlotPageEntry('
|
||||
'uuid: $uuid, '
|
||||
'type: $type, '
|
||||
'setIndex: $setIndex, '
|
||||
'pageIndex: $pageIndex, '
|
||||
'logDone: $logDone'
|
||||
')';
|
||||
}
|
||||
|
||||
class GymModeState {
|
||||
final _logger = Logger('GymModeState');
|
||||
|
||||
// Navigation data
|
||||
final bool isInitialized;
|
||||
|
||||
final List<PageEntry> pages;
|
||||
final int currentPage;
|
||||
|
||||
final TimeOfDay startTime;
|
||||
final DateTime validUntil;
|
||||
|
||||
// User settings
|
||||
final bool showExercisePages;
|
||||
final bool showTimerPages;
|
||||
final bool alertOnCountdownEnd;
|
||||
final bool useCountdownBetweenSets;
|
||||
final Duration countdownDuration;
|
||||
|
||||
// Routine data
|
||||
late final int dayId;
|
||||
late final int iteration;
|
||||
late final Routine routine;
|
||||
|
||||
GymModeState({
|
||||
this.isInitialized = false,
|
||||
this.pages = const [],
|
||||
this.currentPage = 0,
|
||||
|
||||
this.showExercisePages = true,
|
||||
this.showTimerPages = true,
|
||||
this.alertOnCountdownEnd = true,
|
||||
this.useCountdownBetweenSets = false,
|
||||
this.countdownDuration = const Duration(seconds: DEFAULT_COUNTDOWN_DURATION),
|
||||
|
||||
int? dayId,
|
||||
int? iteration,
|
||||
Routine? routine,
|
||||
|
||||
DateTime? validUntil,
|
||||
TimeOfDay? startTime,
|
||||
}) : validUntil = validUntil ?? clock.now().add(DEFAULT_DURATION),
|
||||
startTime = startTime ?? TimeOfDay.fromDateTime(clock.now()) {
|
||||
if (dayId != null) {
|
||||
this.dayId = dayId;
|
||||
}
|
||||
|
||||
if (iteration != null) {
|
||||
this.iteration = iteration;
|
||||
}
|
||||
|
||||
if (routine != null) {
|
||||
this.routine = routine;
|
||||
}
|
||||
}
|
||||
|
||||
GymModeState copyWith({
|
||||
// Navigation data
|
||||
bool? isInitialized,
|
||||
List<PageEntry>? pages,
|
||||
int? currentPage,
|
||||
|
||||
// Routine data
|
||||
int? dayId,
|
||||
int? iteration,
|
||||
DateTime? validUntil,
|
||||
TimeOfDay? startTime,
|
||||
Routine? routine,
|
||||
|
||||
// User settings
|
||||
bool? showExercisePages,
|
||||
bool? showTimerPages,
|
||||
bool? alertOnCountdownEnd,
|
||||
bool? useCountdownBetweenSets,
|
||||
int? countdownDuration,
|
||||
}) {
|
||||
return GymState(
|
||||
exercisePages: exercisePages ?? this.exercisePages,
|
||||
showExercisePages: showExercisePages ?? this.showExercisePages,
|
||||
return GymModeState(
|
||||
isInitialized: isInitialized ?? this.isInitialized,
|
||||
pages: pages ?? this.pages,
|
||||
currentPage: currentPage ?? this.currentPage,
|
||||
|
||||
dayId: dayId ?? this.dayId,
|
||||
validUntil: validUntil ?? this.validUntil.add(DEFAULT_DURATION),
|
||||
iteration: iteration ?? this.iteration,
|
||||
validUntil: validUntil ?? this.validUntil,
|
||||
startTime: startTime ?? this.startTime,
|
||||
routine: routine ?? this.routine,
|
||||
|
||||
showExercisePages: showExercisePages ?? this.showExercisePages,
|
||||
showTimerPages: showTimerPages ?? this.showTimerPages,
|
||||
alertOnCountdownEnd: alertOnCountdownEnd ?? this.alertOnCountdownEnd,
|
||||
useCountdownBetweenSets: useCountdownBetweenSets ?? this.useCountdownBetweenSets,
|
||||
countdownDuration: Duration(
|
||||
seconds: countdownDuration ?? this.countdownDuration.inSeconds,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int get totalPages {
|
||||
// Main pages (start, session, etc.)
|
||||
var count = pages.where((p) => p.type != PageType.set).length;
|
||||
|
||||
// Add all other sub pages (sets, timer, etc.)
|
||||
count += pages.fold(0, (prev, e) => prev + e.slotPages.length);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
DayData get dayDataGym =>
|
||||
routine.dayDataGym.where((e) => e.iteration == iteration && e.day?.id == dayId).first;
|
||||
|
||||
DayData get dayDataDisplay => routine.dayData.firstWhere(
|
||||
(e) => e.iteration == iteration && e.day?.id == dayId,
|
||||
);
|
||||
|
||||
PageEntry? getPageByIndex([int? pageIndex]) {
|
||||
final index = pageIndex ?? currentPage;
|
||||
|
||||
for (final page in pages) {
|
||||
for (final slotPage in page.slotPages) {
|
||||
if (slotPage.pageIndex == index) {
|
||||
return page;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
SlotPageEntry? getSlotEntryPageByIndex([int? pageIndex]) {
|
||||
final index = pageIndex ?? currentPage;
|
||||
|
||||
for (final slotPage in pages.expand((p) => p.slotPages)) {
|
||||
if (slotPage.pageIndex == index) {
|
||||
return slotPage;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
SlotPageEntry? getSlotPageByUUID(String uuid) {
|
||||
for (final slotPage in pages.expand((p) => p.slotPages)) {
|
||||
if (slotPage.uuid == uuid) {
|
||||
return slotPage;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
double get ratioCompleted {
|
||||
if (totalPages == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Note: add 1 to currentPage to make it 1-based
|
||||
return (currentPage + 1) / totalPages;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GymState('
|
||||
'currentPage: $currentPage, '
|
||||
'showExercisePages: $showExercisePages, '
|
||||
'exercisePages: ${exercisePages.length} exercises, '
|
||||
'dayId: $dayId, '
|
||||
'validUntil: $validUntil '
|
||||
'startTime: $startTime, '
|
||||
'showExercisePages: $showExercisePages, '
|
||||
'showTimerPages: $showTimerPages, '
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
class GymStateNotifier extends StateNotifier<GymState> {
|
||||
@Riverpod(keepAlive: true)
|
||||
class GymStateNotifier extends _$GymStateNotifier {
|
||||
final _logger = Logger('GymStateNotifier');
|
||||
|
||||
GymStateNotifier() : super(GymState());
|
||||
@override
|
||||
GymModeState build() {
|
||||
_logger.finer('Initializing GymStateNotifier');
|
||||
return GymModeState();
|
||||
}
|
||||
|
||||
Future<void> loadPrefs() async {
|
||||
final prefs = PreferenceHelper.asyncPref;
|
||||
|
||||
final showExercise = await prefs.getBool(PREFS_SHOW_EXERCISES);
|
||||
if (showExercise != null && showExercise != state.showExercisePages) {
|
||||
state = state.copyWith(showExercisePages: showExercise);
|
||||
}
|
||||
|
||||
final showTimer = await prefs.getBool(PREFS_SHOW_TIMER);
|
||||
if (showTimer != null && showTimer != state.showTimerPages) {
|
||||
state = state.copyWith(showTimerPages: showTimer);
|
||||
}
|
||||
|
||||
final alertOnCountdownEnd = await prefs.getBool(PREFS_ALERT_COUNTDOWN);
|
||||
if (alertOnCountdownEnd != null && alertOnCountdownEnd != state.alertOnCountdownEnd) {
|
||||
state = state.copyWith(alertOnCountdownEnd: alertOnCountdownEnd);
|
||||
}
|
||||
|
||||
final useCountdownBetweenSets = await prefs.getBool(PREFS_USE_COUNTDOWN_BETWEEN_SETS);
|
||||
if (useCountdownBetweenSets != null &&
|
||||
useCountdownBetweenSets != state.useCountdownBetweenSets) {
|
||||
state = state.copyWith(useCountdownBetweenSets: useCountdownBetweenSets);
|
||||
}
|
||||
|
||||
final defaultCountdownDurationSeconds = await prefs.getInt(PREFS_COUNTDOWN_DURATION);
|
||||
if (defaultCountdownDurationSeconds != null &&
|
||||
defaultCountdownDurationSeconds != state.countdownDuration.inSeconds) {
|
||||
state = state.copyWith(
|
||||
countdownDuration: defaultCountdownDurationSeconds,
|
||||
);
|
||||
}
|
||||
|
||||
_logger.finer(
|
||||
'Loaded saved preferences: '
|
||||
'showExercise=$showExercise '
|
||||
'showTimer=$showTimer '
|
||||
'alertOnCountdownEnd=$alertOnCountdownEnd '
|
||||
'useCountdownBetweenSets=$useCountdownBetweenSets '
|
||||
'defaultCountdownDurationSeconds=$defaultCountdownDurationSeconds',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _savePrefs() async {
|
||||
final prefs = PreferenceHelper.asyncPref;
|
||||
await prefs.setBool(PREFS_SHOW_EXERCISES, state.showExercisePages);
|
||||
await prefs.setBool(PREFS_SHOW_TIMER, state.showTimerPages);
|
||||
await prefs.setBool(PREFS_ALERT_COUNTDOWN, state.alertOnCountdownEnd);
|
||||
await prefs.setBool(PREFS_USE_COUNTDOWN_BETWEEN_SETS, state.useCountdownBetweenSets);
|
||||
await prefs.setInt(
|
||||
PREFS_COUNTDOWN_DURATION,
|
||||
state.countdownDuration.inSeconds,
|
||||
);
|
||||
_logger.finer(
|
||||
'Saved preferences: '
|
||||
'showExercise=${state.showExercisePages} '
|
||||
'showTimer=${state.showTimerPages} '
|
||||
'alertOnCountdownEnd=${state.alertOnCountdownEnd} '
|
||||
'useCountdownBetweenSets=${state.useCountdownBetweenSets} '
|
||||
'defaultCountdownDuration=${state.countdownDuration.inSeconds}',
|
||||
);
|
||||
}
|
||||
|
||||
/// Calculates the page entries
|
||||
void calculatePages() {
|
||||
var pageIndex = 0;
|
||||
|
||||
final List<PageEntry> pages = [
|
||||
// Start page
|
||||
PageEntry(type: PageType.start, pageIndex: pageIndex),
|
||||
];
|
||||
|
||||
pageIndex++;
|
||||
for (final slotData in state.dayDataGym.slots) {
|
||||
final slotPageIndex = pageIndex;
|
||||
final slotEntries = <SlotPageEntry>[];
|
||||
int setIndex = 0;
|
||||
|
||||
// exercise overview page
|
||||
if (state.showExercisePages) {
|
||||
// Add one overview page per exercise in the slot (e.g. for supersets)
|
||||
for (final exerciseId in slotData.exerciseIds) {
|
||||
final setConfig = slotData.setConfigs.firstWhereOrNull((c) => c.exerciseId == exerciseId);
|
||||
if (setConfig == null) {
|
||||
_logger.warning('Exercise with ID $exerciseId not found in slotData!!');
|
||||
continue;
|
||||
}
|
||||
|
||||
slotEntries.add(
|
||||
SlotPageEntry(
|
||||
type: SlotPageType.exerciseOverview,
|
||||
setIndex: setIndex,
|
||||
pageIndex: pageIndex,
|
||||
setConfigData: setConfig,
|
||||
),
|
||||
);
|
||||
pageIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
for (final config in slotData.setConfigs) {
|
||||
// Log page
|
||||
slotEntries.add(
|
||||
SlotPageEntry(
|
||||
type: SlotPageType.log,
|
||||
setIndex: setIndex,
|
||||
pageIndex: pageIndex,
|
||||
setConfigData: config,
|
||||
),
|
||||
);
|
||||
pageIndex++;
|
||||
setIndex++;
|
||||
|
||||
// Timer page
|
||||
if (state.showTimerPages) {
|
||||
slotEntries.add(
|
||||
SlotPageEntry(
|
||||
type: SlotPageType.timer,
|
||||
setIndex: setIndex,
|
||||
pageIndex: pageIndex,
|
||||
setConfigData: config,
|
||||
),
|
||||
);
|
||||
pageIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
pages.add(
|
||||
PageEntry(
|
||||
type: PageType.set,
|
||||
pageIndex: slotPageIndex,
|
||||
slotPages: slotEntries,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Session and summary page
|
||||
pages.add(PageEntry(type: PageType.session, pageIndex: pageIndex));
|
||||
pages.add(PageEntry(type: PageType.workoutSummary, pageIndex: pageIndex + 1));
|
||||
|
||||
state = state.copyWith(pages: pages);
|
||||
// _logger.finer(readPageStructure());
|
||||
_logger.finer('Initialized ${state.pages.length} pages');
|
||||
}
|
||||
|
||||
// Recalculates the indices of all pages
|
||||
void recalculateIndices() {
|
||||
var pageIndex = 0;
|
||||
final updatedPages = <PageEntry>[];
|
||||
|
||||
for (final page in state.pages) {
|
||||
final slotPageIndex = pageIndex;
|
||||
var setIndex = 0;
|
||||
final updatedSlotPages = <SlotPageEntry>[];
|
||||
|
||||
for (final slotPage in page.slotPages) {
|
||||
updatedSlotPages.add(
|
||||
slotPage.copyWith(
|
||||
pageIndex: pageIndex,
|
||||
setIndex: setIndex,
|
||||
),
|
||||
);
|
||||
setIndex++;
|
||||
pageIndex++;
|
||||
}
|
||||
|
||||
if (page.type != PageType.set) {
|
||||
pageIndex++;
|
||||
}
|
||||
|
||||
updatedPages.add(
|
||||
page.copyWith(
|
||||
pageIndex: slotPageIndex,
|
||||
slotPages: updatedSlotPages,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
state = state.copyWith(pages: updatedPages);
|
||||
// _logger.fine(readPageStructure());
|
||||
_logger.fine('Recalculated page indices');
|
||||
}
|
||||
|
||||
/// Reads the current page structure for debugging purposes
|
||||
String readPageStructure() {
|
||||
final List<String> out = [];
|
||||
out.add('GymModeState structure:');
|
||||
for (final page in state.pages) {
|
||||
out.add('Page ${page.pageIndex}: ${page.type}');
|
||||
for (final slotPage in page.slotPages) {
|
||||
out.add(
|
||||
' SlotPage ${slotPage.pageIndex.toString().padLeft(2, ' ')} (set index ${slotPage.setIndex}): ${slotPage.type}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
int initData(Routine routine, int dayId, int iteration) {
|
||||
final validUntil = state.validUntil;
|
||||
final currentPage = state.currentPage;
|
||||
|
||||
final shouldReset =
|
||||
(!state.isInitialized || state.isInitialized && dayId != state.dayId) ||
|
||||
validUntil.isBefore(DateTime.now());
|
||||
if (shouldReset) {
|
||||
_logger.fine('Day ID mismatch or expired validUntil date. Resetting to page 0.');
|
||||
}
|
||||
final initialPage = shouldReset ? 0 : currentPage;
|
||||
|
||||
// set dayId and initial page
|
||||
state = state.copyWith(
|
||||
isInitialized: true,
|
||||
dayId: dayId,
|
||||
routine: routine,
|
||||
iteration: iteration,
|
||||
currentPage: initialPage,
|
||||
);
|
||||
|
||||
// Calculate the pages.
|
||||
// Note that this is only done if we need to reset, otherwise we keep the
|
||||
// existing state like the exercises that have already been done
|
||||
if (shouldReset) {
|
||||
calculatePages();
|
||||
}
|
||||
|
||||
_logger.fine('Initialized GymModeState, initialPage=$initialPage');
|
||||
return initialPage;
|
||||
}
|
||||
|
||||
void setCurrentPage(int page) {
|
||||
// _logger.fine('Setting page from ${state.currentPage} to $page');
|
||||
state = state.copyWith(currentPage: page);
|
||||
}
|
||||
|
||||
void toggleExercisePages() {
|
||||
state = state.copyWith(showExercisePages: !state.showExercisePages);
|
||||
void setShowExercisePages(bool value) {
|
||||
state = state.copyWith(showExercisePages: value);
|
||||
calculatePages();
|
||||
_savePrefs();
|
||||
}
|
||||
|
||||
void setDayId(int dayId) {
|
||||
// _logger.fine('Setting day id from ${state.dayId} to $dayId');
|
||||
state = state.copyWith(dayId: dayId);
|
||||
void setShowTimerPages(bool value) {
|
||||
state = state.copyWith(showTimerPages: value);
|
||||
calculatePages();
|
||||
_savePrefs();
|
||||
}
|
||||
|
||||
void setExercisePages(Map<Exercise, int> exercisePages) {
|
||||
// _logger.fine('Setting exercise pages - ${exercisePages.length} exercises');
|
||||
state = state.copyWith(exercisePages: exercisePages);
|
||||
// _logger.fine(
|
||||
// 'Exercise pages set - ${exercisePages.entries.map((e) => '${e.key.id}: ${e.value}').join(', ')}');
|
||||
void setAlertOnCountdownEnd(bool value) {
|
||||
state = state.copyWith(alertOnCountdownEnd: value);
|
||||
_savePrefs();
|
||||
}
|
||||
|
||||
void setUseCountdownBetweenSets(bool value) {
|
||||
state = state.copyWith(useCountdownBetweenSets: value);
|
||||
_savePrefs();
|
||||
}
|
||||
|
||||
void setCountdownDuration(int duration) {
|
||||
state = state.copyWith(countdownDuration: duration);
|
||||
_savePrefs();
|
||||
}
|
||||
|
||||
void markSlotPageAsDone(String uuid, {required bool isDone}) {
|
||||
final slotPage = state.getSlotPageByUUID(uuid);
|
||||
if (slotPage == null) {
|
||||
_logger.warning('No slot page found for UUID $uuid');
|
||||
return;
|
||||
}
|
||||
|
||||
final updatedSlotPage = slotPage.copyWith(logDone: isDone);
|
||||
|
||||
final updatedPages = state.pages.map((page) {
|
||||
if (page.type != PageType.set) {
|
||||
return page;
|
||||
}
|
||||
|
||||
final updatedSlotPages = page.slotPages.map((sp) {
|
||||
if (sp.uuid == uuid) {
|
||||
return updatedSlotPage;
|
||||
}
|
||||
return sp;
|
||||
}).toList();
|
||||
|
||||
return page.copyWith(slotPages: updatedSlotPages);
|
||||
}).toList();
|
||||
|
||||
state = state.copyWith(pages: updatedPages);
|
||||
_logger.fine('Set logDone=$isDone for slot page UUID $uuid');
|
||||
}
|
||||
|
||||
void replaceExercises(
|
||||
String pageEntryUUID, {
|
||||
required int originalExerciseId,
|
||||
required Exercise newExercise,
|
||||
}) {
|
||||
final updatedPages = state.pages.map((page) {
|
||||
if (page.type != PageType.set) {
|
||||
return page;
|
||||
}
|
||||
|
||||
if (page.uuid != pageEntryUUID) {
|
||||
return page;
|
||||
}
|
||||
|
||||
final updatedSlotPages = page.slotPages.map((slotPage) {
|
||||
if (slotPage.setConfigData != null &&
|
||||
slotPage.setConfigData!.exercise.id == originalExerciseId) {
|
||||
final updatedSetConfigData = slotPage.setConfigData!.copyWith(
|
||||
exerciseId: newExercise.id,
|
||||
exercise: newExercise,
|
||||
);
|
||||
return slotPage.copyWith(setConfigData: updatedSetConfigData);
|
||||
}
|
||||
return slotPage;
|
||||
}).toList();
|
||||
|
||||
return page.copyWith(slotPages: updatedSlotPages);
|
||||
}).toList();
|
||||
|
||||
// TODO: this should not be done in-place!
|
||||
state.routine.replaceExercise(originalExerciseId, newExercise);
|
||||
state = state.copyWith(
|
||||
pages: updatedPages,
|
||||
);
|
||||
_logger.fine('Replaced exercise $originalExerciseId with ${newExercise.id}');
|
||||
}
|
||||
|
||||
void addExerciseAfterPage(
|
||||
String pageEntryUUID, {
|
||||
required Exercise newExercise,
|
||||
}) {
|
||||
final List<PageEntry> pages = [];
|
||||
for (final page in state.pages) {
|
||||
pages.add(page);
|
||||
|
||||
if (page.uuid == pageEntryUUID) {
|
||||
final setConfigData = page.slotPages.first.setConfigData!;
|
||||
|
||||
final List<SlotPageEntry> newSlotPages = [];
|
||||
for (var i = 1; i <= 4; i++) {
|
||||
newSlotPages.add(
|
||||
SlotPageEntry(
|
||||
type: SlotPageType.log,
|
||||
pageIndex: 1,
|
||||
setIndex: 0,
|
||||
setConfigData: SetConfigData(
|
||||
textRepr: '-/-',
|
||||
exerciseId: newExercise.id!,
|
||||
exercise: newExercise,
|
||||
slotEntryId: setConfigData.slotEntryId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final newPage = PageEntry(type: PageType.set, pageIndex: 1, slotPages: newSlotPages);
|
||||
|
||||
pages.add(newPage);
|
||||
}
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
pages: pages,
|
||||
);
|
||||
|
||||
recalculateIndices();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_logger.fine('Clearing state');
|
||||
state = state.copyWith(
|
||||
exercisePages: {},
|
||||
isInitialized: false,
|
||||
pages: [],
|
||||
currentPage: 0,
|
||||
dayId: null,
|
||||
validUntil: DateTime.now().add(DEFAULT_DURATION),
|
||||
startTime: TimeOfDay.now(),
|
||||
|
||||
validUntil: clock.now().add(DEFAULT_DURATION),
|
||||
startTime: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
62
lib/providers/gym_state.g.dart
Normal file
62
lib/providers/gym_state.g.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'gym_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(GymStateNotifier)
|
||||
const gymStateProvider = GymStateNotifierProvider._();
|
||||
|
||||
final class GymStateNotifierProvider extends $NotifierProvider<GymStateNotifier, GymModeState> {
|
||||
const GymStateNotifierProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'gymStateProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$gymStateNotifierHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
GymStateNotifier create() => GymStateNotifier();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(GymModeState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<GymModeState>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$gymStateNotifierHash() => r'449bd80d3b534f68af4f0dbb8556c7f093f3b918';
|
||||
|
||||
abstract class _$GymStateNotifier extends $Notifier<GymModeState> {
|
||||
GymModeState build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<GymModeState, GymModeState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<GymModeState, GymModeState>,
|
||||
GymModeState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,9 @@ const DEFAULT_BAR_WEIGHT_LB = 45;
|
||||
|
||||
const PREFS_KEY_PLATES = 'selectedPlates';
|
||||
|
||||
final plateCalculatorProvider =
|
||||
StateNotifierProvider<PlateCalculatorNotifier, PlateCalculatorState>((ref) {
|
||||
return PlateCalculatorNotifier();
|
||||
});
|
||||
final plateCalculatorProvider = NotifierProvider<PlateCalculatorNotifier, PlateCalculatorState>(
|
||||
PlateCalculatorNotifier.new,
|
||||
);
|
||||
|
||||
class PlateCalculatorState {
|
||||
final _logger = Logger('PlateWeightsState');
|
||||
@@ -135,14 +134,19 @@ class PlateCalculatorState {
|
||||
}
|
||||
}
|
||||
|
||||
class PlateCalculatorNotifier extends StateNotifier<PlateCalculatorState> {
|
||||
class PlateCalculatorNotifier extends Notifier<PlateCalculatorState> {
|
||||
final _logger = Logger('PlateCalculatorNotifier');
|
||||
|
||||
late SharedPreferencesAsync prefs;
|
||||
|
||||
PlateCalculatorNotifier({SharedPreferencesAsync? prefs}) : super(PlateCalculatorState()) {
|
||||
PlateCalculatorNotifier({SharedPreferencesAsync? prefs}) : super() {
|
||||
this.prefs = prefs ?? PreferenceHelper.asyncPref;
|
||||
}
|
||||
|
||||
@override
|
||||
PlateCalculatorState build() {
|
||||
_readDataFromSharedPrefs();
|
||||
return PlateCalculatorState();
|
||||
}
|
||||
|
||||
Future<void> saveToSharedPrefs() async {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' hide Consumer;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/core/wide_screen_wrapper.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
@@ -30,31 +31,22 @@ class GymModeArguments {
|
||||
const GymModeArguments(this.routineId, this.dayId, this.iteration);
|
||||
}
|
||||
|
||||
class GymModeScreen extends StatelessWidget {
|
||||
class GymModeScreen extends ConsumerWidget {
|
||||
const GymModeScreen();
|
||||
|
||||
static const routeName = '/gym-mode';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final args = ModalRoute.of(context)!.settings.arguments as GymModeArguments;
|
||||
|
||||
final routinesProvider = context.read<RoutinesProvider>();
|
||||
final routine = routinesProvider.findById(args.routineId);
|
||||
final dayDataDisplay = routine.dayData.firstWhere(
|
||||
(e) => e.iteration == args.iteration && e.day?.id == args.dayId,
|
||||
);
|
||||
final dayDataGym = routine.dayDataGym
|
||||
.where((e) => e.iteration == args.iteration && e.day?.id == args.dayId)
|
||||
.first;
|
||||
|
||||
return Scaffold(
|
||||
// backgroundColor: Theme.of(context).cardColor,
|
||||
// primary: false,
|
||||
//primary: false,
|
||||
body: SafeArea(
|
||||
child: WidescreenWrapper(
|
||||
child: Consumer<RoutinesProvider>(
|
||||
builder: (context, value, child) => GymMode(dayDataGym, dayDataDisplay, args.iteration),
|
||||
builder: (context, value, child) => GymMode(args),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -21,7 +21,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wger/core/wide_screen_wrapper.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/widgets/core/app_bar.dart';
|
||||
import 'package:wger/widgets/routines/workout_logs.dart';
|
||||
import 'package:wger/widgets/routines/logs/log_overview_routine.dart';
|
||||
|
||||
class WorkoutLogsScreen extends StatelessWidget {
|
||||
const WorkoutLogsScreen();
|
||||
|
||||
@@ -71,7 +71,7 @@ class Step1Basics extends StatelessWidget {
|
||||
return AppLocalizations.of(context).selectEntry;
|
||||
}
|
||||
},
|
||||
displayName: (ExerciseCategory c) => getTranslation(c.name, context),
|
||||
displayName: (ExerciseCategory c) => getServerStringTranslation(c.name, context),
|
||||
),
|
||||
AddExerciseMultiselectButton<Equipment>(
|
||||
key: const Key('equipment-multiselect'),
|
||||
@@ -84,7 +84,7 @@ class Step1Basics extends StatelessWidget {
|
||||
onSaved: (dynamic entries) {
|
||||
addExerciseProvider.equipment = entries.cast<Equipment>();
|
||||
},
|
||||
displayName: (Equipment e) => getTranslation(e.name, context),
|
||||
displayName: (Equipment e) => getServerStringTranslation(e.name, context),
|
||||
),
|
||||
AddExerciseMultiselectButton<Muscle>(
|
||||
key: const Key('primary-muscles-multiselect'),
|
||||
@@ -98,7 +98,10 @@ class Step1Basics extends StatelessWidget {
|
||||
addExerciseProvider.primaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
displayName: (Muscle e) =>
|
||||
e.name + (e.nameEn.isNotEmpty ? '\n(${getTranslation(e.nameEn, context)})' : ''),
|
||||
e.name +
|
||||
(e.nameEn.isNotEmpty
|
||||
? '\n(${getServerStringTranslation(e.nameEn, context)})'
|
||||
: ''),
|
||||
),
|
||||
AddExerciseMultiselectButton<Muscle>(
|
||||
key: const Key('secondary-muscles-multiselect'),
|
||||
@@ -112,7 +115,10 @@ class Step1Basics extends StatelessWidget {
|
||||
addExerciseProvider.secondaryMuscles = muscles.cast<Muscle>();
|
||||
},
|
||||
displayName: (Muscle e) =>
|
||||
e.name + (e.nameEn.isNotEmpty ? '\n(${getTranslation(e.nameEn, context)})' : ''),
|
||||
e.name +
|
||||
(e.nameEn.isNotEmpty
|
||||
? '\n(${getServerStringTranslation(e.nameEn, context)})'
|
||||
: ''),
|
||||
),
|
||||
MuscleRowWidget(
|
||||
muscles: provider.primaryMuscles,
|
||||
|
||||
@@ -145,7 +145,10 @@ class _DashboardCalendarWidgetState extends State<DashboardCalendarWidget>
|
||||
|
||||
// Add events to lists
|
||||
_events[date]?.add(
|
||||
Event(EventType.session, '${i18n.impression}: ${session.impressionAsString} $time'),
|
||||
Event(
|
||||
EventType.session,
|
||||
'${i18n.impression}: ${session.impressionAsString(context)} $time',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -229,7 +229,7 @@ class ExerciseDetail extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Chip(
|
||||
label: Text(getTranslation(_exercise.category!.name, context)),
|
||||
label: Text(getServerStringTranslation(_exercise.category!.name, context)),
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor: theme.splashColor,
|
||||
),
|
||||
@@ -241,7 +241,7 @@ class ExerciseDetail extends StatelessWidget {
|
||||
(e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Chip(
|
||||
label: Text(getTranslation(e.name, context)),
|
||||
label: Text(getServerStringTranslation(e.name, context)),
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor: theme.splashColor,
|
||||
),
|
||||
|
||||
@@ -67,7 +67,7 @@ class _ExerciseFilterModalBodyState extends State<ExerciseFilterModalBody> {
|
||||
body: Column(
|
||||
children: filterCategory.items.entries.map((currentEntry) {
|
||||
return SwitchListTile(
|
||||
title: Text(getTranslation(currentEntry.key.name, context)),
|
||||
title: Text(getServerStringTranslation(currentEntry.key.name, context)),
|
||||
value: currentEntry.value,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
|
||||
@@ -52,7 +52,7 @@ class ExerciseListTile extends StatelessWidget {
|
||||
maxLines: 2,
|
||||
),
|
||||
subtitle: Text(
|
||||
'${getTranslation(exercise.category!.name, context)} / ${exercise.equipment.map((e) => getTranslation(e.name, context)).toList().join(', ')}',
|
||||
'${getServerStringTranslation(exercise.category!.name, context)} / ${exercise.equipment.map((e) => getServerStringTranslation(e.name, context)).toList().join(', ')}',
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pushNamed(context, ExerciseDetailScreen.routeName, arguments: exercise);
|
||||
|
||||
@@ -17,27 +17,21 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/slot_entry.dart';
|
||||
|
||||
/// Input widget for Reps In Reserve
|
||||
class RiRInputWidget extends StatefulWidget {
|
||||
final _logger = Logger('RiRInputWidget');
|
||||
|
||||
final num? _initialValue;
|
||||
final ValueChanged<String> onChanged;
|
||||
late String dropdownValue;
|
||||
late double _currentSetSliderValue;
|
||||
|
||||
static const SLIDER_START = -0.5;
|
||||
|
||||
RiRInputWidget(this._initialValue, {required this.onChanged}) {
|
||||
dropdownValue = _initialValue != null ? _initialValue.toString() : SlotEntry.DEFAULT_RIR;
|
||||
|
||||
// Read string RiR into a double
|
||||
if (_initialValue != null) {
|
||||
_currentSetSliderValue = _initialValue.toDouble();
|
||||
} else {
|
||||
_currentSetSliderValue = SLIDER_START;
|
||||
}
|
||||
_logger.finer('Initializing with initial value: $_initialValue');
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -45,6 +39,28 @@ class RiRInputWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RiRInputWidgetState extends State<RiRInputWidget> {
|
||||
late double _currentSetSliderValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentSetSliderValue = widget._initialValue?.toDouble() ?? RiRInputWidget.SLIDER_START;
|
||||
widget._logger.finer('initState - starting slider value: ${widget._initialValue}');
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant RiRInputWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
final newValue = widget._initialValue?.toDouble() ?? RiRInputWidget.SLIDER_START;
|
||||
if (widget._initialValue != oldWidget._initialValue) {
|
||||
widget._logger.finer('didUpdateWidget - new initial value: ${widget._initialValue}');
|
||||
setState(() {
|
||||
_currentSetSliderValue = newValue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the string used in the slider
|
||||
String getSliderLabel(double value) {
|
||||
if (value < 0) {
|
||||
@@ -77,15 +93,15 @@ class _RiRInputWidgetState extends State<RiRInputWidget> {
|
||||
Text(AppLocalizations.of(context).rir),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: widget._currentSetSliderValue,
|
||||
value: _currentSetSliderValue,
|
||||
min: RiRInputWidget.SLIDER_START,
|
||||
max: (SlotEntry.POSSIBLE_RIR_VALUES.length - 2) / 2,
|
||||
divisions: SlotEntry.POSSIBLE_RIR_VALUES.length - 1,
|
||||
label: getSliderLabel(widget._currentSetSliderValue),
|
||||
label: getSliderLabel(_currentSetSliderValue),
|
||||
onChanged: (double value) {
|
||||
widget.onChanged(mapDoubleToAllowedRir(value));
|
||||
setState(() {
|
||||
widget._currentSetSliderValue = value;
|
||||
_currentSetSliderValue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
@@ -142,6 +142,15 @@ class _SessionFormState extends State<SessionForm> {
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).timeStart,
|
||||
errorMaxLines: 2,
|
||||
suffix: IconButton(
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
timeStartController.text = '';
|
||||
widget._session.timeStart = null;
|
||||
}),
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
controller: timeStartController,
|
||||
onFieldSubmitted: (_) {},
|
||||
@@ -187,6 +196,15 @@ class _SessionFormState extends State<SessionForm> {
|
||||
key: const ValueKey('time-end'),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).timeEnd,
|
||||
suffix: IconButton(
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
timeEndController.text = '';
|
||||
widget._session.timeEnd = null;
|
||||
}),
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
controller: timeEndController,
|
||||
onFieldSubmitted: (_) {},
|
||||
|
||||
@@ -16,44 +16,45 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/widgets/exercises/exercises.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
|
||||
class ExerciseOverview extends StatelessWidget {
|
||||
class ExerciseOverview extends ConsumerWidget {
|
||||
final _logger = Logger('ExerciseOverview');
|
||||
final PageController _controller;
|
||||
final Exercise _exercise;
|
||||
final double _ratioCompleted;
|
||||
final Map<Exercise, int> _exercisePages;
|
||||
final int _totalPages;
|
||||
|
||||
const ExerciseOverview(
|
||||
this._controller,
|
||||
this._exercise,
|
||||
this._ratioCompleted,
|
||||
this._exercisePages,
|
||||
this._totalPages,
|
||||
);
|
||||
ExerciseOverview(this._controller);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final page = ref.watch(gymStateProvider).getSlotEntryPageByIndex();
|
||||
|
||||
if (page == null) {
|
||||
_logger.info(
|
||||
'getPageByIndex returned null, showing empty container.',
|
||||
);
|
||||
return Container();
|
||||
}
|
||||
final exercise = page.setConfigData!.exercise;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
NavigationHeader(
|
||||
_exercise.getTranslation(Localizations.localeOf(context).languageCode).name,
|
||||
exercise.getTranslation(Localizations.localeOf(context).languageCode).name,
|
||||
_controller,
|
||||
totalPages: _totalPages,
|
||||
exercisePages: _exercisePages,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: ExerciseDetail(_exercise),
|
||||
child: ExerciseDetail(exercise),
|
||||
),
|
||||
),
|
||||
),
|
||||
NavigationFooter(_controller, _ratioCompleted),
|
||||
NavigationFooter(_controller),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -15,178 +15,107 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart' as provider;
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/day_data.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/exercise_overview.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/log_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/session_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/start_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/timer.dart';
|
||||
import 'package:wger/screens/gym_mode.dart';
|
||||
import 'package:wger/widgets/core/progress_indicator.dart';
|
||||
|
||||
import 'exercise_overview.dart';
|
||||
import 'log_page.dart';
|
||||
import 'session_page.dart';
|
||||
import 'start_page.dart';
|
||||
import 'summary.dart';
|
||||
import 'timer.dart';
|
||||
|
||||
class GymMode extends ConsumerStatefulWidget {
|
||||
final DayData _dayDataGym;
|
||||
final DayData _dayDataDisplay;
|
||||
final int _iteration;
|
||||
final GymModeArguments _args;
|
||||
final _logger = Logger('GymMode');
|
||||
|
||||
GymMode(this._dayDataGym, this._dayDataDisplay, this._iteration);
|
||||
GymMode(this._args);
|
||||
|
||||
@override
|
||||
ConsumerState<GymMode> createState() => _GymModeState();
|
||||
}
|
||||
|
||||
class _GymModeState extends ConsumerState<GymMode> {
|
||||
var _totalElements = 1;
|
||||
var _totalPages = 1;
|
||||
late Future<int> _initData;
|
||||
bool _initialPageJumped = false;
|
||||
|
||||
/// Map with the first (navigation) page for each exercise
|
||||
final Map<Exercise, int> _exercisePages = {};
|
||||
late final PageController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = PageController(initialPage: 0);
|
||||
_initData = _loadGymState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initData = _loadGymState();
|
||||
_controller = PageController(initialPage: 0);
|
||||
_calculatePages();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(gymStateProvider.notifier).setExercisePages(_exercisePages);
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> _loadGymState() async {
|
||||
// Re-fetch the current routine data to ensure we have the latest session
|
||||
// data since it is possible that the user created or deleted it from the
|
||||
// web interface.
|
||||
await context.read<RoutinesProvider>().fetchAndSetRoutineFull(
|
||||
widget._dayDataGym.day!.routineId,
|
||||
widget._logger.fine('Loading gym state');
|
||||
final routine = await context.read<RoutinesProvider>().fetchAndSetRoutineFull(
|
||||
widget._args.routineId,
|
||||
);
|
||||
widget._logger.fine('Refreshed routine data');
|
||||
|
||||
final validUntil = ref.read(gymStateProvider).validUntil;
|
||||
final currentPage = ref.read(gymStateProvider).currentPage;
|
||||
final savedDayId = ref.read(gymStateProvider).dayId;
|
||||
final newDayId = widget._dayDataGym.day!.id!;
|
||||
|
||||
final shouldReset = newDayId != savedDayId || validUntil.isBefore(DateTime.now());
|
||||
if (shouldReset) {
|
||||
widget._logger.fine('Day ID mismatch or expired validUntil date. Resetting to page 0.');
|
||||
}
|
||||
final initialPage = shouldReset ? 0 : currentPage;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(gymStateProvider.notifier)
|
||||
..setDayId(newDayId)
|
||||
..setCurrentPage(initialPage);
|
||||
});
|
||||
final gymViewModel = ref.read(gymStateProvider.notifier);
|
||||
final initialPage = gymViewModel.initData(
|
||||
routine,
|
||||
widget._args.dayId,
|
||||
widget._args.iteration,
|
||||
);
|
||||
await gymViewModel.loadPrefs();
|
||||
gymViewModel.calculatePages();
|
||||
|
||||
return initialPage;
|
||||
}
|
||||
|
||||
void _calculatePages() {
|
||||
for (final slot in widget._dayDataGym.slots) {
|
||||
_totalElements += slot.setConfigs.length;
|
||||
// add 1 for each exercise
|
||||
_totalPages += 1;
|
||||
for (final config in slot.setConfigs) {
|
||||
// add nrOfSets * 2, 1 for log page and 1 for timer
|
||||
_totalPages += (config.nrOfSets! * 2).toInt();
|
||||
}
|
||||
}
|
||||
_exercisePages.clear();
|
||||
var currentPage = 1;
|
||||
|
||||
for (final slot in widget._dayDataGym.slots) {
|
||||
var firstPage = true;
|
||||
for (final config in slot.setConfigs) {
|
||||
final exercise = context.read<ExercisesProvider>().findExerciseById(config.exerciseId);
|
||||
|
||||
if (firstPage) {
|
||||
_exercisePages[exercise] = currentPage;
|
||||
currentPage++;
|
||||
}
|
||||
currentPage += 2;
|
||||
firstPage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> getContent() {
|
||||
final state = ref.watch(gymStateProvider);
|
||||
final exerciseProvider = context.read<ExercisesProvider>();
|
||||
final routinesProvider = context.read<RoutinesProvider>();
|
||||
var currentElement = 1;
|
||||
List<Widget> _getContent(GymModeState state) {
|
||||
final gymState = ref.watch(gymStateProvider);
|
||||
final List<Widget> out = [];
|
||||
|
||||
for (final slotData in widget._dayDataGym.slots) {
|
||||
var firstPage = true;
|
||||
for (final config in slotData.setConfigs) {
|
||||
final ratioCompleted = currentElement / _totalElements;
|
||||
final exercise = exerciseProvider.findExerciseById(config.exerciseId);
|
||||
currentElement++;
|
||||
// Workout overview
|
||||
out.add(StartPage(_controller));
|
||||
|
||||
if (firstPage && state.showExercisePages) {
|
||||
out.add(
|
||||
ExerciseOverview(
|
||||
_controller,
|
||||
exercise,
|
||||
ratioCompleted,
|
||||
state.exercisePages,
|
||||
_totalPages,
|
||||
),
|
||||
);
|
||||
// Sets
|
||||
for (final page in state.pages) {
|
||||
for (final slotPage in page.slotPages) {
|
||||
if (slotPage.type == SlotPageType.exerciseOverview) {
|
||||
out.add(ExerciseOverview(_controller));
|
||||
}
|
||||
|
||||
out.add(
|
||||
LogPage(
|
||||
_controller,
|
||||
config,
|
||||
slotData,
|
||||
exercise,
|
||||
routinesProvider.findById(widget._dayDataGym.day!.routineId),
|
||||
ratioCompleted,
|
||||
state.exercisePages,
|
||||
_totalPages,
|
||||
widget._iteration,
|
||||
),
|
||||
);
|
||||
|
||||
// If there is a rest time, add a countdown timer
|
||||
if (config.restTime != null) {
|
||||
out.add(
|
||||
TimerCountdownWidget(
|
||||
_controller,
|
||||
config.restTime!.toInt(),
|
||||
ratioCompleted,
|
||||
state.exercisePages,
|
||||
_totalPages,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
out.add(TimerWidget(_controller, ratioCompleted, state.exercisePages, _totalPages));
|
||||
if (slotPage.type == SlotPageType.log) {
|
||||
out.add(LogPage(_controller));
|
||||
}
|
||||
|
||||
firstPage = false;
|
||||
// Timer. Use rest time from config data if available, otherwise use user settings
|
||||
final rest = slotPage.setConfigData?.restTime;
|
||||
if (slotPage.type == SlotPageType.timer) {
|
||||
out.add(
|
||||
(rest != null || gymState.useCountdownBetweenSets)
|
||||
? TimerCountdownWidget(
|
||||
_controller,
|
||||
(rest ?? gymState.countdownDuration.inSeconds).toInt(),
|
||||
)
|
||||
: TimerWidget(_controller),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End
|
||||
out.add(SessionPage(_controller));
|
||||
out.add(WorkoutSummary(_controller));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -196,43 +125,40 @@ class _GymModeState extends ConsumerState<GymMode> {
|
||||
future: _initData,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const BoxedProgressIndicator();
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
return Center(child: Text('Error: ${snapshot.error}: ${snapshot.stackTrace}'));
|
||||
} else if (snapshot.connectionState == ConnectionState.done) {
|
||||
final initialPage = snapshot.data!;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!_initialPageJumped && _controller.hasClients) {
|
||||
_controller.jumpToPage(initialPage);
|
||||
setState(() => _initialPageJumped = true);
|
||||
}
|
||||
});
|
||||
|
||||
final state = ref.watch(gymStateProvider);
|
||||
final children = [
|
||||
..._getContent(state),
|
||||
];
|
||||
|
||||
return PageView(
|
||||
controller: _controller,
|
||||
onPageChanged: (page) {
|
||||
ref.read(gymStateProvider.notifier).setCurrentPage(page);
|
||||
|
||||
// Check if the last page is reached
|
||||
if (page == children.length - 1) {
|
||||
widget._logger.finer('Last page reached, clearing gym state');
|
||||
ref.read(gymStateProvider.notifier).clear();
|
||||
}
|
||||
},
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
final initialPage = snapshot.data!;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!_initialPageJumped && _controller.hasClients) {
|
||||
_controller.jumpToPage(initialPage);
|
||||
setState(() => _initialPageJumped = true);
|
||||
}
|
||||
});
|
||||
|
||||
final List<Widget> children = [
|
||||
StartPage(_controller, widget._dayDataDisplay, _exercisePages),
|
||||
...getContent(),
|
||||
SessionPage(
|
||||
context.read<RoutinesProvider>().findById(widget._dayDataGym.day!.routineId),
|
||||
_controller,
|
||||
ref.read(gymStateProvider).startTime,
|
||||
_exercisePages,
|
||||
dayId: widget._dayDataGym.day!.id!,
|
||||
),
|
||||
];
|
||||
|
||||
return PageView(
|
||||
controller: _controller,
|
||||
onPageChanged: (page) {
|
||||
ref.read(gymStateProvider.notifier).setCurrentPage(page);
|
||||
|
||||
// Check if the last page is reached
|
||||
if (page == children.length - 1) {
|
||||
ref.read(gymStateProvider.notifier).clear();
|
||||
}
|
||||
},
|
||||
children: children,
|
||||
);
|
||||
return const Center(child: Text('Unexpected state'));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,12 +23,10 @@ import 'package:provider/provider.dart' as provider;
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/models/workouts/set_config_data.dart';
|
||||
import 'package:wger/models/workouts/slot_data.dart';
|
||||
import 'package:wger/models/workouts/slot_entry.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/providers/plate_weights.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/screens/configure_plates_screen.dart';
|
||||
@@ -41,29 +39,11 @@ import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
import 'package:wger/widgets/routines/plate_calculator.dart';
|
||||
|
||||
class LogPage extends ConsumerStatefulWidget {
|
||||
final PageController _controller;
|
||||
final SetConfigData _configData;
|
||||
final SlotData _slotData;
|
||||
final Exercise _exercise;
|
||||
final Routine _routine;
|
||||
final double _ratioCompleted;
|
||||
final Map<Exercise, int> _exercisePages;
|
||||
final Log _log;
|
||||
final int _totalPages;
|
||||
final _logger = Logger('LogPage');
|
||||
|
||||
LogPage(
|
||||
this._controller,
|
||||
this._configData,
|
||||
this._slotData,
|
||||
this._exercise,
|
||||
this._routine,
|
||||
this._ratioCompleted,
|
||||
this._exercisePages,
|
||||
this._totalPages,
|
||||
int? iteration,
|
||||
) : _log = Log.fromSetConfigData(_configData)
|
||||
..routineId = _routine.id!
|
||||
..iteration = iteration;
|
||||
final PageController _controller;
|
||||
|
||||
LogPage(this._controller);
|
||||
|
||||
@override
|
||||
_LogPageState createState() => _LogPageState();
|
||||
@@ -89,14 +69,40 @@ class _LogPageState extends ConsumerState<LogPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final state = ref.watch(gymStateProvider);
|
||||
|
||||
final page = state.getPageByIndex();
|
||||
if (page == null) {
|
||||
widget._logger.info(
|
||||
'getPageByIndex for ${state.currentPage} returned null, showing empty container.',
|
||||
);
|
||||
return Container();
|
||||
}
|
||||
|
||||
final slotEntryPage = state.getSlotEntryPageByIndex();
|
||||
if (slotEntryPage == null) {
|
||||
widget._logger.info(
|
||||
'getSlotPageByIndex for ${state.currentPage} returned null, showing empty container',
|
||||
);
|
||||
return Container();
|
||||
}
|
||||
|
||||
final setConfigData = slotEntryPage.setConfigData!;
|
||||
|
||||
final log = Log.fromSetConfigData(setConfigData)
|
||||
..routineId = state.routine.id!
|
||||
..iteration = state.iteration;
|
||||
|
||||
// Mark done sets
|
||||
final decorationStyle = slotEntryPage.logDone
|
||||
? TextDecoration.lineThrough
|
||||
: TextDecoration.none;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
NavigationHeader(
|
||||
widget._exercise.getTranslation(Localizations.localeOf(context).languageCode).name,
|
||||
log.exercise.getTranslation(Localizations.localeOf(context).languageCode).name,
|
||||
widget._controller,
|
||||
totalPages: widget._totalPages,
|
||||
exercisePages: widget._exercisePages,
|
||||
),
|
||||
|
||||
Container(
|
||||
@@ -105,34 +111,47 @@ class _LogPageState extends ConsumerState<LogPage> {
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
setConfigData.textRepr,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
decoration: decorationStyle,
|
||||
),
|
||||
),
|
||||
if (setConfigData.type != SlotEntryType.normal)
|
||||
Text(
|
||||
setConfigData.type.name.toUpperCase(),
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
decoration: decorationStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
widget._configData.textRepr,
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
'${slotEntryPage.setIndex + 1} / ${page.slotPages.where((e) => e.type == SlotPageType.log).length}',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (widget._configData.type != SlotEntryType.normal)
|
||||
Text(
|
||||
widget._configData.type.name.toUpperCase(),
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget._log.exercise.showPlateCalculator) const LogsPlatesWidget(),
|
||||
if (widget._slotData.comment.isNotEmpty)
|
||||
Text(widget._slotData.comment, textAlign: TextAlign.center),
|
||||
if (log.exercise.showPlateCalculator) const LogsPlatesWidget(),
|
||||
if (slotEntryPage.setConfigData!.comment.isNotEmpty)
|
||||
Text(slotEntryPage.setConfigData!.comment, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: (widget._routine.filterLogsByExercise(widget._exercise.id!).isNotEmpty)
|
||||
child: (state.routine.filterLogsByExercise(log.exercise.id!).isNotEmpty)
|
||||
? LogsPastLogsWidget(
|
||||
log: widget._log,
|
||||
pastLogs: widget._routine.filterLogsByExercise(widget._exercise.id!),
|
||||
log: log,
|
||||
pastLogs: state.routine.filterLogsByExercise(log.exercise.id!),
|
||||
onCopy: (pastLog) {
|
||||
_logFormKey.currentState?.copyFromPastLog(pastLog);
|
||||
},
|
||||
@@ -153,14 +172,14 @@ class _LogPageState extends ConsumerState<LogPage> {
|
||||
child: LogFormWidget(
|
||||
key: _logFormKey,
|
||||
controller: widget._controller,
|
||||
configData: widget._configData,
|
||||
log: widget._log,
|
||||
configData: setConfigData,
|
||||
log: log,
|
||||
focusNode: focusNode,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
NavigationFooter(widget._controller, widget._ratioCompleted),
|
||||
NavigationFooter(widget._controller),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -294,8 +313,10 @@ class LogsRepsWidget extends StatelessWidget {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add, color: Colors.black),
|
||||
onPressed: () {
|
||||
final value = controller.text.isNotEmpty ? controller.text : '0';
|
||||
|
||||
try {
|
||||
final newValue = numberFormat.parse(controller.text) + repsValueChange;
|
||||
final newValue = numberFormat.parse(value) + repsValueChange;
|
||||
setStateCallback(() {
|
||||
log.repetitions = newValue;
|
||||
controller.text = numberFormat.format(newValue);
|
||||
@@ -393,8 +414,10 @@ class LogsWeightWidget extends ConsumerWidget {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add, color: Colors.black),
|
||||
onPressed: () {
|
||||
final value = controller.text.isNotEmpty ? controller.text : '0';
|
||||
|
||||
try {
|
||||
final newValue = numberFormat.parse(controller.text) + weightValueChange;
|
||||
final newValue = numberFormat.parse(value) + weightValueChange;
|
||||
setStateCallback(() {
|
||||
log.weight = newValue;
|
||||
controller.text = numberFormat.format(newValue);
|
||||
@@ -441,7 +464,8 @@ class LogsPastLogsWidget extends StatelessWidget {
|
||||
),
|
||||
...pastLogs.map((pastLog) {
|
||||
return ListTile(
|
||||
title: Text(pastLog.singleLogRepTextNoNl),
|
||||
key: ValueKey('past-log-${pastLog.id}'),
|
||||
title: Text(pastLog.repTextNoNl(context)),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode).format(pastLog.date),
|
||||
),
|
||||
@@ -472,12 +496,14 @@ class LogsPastLogsWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
class LogFormWidget extends ConsumerStatefulWidget {
|
||||
final _logger = Logger('LogFormWidget');
|
||||
|
||||
final PageController controller;
|
||||
final SetConfigData configData;
|
||||
final Log log;
|
||||
final FocusNode focusNode;
|
||||
|
||||
const LogFormWidget({
|
||||
LogFormWidget({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.configData,
|
||||
@@ -493,6 +519,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
final _form = GlobalKey<FormState>();
|
||||
var _detailed = false;
|
||||
bool _isSaving = false;
|
||||
late Log _log;
|
||||
|
||||
late final TextEditingController _repetitionsController;
|
||||
late final TextEditingController _weightController;
|
||||
@@ -501,6 +528,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_log = widget.log;
|
||||
_repetitionsController = TextEditingController();
|
||||
_weightController = TextEditingController();
|
||||
|
||||
@@ -533,7 +561,13 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
_repetitionsController.text = pastLog.repetitions != null
|
||||
? numberFormat.format(pastLog.repetitions)
|
||||
: '';
|
||||
widget._logger.finer('Setting log repetitions to ${_repetitionsController.text}');
|
||||
|
||||
_weightController.text = pastLog.weight != null ? numberFormat.format(pastLog.weight) : '';
|
||||
widget._logger.finer('Setting log weight to ${_weightController.text}');
|
||||
|
||||
_log.rir = pastLog.rir;
|
||||
widget._logger.finer('Setting log rir to ${_log.rir}');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -559,7 +593,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
controller: _repetitionsController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
log: widget.log,
|
||||
log: _log,
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
@@ -571,7 +605,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
controller: _weightController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
log: widget.log,
|
||||
log: _log,
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
@@ -588,7 +622,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
controller: _repetitionsController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
log: widget.log,
|
||||
log: _log,
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
@@ -597,7 +631,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: RepetitionUnitInputWidget(
|
||||
widget.log.repetitionsUnitId,
|
||||
_log.repetitionsUnitId,
|
||||
onChanged: (v) => {},
|
||||
),
|
||||
),
|
||||
@@ -613,7 +647,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
controller: _weightController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
log: widget.log,
|
||||
log: _log,
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
@@ -621,19 +655,19 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: WeightUnitInputWidget(widget.log.weightUnitId, onChanged: (v) => {}),
|
||||
child: WeightUnitInputWidget(_log.weightUnitId, onChanged: (v) => {}),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
if (_detailed)
|
||||
RiRInputWidget(
|
||||
widget.log.rir,
|
||||
_log.rir,
|
||||
onChanged: (value) {
|
||||
if (value == '') {
|
||||
widget.log.rir = null;
|
||||
_log.rir = null;
|
||||
} else {
|
||||
widget.log.rir = num.parse(value);
|
||||
_log.rir = num.parse(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -659,10 +693,15 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
_form.currentState!.save();
|
||||
|
||||
try {
|
||||
final gymState = ref.read(gymStateProvider);
|
||||
final gymProvider = ref.read(gymStateProvider.notifier);
|
||||
|
||||
await provider.Provider.of<RoutinesProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addLog(widget.log);
|
||||
).addLog(_log);
|
||||
final page = gymState.getSlotEntryPageByIndex()!;
|
||||
gymProvider.markSlotPageAsDone(page.uuid, isDone: true);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
@@ -17,130 +17,18 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/theme/theme.dart';
|
||||
|
||||
class NavigationFooter extends StatelessWidget {
|
||||
final PageController _controller;
|
||||
final double _ratioCompleted;
|
||||
final bool showPrevious;
|
||||
final bool showNext;
|
||||
|
||||
const NavigationFooter(
|
||||
this._controller,
|
||||
this._ratioCompleted, {
|
||||
this.showPrevious = true,
|
||||
this.showNext = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
if (showPrevious)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
onPressed: () {
|
||||
_controller.previousPage(
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox(width: 48),
|
||||
Expanded(
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 3,
|
||||
value: _ratioCompleted,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(wgerPrimaryColor),
|
||||
),
|
||||
),
|
||||
if (showNext)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
onPressed: () {
|
||||
_controller.nextPage(
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox(width: 48),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:wger/widgets/routines/gym_mode/workout_menu.dart';
|
||||
|
||||
class NavigationHeader extends StatelessWidget {
|
||||
final PageController _controller;
|
||||
final String _title;
|
||||
final Map<Exercise, int> exercisePages;
|
||||
final int? totalPages;
|
||||
final bool showEndWorkoutButton;
|
||||
|
||||
const NavigationHeader(
|
||||
this._title,
|
||||
this._controller, {
|
||||
this.totalPages,
|
||||
required this.exercisePages,
|
||||
});
|
||||
|
||||
Widget getDialog(BuildContext context) {
|
||||
final TextButton? endWorkoutButton = totalPages != null
|
||||
? TextButton(
|
||||
child: Text(AppLocalizations.of(context).endWorkout),
|
||||
onPressed: () {
|
||||
_controller.animateToPage(
|
||||
totalPages!,
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
: null;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).jumpTo,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
...exercisePages.keys.map((e) {
|
||||
return ListTile(
|
||||
title: Text(e.getTranslation(Localizations.localeOf(context).languageCode).name),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
_controller.animateToPage(
|
||||
exercisePages[e]!,
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
?endWorkoutButton,
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
const NavigationHeader(this._title, this._controller, {this.showEndWorkoutButton = true});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -163,11 +51,11 @@ class NavigationHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.toc),
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => getDialog(context),
|
||||
builder: (ctx) => WorkoutMenuDialog(_controller),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -175,3 +63,65 @@ class NavigationHeader extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NavigationFooter extends ConsumerWidget {
|
||||
final PageController _controller;
|
||||
final bool showPrevious;
|
||||
final bool showNext;
|
||||
|
||||
const NavigationFooter(
|
||||
this._controller, {
|
||||
this.showPrevious = true,
|
||||
this.showNext = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final gymState = ref.watch(gymStateProvider);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (showPrevious)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
onPressed: () {
|
||||
_controller.previousPage(
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox(width: 48),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => WorkoutMenuDialog(_controller, initialIndex: 1),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 3,
|
||||
value: gymState.ratioCompleted,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(wgerPrimaryColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showNext)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
onPressed: () {
|
||||
_controller.nextPage(
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox(width: 48),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -17,60 +17,57 @@
|
||||
*/
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/date.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/models/workouts/session.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/widgets/routines/forms/session.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
|
||||
class SessionPage extends StatelessWidget {
|
||||
final Routine _routine;
|
||||
final WorkoutSession _session;
|
||||
class SessionPage extends ConsumerWidget {
|
||||
final PageController _controller;
|
||||
final Map<Exercise, int> _exercisePages;
|
||||
|
||||
SessionPage(
|
||||
this._routine,
|
||||
this._controller,
|
||||
TimeOfDay start,
|
||||
this._exercisePages, {
|
||||
int? dayId,
|
||||
}) : _session = _routine.sessions
|
||||
.map((sessionApi) => sessionApi.session)
|
||||
.firstWhere(
|
||||
(session) => session.date.isSameDayAs(clock.now()),
|
||||
orElse: () => WorkoutSession(
|
||||
dayId: dayId,
|
||||
routineId: _routine.id!,
|
||||
impression: DEFAULT_IMPRESSION,
|
||||
date: clock.now(),
|
||||
timeStart: start,
|
||||
timeEnd: TimeOfDay.fromDateTime(clock.now()),
|
||||
),
|
||||
);
|
||||
const SessionPage(this._controller);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(gymStateProvider);
|
||||
|
||||
final session = state.routine.sessions
|
||||
.map((sessionApi) => sessionApi.session)
|
||||
.firstWhere(
|
||||
(session) => session.date.isSameDayAs(clock.now()),
|
||||
orElse: () => WorkoutSession(
|
||||
dayId: state.dayId,
|
||||
routineId: state.routine.id,
|
||||
impression: DEFAULT_IMPRESSION,
|
||||
date: clock.now(),
|
||||
timeStart: state.startTime,
|
||||
timeEnd: TimeOfDay.fromDateTime(clock.now()),
|
||||
),
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
NavigationHeader(
|
||||
AppLocalizations.of(context).workoutSession,
|
||||
_controller,
|
||||
exercisePages: _exercisePages,
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: SessionForm(
|
||||
_routine.id!,
|
||||
onSaved: () => Navigator.of(context).pop(),
|
||||
session: _session,
|
||||
state.routine.id,
|
||||
onSaved: () => _controller.nextPage(
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
),
|
||||
session: session,
|
||||
),
|
||||
),
|
||||
NavigationFooter(_controller, 1, showNext: false),
|
||||
NavigationFooter(_controller),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,78 +1,243 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/day.dart';
|
||||
import 'package:wger/models/workouts/day_data.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/widgets/exercises/images.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
|
||||
class StartPage extends StatelessWidget {
|
||||
final PageController _controller;
|
||||
final DayData _dayData;
|
||||
final Map<Exercise, int> _exercisePages;
|
||||
class GymModeOptions extends ConsumerStatefulWidget {
|
||||
const GymModeOptions({super.key});
|
||||
|
||||
const StartPage(this._controller, this._dayData, this._exercisePages);
|
||||
@override
|
||||
ConsumerState<GymModeOptions> createState() => _GymModeOptionsState();
|
||||
}
|
||||
|
||||
class _GymModeOptionsState extends ConsumerState<GymModeOptions> {
|
||||
bool _showOptions = false;
|
||||
late TextEditingController _countdownController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final initial = ref.read(gymStateProvider).countdownDuration.inSeconds.toString();
|
||||
_countdownController = TextEditingController(text: initial);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_countdownController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gymState = ref.watch(gymStateProvider);
|
||||
final gymNotifier = ref.watch(gymStateProvider.notifier);
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
// If the value in the provider changed, update the controller text
|
||||
final currentText = gymState.countdownDuration.inSeconds.toString();
|
||||
if (_countdownController.text != currentText) {
|
||||
_countdownController.text = currentText;
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
AnimatedCrossFade(
|
||||
firstChild: const SizedBox.shrink(),
|
||||
secondChild: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 400),
|
||||
child: Card(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
key: const ValueKey('gym-mode-option-show-exercises'),
|
||||
title: Text(i18n.gymModeShowExercises),
|
||||
value: gymState.showExercisePages,
|
||||
onChanged: (value) => gymNotifier.setShowExercisePages(value),
|
||||
),
|
||||
SwitchListTile(
|
||||
key: const ValueKey('gym-mode-option-show-timer'),
|
||||
title: Text(i18n.gymModeShowTimer),
|
||||
value: gymState.showTimerPages,
|
||||
onChanged: (value) => gymNotifier.setShowTimerPages(value),
|
||||
),
|
||||
ListTile(
|
||||
key: const ValueKey('gym-mode-timer-type'),
|
||||
enabled: gymState.showTimerPages,
|
||||
title: Text(i18n.gymModeTimerType),
|
||||
trailing: DropdownButton<bool>(
|
||||
key: const ValueKey('countdown-type-dropdown'),
|
||||
value: gymState.useCountdownBetweenSets,
|
||||
onChanged: gymState.showTimerPages
|
||||
? (bool? newValue) {
|
||||
if (newValue != null) {
|
||||
gymNotifier.setUseCountdownBetweenSets(newValue);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
items: [false, true].map<DropdownMenuItem<bool>>((bool value) {
|
||||
final label = value ? i18n.countdown : i18n.stopwatch;
|
||||
|
||||
return DropdownMenuItem<bool>(value: value, child: Text(label));
|
||||
}).toList(),
|
||||
),
|
||||
subtitle: Text(i18n.gymModeTimerTypeHelText),
|
||||
),
|
||||
ListTile(
|
||||
key: const ValueKey('gym-mode-default-countdown-time'),
|
||||
enabled: gymState.showTimerPages,
|
||||
title: TextFormField(
|
||||
controller: _countdownController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
labelText: i18n.gymModeDefaultCountdownTime,
|
||||
suffix: IconButton(
|
||||
onPressed: gymState.showTimerPages && gymState.useCountdownBetweenSets
|
||||
? () => gymNotifier.setCountdownDuration(
|
||||
DEFAULT_COUNTDOWN_DURATION,
|
||||
)
|
||||
: null,
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
final intValue = int.tryParse(value);
|
||||
if (intValue != null &&
|
||||
intValue > 0 &&
|
||||
intValue < MAX_COUNTDOWN_DURATION) {
|
||||
gymNotifier.setCountdownDuration(intValue);
|
||||
}
|
||||
},
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (String? value) {
|
||||
final intValue = int.tryParse(value!);
|
||||
if (intValue == null ||
|
||||
intValue < MIN_COUNTDOWN_DURATION ||
|
||||
intValue > MAX_COUNTDOWN_DURATION) {
|
||||
return i18n.formMinMaxValues(
|
||||
MIN_COUNTDOWN_DURATION,
|
||||
MAX_COUNTDOWN_DURATION,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
enabled: gymState.showTimerPages && gymState.useCountdownBetweenSets,
|
||||
),
|
||||
),
|
||||
SwitchListTile(
|
||||
key: const ValueKey('gym-mode-notify-countdown'),
|
||||
title: Text(i18n.gymModeNotifyOnCountdownFinish),
|
||||
value: gymState.alertOnCountdownEnd,
|
||||
onChanged: (gymState.showTimerPages && gymState.useCountdownBetweenSets)
|
||||
? (value) => gymNotifier.setAlertOnCountdownEnd(value)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
crossFadeState: _showOptions ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
),
|
||||
|
||||
ListTile(
|
||||
key: const ValueKey('gym-mode-options-tile'),
|
||||
title: Text(i18n.settingsTitle),
|
||||
leading: const Icon(Icons.settings),
|
||||
onTap: () => setState(() => _showOptions = !_showOptions),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StartPage extends ConsumerWidget {
|
||||
final PageController _controller;
|
||||
|
||||
const StartPage(this._controller);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final dayDataDisplay = ref.watch(gymStateProvider).dayDataDisplay;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
NavigationHeader(
|
||||
AppLocalizations.of(context).todaysWorkout,
|
||||
_controller,
|
||||
exercisePages: _exercisePages,
|
||||
showEndWorkoutButton: false,
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
if (_dayData.day!.isSpecialType)
|
||||
if (dayDataDisplay.day!.isSpecialType)
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Text(
|
||||
'${_dayData.day!.type.name.toUpperCase()}\n${_dayData.day!.type.i18Label(AppLocalizations.of(context))}',
|
||||
'${dayDataDisplay.day!.type.name.toUpperCase()}\n${dayDataDisplay.day!.type.i18Label(AppLocalizations.of(context))}',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
..._dayData.slots.map((slotData) {
|
||||
return Column(
|
||||
children: [
|
||||
...slotData.setConfigs
|
||||
.fold<Map<Exercise, List<String>>>({}, (acc, entry) {
|
||||
acc.putIfAbsent(entry.exercise, () => []).add(entry.textReprWithType);
|
||||
return acc;
|
||||
})
|
||||
.entries
|
||||
.map((entry) {
|
||||
final exercise = entry.key;
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: SizedBox(
|
||||
width: 45,
|
||||
child: ExerciseImageWidget(image: exercise.getMainImage),
|
||||
),
|
||||
title: Text(
|
||||
exercise
|
||||
.getTranslation(Localizations.localeOf(context).languageCode)
|
||||
.name,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: entry.value.map((text) => Text(text)).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
...dayDataDisplay.slots
|
||||
.expand((slot) => slot.setConfigs)
|
||||
.fold<Map<Exercise, List<String>>>({}, (acc, entry) {
|
||||
acc.putIfAbsent(entry.exercise, () => []).add(entry.textReprWithType);
|
||||
return acc;
|
||||
})
|
||||
.entries
|
||||
.map((entry) {
|
||||
final exercise = entry.key;
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: SizedBox(
|
||||
width: 45,
|
||||
child: ExerciseImageWidget(image: exercise.getMainImage),
|
||||
),
|
||||
title: Text(
|
||||
exercise
|
||||
.getTranslation(Localizations.localeOf(context).languageCode)
|
||||
.name,
|
||||
),
|
||||
subtitle: Text(entry.value.toList().join('\n')),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
const GymModeOptions(),
|
||||
FilledButton(
|
||||
child: Text(AppLocalizations.of(context).start),
|
||||
onPressed: () {
|
||||
@@ -82,7 +247,7 @@ class StartPage extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
NavigationFooter(_controller, 0, showPrevious: false),
|
||||
NavigationFooter(_controller, showPrevious: false),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
209
lib/widgets/routines/gym_mode/summary.dart
Normal file
209
lib/widgets/routines/gym_mode/summary.dart
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/date.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/models/workouts/session_api.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/widgets/core/progress_indicator.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
|
||||
import '../logs/exercises_expansion_card.dart';
|
||||
import '../logs/muscle_groups.dart';
|
||||
|
||||
class WorkoutSummary extends ConsumerStatefulWidget {
|
||||
final _logger = Logger('WorkoutSummary');
|
||||
|
||||
final PageController _controller;
|
||||
|
||||
WorkoutSummary(this._controller);
|
||||
|
||||
@override
|
||||
ConsumerState<WorkoutSummary> createState() => _WorkoutSummaryState();
|
||||
}
|
||||
|
||||
class _WorkoutSummaryState extends ConsumerState<WorkoutSummary> {
|
||||
late Future<void> _initData;
|
||||
late Routine _routine;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initData = _reloadRoutineData();
|
||||
}
|
||||
|
||||
Future<void> _reloadRoutineData() async {
|
||||
widget._logger.fine('Loading routine data');
|
||||
final gymState = ref.read(gymStateProvider);
|
||||
|
||||
_routine = await context.read<RoutinesProvider>().fetchAndSetRoutineFull(
|
||||
gymState.routine.id!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
NavigationHeader(
|
||||
AppLocalizations.of(context).workoutCompleted,
|
||||
widget._controller,
|
||||
showEndWorkoutButton: false,
|
||||
),
|
||||
Expanded(
|
||||
child: FutureBuilder<void>(
|
||||
future: _initData,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const BoxedProgressIndicator();
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}: ${snapshot.stackTrace}'));
|
||||
} else if (snapshot.connectionState == ConnectionState.done) {
|
||||
return WorkoutSessionStats(
|
||||
_routine.sessions.firstWhereOrNull(
|
||||
(s) => s.session.date.isSameDayAs(clock.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return const Center(child: Text('Unexpected state!'));
|
||||
},
|
||||
),
|
||||
),
|
||||
NavigationFooter(widget._controller, showNext: false),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkoutSessionStats extends ConsumerWidget {
|
||||
final _logger = Logger('WorkoutSessionStats');
|
||||
final WorkoutSessionApi? _sessionApi;
|
||||
|
||||
WorkoutSessionStats(this._sessionApi, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
if (_sessionApi == null) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'Nothing logged yet.',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final session = _sessionApi.session;
|
||||
final sessionDuration = session.duration;
|
||||
final totalVolume = _sessionApi.volume;
|
||||
|
||||
/// We assume that users will do exercises (mostly) either in metric or imperial
|
||||
/// units so we just display the higher one.
|
||||
String volumeUnit;
|
||||
num volumeValue;
|
||||
if (totalVolume['metric']! > totalVolume['imperial']!) {
|
||||
volumeValue = totalVolume['metric']!;
|
||||
volumeUnit = i18n.kg;
|
||||
} else {
|
||||
volumeValue = totalVolume['imperial']!;
|
||||
volumeUnit = i18n.lb;
|
||||
}
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InfoCard(
|
||||
title: i18n.duration,
|
||||
value: sessionDuration != null
|
||||
? i18n.durationHoursMinutes(
|
||||
sessionDuration.inHours,
|
||||
sessionDuration.inMinutes.remainder(60),
|
||||
)
|
||||
: '-/-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: InfoCard(
|
||||
title: i18n.volume,
|
||||
value: '${volumeValue.toStringAsFixed(0)} $volumeUnit',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// const SizedBox(height: 16),
|
||||
// InfoCard(
|
||||
// title: 'Personal Records',
|
||||
// value: prCount.toString(),
|
||||
// color: theme.colorScheme.tertiaryContainer,
|
||||
// ),
|
||||
const SizedBox(height: 10),
|
||||
MuscleGroupsCard(_sessionApi.logs),
|
||||
const SizedBox(height: 10),
|
||||
ExercisesCard(_sessionApi),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
ref.read(gymStateProvider.notifier).clear();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(i18n.endWorkout),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InfoCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String value;
|
||||
final Color? color;
|
||||
|
||||
const InfoCard({required this.title, required this.value, this.color, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Card(
|
||||
color: color,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
Text(value, style: theme.textTheme.headlineMedium),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -18,19 +18,18 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/theme/theme.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
|
||||
class TimerWidget extends StatefulWidget {
|
||||
final PageController _controller;
|
||||
final double _ratioCompleted;
|
||||
final Map<Exercise, int> _exercisePages;
|
||||
final _totalPages;
|
||||
|
||||
const TimerWidget(this._controller, this._ratioCompleted, this._exercisePages, this._totalPages);
|
||||
const TimerWidget(this._controller);
|
||||
|
||||
@override
|
||||
_TimerWidgetState createState() => _TimerWidgetState();
|
||||
@@ -69,8 +68,6 @@ class _TimerWidgetState extends State<TimerWidget> {
|
||||
NavigationHeader(
|
||||
AppLocalizations.of(context).pause,
|
||||
widget._controller,
|
||||
totalPages: widget._totalPages,
|
||||
exercisePages: widget._exercisePages,
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
@@ -80,35 +77,31 @@ class _TimerWidgetState extends State<TimerWidget> {
|
||||
),
|
||||
),
|
||||
),
|
||||
NavigationFooter(widget._controller, widget._ratioCompleted),
|
||||
NavigationFooter(widget._controller),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimerCountdownWidget extends StatefulWidget {
|
||||
class TimerCountdownWidget extends ConsumerStatefulWidget {
|
||||
final PageController _controller;
|
||||
final double _ratioCompleted;
|
||||
final int _seconds;
|
||||
final Map<Exercise, int> _exercisePages;
|
||||
final int _totalPages;
|
||||
|
||||
const TimerCountdownWidget(
|
||||
this._controller,
|
||||
this._seconds,
|
||||
this._ratioCompleted,
|
||||
this._exercisePages,
|
||||
this._totalPages,
|
||||
);
|
||||
|
||||
@override
|
||||
_TimerCountdownWidgetState createState() => _TimerCountdownWidgetState();
|
||||
}
|
||||
|
||||
class _TimerCountdownWidgetState extends State<TimerCountdownWidget> {
|
||||
class _TimerCountdownWidgetState extends ConsumerState<TimerCountdownWidget> {
|
||||
late DateTime _endTime;
|
||||
late Timer _uiTimer;
|
||||
|
||||
bool _hasNotified = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -131,24 +124,40 @@ class _TimerCountdownWidgetState extends State<TimerCountdownWidget> {
|
||||
final remaining = _endTime.difference(DateTime.now());
|
||||
final remainingSeconds = remaining.inSeconds <= 0 ? 0 : remaining.inSeconds;
|
||||
final displayTime = DateTime(2000, 1, 1, 0, 0, 0).add(Duration(seconds: remainingSeconds));
|
||||
final gymState = ref.watch(gymStateProvider);
|
||||
|
||||
// When countdown finishes, notify ONCE, and respect settings
|
||||
if (remainingSeconds == 0 && !_hasNotified) {
|
||||
if (gymState.alertOnCountdownEnd) {
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
// Not that this only works on desktop platforms
|
||||
SystemSound.play(SystemSoundType.alert);
|
||||
}
|
||||
setState(() {
|
||||
_hasNotified = true;
|
||||
});
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
NavigationHeader(
|
||||
AppLocalizations.of(context).pause,
|
||||
widget._controller,
|
||||
totalPages: widget._totalPages,
|
||||
exercisePages: widget._exercisePages,
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
DateFormat('m:ss').format(displayTime),
|
||||
style: Theme.of(context).textTheme.displayLarge!.copyWith(color: wgerPrimaryColor),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('m:ss').format(displayTime),
|
||||
style: Theme.of(context).textTheme.displayLarge!.copyWith(color: wgerPrimaryColor),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
NavigationFooter(widget._controller, widget._ratioCompleted),
|
||||
NavigationFooter(widget._controller),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
454
lib/widgets/routines/gym_mode/workout_menu.dart
Normal file
454
lib/widgets/routines/gym_mode/workout_menu.dart
Normal file
@@ -0,0 +1,454 @@
|
||||
// /*
|
||||
// * This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
// * Copyright (C) 2020, 2025 wger Team
|
||||
// *
|
||||
// * wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
// * it under the terms of the GNU Affero General Public License as published by
|
||||
// * the Free Software Foundation, either version 3 of the License, or
|
||||
// * (at your option) any later version.
|
||||
// *
|
||||
// * wger Workout Manager is distributed in the hope that it will be useful,
|
||||
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// * GNU Affero General Public License for more details.
|
||||
// *
|
||||
// * You should have received a copy of the GNU Affero General Public License
|
||||
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// */
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/widgets/exercises/autocompleter.dart';
|
||||
|
||||
class WorkoutMenu extends StatelessWidget {
|
||||
final PageController _controller;
|
||||
final int initialIndex;
|
||||
|
||||
const WorkoutMenu(this._controller, {this.initialIndex = 0, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
initialIndex: initialIndex,
|
||||
length: 2,
|
||||
child: Column(
|
||||
children: [
|
||||
const TabBar(
|
||||
tabs: [
|
||||
Tab(icon: Icon(Icons.menu_open)),
|
||||
Tab(icon: Icon(Icons.stacked_bar_chart)),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
NavigationTab(_controller),
|
||||
ProgressionTab(_controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NavigationTab extends ConsumerWidget {
|
||||
final PageController _controller;
|
||||
|
||||
const NavigationTab(this._controller);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(gymStateProvider);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
...state.pages.where((pageEntry) => pageEntry.type == PageType.set).map((page) {
|
||||
return ListTile(
|
||||
leading: page.allLogsDone ? const Icon(Icons.check) : null,
|
||||
title: Text(
|
||||
page.exercises
|
||||
.map(
|
||||
(exercise) => exercise
|
||||
.getTranslation(Localizations.localeOf(context).languageCode)
|
||||
.name,
|
||||
)
|
||||
.toList()
|
||||
.join('\n'),
|
||||
style: TextStyle(
|
||||
decoration: page.allLogsDone ? TextDecoration.lineThrough : TextDecoration.none,
|
||||
),
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
_controller.animateToPage(
|
||||
page.pageIndex,
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProgressionTab extends ConsumerStatefulWidget {
|
||||
final _logger = Logger('ProgressionTab');
|
||||
final PageController _controller;
|
||||
|
||||
ProgressionTab(this._controller, {super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ProgressionTab> createState() => _ProgressionTabState();
|
||||
}
|
||||
|
||||
class _ProgressionTabState extends ConsumerState<ProgressionTab> {
|
||||
String? showSwapWidgetToPage;
|
||||
String? showAddExerciseWidgetToPage;
|
||||
_ProgressionTabState();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = ref.watch(gymStateProvider);
|
||||
final theme = Theme.of(context);
|
||||
final languageCode = Localizations.localeOf(context).languageCode;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
...state.pages.where((page) => page.type == PageType.set).map((page) {
|
||||
if (page.exercises.isEmpty) {
|
||||
widget._logger.warning('Page ${page.uuid} has no exercises, skipping');
|
||||
return Container();
|
||||
}
|
||||
|
||||
// For supersets, prefix the exercise with A, B, C so it can be identified
|
||||
// in the set list below
|
||||
final isSuperset = page.exercises.length > 1;
|
||||
final pageExerciseTitle = isSuperset
|
||||
? page.exercises
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) {
|
||||
final label = String.fromCharCode(65 + entry.key);
|
||||
final name = entry.value
|
||||
.getTranslation(Localizations.localeOf(context).languageCode)
|
||||
.name;
|
||||
return '$label: $name';
|
||||
})
|
||||
.join('\n')
|
||||
: page.exercises.first.getTranslation(languageCode).name;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(pageExerciseTitle, style: Theme.of(context).textTheme.bodyLarge),
|
||||
...page.slotPages.where((slotPage) => slotPage.type == SlotPageType.log).map(
|
||||
(slotPage) {
|
||||
String setPrefix = '';
|
||||
if (isSuperset) {
|
||||
final exerciseIndex = page.exercises.indexWhere(
|
||||
(ex) => ex.id == slotPage.setConfigData!.exercise.id,
|
||||
);
|
||||
if (exerciseIndex != -1) {
|
||||
setPrefix = '${String.fromCharCode(65 + exerciseIndex)}: ';
|
||||
}
|
||||
}
|
||||
|
||||
// Sets that are done are marked with a strikethrough
|
||||
final decoration = slotPage.logDone
|
||||
? TextDecoration.lineThrough
|
||||
: TextDecoration.none;
|
||||
|
||||
// Sets that are done have a lighter color
|
||||
final color = slotPage.logDone
|
||||
? theme.colorScheme.onSurface.withValues(alpha: 0.6)
|
||||
: null;
|
||||
|
||||
// The row for the current page is highlighted in bold
|
||||
final fontWeight = state.currentPage == slotPage.pageIndex
|
||||
? FontWeight.bold
|
||||
: null;
|
||||
|
||||
IconData icon = Icons.circle_outlined;
|
||||
if (slotPage.logDone) {
|
||||
icon = Icons.check_circle_rounded;
|
||||
} else if (state.currentPage == slotPage.pageIndex) {
|
||||
icon = Icons.play_circle_fill;
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'$setPrefix${slotPage.setConfigData!.textReprWithType}',
|
||||
style: theme.textTheme.bodyMedium!.copyWith(
|
||||
decoration: decoration,
|
||||
fontWeight: fontWeight,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: page.allLogsDone
|
||||
? null
|
||||
: () {
|
||||
if (showSwapWidgetToPage == page.uuid) {
|
||||
setState(() {
|
||||
showSwapWidgetToPage = null;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
showSwapWidgetToPage = page.uuid;
|
||||
showAddExerciseWidgetToPage = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
key: ValueKey('swap-icon-${page.uuid}'),
|
||||
showSwapWidgetToPage == page.uuid
|
||||
? Icons.change_circle
|
||||
: Icons.change_circle_outlined,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: page.allLogsDone
|
||||
? null
|
||||
: () {
|
||||
if (showAddExerciseWidgetToPage == page.uuid) {
|
||||
setState(() {
|
||||
showAddExerciseWidgetToPage = null;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
showAddExerciseWidgetToPage = page.uuid;
|
||||
showSwapWidgetToPage = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
key: ValueKey('add-icon-${page.uuid}'),
|
||||
showAddExerciseWidgetToPage == page.uuid ? Icons.add_circle : Icons.add,
|
||||
),
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
widget._controller.animateToPage(
|
||||
page.pageIndex,
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (showSwapWidgetToPage == page.uuid)
|
||||
ExerciseSwapWidget(
|
||||
page.uuid,
|
||||
onDone: () {
|
||||
setState(() {
|
||||
showSwapWidgetToPage = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (showAddExerciseWidgetToPage == page.uuid)
|
||||
ExerciseAddWidget(
|
||||
page.uuid,
|
||||
onDone: () {
|
||||
setState(() {
|
||||
showAddExerciseWidgetToPage = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Swapping or adding an exercise only affects the current workout, '
|
||||
'no changes are saved.',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExerciseSwapWidget extends ConsumerWidget {
|
||||
final _logger = Logger('ExerciseSwapWidget');
|
||||
|
||||
final String pageUUID;
|
||||
final VoidCallback? onDone;
|
||||
|
||||
ExerciseSwapWidget(this.pageUUID, {this.onDone, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(gymStateProvider);
|
||||
final gymProvider = ref.read(gymStateProvider.notifier);
|
||||
final page = state.pages.firstWhere((p) => p.uuid == pageUUID);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Column(
|
||||
children: [
|
||||
...page.exercises.map((e) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
e.getTranslation(Localizations.localeOf(context).languageCode).name,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
const Icon(Icons.swap_vert),
|
||||
ExerciseAutocompleter(
|
||||
onExerciseSelected: (exercise) {
|
||||
gymProvider.replaceExercises(
|
||||
page.uuid,
|
||||
originalExerciseId: e.id!,
|
||||
newExercise: exercise,
|
||||
);
|
||||
onDone?.call();
|
||||
_logger.fine('Replaced exercise ${e.id} with ${exercise.id}');
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExerciseAddWidget extends ConsumerWidget {
|
||||
final _logger = Logger('ExerciseAddWidget');
|
||||
|
||||
final String pageUUID;
|
||||
final VoidCallback? onDone;
|
||||
|
||||
ExerciseAddWidget(this.pageUUID, {this.onDone, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(gymStateProvider);
|
||||
final gymProvider = ref.read(gymStateProvider.notifier);
|
||||
final page = state.pages.firstWhere((p) => p.uuid == pageUUID);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Column(
|
||||
children: [
|
||||
ExerciseAutocompleter(
|
||||
onExerciseSelected: (exercise) {
|
||||
gymProvider.addExerciseAfterPage(
|
||||
page.uuid,
|
||||
newExercise: exercise,
|
||||
);
|
||||
onDone?.call();
|
||||
_logger.fine('Added exercise ${exercise.id} after page $pageUUID');
|
||||
},
|
||||
),
|
||||
const Icon(Icons.arrow_downward),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkoutMenuDialog extends ConsumerWidget {
|
||||
final PageController controller;
|
||||
final bool showEndWorkoutButton;
|
||||
final int initialIndex;
|
||||
|
||||
const WorkoutMenuDialog(
|
||||
this.controller, {
|
||||
super.key,
|
||||
this.showEndWorkoutButton = true,
|
||||
this.initialIndex = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final gymState = ref.watch(gymStateProvider);
|
||||
|
||||
final endWorkoutButton = true
|
||||
? TextButton(
|
||||
child: Text(AppLocalizations.of(context).endWorkout),
|
||||
onPressed: () {
|
||||
controller.animateToPage(
|
||||
gymState.totalPages,
|
||||
duration: DEFAULT_ANIMATION_DURATION,
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
: null;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).jumpTo,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: WorkoutMenu(controller, initialIndex: initialIndex),
|
||||
),
|
||||
actions: [
|
||||
?endWorkoutButton,
|
||||
TextButton(
|
||||
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:wger/helpers/colors.dart';
|
||||
import 'package:wger/helpers/date.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/helpers/misc.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/models/workouts/session.dart';
|
||||
import 'package:wger/widgets/measurements/charts.dart';
|
||||
import 'package:wger/widgets/routines/charts.dart';
|
||||
import 'package:wger/widgets/routines/forms/session.dart';
|
||||
|
||||
class SessionInfo extends StatefulWidget {
|
||||
final WorkoutSession _session;
|
||||
|
||||
const SessionInfo(this._session);
|
||||
|
||||
@override
|
||||
State<SessionInfo> createState() => _SessionInfoState();
|
||||
}
|
||||
|
||||
class _SessionInfoState extends State<SessionInfo> {
|
||||
bool editMode = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(
|
||||
i18n.workoutSession,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(widget._session.date),
|
||||
),
|
||||
onTap: () => setState(() => editMode = !editMode),
|
||||
trailing: Icon(editMode ? Icons.edit_off : Icons.edit),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
if (editMode)
|
||||
SessionForm(
|
||||
widget._session.routineId!,
|
||||
onSaved: () => setState(() => editMode = false),
|
||||
session: widget._session,
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: [
|
||||
_buildInfoRow(
|
||||
context,
|
||||
i18n.timeStart,
|
||||
widget._session.timeStart != null
|
||||
? MaterialLocalizations.of(
|
||||
context,
|
||||
).formatTimeOfDay(widget._session.timeStart!)
|
||||
: '-/-',
|
||||
),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
i18n.timeEnd,
|
||||
widget._session.timeEnd != null
|
||||
? MaterialLocalizations.of(context).formatTimeOfDay(widget._session.timeEnd!)
|
||||
: '-/-',
|
||||
),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
i18n.impression,
|
||||
widget._session.impressionAsString,
|
||||
),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
i18n.notes,
|
||||
widget._session.notes.isNotEmpty ? widget._session.notes : '-/-',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(BuildContext context, String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$label: ',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Expanded(child: Text(value)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExerciseLogChart extends StatelessWidget {
|
||||
final Map<num, List<Log>> _logs;
|
||||
final DateTime _selectedDate;
|
||||
|
||||
const ExerciseLogChart(this._logs, this._selectedDate);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = generateChartColors(_logs.keys.length).iterator;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
LogChartWidgetFl(_logs, _selectedDate),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
..._logs.keys.map((reps) {
|
||||
colors.moveNext();
|
||||
|
||||
return Indicator(
|
||||
color: colors.current,
|
||||
text: formatNum(reps).toString(),
|
||||
isSquare: false,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DayLogWidget extends StatelessWidget {
|
||||
final DateTime _date;
|
||||
final Routine _routine;
|
||||
|
||||
const DayLogWidget(this._date, this._routine);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sessionApi = _routine.sessions.firstWhere(
|
||||
(sessionApi) => sessionApi.session.date.isSameDayAs(_date),
|
||||
);
|
||||
final exercises = sessionApi.exercises;
|
||||
|
||||
return Card(
|
||||
child: Column(
|
||||
children: [
|
||||
SessionInfo(sessionApi.session),
|
||||
...exercises.map((exercise) {
|
||||
final translation = exercise.getTranslation(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
);
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
translation.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
...sessionApi.logs
|
||||
.where((l) => l.exerciseId == exercise.id)
|
||||
.map(
|
||||
(log) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(log.singleLogRepTextNoNl),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
key: ValueKey('delete-log-${log.id}'),
|
||||
onPressed: () {
|
||||
showDeleteDialog(context, translation.name, log);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: ExerciseLogChart(
|
||||
_routine.groupLogsByRepetition(
|
||||
logs: _routine.filterLogsByExercise(exercise.id!),
|
||||
filterNullReps: true,
|
||||
filterNullWeights: true,
|
||||
),
|
||||
_date,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
101
lib/widgets/routines/logs/day_logs_container.dart
Normal file
101
lib/widgets/routines/logs/day_logs_container.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wger/helpers/date.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
|
||||
import 'exercise_log_chart.dart';
|
||||
import 'muscle_groups.dart';
|
||||
import 'session_info.dart';
|
||||
|
||||
class DayLogWidget extends StatelessWidget {
|
||||
final DateTime _date;
|
||||
final Routine _routine;
|
||||
|
||||
const DayLogWidget(this._date, this._routine);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sessionApi = _routine.sessions.firstWhere(
|
||||
(sessionApi) => sessionApi.session.date.isSameDayAs(_date),
|
||||
);
|
||||
final exercises = sessionApi.exercises;
|
||||
|
||||
return Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Card(child: SessionInfo(sessionApi.session)),
|
||||
MuscleGroupsCard(sessionApi.logs),
|
||||
|
||||
Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
...exercises.map((exercise) {
|
||||
final translation = exercise.getTranslation(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
);
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
translation.name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
...sessionApi.logs
|
||||
.where((l) => l.exerciseId == exercise.id)
|
||||
.map(
|
||||
(log) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(log.repTextNoNl(context)),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
key: ValueKey('delete-log-${log.id}'),
|
||||
onPressed: () {
|
||||
showDeleteDialog(context, translation.name, log);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: ExerciseLogChart(
|
||||
_routine.groupLogsByRepetition(
|
||||
logs: _routine.filterLogsByExercise(exercise.id!),
|
||||
filterNullReps: true,
|
||||
filterNullWeights: true,
|
||||
),
|
||||
_date,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
61
lib/widgets/routines/logs/exercise_log_chart.dart
Normal file
61
lib/widgets/routines/logs/exercise_log_chart.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:wger/helpers/colors.dart';
|
||||
import 'package:wger/helpers/misc.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/widgets/measurements/charts.dart';
|
||||
import 'package:wger/widgets/routines/charts.dart';
|
||||
|
||||
class ExerciseLogChart extends StatelessWidget {
|
||||
final Map<num, List<Log>> _logs;
|
||||
final DateTime _selectedDate;
|
||||
|
||||
const ExerciseLogChart(this._logs, this._selectedDate);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = generateChartColors(_logs.keys.length).iterator;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
LogChartWidgetFl(_logs, _selectedDate),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
..._logs.keys.map((reps) {
|
||||
colors.moveNext();
|
||||
|
||||
return Indicator(
|
||||
color: colors.current,
|
||||
text: formatNum(reps).toString(),
|
||||
isSquare: false,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
115
lib/widgets/routines/logs/exercises_expansion_card.dart
Normal file
115
lib/widgets/routines/logs/exercises_expansion_card.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wger/helpers/i18n.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/models/workouts/session_api.dart';
|
||||
|
||||
class ExercisesCard extends StatelessWidget {
|
||||
final WorkoutSessionApi session;
|
||||
|
||||
const ExercisesCard(this.session, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final exercises = session.exercises;
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).exercises,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...exercises.map((exercise) {
|
||||
final logs = session.logs.where((log) => log.exerciseId == exercise.id).toList();
|
||||
return _ExerciseExpansionTile(exercise: exercise, logs: logs);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ExerciseExpansionTile extends StatelessWidget {
|
||||
const _ExerciseExpansionTile({
|
||||
required this.exercise,
|
||||
required this.logs,
|
||||
});
|
||||
|
||||
final Exercise exercise;
|
||||
final List<Log> logs;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final languageCode = Localizations.localeOf(context).languageCode;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final topSet = logs.isEmpty
|
||||
? null
|
||||
: logs.reduce((a, b) => (a.weight ?? 0) > (b.weight ?? 0) ? a : b);
|
||||
final topSetWeight = topSet?.weight?.toStringAsFixed(0) ?? 'N/A';
|
||||
final topSetWeightUnit = topSet?.weightUnitObj != null
|
||||
? getServerStringTranslation(topSet!.weightUnitObj!.name, context)
|
||||
: '';
|
||||
return ExpansionTile(
|
||||
// leading: const Icon(Icons.fitness_center),
|
||||
title: Text(exercise.getTranslation(languageCode).name, style: theme.textTheme.titleMedium),
|
||||
subtitle: Text('Top set: $topSetWeight $topSetWeightUnit'),
|
||||
children: logs.map((log) => _SetDataRow(log: log)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SetDataRow extends StatelessWidget {
|
||||
const _SetDataRow({required this.log});
|
||||
|
||||
final Log log;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 5,
|
||||
children: [
|
||||
Text(
|
||||
log.repTextNoNl(context),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
// if (log.volume() > 0)
|
||||
// Text(
|
||||
// '${log.volume().toStringAsFixed(0)} ${getServerStringTranslation(log.weightUnitObj!.name, context)}',
|
||||
// style: theme.textTheme.bodyMedium,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -23,7 +23,7 @@ import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/theme/theme.dart';
|
||||
import 'package:wger/widgets/routines/log.dart';
|
||||
import 'package:wger/widgets/routines/logs/day_logs_container.dart';
|
||||
|
||||
class WorkoutLogs extends StatelessWidget {
|
||||
final Routine _routine;
|
||||
@@ -39,14 +39,6 @@ class WorkoutLogs extends StatelessWidget {
|
||||
AppLocalizations.of(context).labelWorkoutLogs,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).logHelpEntries,
|
||||
textAlign: TextAlign.justify,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).logHelpEntriesUnits,
|
||||
textAlign: TextAlign.justify,
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: WorkoutLogCalendar(_routine),
|
||||
@@ -141,16 +133,29 @@ class _WorkoutLogCalendarState extends State<WorkoutLogCalendar> {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
SizedBox(
|
||||
child: ValueListenableBuilder<List<DateTime>>(
|
||||
valueListenable: _selectedEvents,
|
||||
builder: (context, logEvents, _) {
|
||||
// At the moment there is only one "event" per day
|
||||
return logEvents.isNotEmpty
|
||||
? DayLogWidget(logEvents.first, widget._routine)
|
||||
: Container();
|
||||
},
|
||||
),
|
||||
ExpansionTile(
|
||||
showTrailingIcon: false,
|
||||
dense: true,
|
||||
title: const Align(alignment: Alignment.centerLeft, child: Icon(Icons.info_outline)),
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).logHelpEntries,
|
||||
textAlign: TextAlign.justify,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).logHelpEntriesUnits,
|
||||
textAlign: TextAlign.justify,
|
||||
),
|
||||
],
|
||||
),
|
||||
ValueListenableBuilder<List<DateTime>>(
|
||||
valueListenable: _selectedEvents,
|
||||
builder: (context, logEvents, _) {
|
||||
// At the moment there is only one "event" per day
|
||||
return logEvents.isNotEmpty
|
||||
? DayLogWidget(logEvents.first, widget._routine)
|
||||
: Container();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
138
lib/widgets/routines/logs/muscle_groups.dart
Normal file
138
lib/widgets/routines/logs/muscle_groups.dart
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
|
||||
class MuscleGroup {
|
||||
final String name;
|
||||
final double percentage;
|
||||
final Color color;
|
||||
|
||||
MuscleGroup(this.name, this.percentage, this.color);
|
||||
}
|
||||
|
||||
class MuscleGroupsCard extends StatelessWidget {
|
||||
final List<Log> logs;
|
||||
|
||||
const MuscleGroupsCard(this.logs, {super.key});
|
||||
|
||||
List<MuscleGroup> _getMuscleGroups(BuildContext context) {
|
||||
final allMuscles = logs
|
||||
.expand((log) => [...log.exercise.muscles, ...log.exercise.musclesSecondary])
|
||||
.toList();
|
||||
if (allMuscles.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
final muscleCounts = allMuscles.groupListsBy((muscle) => muscle.nameTranslated(context));
|
||||
final total = allMuscles.length;
|
||||
|
||||
int colorIndex = 0;
|
||||
final colors = [
|
||||
Colors.blue,
|
||||
Colors.green,
|
||||
Colors.orange,
|
||||
Colors.purple,
|
||||
Colors.red,
|
||||
Colors.teal,
|
||||
Colors.deepOrange,
|
||||
Colors.indigo,
|
||||
Colors.pink,
|
||||
Colors.brown,
|
||||
Colors.cyan,
|
||||
Colors.lime,
|
||||
Colors.amber,
|
||||
Colors.lightGreen,
|
||||
Colors.deepPurple,
|
||||
];
|
||||
|
||||
return muscleCounts.entries.map((entry) {
|
||||
final percentage = (entry.value.length / total) * 100;
|
||||
final color = colors[colorIndex % colors.length];
|
||||
colorIndex++;
|
||||
return MuscleGroup(entry.key, percentage, color);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final muscles = _getMuscleGroups(context);
|
||||
final theme = Theme.of(context);
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
i18n.muscles,
|
||||
style: theme.textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
sections: muscles.map((muscle) {
|
||||
return PieChartSectionData(
|
||||
color: muscle.color,
|
||||
value: muscle.percentage,
|
||||
title: i18n.percentValue(muscle.percentage.toStringAsFixed(0)),
|
||||
radius: 50,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onPrimary,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
sectionsSpace: 2,
|
||||
centerSpaceRadius: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 8,
|
||||
children: muscles.map((muscle) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: muscle.color,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(muscle.name),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
113
lib/widgets/routines/logs/session_info.dart
Normal file
113
lib/widgets/routines/logs/session_info.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/session.dart';
|
||||
import 'package:wger/widgets/routines/forms/session.dart';
|
||||
|
||||
class SessionInfo extends StatefulWidget {
|
||||
final WorkoutSession _session;
|
||||
|
||||
const SessionInfo(this._session);
|
||||
|
||||
@override
|
||||
State<SessionInfo> createState() => _SessionInfoState();
|
||||
}
|
||||
|
||||
class _SessionInfoState extends State<SessionInfo> {
|
||||
bool editMode = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(
|
||||
i18n.workoutSession,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(widget._session.date),
|
||||
),
|
||||
onTap: () => setState(() => editMode = !editMode),
|
||||
trailing: Icon(editMode ? Icons.edit_off : Icons.edit),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
if (editMode)
|
||||
SessionForm(
|
||||
widget._session.routineId!,
|
||||
onSaved: () => setState(() => editMode = false),
|
||||
session: widget._session,
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: [
|
||||
SessionRow(
|
||||
label: i18n.impression,
|
||||
value: widget._session.impressionAsString(context),
|
||||
),
|
||||
SessionRow(
|
||||
label: i18n.duration,
|
||||
value: widget._session.durationTxtWithStartEnd(context),
|
||||
),
|
||||
SessionRow(
|
||||
label: i18n.notes,
|
||||
value: widget._session.notes.isNotEmpty ? widget._session.notes : '-/-',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SessionRow extends StatelessWidget {
|
||||
const SessionRow({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$label: ',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Expanded(child: Text(value)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
222
pubspec.lock
222
pubspec.lock
@@ -13,10 +13,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
||||
sha256: a40a0cee526a7e1f387c6847bd8a5ccbf510a75952ef8a28338e989558072cb0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.4.1"
|
||||
version: "8.4.0"
|
||||
analyzer_buffer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_buffer
|
||||
sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.11"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_plugin
|
||||
sha256: "08cfefa90b4f4dd3b447bda831cecf644029f9f8e22820f6ee310213ebe2dd53"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.10"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -53,10 +69,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9
|
||||
sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "4.0.3"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -93,10 +109,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.12.0"
|
||||
version: "8.12.1"
|
||||
camera:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -109,10 +125,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_android_camerax
|
||||
sha256: d96bf774152dd2a0aee64c59ba6b914b5efb04ec5a25b56e17b7e898869cc07c
|
||||
sha256: "1f1d1ff65223c59018d58bdac5211417c2af60bcb469c9d26f928dd412eb91cf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.24+2"
|
||||
version: "0.6.24+3"
|
||||
camera_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -185,6 +201,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.8"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -225,6 +249,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -257,6 +289,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
custom_lint_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_core
|
||||
sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.1"
|
||||
custom_lint_visitor:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_visitor
|
||||
sha256: "91f2a81e9f0abb4b9f3bb529f78b6227ce6050300d1ae5b1e2c69c66c7a566d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0+8.4.0"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -317,18 +365,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "80a877f5ec570c4fb3b40720a70b6f31e8bb1315a464b4d3e92fe82754d4b21a"
|
||||
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+3"
|
||||
version: "0.9.4"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "44f24d102e368370951b98ffe86c7325b38349e634578312976607d28cc6d747"
|
||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4+6"
|
||||
version: "0.9.5"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -370,7 +418,7 @@ packages:
|
||||
source: hosted
|
||||
version: "8.4.0"
|
||||
flex_seed_scheme:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flex_seed_scheme
|
||||
sha256: a3183753bbcfc3af106224bff3ab3e1844b73f58062136b7499919f49f3667e7
|
||||
@@ -460,18 +508,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687"
|
||||
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.32"
|
||||
version: "2.0.33"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
|
||||
sha256: "9e2d6907f12cc7d23a846847615941bddee8709bf2bfd274acdf5e80bcf22fde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
version: "3.0.3"
|
||||
flutter_staggered_grid_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -546,6 +594,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -627,10 +683,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: a1cd1584fae64f6ecca63113fd5450e3483c097cc05e43a2f073330f62adcabe
|
||||
sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+8"
|
||||
version: "0.8.13+10"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -700,6 +756,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
json_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -844,6 +908,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -896,10 +968,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "95c68a74d3cab950fd0ed8073d9fab15c1c06eb1f3eec68676e87aabc9ecee5a"
|
||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.21"
|
||||
version: "2.2.22"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1072,10 +1144,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
|
||||
sha256: c406de02bff19d920b832bddfb8283548bfa05ce41c59afba57ce643e116aa59
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
version: "3.0.3"
|
||||
riverpod_analyzer_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod_analyzer_utils
|
||||
sha256: a0f68adb078b790faa3c655110a017f9a7b7b079a57bbd40f540e80dce5fcd29
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0-dev.7"
|
||||
riverpod_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod_annotation
|
||||
sha256: "7230014155777fc31ba3351bc2cb5a3b5717b11bfafe52b1553cb47d385f8897"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
riverpod_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_generator
|
||||
sha256: "49894543a42cf7a9954fc4e7366b6d3cb2e6ec0fa07775f660afcdd92d097702"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1088,10 +1184,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "07d552dbe8e71ed720e5205e760438ff4ecfb76ec3b32ea664350e2ca4b0c43b"
|
||||
sha256: "46a46fd64659eff15f4638bbe19de43f9483f0e0bf024a9fb6b3582064bacc7b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.16"
|
||||
version: "2.4.17"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1140,6 +1236,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1165,10 +1277,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "0d6c4e1080176801112ed1111226d13cd4f74947383aabafbd9726a22083aeaa"
|
||||
sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
version: "4.1.1"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1177,6 +1289,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.8"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1273,6 +1401,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.26.3"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1281,6 +1417,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.12"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1301,10 +1445,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: dff5e50339bf30b06d7950b50fda58164d3d8c40042b104ed041ddc520fbff28
|
||||
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.25"
|
||||
version: "6.3.28"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1353,6 +1497,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1413,10 +1565,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_android
|
||||
sha256: "36913f94430b474c4a9033d59b7552b800e736a8521e7166e84895ddcedd0b03"
|
||||
sha256: "3f7ef3fb7b29f510e58f4d56b6ffbc3463b1071f2cf56e10f8d25f5b991ed85b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.8.19"
|
||||
version: "2.8.21"
|
||||
video_player_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1489,6 +1641,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
15
pubspec.yaml
15
pubspec.yaml
@@ -40,7 +40,6 @@ dependencies:
|
||||
equatable: ^2.0.7
|
||||
fl_chart: ^1.1.1
|
||||
flex_color_scheme: ^8.3.1
|
||||
flex_seed_scheme: ^4.0.1
|
||||
flutter_html: ^3.0.0
|
||||
flutter_staggered_grid_view: ^0.7.0
|
||||
flutter_svg: ^2.2.3
|
||||
@@ -50,24 +49,25 @@ dependencies:
|
||||
font_awesome_flutter: ^10.12.0
|
||||
freezed_annotation: ^3.0.0
|
||||
get_it: ^8.3.0
|
||||
http: ^1.5.0
|
||||
image_picker: ^1.2.0
|
||||
http: ^1.6.0
|
||||
image_picker: ^1.2.1
|
||||
intl: ^0.20.0
|
||||
json_annotation: ^4.8.1
|
||||
multi_select_flutter: ^4.1.3
|
||||
package_info_plus: ^9.0.0
|
||||
path: ^1.9.0
|
||||
path_provider: ^2.1.5
|
||||
provider: ^6.1.5
|
||||
provider: ^6.1.5+1
|
||||
rive: ^0.13.20
|
||||
shared_preferences: ^2.5.3
|
||||
sqlite3_flutter_libs: ^0.5.41
|
||||
table_calendar: ^3.0.8
|
||||
url_launcher: ^6.3.2
|
||||
version: ^3.0.2
|
||||
video_player: ^2.10.0
|
||||
video_player: ^2.10.1
|
||||
logging: ^1.3.0
|
||||
flutter_riverpod: ^2.6.1
|
||||
flutter_riverpod: ^3.0.3
|
||||
riverpod_annotation: ^3.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -80,9 +80,10 @@ dev_dependencies:
|
||||
flutter_lints: ^6.0.0
|
||||
freezed: ^3.2.0
|
||||
json_serializable: ^6.11.2
|
||||
mockito: ^5.6.1
|
||||
mockito: ^5.4.4
|
||||
network_image_mock: ^2.1.1
|
||||
shared_preferences_platform_interface: ^2.0.0
|
||||
riverpod_generator: ^3.0.3
|
||||
|
||||
# Script to read out unused translations
|
||||
#translations_cleaner: ^0.0.5
|
||||
|
||||
@@ -48,7 +48,10 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#head, [url], {#headers: headers}),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(this, Invocation.method(#head, [url], {#headers: headers})),
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
Invocation.method(#head, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<_i2.Response>);
|
||||
@@ -58,7 +61,10 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#get, [url], {#headers: headers}),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(this, Invocation.method(#get, [url], {#headers: headers})),
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
Invocation.method(#get, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<_i2.Response>);
|
||||
@@ -71,7 +77,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i4.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#post,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
@@ -93,7 +103,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i4.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#put,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
@@ -115,7 +129,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i4.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#patch,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
@@ -160,13 +178,19 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#read, [url], {#headers: headers}),
|
||||
returnValue: _i3.Future<String>.value(
|
||||
_i5.dummyValue<String>(this, Invocation.method(#read, [url], {#headers: headers})),
|
||||
_i5.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#read, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<String>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i6.Uint8List> readBytes(Uri? url, {Map<String, String>? headers}) =>
|
||||
_i3.Future<_i6.Uint8List> readBytes(
|
||||
Uri? url, {
|
||||
Map<String, String>? headers,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#readBytes, [url], {#headers: headers}),
|
||||
returnValue: _i3.Future<_i6.Uint8List>.value(_i6.Uint8List(0)),
|
||||
@@ -178,12 +202,17 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#send, [request]),
|
||||
returnValue: _i3.Future<_i2.StreamedResponse>.value(
|
||||
_FakeStreamedResponse_1(this, Invocation.method(#send, [request])),
|
||||
_FakeStreamedResponse_1(
|
||||
this,
|
||||
Invocation.method(#send, [request]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<_i2.StreamedResponse>);
|
||||
|
||||
@override
|
||||
void close() =>
|
||||
super.noSuchMethod(Invocation.method(#close, []), returnValueForMissingStub: null);
|
||||
void close() => super.noSuchMethod(
|
||||
Invocation.method(#close, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,7 +130,10 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@@ -138,18 +141,27 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
_i3.ExerciseDatabase get database =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#database),
|
||||
returnValue: _FakeExerciseDatabase_1(this, Invocation.getter(#database)),
|
||||
returnValue: _FakeExerciseDatabase_1(
|
||||
this,
|
||||
Invocation.getter(#database),
|
||||
),
|
||||
)
|
||||
as _i3.ExerciseDatabase);
|
||||
|
||||
@override
|
||||
List<_i4.Exercise> get exercises =>
|
||||
(super.noSuchMethod(Invocation.getter(#exercises), returnValue: <_i4.Exercise>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#exercises),
|
||||
returnValue: <_i4.Exercise>[],
|
||||
)
|
||||
as List<_i4.Exercise>);
|
||||
|
||||
@override
|
||||
List<_i4.Exercise> get filteredExercises =>
|
||||
(super.noSuchMethod(Invocation.getter(#filteredExercises), returnValue: <_i4.Exercise>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#filteredExercises),
|
||||
returnValue: <_i4.Exercise>[],
|
||||
)
|
||||
as List<_i4.Exercise>);
|
||||
|
||||
@override
|
||||
@@ -162,31 +174,47 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
|
||||
@override
|
||||
List<_i5.ExerciseCategory> get categories =>
|
||||
(super.noSuchMethod(Invocation.getter(#categories), returnValue: <_i5.ExerciseCategory>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#categories),
|
||||
returnValue: <_i5.ExerciseCategory>[],
|
||||
)
|
||||
as List<_i5.ExerciseCategory>);
|
||||
|
||||
@override
|
||||
List<_i7.Muscle> get muscles =>
|
||||
(super.noSuchMethod(Invocation.getter(#muscles), returnValue: <_i7.Muscle>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#muscles),
|
||||
returnValue: <_i7.Muscle>[],
|
||||
)
|
||||
as List<_i7.Muscle>);
|
||||
|
||||
@override
|
||||
List<_i6.Equipment> get equipment =>
|
||||
(super.noSuchMethod(Invocation.getter(#equipment), returnValue: <_i6.Equipment>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#equipment),
|
||||
returnValue: <_i6.Equipment>[],
|
||||
)
|
||||
as List<_i6.Equipment>);
|
||||
|
||||
@override
|
||||
List<_i8.Language> get languages =>
|
||||
(super.noSuchMethod(Invocation.getter(#languages), returnValue: <_i8.Language>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#languages),
|
||||
returnValue: <_i8.Language>[],
|
||||
)
|
||||
as List<_i8.Language>);
|
||||
|
||||
@override
|
||||
set database(_i3.ExerciseDatabase? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#database, value), returnValueForMissingStub: null);
|
||||
set database(_i3.ExerciseDatabase? value) => super.noSuchMethod(
|
||||
Invocation.setter(#database, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set exercises(List<_i4.Exercise>? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#exercises, value), returnValueForMissingStub: null);
|
||||
set exercises(List<_i4.Exercise>? value) => super.noSuchMethod(
|
||||
Invocation.setter(#exercises, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set filteredExercises(List<_i4.Exercise>? newFilteredExercises) => super.noSuchMethod(
|
||||
@@ -195,8 +223,10 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
set languages(List<_i8.Language>? languages) =>
|
||||
super.noSuchMethod(Invocation.setter(#languages, languages), returnValueForMissingStub: null);
|
||||
set languages(List<_i8.Language>? languages) => super.noSuchMethod(
|
||||
Invocation.setter(#languages, languages),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
@@ -212,8 +242,10 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
as _i18.Future<void>);
|
||||
|
||||
@override
|
||||
void initFilters() =>
|
||||
super.noSuchMethod(Invocation.method(#initFilters, []), returnValueForMissingStub: null);
|
||||
void initFilters() => super.noSuchMethod(
|
||||
Invocation.method(#initFilters, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i18.Future<void> findByFilters() =>
|
||||
@@ -225,19 +257,27 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
as _i18.Future<void>);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i4.Exercise findExerciseById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findExerciseById, [id]),
|
||||
returnValue: _FakeExercise_2(this, Invocation.method(#findExerciseById, [id])),
|
||||
returnValue: _FakeExercise_2(
|
||||
this,
|
||||
Invocation.method(#findExerciseById, [id]),
|
||||
),
|
||||
)
|
||||
as _i4.Exercise);
|
||||
|
||||
@override
|
||||
List<_i4.Exercise> findExercisesByVariationId(int? variationId, {int? exerciseIdToExclude}) =>
|
||||
List<_i4.Exercise> findExercisesByVariationId(
|
||||
int? variationId, {
|
||||
int? exerciseIdToExclude,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#findExercisesByVariationId,
|
||||
@@ -252,7 +292,10 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
_i5.ExerciseCategory findCategoryById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findCategoryById, [id]),
|
||||
returnValue: _FakeExerciseCategory_3(this, Invocation.method(#findCategoryById, [id])),
|
||||
returnValue: _FakeExerciseCategory_3(
|
||||
this,
|
||||
Invocation.method(#findCategoryById, [id]),
|
||||
),
|
||||
)
|
||||
as _i5.ExerciseCategory);
|
||||
|
||||
@@ -260,7 +303,10 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
_i6.Equipment findEquipmentById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findEquipmentById, [id]),
|
||||
returnValue: _FakeEquipment_4(this, Invocation.method(#findEquipmentById, [id])),
|
||||
returnValue: _FakeEquipment_4(
|
||||
this,
|
||||
Invocation.method(#findEquipmentById, [id]),
|
||||
),
|
||||
)
|
||||
as _i6.Equipment);
|
||||
|
||||
@@ -268,7 +314,10 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
_i7.Muscle findMuscleById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findMuscleById, [id]),
|
||||
returnValue: _FakeMuscle_5(this, Invocation.method(#findMuscleById, [id])),
|
||||
returnValue: _FakeMuscle_5(
|
||||
this,
|
||||
Invocation.method(#findMuscleById, [id]),
|
||||
),
|
||||
)
|
||||
as _i7.Muscle);
|
||||
|
||||
@@ -276,7 +325,10 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
_i8.Language findLanguageById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findLanguageById, [id]),
|
||||
returnValue: _FakeLanguage_6(this, Invocation.method(#findLanguageById, [id])),
|
||||
returnValue: _FakeLanguage_6(
|
||||
this,
|
||||
Invocation.method(#findLanguageById, [id]),
|
||||
),
|
||||
)
|
||||
as _i8.Language);
|
||||
|
||||
@@ -339,11 +391,17 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
int? exerciseId,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#handleUpdateExerciseFromApi, [database, exerciseId]),
|
||||
Invocation.method(#handleUpdateExerciseFromApi, [
|
||||
database,
|
||||
exerciseId,
|
||||
]),
|
||||
returnValue: _i18.Future<_i4.Exercise>.value(
|
||||
_FakeExercise_2(
|
||||
this,
|
||||
Invocation.method(#handleUpdateExerciseFromApi, [database, exerciseId]),
|
||||
Invocation.method(#handleUpdateExerciseFromApi, [
|
||||
database,
|
||||
exerciseId,
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -352,7 +410,9 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
@override
|
||||
_i18.Future<void> initCacheTimesLocalPrefs({dynamic forceInit = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#initCacheTimesLocalPrefs, [], {#forceInit: forceInit}),
|
||||
Invocation.method(#initCacheTimesLocalPrefs, [], {
|
||||
#forceInit: forceInit,
|
||||
}),
|
||||
returnValue: _i18.Future<void>.value(),
|
||||
returnValueForMissingStub: _i18.Future<void>.value(),
|
||||
)
|
||||
@@ -449,7 +509,9 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
[name],
|
||||
{#languageCode: languageCode, #searchEnglish: searchEnglish},
|
||||
),
|
||||
returnValue: _i18.Future<List<_i4.Exercise>>.value(<_i4.Exercise>[]),
|
||||
returnValue: _i18.Future<List<_i4.Exercise>>.value(
|
||||
<_i4.Exercise>[],
|
||||
),
|
||||
)
|
||||
as _i18.Future<List<_i4.Exercise>>);
|
||||
|
||||
@@ -466,12 +528,16 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [NutritionPlansProvider].
|
||||
@@ -486,7 +552,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@@ -494,41 +563,59 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
_i9.IngredientDatabase get database =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#database),
|
||||
returnValue: _FakeIngredientDatabase_7(this, Invocation.getter(#database)),
|
||||
returnValue: _FakeIngredientDatabase_7(
|
||||
this,
|
||||
Invocation.getter(#database),
|
||||
),
|
||||
)
|
||||
as _i9.IngredientDatabase);
|
||||
|
||||
@override
|
||||
List<_i13.Ingredient> get ingredients =>
|
||||
(super.noSuchMethod(Invocation.getter(#ingredients), returnValue: <_i13.Ingredient>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#ingredients),
|
||||
returnValue: <_i13.Ingredient>[],
|
||||
)
|
||||
as List<_i13.Ingredient>);
|
||||
|
||||
@override
|
||||
List<_i10.NutritionalPlan> get items =>
|
||||
(super.noSuchMethod(Invocation.getter(#items), returnValue: <_i10.NutritionalPlan>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#items),
|
||||
returnValue: <_i10.NutritionalPlan>[],
|
||||
)
|
||||
as List<_i10.NutritionalPlan>);
|
||||
|
||||
@override
|
||||
set database(_i9.IngredientDatabase? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#database, value), returnValueForMissingStub: null);
|
||||
set database(_i9.IngredientDatabase? value) => super.noSuchMethod(
|
||||
Invocation.setter(#database, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set ingredients(List<_i13.Ingredient>? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#ingredients, value), returnValueForMissingStub: null);
|
||||
set ingredients(List<_i13.Ingredient>? value) => super.noSuchMethod(
|
||||
Invocation.setter(#ingredients, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i10.NutritionalPlan findById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findById, [id]),
|
||||
returnValue: _FakeNutritionalPlan_8(this, Invocation.method(#findById, [id])),
|
||||
returnValue: _FakeNutritionalPlan_8(
|
||||
this,
|
||||
Invocation.method(#findById, [id]),
|
||||
),
|
||||
)
|
||||
as _i10.NutritionalPlan);
|
||||
|
||||
@@ -559,7 +646,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchAndSetPlanSparse, [planId]),
|
||||
returnValue: _i18.Future<_i10.NutritionalPlan>.value(
|
||||
_FakeNutritionalPlan_8(this, Invocation.method(#fetchAndSetPlanSparse, [planId])),
|
||||
_FakeNutritionalPlan_8(
|
||||
this,
|
||||
Invocation.method(#fetchAndSetPlanSparse, [planId]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i18.Future<_i10.NutritionalPlan>);
|
||||
@@ -569,7 +659,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchAndSetPlanFull, [planId]),
|
||||
returnValue: _i18.Future<_i10.NutritionalPlan>.value(
|
||||
_FakeNutritionalPlan_8(this, Invocation.method(#fetchAndSetPlanFull, [planId])),
|
||||
_FakeNutritionalPlan_8(
|
||||
this,
|
||||
Invocation.method(#fetchAndSetPlanFull, [planId]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i18.Future<_i10.NutritionalPlan>);
|
||||
@@ -579,7 +672,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#addPlan, [planData]),
|
||||
returnValue: _i18.Future<_i10.NutritionalPlan>.value(
|
||||
_FakeNutritionalPlan_8(this, Invocation.method(#addPlan, [planData])),
|
||||
_FakeNutritionalPlan_8(
|
||||
this,
|
||||
Invocation.method(#addPlan, [planData]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i18.Future<_i10.NutritionalPlan>);
|
||||
@@ -632,11 +728,17 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
as _i18.Future<void>);
|
||||
|
||||
@override
|
||||
_i18.Future<_i12.MealItem> addMealItem(_i12.MealItem? mealItem, _i11.Meal? meal) =>
|
||||
_i18.Future<_i12.MealItem> addMealItem(
|
||||
_i12.MealItem? mealItem,
|
||||
_i11.Meal? meal,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#addMealItem, [mealItem, meal]),
|
||||
returnValue: _i18.Future<_i12.MealItem>.value(
|
||||
_FakeMealItem_10(this, Invocation.method(#addMealItem, [mealItem, meal])),
|
||||
_FakeMealItem_10(
|
||||
this,
|
||||
Invocation.method(#addMealItem, [mealItem, meal]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i18.Future<_i12.MealItem>);
|
||||
@@ -659,17 +761,41 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
)
|
||||
as _i18.Future<void>);
|
||||
|
||||
@override
|
||||
_i18.Future<void> cacheIngredient(
|
||||
_i13.Ingredient? ingredient, {
|
||||
_i9.IngredientDatabase? database,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#cacheIngredient,
|
||||
[ingredient],
|
||||
{#database: database},
|
||||
),
|
||||
returnValue: _i18.Future<void>.value(),
|
||||
returnValueForMissingStub: _i18.Future<void>.value(),
|
||||
)
|
||||
as _i18.Future<void>);
|
||||
|
||||
@override
|
||||
_i18.Future<_i13.Ingredient> fetchIngredient(
|
||||
int? ingredientId, {
|
||||
_i9.IngredientDatabase? database,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchIngredient, [ingredientId], {#database: database}),
|
||||
Invocation.method(
|
||||
#fetchIngredient,
|
||||
[ingredientId],
|
||||
{#database: database},
|
||||
),
|
||||
returnValue: _i18.Future<_i13.Ingredient>.value(
|
||||
_FakeIngredient_11(
|
||||
this,
|
||||
Invocation.method(#fetchIngredient, [ingredientId], {#database: database}),
|
||||
Invocation.method(
|
||||
#fetchIngredient,
|
||||
[ingredientId],
|
||||
{#database: database},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -696,7 +822,9 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
[name],
|
||||
{#languageCode: languageCode, #searchEnglish: searchEnglish},
|
||||
),
|
||||
returnValue: _i18.Future<List<_i13.Ingredient>>.value(<_i13.Ingredient>[]),
|
||||
returnValue: _i18.Future<List<_i13.Ingredient>>.value(
|
||||
<_i13.Ingredient>[],
|
||||
),
|
||||
)
|
||||
as _i18.Future<List<_i13.Ingredient>>);
|
||||
|
||||
@@ -724,7 +852,11 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
DateTime? dateTime,
|
||||
]) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#logIngredientToDiary, [mealItem, planId, dateTime]),
|
||||
Invocation.method(#logIngredientToDiary, [
|
||||
mealItem,
|
||||
planId,
|
||||
dateTime,
|
||||
]),
|
||||
returnValue: _i18.Future<void>.value(),
|
||||
returnValueForMissingStub: _i18.Future<void>.value(),
|
||||
)
|
||||
@@ -761,12 +893,16 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [UserProvider].
|
||||
@@ -779,14 +915,20 @@ class MockUserProvider extends _i1.Mock implements _i21.UserProvider {
|
||||
|
||||
@override
|
||||
_i22.ThemeMode get themeMode =>
|
||||
(super.noSuchMethod(Invocation.getter(#themeMode), returnValue: _i22.ThemeMode.system)
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#themeMode),
|
||||
returnValue: _i22.ThemeMode.system,
|
||||
)
|
||||
as _i22.ThemeMode);
|
||||
|
||||
@override
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@@ -794,33 +936,46 @@ class MockUserProvider extends _i1.Mock implements _i21.UserProvider {
|
||||
_i14.SharedPreferencesAsync get prefs =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#prefs),
|
||||
returnValue: _FakeSharedPreferencesAsync_12(this, Invocation.getter(#prefs)),
|
||||
returnValue: _FakeSharedPreferencesAsync_12(
|
||||
this,
|
||||
Invocation.getter(#prefs),
|
||||
),
|
||||
)
|
||||
as _i14.SharedPreferencesAsync);
|
||||
|
||||
@override
|
||||
set themeMode(_i22.ThemeMode? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#themeMode, value), returnValueForMissingStub: null);
|
||||
set themeMode(_i22.ThemeMode? value) => super.noSuchMethod(
|
||||
Invocation.setter(#themeMode, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set prefs(_i14.SharedPreferencesAsync? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#prefs, value), returnValueForMissingStub: null);
|
||||
set prefs(_i14.SharedPreferencesAsync? value) => super.noSuchMethod(
|
||||
Invocation.setter(#prefs, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set profile(_i23.Profile? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#profile, value), returnValueForMissingStub: null);
|
||||
set profile(_i23.Profile? value) => super.noSuchMethod(
|
||||
Invocation.setter(#profile, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void setThemeMode(_i22.ThemeMode? mode) =>
|
||||
super.noSuchMethod(Invocation.method(#setThemeMode, [mode]), returnValueForMissingStub: null);
|
||||
void setThemeMode(_i22.ThemeMode? mode) => super.noSuchMethod(
|
||||
Invocation.method(#setThemeMode, [mode]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i18.Future<void> fetchAndSetProfile() =>
|
||||
@@ -862,12 +1017,16 @@ class MockUserProvider extends _i1.Mock implements _i21.UserProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [WgerBaseProvider].
|
||||
@@ -895,23 +1054,34 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
as _i16.Client);
|
||||
|
||||
@override
|
||||
set auth(_i15.AuthProvider? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#auth, value), returnValueForMissingStub: null);
|
||||
set auth(_i15.AuthProvider? value) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i16.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i16.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {#includeAuth: includeAuth}),
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(String? path, {int? id, String? objectMethod, Map<String, dynamic>? query}) =>
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -946,18 +1116,28 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
as _i18.Future<List<dynamic>>);
|
||||
|
||||
@override
|
||||
_i18.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i18.Future<Map<String, dynamic>> post(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i18.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i18.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i18.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i18.Future<Map<String, dynamic>> patch(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i18.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i18.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i18.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i18.Future<Map<String, dynamic>>);
|
||||
|
||||
@@ -966,7 +1146,10 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i18.Future<_i16.Response>.value(
|
||||
_FakeResponse_16(this, Invocation.method(#deleteRequest, [url, id])),
|
||||
_FakeResponse_16(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i18.Future<_i16.Response>);
|
||||
@@ -993,7 +1176,9 @@ class MockSharedPreferencesAsync extends _i1.Mock implements _i14.SharedPreferen
|
||||
_i18.Future<Map<String, Object?>> getAll({Set<String>? allowList}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getAll, [], {#allowList: allowList}),
|
||||
returnValue: _i18.Future<Map<String, Object?>>.value(<String, Object?>{}),
|
||||
returnValue: _i18.Future<Map<String, Object?>>.value(
|
||||
<String, Object?>{},
|
||||
),
|
||||
)
|
||||
as _i18.Future<Map<String, Object?>>);
|
||||
|
||||
@@ -1007,7 +1192,10 @@ class MockSharedPreferencesAsync extends _i1.Mock implements _i14.SharedPreferen
|
||||
|
||||
@override
|
||||
_i18.Future<int?> getInt(String? key) =>
|
||||
(super.noSuchMethod(Invocation.method(#getInt, [key]), returnValue: _i18.Future<int?>.value())
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getInt, [key]),
|
||||
returnValue: _i18.Future<int?>.value(),
|
||||
)
|
||||
as _i18.Future<int?>);
|
||||
|
||||
@override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,6 @@ import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/providers/add_exercise.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:wger/providers/user.dart';
|
||||
@@ -136,8 +135,8 @@ void main() {
|
||||
|
||||
group('Form Field Validation Tests', () {
|
||||
testWidgets('Exercise name field is required and displays validation error', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Setup: Create verified user with required data
|
||||
setupFullVerifiedUserContext();
|
||||
|
||||
@@ -187,8 +186,8 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Alternative names field accepts multiple lines of text', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Setup: Create verified user
|
||||
setupFullVerifiedUserContext();
|
||||
|
||||
@@ -438,8 +437,8 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Provider clear method is called after successful submission', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Setup: Mock successful submission flow
|
||||
setupFullVerifiedUserContext();
|
||||
when(mockAddExerciseProvider.postExerciseToServer()).thenAnswer((_) async => 1);
|
||||
@@ -518,4 +517,4 @@ void main() {
|
||||
expect(profileButton, findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,60 +40,50 @@ import 'package:wger/providers/user.dart' as _i17;
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
class _FakeWgerBaseProvider_0 extends _i1.SmartFake
|
||||
implements _i2.WgerBaseProvider {
|
||||
class _FakeWgerBaseProvider_0 extends _i1.SmartFake implements _i2.WgerBaseProvider {
|
||||
_FakeWgerBaseProvider_0(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeVariation_1 extends _i1.SmartFake implements _i3.Variation {
|
||||
_FakeVariation_1(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeVariation_1(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeSharedPreferencesAsync_2 extends _i1.SmartFake
|
||||
implements _i4.SharedPreferencesAsync {
|
||||
class _FakeSharedPreferencesAsync_2 extends _i1.SmartFake implements _i4.SharedPreferencesAsync {
|
||||
_FakeSharedPreferencesAsync_2(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeExerciseDatabase_3 extends _i1.SmartFake
|
||||
implements _i5.ExerciseDatabase {
|
||||
class _FakeExerciseDatabase_3 extends _i1.SmartFake implements _i5.ExerciseDatabase {
|
||||
_FakeExerciseDatabase_3(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeExercise_4 extends _i1.SmartFake implements _i6.Exercise {
|
||||
_FakeExercise_4(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeExercise_4(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeExerciseCategory_5 extends _i1.SmartFake
|
||||
implements _i7.ExerciseCategory {
|
||||
class _FakeExerciseCategory_5 extends _i1.SmartFake implements _i7.ExerciseCategory {
|
||||
_FakeExerciseCategory_5(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeEquipment_6 extends _i1.SmartFake implements _i8.Equipment {
|
||||
_FakeEquipment_6(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeEquipment_6(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeMuscle_7 extends _i1.SmartFake implements _i9.Muscle {
|
||||
_FakeMuscle_7(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeMuscle_7(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeLanguage_8 extends _i1.SmartFake implements _i10.Language {
|
||||
_FakeLanguage_8(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeLanguage_8(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
/// A class which mocks [AddExerciseProvider].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockAddExerciseProvider extends _i1.Mock
|
||||
implements _i11.AddExerciseProvider {
|
||||
class MockAddExerciseProvider extends _i1.Mock implements _i11.AddExerciseProvider {
|
||||
MockAddExerciseProvider() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
@@ -154,8 +144,7 @@ class MockAddExerciseProvider extends _i1.Mock
|
||||
|
||||
@override
|
||||
bool get newVariation =>
|
||||
(super.noSuchMethod(Invocation.getter(#newVariation), returnValue: false)
|
||||
as bool);
|
||||
(super.noSuchMethod(Invocation.getter(#newVariation), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
_i3.Variation get variation =>
|
||||
@@ -284,8 +273,7 @@ class MockAddExerciseProvider extends _i1.Mock
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
|
||||
as bool);
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() => super.noSuchMethod(
|
||||
@@ -294,11 +282,10 @@ class MockAddExerciseProvider extends _i1.Mock
|
||||
);
|
||||
|
||||
@override
|
||||
void addExerciseImages(List<_i12.ExerciseSubmissionImage>? images) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(#addExerciseImages, [images]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
void addExerciseImages(List<_i12.ExerciseSubmissionImage>? images) => super.noSuchMethod(
|
||||
Invocation.method(#addExerciseImages, [images]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void removeImage(String? path) => super.noSuchMethod(
|
||||
@@ -422,8 +409,7 @@ class MockUserProvider extends _i1.Mock implements _i17.UserProvider {
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
|
||||
as bool);
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() => super.noSuchMethod(
|
||||
@@ -588,11 +574,10 @@ class MockExercisesProvider extends _i1.Mock implements _i20.ExercisesProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
set filteredExercises(List<_i6.Exercise>? newFilteredExercises) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.setter(#filteredExercises, newFilteredExercises),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
set filteredExercises(List<_i6.Exercise>? newFilteredExercises) => super.noSuchMethod(
|
||||
Invocation.setter(#filteredExercises, newFilteredExercises),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set languages(List<_i10.Language>? languages) => super.noSuchMethod(
|
||||
@@ -602,8 +587,7 @@ class MockExercisesProvider extends _i1.Mock implements _i20.ExercisesProvider {
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
|
||||
as bool);
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
_i15.Future<void> setFilters(_i20.Filters? newFilters) =>
|
||||
|
||||
@@ -74,7 +74,10 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@@ -82,18 +85,27 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
_i3.ExerciseDatabase get database =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#database),
|
||||
returnValue: _FakeExerciseDatabase_1(this, Invocation.getter(#database)),
|
||||
returnValue: _FakeExerciseDatabase_1(
|
||||
this,
|
||||
Invocation.getter(#database),
|
||||
),
|
||||
)
|
||||
as _i3.ExerciseDatabase);
|
||||
|
||||
@override
|
||||
List<_i4.Exercise> get exercises =>
|
||||
(super.noSuchMethod(Invocation.getter(#exercises), returnValue: <_i4.Exercise>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#exercises),
|
||||
returnValue: <_i4.Exercise>[],
|
||||
)
|
||||
as List<_i4.Exercise>);
|
||||
|
||||
@override
|
||||
List<_i4.Exercise> get filteredExercises =>
|
||||
(super.noSuchMethod(Invocation.getter(#filteredExercises), returnValue: <_i4.Exercise>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#filteredExercises),
|
||||
returnValue: <_i4.Exercise>[],
|
||||
)
|
||||
as List<_i4.Exercise>);
|
||||
|
||||
@override
|
||||
@@ -106,31 +118,47 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
|
||||
@override
|
||||
List<_i5.ExerciseCategory> get categories =>
|
||||
(super.noSuchMethod(Invocation.getter(#categories), returnValue: <_i5.ExerciseCategory>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#categories),
|
||||
returnValue: <_i5.ExerciseCategory>[],
|
||||
)
|
||||
as List<_i5.ExerciseCategory>);
|
||||
|
||||
@override
|
||||
List<_i7.Muscle> get muscles =>
|
||||
(super.noSuchMethod(Invocation.getter(#muscles), returnValue: <_i7.Muscle>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#muscles),
|
||||
returnValue: <_i7.Muscle>[],
|
||||
)
|
||||
as List<_i7.Muscle>);
|
||||
|
||||
@override
|
||||
List<_i6.Equipment> get equipment =>
|
||||
(super.noSuchMethod(Invocation.getter(#equipment), returnValue: <_i6.Equipment>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#equipment),
|
||||
returnValue: <_i6.Equipment>[],
|
||||
)
|
||||
as List<_i6.Equipment>);
|
||||
|
||||
@override
|
||||
List<_i8.Language> get languages =>
|
||||
(super.noSuchMethod(Invocation.getter(#languages), returnValue: <_i8.Language>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#languages),
|
||||
returnValue: <_i8.Language>[],
|
||||
)
|
||||
as List<_i8.Language>);
|
||||
|
||||
@override
|
||||
set database(_i3.ExerciseDatabase? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#database, value), returnValueForMissingStub: null);
|
||||
set database(_i3.ExerciseDatabase? value) => super.noSuchMethod(
|
||||
Invocation.setter(#database, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set exercises(List<_i4.Exercise>? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#exercises, value), returnValueForMissingStub: null);
|
||||
set exercises(List<_i4.Exercise>? value) => super.noSuchMethod(
|
||||
Invocation.setter(#exercises, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set filteredExercises(List<_i4.Exercise>? newFilteredExercises) => super.noSuchMethod(
|
||||
@@ -139,8 +167,10 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
set languages(List<_i8.Language>? languages) =>
|
||||
super.noSuchMethod(Invocation.setter(#languages, languages), returnValueForMissingStub: null);
|
||||
set languages(List<_i8.Language>? languages) => super.noSuchMethod(
|
||||
Invocation.setter(#languages, languages),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
@@ -156,8 +186,10 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
as _i10.Future<void>);
|
||||
|
||||
@override
|
||||
void initFilters() =>
|
||||
super.noSuchMethod(Invocation.method(#initFilters, []), returnValueForMissingStub: null);
|
||||
void initFilters() => super.noSuchMethod(
|
||||
Invocation.method(#initFilters, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i10.Future<void> findByFilters() =>
|
||||
@@ -169,19 +201,27 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
as _i10.Future<void>);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i4.Exercise findExerciseById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findExerciseById, [id]),
|
||||
returnValue: _FakeExercise_2(this, Invocation.method(#findExerciseById, [id])),
|
||||
returnValue: _FakeExercise_2(
|
||||
this,
|
||||
Invocation.method(#findExerciseById, [id]),
|
||||
),
|
||||
)
|
||||
as _i4.Exercise);
|
||||
|
||||
@override
|
||||
List<_i4.Exercise> findExercisesByVariationId(int? variationId, {int? exerciseIdToExclude}) =>
|
||||
List<_i4.Exercise> findExercisesByVariationId(
|
||||
int? variationId, {
|
||||
int? exerciseIdToExclude,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#findExercisesByVariationId,
|
||||
@@ -196,7 +236,10 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
_i5.ExerciseCategory findCategoryById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findCategoryById, [id]),
|
||||
returnValue: _FakeExerciseCategory_3(this, Invocation.method(#findCategoryById, [id])),
|
||||
returnValue: _FakeExerciseCategory_3(
|
||||
this,
|
||||
Invocation.method(#findCategoryById, [id]),
|
||||
),
|
||||
)
|
||||
as _i5.ExerciseCategory);
|
||||
|
||||
@@ -204,7 +247,10 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
_i6.Equipment findEquipmentById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findEquipmentById, [id]),
|
||||
returnValue: _FakeEquipment_4(this, Invocation.method(#findEquipmentById, [id])),
|
||||
returnValue: _FakeEquipment_4(
|
||||
this,
|
||||
Invocation.method(#findEquipmentById, [id]),
|
||||
),
|
||||
)
|
||||
as _i6.Equipment);
|
||||
|
||||
@@ -212,7 +258,10 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
_i7.Muscle findMuscleById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findMuscleById, [id]),
|
||||
returnValue: _FakeMuscle_5(this, Invocation.method(#findMuscleById, [id])),
|
||||
returnValue: _FakeMuscle_5(
|
||||
this,
|
||||
Invocation.method(#findMuscleById, [id]),
|
||||
),
|
||||
)
|
||||
as _i7.Muscle);
|
||||
|
||||
@@ -220,7 +269,10 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
_i8.Language findLanguageById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findLanguageById, [id]),
|
||||
returnValue: _FakeLanguage_6(this, Invocation.method(#findLanguageById, [id])),
|
||||
returnValue: _FakeLanguage_6(
|
||||
this,
|
||||
Invocation.method(#findLanguageById, [id]),
|
||||
),
|
||||
)
|
||||
as _i8.Language);
|
||||
|
||||
@@ -283,11 +335,17 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
int? exerciseId,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#handleUpdateExerciseFromApi, [database, exerciseId]),
|
||||
Invocation.method(#handleUpdateExerciseFromApi, [
|
||||
database,
|
||||
exerciseId,
|
||||
]),
|
||||
returnValue: _i10.Future<_i4.Exercise>.value(
|
||||
_FakeExercise_2(
|
||||
this,
|
||||
Invocation.method(#handleUpdateExerciseFromApi, [database, exerciseId]),
|
||||
Invocation.method(#handleUpdateExerciseFromApi, [
|
||||
database,
|
||||
exerciseId,
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -296,7 +354,9 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
@override
|
||||
_i10.Future<void> initCacheTimesLocalPrefs({dynamic forceInit = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#initCacheTimesLocalPrefs, [], {#forceInit: forceInit}),
|
||||
Invocation.method(#initCacheTimesLocalPrefs, [], {
|
||||
#forceInit: forceInit,
|
||||
}),
|
||||
returnValue: _i10.Future<void>.value(),
|
||||
returnValueForMissingStub: _i10.Future<void>.value(),
|
||||
)
|
||||
@@ -393,7 +453,9 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
[name],
|
||||
{#languageCode: languageCode, #searchEnglish: searchEnglish},
|
||||
),
|
||||
returnValue: _i10.Future<List<_i4.Exercise>>.value(<_i4.Exercise>[]),
|
||||
returnValue: _i10.Future<List<_i4.Exercise>>.value(
|
||||
<_i4.Exercise>[],
|
||||
),
|
||||
)
|
||||
as _i10.Future<List<_i4.Exercise>>);
|
||||
|
||||
@@ -410,10 +472,14 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,12 +54,17 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
|
||||
@override
|
||||
List<_i5.Image> get images =>
|
||||
(super.noSuchMethod(Invocation.getter(#images), returnValue: <_i5.Image>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#images),
|
||||
returnValue: <_i5.Image>[],
|
||||
)
|
||||
as List<_i5.Image>);
|
||||
|
||||
@override
|
||||
set images(List<_i5.Image>? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#images, value), returnValueForMissingStub: null);
|
||||
set images(List<_i5.Image>? value) => super.noSuchMethod(
|
||||
Invocation.setter(#images, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i2.AuthProvider get auth =>
|
||||
@@ -78,20 +83,26 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
as _i3.Client);
|
||||
|
||||
@override
|
||||
set auth(_i2.AuthProvider? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#auth, value), returnValueForMissingStub: null);
|
||||
set auth(_i2.AuthProvider? value) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i3.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i3.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i6.Future<void> fetchAndSetGallery() =>
|
||||
@@ -132,13 +143,20 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {#includeAuth: includeAuth}),
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(String? path, {int? id, String? objectMethod, Map<String, dynamic>? query}) =>
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -176,15 +194,22 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
_i6.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i6.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i6.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> patch(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i6.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i6.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i6.Future<Map<String, dynamic>>);
|
||||
|
||||
@@ -193,7 +218,10 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i6.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#deleteRequest, [url, id])),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i6.Future<_i3.Response>);
|
||||
@@ -211,10 +239,14 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,12 +54,17 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
|
||||
@override
|
||||
List<_i5.Image> get images =>
|
||||
(super.noSuchMethod(Invocation.getter(#images), returnValue: <_i5.Image>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#images),
|
||||
returnValue: <_i5.Image>[],
|
||||
)
|
||||
as List<_i5.Image>);
|
||||
|
||||
@override
|
||||
set images(List<_i5.Image>? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#images, value), returnValueForMissingStub: null);
|
||||
set images(List<_i5.Image>? value) => super.noSuchMethod(
|
||||
Invocation.setter(#images, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i2.AuthProvider get auth =>
|
||||
@@ -78,20 +83,26 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
as _i3.Client);
|
||||
|
||||
@override
|
||||
set auth(_i2.AuthProvider? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#auth, value), returnValueForMissingStub: null);
|
||||
set auth(_i2.AuthProvider? value) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i3.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i3.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i6.Future<void> fetchAndSetGallery() =>
|
||||
@@ -132,13 +143,20 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {#includeAuth: includeAuth}),
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(String? path, {int? id, String? objectMethod, Map<String, dynamic>? query}) =>
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -176,15 +194,22 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
_i6.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i6.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i6.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> patch(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i6.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i6.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i6.Future<Map<String, dynamic>>);
|
||||
|
||||
@@ -193,7 +218,10 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i6.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#deleteRequest, [url, id])),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i6.Future<_i3.Response>);
|
||||
@@ -211,10 +239,14 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,13 +49,19 @@ class MockMeasurementProvider extends _i1.Mock implements _i4.MeasurementProvide
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@override
|
||||
List<_i3.MeasurementCategory> get categories =>
|
||||
(super.noSuchMethod(Invocation.getter(#categories), returnValue: <_i3.MeasurementCategory>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#categories),
|
||||
returnValue: <_i3.MeasurementCategory>[],
|
||||
)
|
||||
as List<_i3.MeasurementCategory>);
|
||||
|
||||
@override
|
||||
@@ -63,8 +69,10 @@ class MockMeasurementProvider extends _i1.Mock implements _i4.MeasurementProvide
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.MeasurementCategory findCategoryById(int? id) =>
|
||||
@@ -158,7 +166,13 @@ class MockMeasurementProvider extends _i1.Mock implements _i4.MeasurementProvide
|
||||
DateTime? newDate,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#editEntry, [id, categoryId, newValue, newNotes, newDate]),
|
||||
Invocation.method(#editEntry, [
|
||||
id,
|
||||
categoryId,
|
||||
newValue,
|
||||
newNotes,
|
||||
newDate,
|
||||
]),
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
)
|
||||
@@ -177,10 +191,14 @@ class MockMeasurementProvider extends _i1.Mock implements _i4.MeasurementProvide
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,23 +66,34 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
as _i3.Client);
|
||||
|
||||
@override
|
||||
set auth(_i2.AuthProvider? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#auth, value), returnValueForMissingStub: null);
|
||||
set auth(_i2.AuthProvider? value) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i3.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i3.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {#includeAuth: includeAuth}),
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(String? path, {int? id, String? objectMethod, Map<String, dynamic>? query}) =>
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -120,15 +131,22 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
_i5.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i5.Future<Map<String, dynamic>> patch(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i5.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@@ -137,7 +155,10 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#deleteRequest, [url, id])),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.Response>);
|
||||
|
||||
@@ -30,44 +30,37 @@ import 'package:wger/providers/nutrition.dart' as _i8;
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
class _FakeWgerBaseProvider_0 extends _i1.SmartFake
|
||||
implements _i2.WgerBaseProvider {
|
||||
class _FakeWgerBaseProvider_0 extends _i1.SmartFake implements _i2.WgerBaseProvider {
|
||||
_FakeWgerBaseProvider_0(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeIngredientDatabase_1 extends _i1.SmartFake
|
||||
implements _i3.IngredientDatabase {
|
||||
class _FakeIngredientDatabase_1 extends _i1.SmartFake implements _i3.IngredientDatabase {
|
||||
_FakeIngredientDatabase_1(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeNutritionalPlan_2 extends _i1.SmartFake
|
||||
implements _i4.NutritionalPlan {
|
||||
class _FakeNutritionalPlan_2 extends _i1.SmartFake implements _i4.NutritionalPlan {
|
||||
_FakeNutritionalPlan_2(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeMeal_3 extends _i1.SmartFake implements _i5.Meal {
|
||||
_FakeMeal_3(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeMeal_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeMealItem_4 extends _i1.SmartFake implements _i6.MealItem {
|
||||
_FakeMealItem_4(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeMealItem_4(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeIngredient_5 extends _i1.SmartFake implements _i7.Ingredient {
|
||||
_FakeIngredient_5(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeIngredient_5(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
/// A class which mocks [NutritionPlansProvider].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockNutritionPlansProvider extends _i1.Mock
|
||||
implements _i8.NutritionPlansProvider {
|
||||
class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansProvider {
|
||||
MockNutritionPlansProvider() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
@@ -124,8 +117,7 @@ class MockNutritionPlansProvider extends _i1.Mock
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
|
||||
as bool);
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() => super.noSuchMethod(
|
||||
|
||||
@@ -348,6 +348,10 @@ void main() {
|
||||
),
|
||||
).thenAnswer((_) => Future.value([ingredient1]));
|
||||
|
||||
when(
|
||||
mockNutrition.cacheIngredient(any),
|
||||
).thenAnswer((_) => Future.value(null));
|
||||
|
||||
await tester.enterText(find.byType(TextFormField).first, 'Water');
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 600));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@@ -30,44 +30,37 @@ import 'package:wger/providers/nutrition.dart' as _i8;
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
class _FakeWgerBaseProvider_0 extends _i1.SmartFake
|
||||
implements _i2.WgerBaseProvider {
|
||||
class _FakeWgerBaseProvider_0 extends _i1.SmartFake implements _i2.WgerBaseProvider {
|
||||
_FakeWgerBaseProvider_0(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeIngredientDatabase_1 extends _i1.SmartFake
|
||||
implements _i3.IngredientDatabase {
|
||||
class _FakeIngredientDatabase_1 extends _i1.SmartFake implements _i3.IngredientDatabase {
|
||||
_FakeIngredientDatabase_1(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeNutritionalPlan_2 extends _i1.SmartFake
|
||||
implements _i4.NutritionalPlan {
|
||||
class _FakeNutritionalPlan_2 extends _i1.SmartFake implements _i4.NutritionalPlan {
|
||||
_FakeNutritionalPlan_2(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeMeal_3 extends _i1.SmartFake implements _i5.Meal {
|
||||
_FakeMeal_3(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeMeal_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeMealItem_4 extends _i1.SmartFake implements _i6.MealItem {
|
||||
_FakeMealItem_4(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeMealItem_4(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeIngredient_5 extends _i1.SmartFake implements _i7.Ingredient {
|
||||
_FakeIngredient_5(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
_FakeIngredient_5(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
/// A class which mocks [NutritionPlansProvider].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockNutritionPlansProvider extends _i1.Mock
|
||||
implements _i8.NutritionPlansProvider {
|
||||
class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansProvider {
|
||||
MockNutritionPlansProvider() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
@@ -124,8 +117,7 @@ class MockNutritionPlansProvider extends _i1.Mock
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
|
||||
as bool);
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() => super.noSuchMethod(
|
||||
|
||||
@@ -76,23 +76,34 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
as _i3.Client);
|
||||
|
||||
@override
|
||||
set auth(_i2.AuthProvider? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#auth, value), returnValueForMissingStub: null);
|
||||
set auth(_i2.AuthProvider? value) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i3.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i3.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {#includeAuth: includeAuth}),
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(String? path, {int? id, String? objectMethod, Map<String, dynamic>? query}) =>
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -130,15 +141,22 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
_i5.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i5.Future<Map<String, dynamic>> patch(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i5.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@@ -147,7 +165,10 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#deleteRequest, [url, id])),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.Response>);
|
||||
@@ -163,12 +184,18 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
|
||||
|
||||
@override
|
||||
Map<String, String> get metadata =>
|
||||
(super.noSuchMethod(Invocation.getter(#metadata), returnValue: <String, String>{})
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#metadata),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
_i2.AuthState get state =>
|
||||
(super.noSuchMethod(Invocation.getter(#state), returnValue: _i2.AuthState.updateRequired)
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#state),
|
||||
returnValue: _i2.AuthState.updateRequired,
|
||||
)
|
||||
as _i2.AuthState);
|
||||
|
||||
@override
|
||||
@@ -187,16 +214,22 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
|
||||
bool get isAuth => (super.noSuchMethod(Invocation.getter(#isAuth), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
set token(String? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#token, value), returnValueForMissingStub: null);
|
||||
set token(String? value) => super.noSuchMethod(
|
||||
Invocation.setter(#token, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set serverUrl(String? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#serverUrl, value), returnValueForMissingStub: null);
|
||||
set serverUrl(String? value) => super.noSuchMethod(
|
||||
Invocation.setter(#serverUrl, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set serverVersion(String? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#serverVersion, value), returnValueForMissingStub: null);
|
||||
set serverVersion(String? value) => super.noSuchMethod(
|
||||
Invocation.setter(#serverVersion, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set applicationVersion(_i6.PackageInfo? value) => super.noSuchMethod(
|
||||
@@ -205,20 +238,28 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
set metadata(Map<String, String>? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#metadata, value), returnValueForMissingStub: null);
|
||||
set metadata(Map<String, String>? value) => super.noSuchMethod(
|
||||
Invocation.setter(#metadata, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set state(_i2.AuthState? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#state, value), returnValueForMissingStub: null);
|
||||
set state(_i2.AuthState? value) => super.noSuchMethod(
|
||||
Invocation.setter(#state, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i3.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i3.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set dataInit(bool? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#dataInit, value), returnValueForMissingStub: null);
|
||||
set dataInit(bool? value) => super.noSuchMethod(
|
||||
Invocation.setter(#dataInit, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
@@ -275,7 +316,9 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
|
||||
#serverUrl: serverUrl,
|
||||
#locale: locale,
|
||||
}),
|
||||
returnValue: _i5.Future<_i2.LoginActions>.value(_i2.LoginActions.update),
|
||||
returnValue: _i5.Future<_i2.LoginActions>.value(
|
||||
_i2.LoginActions.update,
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i2.LoginActions>);
|
||||
|
||||
@@ -287,8 +330,15 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
|
||||
String? apiToken,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#login, [username, password, serverUrl, apiToken]),
|
||||
returnValue: _i5.Future<_i2.LoginActions>.value(_i2.LoginActions.update),
|
||||
Invocation.method(#login, [
|
||||
username,
|
||||
password,
|
||||
serverUrl,
|
||||
apiToken,
|
||||
]),
|
||||
returnValue: _i5.Future<_i2.LoginActions>.value(
|
||||
_i2.LoginActions.update,
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i2.LoginActions>);
|
||||
|
||||
@@ -297,7 +347,10 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getServerUrlFromPrefs, []),
|
||||
returnValue: _i5.Future<String>.value(
|
||||
_i7.dummyValue<String>(this, Invocation.method(#getServerUrlFromPrefs, [])),
|
||||
_i7.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#getServerUrlFromPrefs, []),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<String>);
|
||||
@@ -324,7 +377,10 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
|
||||
String getAppNameHeader() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getAppNameHeader, []),
|
||||
returnValue: _i7.dummyValue<String>(this, Invocation.method(#getAppNameHeader, [])),
|
||||
returnValue: _i7.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#getAppNameHeader, []),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@@ -341,12 +397,16 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [Client].
|
||||
@@ -362,7 +422,10 @@ class MockClient extends _i1.Mock implements _i3.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#head, [url], {#headers: headers}),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#head, [url], {#headers: headers})),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#head, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.Response>);
|
||||
@@ -372,7 +435,10 @@ class MockClient extends _i1.Mock implements _i3.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#get, [url], {#headers: headers}),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#get, [url], {#headers: headers})),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#get, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.Response>);
|
||||
@@ -385,7 +451,11 @@ class MockClient extends _i1.Mock implements _i3.Client {
|
||||
_i9.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#post,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
@@ -407,7 +477,11 @@ class MockClient extends _i1.Mock implements _i3.Client {
|
||||
_i9.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#put,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
@@ -429,7 +503,11 @@ class MockClient extends _i1.Mock implements _i3.Client {
|
||||
_i9.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#patch,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
@@ -474,13 +552,19 @@ class MockClient extends _i1.Mock implements _i3.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#read, [url], {#headers: headers}),
|
||||
returnValue: _i5.Future<String>.value(
|
||||
_i7.dummyValue<String>(this, Invocation.method(#read, [url], {#headers: headers})),
|
||||
_i7.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#read, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<String>);
|
||||
|
||||
@override
|
||||
_i5.Future<_i10.Uint8List> readBytes(Uri? url, {Map<String, String>? headers}) =>
|
||||
_i5.Future<_i10.Uint8List> readBytes(
|
||||
Uri? url, {
|
||||
Map<String, String>? headers,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#readBytes, [url], {#headers: headers}),
|
||||
returnValue: _i5.Future<_i10.Uint8List>.value(_i10.Uint8List(0)),
|
||||
@@ -492,12 +576,17 @@ class MockClient extends _i1.Mock implements _i3.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#send, [request]),
|
||||
returnValue: _i5.Future<_i3.StreamedResponse>.value(
|
||||
_FakeStreamedResponse_4(this, Invocation.method(#send, [request])),
|
||||
_FakeStreamedResponse_4(
|
||||
this,
|
||||
Invocation.method(#send, [request]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.StreamedResponse>);
|
||||
|
||||
@override
|
||||
void close() =>
|
||||
super.noSuchMethod(Invocation.method(#close, []), returnValueForMissingStub: null);
|
||||
void close() => super.noSuchMethod(
|
||||
Invocation.method(#close, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,12 +61,18 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
|
||||
|
||||
@override
|
||||
Map<String, String> get metadata =>
|
||||
(super.noSuchMethod(Invocation.getter(#metadata), returnValue: <String, String>{})
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#metadata),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
_i3.AuthState get state =>
|
||||
(super.noSuchMethod(Invocation.getter(#state), returnValue: _i3.AuthState.updateRequired)
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#state),
|
||||
returnValue: _i3.AuthState.updateRequired,
|
||||
)
|
||||
as _i3.AuthState);
|
||||
|
||||
@override
|
||||
@@ -85,16 +91,22 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
|
||||
bool get isAuth => (super.noSuchMethod(Invocation.getter(#isAuth), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
set token(String? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#token, value), returnValueForMissingStub: null);
|
||||
set token(String? value) => super.noSuchMethod(
|
||||
Invocation.setter(#token, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set serverUrl(String? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#serverUrl, value), returnValueForMissingStub: null);
|
||||
set serverUrl(String? value) => super.noSuchMethod(
|
||||
Invocation.setter(#serverUrl, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set serverVersion(String? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#serverVersion, value), returnValueForMissingStub: null);
|
||||
set serverVersion(String? value) => super.noSuchMethod(
|
||||
Invocation.setter(#serverVersion, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set applicationVersion(_i4.PackageInfo? value) => super.noSuchMethod(
|
||||
@@ -103,20 +115,28 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
set metadata(Map<String, String>? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#metadata, value), returnValueForMissingStub: null);
|
||||
set metadata(Map<String, String>? value) => super.noSuchMethod(
|
||||
Invocation.setter(#metadata, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set state(_i3.AuthState? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#state, value), returnValueForMissingStub: null);
|
||||
set state(_i3.AuthState? value) => super.noSuchMethod(
|
||||
Invocation.setter(#state, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i2.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i2.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set dataInit(bool? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#dataInit, value), returnValueForMissingStub: null);
|
||||
set dataInit(bool? value) => super.noSuchMethod(
|
||||
Invocation.setter(#dataInit, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
@@ -173,7 +193,9 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
|
||||
#serverUrl: serverUrl,
|
||||
#locale: locale,
|
||||
}),
|
||||
returnValue: _i5.Future<_i3.LoginActions>.value(_i3.LoginActions.update),
|
||||
returnValue: _i5.Future<_i3.LoginActions>.value(
|
||||
_i3.LoginActions.update,
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.LoginActions>);
|
||||
|
||||
@@ -185,8 +207,15 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
|
||||
String? apiToken,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#login, [username, password, serverUrl, apiToken]),
|
||||
returnValue: _i5.Future<_i3.LoginActions>.value(_i3.LoginActions.update),
|
||||
Invocation.method(#login, [
|
||||
username,
|
||||
password,
|
||||
serverUrl,
|
||||
apiToken,
|
||||
]),
|
||||
returnValue: _i5.Future<_i3.LoginActions>.value(
|
||||
_i3.LoginActions.update,
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.LoginActions>);
|
||||
|
||||
@@ -195,7 +224,10 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getServerUrlFromPrefs, []),
|
||||
returnValue: _i5.Future<String>.value(
|
||||
_i6.dummyValue<String>(this, Invocation.method(#getServerUrlFromPrefs, [])),
|
||||
_i6.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#getServerUrlFromPrefs, []),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<String>);
|
||||
@@ -222,7 +254,10 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
|
||||
String getAppNameHeader() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getAppNameHeader, []),
|
||||
returnValue: _i6.dummyValue<String>(this, Invocation.method(#getAppNameHeader, [])),
|
||||
returnValue: _i6.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#getAppNameHeader, []),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@@ -239,12 +274,16 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [WgerBaseProvider].
|
||||
@@ -272,23 +311,34 @@ class MockWgerBaseProvider extends _i1.Mock implements _i8.WgerBaseProvider {
|
||||
as _i2.Client);
|
||||
|
||||
@override
|
||||
set auth(_i3.AuthProvider? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#auth, value), returnValueForMissingStub: null);
|
||||
set auth(_i3.AuthProvider? value) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i2.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i2.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {#includeAuth: includeAuth}),
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(String? path, {int? id, String? objectMethod, Map<String, dynamic>? query}) =>
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -326,15 +376,22 @@ class MockWgerBaseProvider extends _i1.Mock implements _i8.WgerBaseProvider {
|
||||
_i5.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i5.Future<Map<String, dynamic>> patch(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i5.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@@ -343,7 +400,10 @@ class MockWgerBaseProvider extends _i1.Mock implements _i8.WgerBaseProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i5.Future<_i2.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#deleteRequest, [url, id])),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i2.Response>);
|
||||
@@ -362,7 +422,10 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#head, [url], {#headers: headers}),
|
||||
returnValue: _i5.Future<_i2.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#head, [url], {#headers: headers})),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#head, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i2.Response>);
|
||||
@@ -372,7 +435,10 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#get, [url], {#headers: headers}),
|
||||
returnValue: _i5.Future<_i2.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#get, [url], {#headers: headers})),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#get, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i2.Response>);
|
||||
@@ -385,7 +451,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i9.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#post,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i5.Future<_i2.Response>.value(
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
@@ -407,7 +477,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i9.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#put,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i5.Future<_i2.Response>.value(
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
@@ -429,7 +503,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i9.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#patch,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i5.Future<_i2.Response>.value(
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
@@ -474,13 +552,19 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#read, [url], {#headers: headers}),
|
||||
returnValue: _i5.Future<String>.value(
|
||||
_i6.dummyValue<String>(this, Invocation.method(#read, [url], {#headers: headers})),
|
||||
_i6.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#read, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<String>);
|
||||
|
||||
@override
|
||||
_i5.Future<_i10.Uint8List> readBytes(Uri? url, {Map<String, String>? headers}) =>
|
||||
_i5.Future<_i10.Uint8List> readBytes(
|
||||
Uri? url, {
|
||||
Map<String, String>? headers,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#readBytes, [url], {#headers: headers}),
|
||||
returnValue: _i5.Future<_i10.Uint8List>.value(_i10.Uint8List(0)),
|
||||
@@ -492,12 +576,17 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#send, [request]),
|
||||
returnValue: _i5.Future<_i2.StreamedResponse>.value(
|
||||
_FakeStreamedResponse_4(this, Invocation.method(#send, [request])),
|
||||
_FakeStreamedResponse_4(
|
||||
this,
|
||||
Invocation.method(#send, [request]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i2.StreamedResponse>);
|
||||
|
||||
@override
|
||||
void close() =>
|
||||
super.noSuchMethod(Invocation.method(#close, []), returnValueForMissingStub: null);
|
||||
void close() => super.noSuchMethod(
|
||||
Invocation.method(#close, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,10 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#head, [url], {#headers: headers}),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(this, Invocation.method(#head, [url], {#headers: headers})),
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
Invocation.method(#head, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<_i2.Response>);
|
||||
@@ -58,7 +61,10 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#get, [url], {#headers: headers}),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(this, Invocation.method(#get, [url], {#headers: headers})),
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
Invocation.method(#get, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<_i2.Response>);
|
||||
@@ -71,7 +77,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i4.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#post,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
@@ -93,7 +103,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i4.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#put,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
@@ -115,7 +129,11 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
_i4.Encoding? encoding,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [url], {#headers: headers, #body: body, #encoding: encoding}),
|
||||
Invocation.method(
|
||||
#patch,
|
||||
[url],
|
||||
{#headers: headers, #body: body, #encoding: encoding},
|
||||
),
|
||||
returnValue: _i3.Future<_i2.Response>.value(
|
||||
_FakeResponse_0(
|
||||
this,
|
||||
@@ -160,13 +178,19 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#read, [url], {#headers: headers}),
|
||||
returnValue: _i3.Future<String>.value(
|
||||
_i5.dummyValue<String>(this, Invocation.method(#read, [url], {#headers: headers})),
|
||||
_i5.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#read, [url], {#headers: headers}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<String>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i6.Uint8List> readBytes(Uri? url, {Map<String, String>? headers}) =>
|
||||
_i3.Future<_i6.Uint8List> readBytes(
|
||||
Uri? url, {
|
||||
Map<String, String>? headers,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#readBytes, [url], {#headers: headers}),
|
||||
returnValue: _i3.Future<_i6.Uint8List>.value(_i6.Uint8List(0)),
|
||||
@@ -178,12 +202,17 @@ class MockClient extends _i1.Mock implements _i2.Client {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#send, [request]),
|
||||
returnValue: _i3.Future<_i2.StreamedResponse>.value(
|
||||
_FakeStreamedResponse_1(this, Invocation.method(#send, [request])),
|
||||
_FakeStreamedResponse_1(
|
||||
this,
|
||||
Invocation.method(#send, [request]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<_i2.StreamedResponse>);
|
||||
|
||||
@override
|
||||
void close() =>
|
||||
super.noSuchMethod(Invocation.method(#close, []), returnValueForMissingStub: null);
|
||||
void close() => super.noSuchMethod(
|
||||
Invocation.method(#close, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
276
test/providers/gym_state_test.dart
Normal file
276
test/providers/gym_state_test.dart
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
|
||||
import '../../test_data/exercises.dart';
|
||||
import '../../test_data/routines.dart';
|
||||
|
||||
void main() {
|
||||
late GymStateNotifier notifier;
|
||||
late ProviderContainer container;
|
||||
|
||||
setUp(() {
|
||||
container = ProviderContainer.test();
|
||||
notifier = container.read(gymStateProvider.notifier);
|
||||
notifier.state = notifier.state.copyWith(
|
||||
showExercisePages: true,
|
||||
showTimerPages: true,
|
||||
dayId: 1,
|
||||
iteration: 1,
|
||||
routine: getTestRoutine(),
|
||||
);
|
||||
notifier.calculatePages();
|
||||
});
|
||||
|
||||
group('GymStateNotifier.markSlotPageAsDone', () {
|
||||
test('Correctly changes the flag', () {
|
||||
// Arrange
|
||||
final slotPage = notifier.state.pages[1].slotPages[1];
|
||||
expect(slotPage.type, SlotPageType.log);
|
||||
expect(
|
||||
notifier.state.pages.every((p) => p.slotPages.every((s) => !s.logDone)),
|
||||
true,
|
||||
reason: 'All slot pages are initially not done',
|
||||
);
|
||||
|
||||
// Act
|
||||
notifier.markSlotPageAsDone(slotPage.uuid, isDone: true);
|
||||
|
||||
// Assert
|
||||
for (final page in notifier.state.pages.where((p) => p.type == PageType.set)) {
|
||||
for (final slot in page.slotPages.where((s) => s.type == SlotPageType.log)) {
|
||||
if (slot.uuid == slotPage.uuid) {
|
||||
expect(slot.logDone, true);
|
||||
} else {
|
||||
expect(slot.logDone, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group('GymStateNotifier.recalculateIndices', () {
|
||||
test('Correctly recalculates indices if new pages are added', () {
|
||||
// Arrange
|
||||
final newPages = [
|
||||
...notifier.state.pages.sublist(0, 2),
|
||||
PageEntry(
|
||||
type: PageType.set,
|
||||
pageIndex: 1111,
|
||||
uuid: 'new-page-1',
|
||||
),
|
||||
PageEntry(
|
||||
type: PageType.set,
|
||||
pageIndex: 9,
|
||||
uuid: 'new-page-2',
|
||||
),
|
||||
...notifier.state.pages.sublist(2),
|
||||
PageEntry(
|
||||
type: PageType.set,
|
||||
pageIndex: 0,
|
||||
uuid: 'new-page-3',
|
||||
slotPages: [
|
||||
SlotPageEntry(
|
||||
type: SlotPageType.timer,
|
||||
pageIndex: 10,
|
||||
setIndex: 9,
|
||||
uuid: 'new-slot-1',
|
||||
),
|
||||
SlotPageEntry(
|
||||
type: SlotPageType.timer,
|
||||
pageIndex: 10,
|
||||
setIndex: 6,
|
||||
uuid: 'new-slot-2',
|
||||
),
|
||||
SlotPageEntry(
|
||||
type: SlotPageType.timer,
|
||||
pageIndex: 100,
|
||||
setIndex: 100,
|
||||
uuid: 'new-slot-3',
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
notifier.state = notifier.state.copyWith(pages: newPages);
|
||||
|
||||
// Act
|
||||
notifier.recalculateIndices();
|
||||
|
||||
// Assert
|
||||
final pages = notifier.state.pages;
|
||||
expect(pages[0].pageIndex, 0);
|
||||
expect(pages[1].pageIndex, 1);
|
||||
|
||||
// These three have the same pageIndex because the new ones don't have any slot
|
||||
// pages (this should not happen in practice)
|
||||
expect(pages[2].pageIndex, 8);
|
||||
expect(pages[3].pageIndex, 8);
|
||||
expect(pages[4].pageIndex, 8);
|
||||
|
||||
expect(pages[5].pageIndex, 15);
|
||||
expect(pages[6].pageIndex, 16);
|
||||
expect(pages[7].pageIndex, 17);
|
||||
|
||||
// Preserve the order of new pages
|
||||
expect(pages[7].uuid, 'new-page-3');
|
||||
|
||||
// Slot pages have correct indices, the original order is preserved
|
||||
final slotPages = pages[7].slotPages;
|
||||
expect(slotPages[0].uuid, 'new-slot-1');
|
||||
expect(slotPages[0].pageIndex, 17);
|
||||
expect(slotPages[0].setIndex, 0);
|
||||
expect(slotPages[1].uuid, 'new-slot-2');
|
||||
expect(slotPages[1].pageIndex, 18);
|
||||
expect(slotPages[1].setIndex, 1);
|
||||
expect(slotPages[2].uuid, 'new-slot-3');
|
||||
expect(slotPages[2].pageIndex, 19);
|
||||
expect(slotPages[2].setIndex, 2);
|
||||
});
|
||||
});
|
||||
|
||||
group('GymStateNotifier.replaceExercises', () {
|
||||
test('Correctly swaps an exercise', () {
|
||||
// Arrange
|
||||
final slotPage = notifier.state.pages[1].slotPages[1];
|
||||
expect(slotPage.type, SlotPageType.log);
|
||||
notifier.state.pages.every((p) => p.exercises.every((s) => s.id != testSquats.id));
|
||||
|
||||
// Act
|
||||
notifier.replaceExercises(slotPage.uuid, originalExerciseId: 1, newExercise: testSquats);
|
||||
// print(notifier.readPageStructure());
|
||||
|
||||
// Assert
|
||||
expect(notifier.state.pages[1].exercises.first.id, testSquats.id);
|
||||
});
|
||||
});
|
||||
|
||||
group('GymStateNotifier.calculatePages', () {
|
||||
test(
|
||||
'Correctly generates pages - exercise and timer',
|
||||
() {
|
||||
// Arrange
|
||||
notifier.state = notifier.state.copyWith(
|
||||
showExercisePages: true,
|
||||
showTimerPages: true,
|
||||
);
|
||||
|
||||
// Act
|
||||
notifier.calculatePages();
|
||||
|
||||
// Assert
|
||||
final pages = notifier.state.pages;
|
||||
final setEntry = pages.firstWhere((p) => p.type == PageType.set);
|
||||
expect(pages.length, 5, reason: '5 PageEntries (start, set 1, set 2, session, summary)');
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.log).length,
|
||||
3,
|
||||
reason: 'Three sets',
|
||||
);
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.timer).length,
|
||||
3,
|
||||
reason: 'One timer after each set',
|
||||
);
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.exerciseOverview).length,
|
||||
1,
|
||||
reason: 'One exercise overview at the start',
|
||||
);
|
||||
expect(setEntry.slotPages[0].type, SlotPageType.exerciseOverview);
|
||||
expect(setEntry.slotPages[1].type, SlotPageType.log);
|
||||
expect(setEntry.slotPages[2].type, SlotPageType.timer);
|
||||
expect(notifier.state.totalPages, 17);
|
||||
},
|
||||
);
|
||||
|
||||
test('Correctly generates pages - no exercises and no timer', () {
|
||||
// Arrange
|
||||
notifier.state = notifier.state.copyWith(
|
||||
showExercisePages: false,
|
||||
showTimerPages: false,
|
||||
);
|
||||
|
||||
// Act
|
||||
notifier.calculatePages();
|
||||
|
||||
// Assert
|
||||
final pages = notifier.state.pages;
|
||||
final setEntry = pages.firstWhere((p) => p.type == PageType.set);
|
||||
expect(pages.length, 5, reason: '4 PageEntries (start, set 1, set 2, session, summary)');
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.log).length,
|
||||
3,
|
||||
reason: 'Three sets',
|
||||
);
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.timer).length,
|
||||
0,
|
||||
reason: 'No timer',
|
||||
);
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.exerciseOverview).length,
|
||||
0,
|
||||
reason: 'No overview',
|
||||
);
|
||||
expect(setEntry.slotPages[0].type, SlotPageType.log);
|
||||
expect(setEntry.slotPages[1].type, SlotPageType.log);
|
||||
expect(setEntry.slotPages[2].type, SlotPageType.log);
|
||||
expect(notifier.state.totalPages, 9);
|
||||
});
|
||||
|
||||
test('Correctly generates pages - exercises and no timer', () {
|
||||
// Arrange
|
||||
notifier.state = notifier.state.copyWith(
|
||||
showExercisePages: true,
|
||||
showTimerPages: false,
|
||||
);
|
||||
|
||||
// Act
|
||||
notifier.calculatePages();
|
||||
|
||||
// Assert
|
||||
final pages = notifier.state.pages;
|
||||
final setEntry = pages.firstWhere((p) => p.type == PageType.set);
|
||||
expect(pages.length, 5, reason: '5 PageEntries (start, set 1, set 2, session, summary)');
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.log).length,
|
||||
3,
|
||||
reason: 'Three sets',
|
||||
);
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.timer).length,
|
||||
0,
|
||||
reason: 'No timer',
|
||||
);
|
||||
expect(
|
||||
setEntry.slotPages.where((p) => p.type == SlotPageType.exerciseOverview).length,
|
||||
1,
|
||||
reason: 'One exercise overview at the start',
|
||||
);
|
||||
expect(setEntry.slotPages.length, 4);
|
||||
expect(setEntry.slotPages[0].type, SlotPageType.exerciseOverview);
|
||||
expect(setEntry.slotPages[1].type, SlotPageType.log);
|
||||
expect(setEntry.slotPages[2].type, SlotPageType.log);
|
||||
expect(setEntry.slotPages[3].type, SlotPageType.log);
|
||||
expect(notifier.state.totalPages, 11);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart'; // Added for annotations
|
||||
import 'package:mockito/mockito.dart'; // Added for mockito
|
||||
@@ -12,6 +13,7 @@ import 'plate_calculator_test.mocks.dart';
|
||||
void main() {
|
||||
group('PlateWeightsNotifier', () {
|
||||
late PlateCalculatorNotifier notifier;
|
||||
late ProviderContainer container;
|
||||
late MockSharedPreferencesAsync mockPrefs;
|
||||
|
||||
setUp(() {
|
||||
@@ -19,7 +21,14 @@ void main() {
|
||||
when(mockPrefs.getString(PREFS_KEY_PLATES)).thenAnswer((_) async => null);
|
||||
when(mockPrefs.setString(any, any)).thenAnswer((_) async => true);
|
||||
|
||||
notifier = PlateCalculatorNotifier(prefs: mockPrefs);
|
||||
container = ProviderContainer.test(
|
||||
overrides: [
|
||||
plateCalculatorProvider.overrideWith(
|
||||
() => PlateCalculatorNotifier(prefs: mockPrefs),
|
||||
),
|
||||
],
|
||||
);
|
||||
notifier = container.read(plateCalculatorProvider.notifier);
|
||||
});
|
||||
|
||||
test('toggleSelection adds and removes plates', () async {
|
||||
|
||||
@@ -21,7 +21,6 @@ import 'package:shared_preferences/src/shared_preferences_async.dart' as _i2;
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
/// A class which mocks [SharedPreferencesAsync].
|
||||
///
|
||||
@@ -44,7 +43,9 @@ class MockSharedPreferencesAsync extends _i1.Mock implements _i2.SharedPreferenc
|
||||
_i3.Future<Map<String, Object?>> getAll({Set<String>? allowList}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getAll, [], {#allowList: allowList}),
|
||||
returnValue: _i3.Future<Map<String, Object?>>.value(<String, Object?>{}),
|
||||
returnValue: _i3.Future<Map<String, Object?>>.value(
|
||||
<String, Object?>{},
|
||||
),
|
||||
)
|
||||
as _i3.Future<Map<String, Object?>>);
|
||||
|
||||
@@ -58,7 +59,10 @@ class MockSharedPreferencesAsync extends _i1.Mock implements _i2.SharedPreferenc
|
||||
|
||||
@override
|
||||
_i3.Future<int?> getInt(String? key) =>
|
||||
(super.noSuchMethod(Invocation.method(#getInt, [key]), returnValue: _i3.Future<int?>.value())
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getInt, [key]),
|
||||
returnValue: _i3.Future<int?>.value(),
|
||||
)
|
||||
as _i3.Future<int?>);
|
||||
|
||||
@override
|
||||
|
||||
@@ -92,44 +92,64 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@override
|
||||
List<_i5.Routine> get items =>
|
||||
(super.noSuchMethod(Invocation.getter(#items), returnValue: <_i5.Routine>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#items),
|
||||
returnValue: <_i5.Routine>[],
|
||||
)
|
||||
as List<_i5.Routine>);
|
||||
|
||||
@override
|
||||
List<_i3.WeightUnit> get weightUnits =>
|
||||
(super.noSuchMethod(Invocation.getter(#weightUnits), returnValue: <_i3.WeightUnit>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#weightUnits),
|
||||
returnValue: <_i3.WeightUnit>[],
|
||||
)
|
||||
as List<_i3.WeightUnit>);
|
||||
|
||||
@override
|
||||
_i3.WeightUnit get defaultWeightUnit =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#defaultWeightUnit),
|
||||
returnValue: _FakeWeightUnit_1(this, Invocation.getter(#defaultWeightUnit)),
|
||||
returnValue: _FakeWeightUnit_1(
|
||||
this,
|
||||
Invocation.getter(#defaultWeightUnit),
|
||||
),
|
||||
)
|
||||
as _i3.WeightUnit);
|
||||
|
||||
@override
|
||||
List<_i4.RepetitionUnit> get repetitionUnits =>
|
||||
(super.noSuchMethod(Invocation.getter(#repetitionUnits), returnValue: <_i4.RepetitionUnit>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#repetitionUnits),
|
||||
returnValue: <_i4.RepetitionUnit>[],
|
||||
)
|
||||
as List<_i4.RepetitionUnit>);
|
||||
|
||||
@override
|
||||
_i4.RepetitionUnit get defaultRepetitionUnit =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#defaultRepetitionUnit),
|
||||
returnValue: _FakeRepetitionUnit_2(this, Invocation.getter(#defaultRepetitionUnit)),
|
||||
returnValue: _FakeRepetitionUnit_2(
|
||||
this,
|
||||
Invocation.getter(#defaultRepetitionUnit),
|
||||
),
|
||||
)
|
||||
as _i4.RepetitionUnit);
|
||||
|
||||
@override
|
||||
set activeRoutine(_i5.Routine? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#activeRoutine, value), returnValueForMissingStub: null);
|
||||
set activeRoutine(_i5.Routine? value) => super.noSuchMethod(
|
||||
Invocation.setter(#activeRoutine, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod(
|
||||
@@ -148,14 +168,19 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.WeightUnit findWeightUnitById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findWeightUnitById, [id]),
|
||||
returnValue: _FakeWeightUnit_1(this, Invocation.method(#findWeightUnitById, [id])),
|
||||
returnValue: _FakeWeightUnit_1(
|
||||
this,
|
||||
Invocation.method(#findWeightUnitById, [id]),
|
||||
),
|
||||
)
|
||||
as _i3.WeightUnit);
|
||||
|
||||
@@ -172,20 +197,30 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
|
||||
@override
|
||||
List<_i5.Routine> getPlans() =>
|
||||
(super.noSuchMethod(Invocation.method(#getPlans, []), returnValue: <_i5.Routine>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getPlans, []),
|
||||
returnValue: <_i5.Routine>[],
|
||||
)
|
||||
as List<_i5.Routine>);
|
||||
|
||||
@override
|
||||
_i5.Routine findById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findById, [id]),
|
||||
returnValue: _FakeRoutine_3(this, Invocation.method(#findById, [id])),
|
||||
returnValue: _FakeRoutine_3(
|
||||
this,
|
||||
Invocation.method(#findById, [id]),
|
||||
),
|
||||
)
|
||||
as _i5.Routine);
|
||||
|
||||
@override
|
||||
int findIndexById(int? id) =>
|
||||
(super.noSuchMethod(Invocation.method(#findIndexById, [id]), returnValue: 0) as int);
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findIndexById, [id]),
|
||||
returnValue: 0,
|
||||
)
|
||||
as int);
|
||||
|
||||
@override
|
||||
_i13.Future<void> fetchAndSetAllRoutinesFull() =>
|
||||
@@ -211,7 +246,11 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
Map<int, _i15.Exercise>? exercises,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#setExercisesAndUnits, [entries], {#exercises: exercises}),
|
||||
Invocation.method(
|
||||
#setExercisesAndUnits,
|
||||
[entries],
|
||||
{#exercises: exercises},
|
||||
),
|
||||
returnValue: _i13.Future<void>.value(),
|
||||
returnValueForMissingStub: _i13.Future<void>.value(),
|
||||
)
|
||||
@@ -222,7 +261,10 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchAndSetRoutineSparse, [planId]),
|
||||
returnValue: _i13.Future<_i5.Routine>.value(
|
||||
_FakeRoutine_3(this, Invocation.method(#fetchAndSetRoutineSparse, [planId])),
|
||||
_FakeRoutine_3(
|
||||
this,
|
||||
Invocation.method(#fetchAndSetRoutineSparse, [planId]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i13.Future<_i5.Routine>);
|
||||
@@ -232,7 +274,10 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchAndSetRoutineFull, [routineId]),
|
||||
returnValue: _i13.Future<_i5.Routine>.value(
|
||||
_FakeRoutine_3(this, Invocation.method(#fetchAndSetRoutineFull, [routineId])),
|
||||
_FakeRoutine_3(
|
||||
this,
|
||||
Invocation.method(#fetchAndSetRoutineFull, [routineId]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i13.Future<_i5.Routine>);
|
||||
@@ -367,11 +412,17 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
as _i13.Future<void>);
|
||||
|
||||
@override
|
||||
_i13.Future<_i8.SlotEntry> addSlotEntry(_i8.SlotEntry? entry, int? routineId) =>
|
||||
_i13.Future<_i8.SlotEntry> addSlotEntry(
|
||||
_i8.SlotEntry? entry,
|
||||
int? routineId,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#addSlotEntry, [entry, routineId]),
|
||||
returnValue: _i13.Future<_i8.SlotEntry>.value(
|
||||
_FakeSlotEntry_6(this, Invocation.method(#addSlotEntry, [entry, routineId])),
|
||||
_FakeSlotEntry_6(
|
||||
this,
|
||||
Invocation.method(#addSlotEntry, [entry, routineId]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i13.Future<_i8.SlotEntry>);
|
||||
@@ -398,26 +449,41 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
String getConfigUrl(_i8.ConfigType? type) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getConfigUrl, [type]),
|
||||
returnValue: _i16.dummyValue<String>(this, Invocation.method(#getConfigUrl, [type])),
|
||||
returnValue: _i16.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#getConfigUrl, [type]),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
_i13.Future<_i9.BaseConfig> editConfig(_i9.BaseConfig? config, _i8.ConfigType? type) =>
|
||||
_i13.Future<_i9.BaseConfig> editConfig(
|
||||
_i9.BaseConfig? config,
|
||||
_i8.ConfigType? type,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#editConfig, [config, type]),
|
||||
returnValue: _i13.Future<_i9.BaseConfig>.value(
|
||||
_FakeBaseConfig_7(this, Invocation.method(#editConfig, [config, type])),
|
||||
_FakeBaseConfig_7(
|
||||
this,
|
||||
Invocation.method(#editConfig, [config, type]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i13.Future<_i9.BaseConfig>);
|
||||
|
||||
@override
|
||||
_i13.Future<_i9.BaseConfig> addConfig(_i9.BaseConfig? config, _i8.ConfigType? type) =>
|
||||
_i13.Future<_i9.BaseConfig> addConfig(
|
||||
_i9.BaseConfig? config,
|
||||
_i8.ConfigType? type,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#addConfig, [config, type]),
|
||||
returnValue: _i13.Future<_i9.BaseConfig>.value(
|
||||
_FakeBaseConfig_7(this, Invocation.method(#addConfig, [config, type])),
|
||||
_FakeBaseConfig_7(
|
||||
this,
|
||||
Invocation.method(#addConfig, [config, type]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i13.Future<_i9.BaseConfig>);
|
||||
@@ -432,7 +498,11 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
as _i13.Future<void>);
|
||||
|
||||
@override
|
||||
_i13.Future<void> handleConfig(_i8.SlotEntry? entry, num? value, _i8.ConfigType? type) =>
|
||||
_i13.Future<void> handleConfig(
|
||||
_i8.SlotEntry? entry,
|
||||
num? value,
|
||||
_i8.ConfigType? type,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#handleConfig, [entry, value, type]),
|
||||
returnValue: _i13.Future<void>.value(),
|
||||
@@ -444,16 +514,24 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
_i13.Future<List<_i10.WorkoutSession>> fetchSessionData() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchSessionData, []),
|
||||
returnValue: _i13.Future<List<_i10.WorkoutSession>>.value(<_i10.WorkoutSession>[]),
|
||||
returnValue: _i13.Future<List<_i10.WorkoutSession>>.value(
|
||||
<_i10.WorkoutSession>[],
|
||||
),
|
||||
)
|
||||
as _i13.Future<List<_i10.WorkoutSession>>);
|
||||
|
||||
@override
|
||||
_i13.Future<_i10.WorkoutSession> addSession(_i10.WorkoutSession? session, int? routineId) =>
|
||||
_i13.Future<_i10.WorkoutSession> addSession(
|
||||
_i10.WorkoutSession? session,
|
||||
int? routineId,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#addSession, [session, routineId]),
|
||||
returnValue: _i13.Future<_i10.WorkoutSession>.value(
|
||||
_FakeWorkoutSession_8(this, Invocation.method(#addSession, [session, routineId])),
|
||||
_FakeWorkoutSession_8(
|
||||
this,
|
||||
Invocation.method(#addSession, [session, routineId]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i13.Future<_i10.WorkoutSession>);
|
||||
@@ -463,7 +541,10 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#editSession, [session]),
|
||||
returnValue: _i13.Future<_i10.WorkoutSession>.value(
|
||||
_FakeWorkoutSession_8(this, Invocation.method(#editSession, [session])),
|
||||
_FakeWorkoutSession_8(
|
||||
this,
|
||||
Invocation.method(#editSession, [session]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i13.Future<_i10.WorkoutSession>);
|
||||
@@ -500,10 +581,14 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 15 KiB |
BIN
test/routine/gym_mode/goldens/gym_mode_progression_tab.png
Normal file
BIN
test/routine/gym_mode/goldens/gym_mode_progression_tab.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
311
test/routine/gym_mode/gym_mode_test.dart
Normal file
311
test/routine/gym_mode/gym_mode_test.dart
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
|
||||
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/base_provider.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/screens/gym_mode.dart';
|
||||
import 'package:wger/screens/routine_screen.dart';
|
||||
import 'package:wger/widgets/routines/forms/reps_unit.dart';
|
||||
import 'package:wger/widgets/routines/forms/rir.dart';
|
||||
import 'package:wger/widgets/routines/forms/weight_unit.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/exercise_overview.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/log_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/session_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/start_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/summary.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/timer.dart';
|
||||
|
||||
import '../../../test_data/exercises.dart';
|
||||
import '../../../test_data/routines.dart';
|
||||
import 'gym_mode_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([WgerBaseProvider, ExercisesProvider, RoutinesProvider])
|
||||
void main() {
|
||||
final key = GlobalKey<NavigatorState>();
|
||||
|
||||
final mockRoutinesProvider = MockRoutinesProvider();
|
||||
final mockExerciseProvider = MockExercisesProvider();
|
||||
final testRoutine = getTestRoutine();
|
||||
final testExercises = getTestExercises();
|
||||
|
||||
setUp(() {
|
||||
when(mockRoutinesProvider.findById(any)).thenReturn(testRoutine);
|
||||
when(mockRoutinesProvider.items).thenReturn([testRoutine]);
|
||||
when(mockRoutinesProvider.repetitionUnits).thenReturn(testRepetitionUnits);
|
||||
when(mockRoutinesProvider.findRepetitionUnitById(1)).thenReturn(testRepetitionUnit1);
|
||||
when(mockRoutinesProvider.weightUnits).thenReturn(testWeightUnits);
|
||||
when(mockRoutinesProvider.findWeightUnitById(1)).thenReturn(testWeightUnit1);
|
||||
when(
|
||||
mockRoutinesProvider.fetchAndSetRoutineFull(any),
|
||||
).thenAnswer((_) => Future.value(testRoutine));
|
||||
|
||||
SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty();
|
||||
});
|
||||
|
||||
Widget renderGymMode({locale = 'en'}) {
|
||||
return ChangeNotifierProvider<RoutinesProvider>(
|
||||
create: (context) => mockRoutinesProvider,
|
||||
child: ChangeNotifierProvider<ExercisesProvider>(
|
||||
create: (context) => mockExerciseProvider,
|
||||
child: riverpod.ProviderScope(
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
navigatorKey: key,
|
||||
home: TextButton(
|
||||
onPressed: () => key.currentState!.push(
|
||||
MaterialPageRoute<void>(
|
||||
settings: const RouteSettings(arguments: GymModeArguments(1, 1, 1)),
|
||||
builder: (_) => const GymModeScreen(),
|
||||
),
|
||||
),
|
||||
child: const SizedBox(),
|
||||
),
|
||||
routes: {RoutineScreen.routeName: (ctx) => const RoutineScreen()},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'Test the widgets on the gym mode screen',
|
||||
(WidgetTester tester) async {
|
||||
when(mockExerciseProvider.findExerciseById(1)).thenReturn(testExercises[0]);
|
||||
when(mockExerciseProvider.findExerciseById(6)).thenReturn(testExercises[5]);
|
||||
when(
|
||||
mockExerciseProvider.findExercisesByVariationId(
|
||||
null,
|
||||
exerciseIdToExclude: anyNamed('exerciseIdToExclude'),
|
||||
),
|
||||
).thenReturn([]);
|
||||
|
||||
await withClock(Clock.fixed(DateTime(2025, 3, 29, 14, 33)), () async {
|
||||
await tester.pumpWidget(renderGymMode());
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byType(TextButton));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
//await tester.ensureVisible(find.byKey(Key(key as String)));
|
||||
//
|
||||
// Start page
|
||||
//
|
||||
expect(find.byType(StartPage), findsOneWidget);
|
||||
expect(find.text('Your workout today'), findsOneWidget);
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.menu), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsNothing);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - exercise overview page
|
||||
//
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.byType(ExerciseOverview), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.menu), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.drag(find.byType(ExerciseOverview), const Offset(-500.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - Log
|
||||
//
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
expect(find.byType(Form), findsOneWidget);
|
||||
expect(find.text('10 × 10 kg (1.5 RiR)'), findsOneWidget);
|
||||
expect(find.text('12 × 10 kg (2 RiR)'), findsOneWidget);
|
||||
|
||||
// TODO: commented out for now
|
||||
// expect(find.text('Make sure to warm up'), findsOneWidget, reason: 'Set comment');
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.menu), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
|
||||
// Form shows only weight and reps
|
||||
expect(find.byType(SwitchListTile), findsOneWidget);
|
||||
expect(find.byType(TextFormField), findsNWidgets(2));
|
||||
expect(find.byType(RepetitionUnitInputWidget), findsNothing);
|
||||
expect(find.byType(WeightUnitInputWidget), findsNothing);
|
||||
expect(find.byType(RiRInputWidget), findsNothing);
|
||||
|
||||
// Form shows unit and rir after tapping the toggle button
|
||||
await tester.tap(find.byType(SwitchListTile));
|
||||
await tester.pump();
|
||||
expect(find.byType(RepetitionUnitInputWidget), findsOneWidget);
|
||||
expect(find.byType(WeightUnitInputWidget), findsOneWidget);
|
||||
expect(find.byType(RiRInputWidget), findsOneWidget);
|
||||
await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - pause
|
||||
//
|
||||
expect(find.text('Pause'), findsOneWidget);
|
||||
expect(find.byType(TimerCountdownWidget), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.menu), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - log
|
||||
//
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
expect(find.byType(Form), findsOneWidget);
|
||||
await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Pause
|
||||
//
|
||||
expect(find.text('Pause'), findsOneWidget);
|
||||
expect(find.byType(TimerCountdownWidget), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - log
|
||||
//
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
expect(find.byType(Form), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Pause
|
||||
//
|
||||
expect(find.text('Pause'), findsOneWidget);
|
||||
expect(find.byType(TimerCountdownWidget), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - overview
|
||||
//
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byType(ExerciseOverview), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - log
|
||||
//
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - timer
|
||||
//
|
||||
expect(find.byType(TimerWidget), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - log
|
||||
//
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - timer
|
||||
//
|
||||
expect(find.byType(TimerWidget), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - log
|
||||
//
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - timer
|
||||
//
|
||||
expect(find.byType(TimerWidget), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Session
|
||||
//
|
||||
expect(find.text('Workout session'), findsOneWidget);
|
||||
expect(find.byType(SessionPage), findsOneWidget);
|
||||
expect(find.byType(Form), findsOneWidget);
|
||||
expect(find.byIcon(Icons.sentiment_very_dissatisfied), findsOneWidget);
|
||||
expect(find.byIcon(Icons.sentiment_neutral), findsOneWidget);
|
||||
expect(find.byIcon(Icons.sentiment_very_satisfied), findsOneWidget);
|
||||
expect(
|
||||
find.text('14:33'),
|
||||
findsNWidgets(2),
|
||||
reason: 'start and end time are the same',
|
||||
);
|
||||
final toggleButtons = tester.widget<ToggleButtons>(find.byType(ToggleButtons));
|
||||
expect(toggleButtons.isSelected[1], isTrue);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Workout summary
|
||||
//
|
||||
expect(find.byType(WorkoutSummary), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsNothing);
|
||||
});
|
||||
},
|
||||
semanticsEnabled: false,
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in wger/test/routine/gym_mode_screen_test.dart.
|
||||
// in wger/test/routine/gym_mode/gym_mode_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) wger Team
|
||||
* Copyright (c) 2020, wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
@@ -26,38 +27,55 @@ import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/models/workouts/session.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/session_page.dart';
|
||||
|
||||
import '../../test_data/routines.dart';
|
||||
import 'gym_mode_session_screen_test.mocks.dart';
|
||||
import '../../../test_data/routines.dart';
|
||||
import 'session_page_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([RoutinesProvider])
|
||||
void main() {
|
||||
final mockRoutinesProvider = MockRoutinesProvider();
|
||||
late Routine testRoutine;
|
||||
late GymStateNotifier notifier;
|
||||
late ProviderContainer container;
|
||||
|
||||
setUp(() {
|
||||
testRoutine = getTestRoutine();
|
||||
|
||||
container = ProviderContainer.test();
|
||||
notifier = container.read(gymStateProvider.notifier);
|
||||
notifier.state = notifier.state.copyWith(
|
||||
showExercisePages: true,
|
||||
showTimerPages: true,
|
||||
dayId: 1,
|
||||
iteration: 1,
|
||||
routine: getTestRoutine(),
|
||||
);
|
||||
notifier.calculatePages();
|
||||
when(mockRoutinesProvider.editSession(any)).thenAnswer(
|
||||
(_) => Future.value(testRoutine.sessions[0].session),
|
||||
);
|
||||
});
|
||||
|
||||
Widget renderSessionPage({locale = 'en'}) {
|
||||
return ChangeNotifierProvider<RoutinesProvider>(
|
||||
create: (context) => mockRoutinesProvider,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: SessionPage(
|
||||
testRoutine,
|
||||
PageController(),
|
||||
const TimeOfDay(hour: 13, minute: 35),
|
||||
const {},
|
||||
final controller = PageController(initialPage: 0);
|
||||
|
||||
return UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: ChangeNotifierProvider<RoutinesProvider>(
|
||||
create: (context) => mockRoutinesProvider,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: PageView(
|
||||
controller: controller,
|
||||
children: [
|
||||
SessionPage(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -79,6 +97,9 @@ void main() {
|
||||
testRoutine.sessions[0].session.timeStart = null;
|
||||
testRoutine.sessions[0].session.timeEnd = null;
|
||||
|
||||
notifier.state = notifier.state.copyWith(routine: testRoutine);
|
||||
notifier.calculatePages();
|
||||
|
||||
withClock(Clock.fixed(DateTime(2021, 5, 1)), () async {
|
||||
await tester.pumpWidget(renderSessionPage());
|
||||
|
||||
@@ -93,10 +114,17 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Test correct default data (no existing session)', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
testRoutine.sessions = [];
|
||||
final timeNow = timeToString(TimeOfDay.now())!;
|
||||
notifier.state = notifier.state.copyWith(
|
||||
startTime: const TimeOfDay(hour: 13, minute: 35),
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(renderSessionPage());
|
||||
|
||||
// Assert
|
||||
expect(find.text('13:35'), findsOneWidget);
|
||||
expect(find.text(timeNow), findsOneWidget);
|
||||
final toggleButtons = tester.widget<ToggleButtons>(find.byType(ToggleButtons));
|
||||
@@ -110,6 +138,7 @@ void main() {
|
||||
final captured =
|
||||
verify(mockRoutinesProvider.editSession(captureAny)).captured.single as WorkoutSession;
|
||||
|
||||
print(captured);
|
||||
expect(captured.id, 1);
|
||||
expect(captured.impression, 3);
|
||||
expect(captured.notes, equals('This is a note'));
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in wger/test/routine/gym_mode_session_screen_test.dart.
|
||||
// in wger/test/routine/gym_mode/session_page_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
98
test/routine/gym_mode/workout_menu_test.dart
Normal file
98
test/routine/gym_mode/workout_menu_test.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/workout_menu.dart';
|
||||
|
||||
import '../../../test_data/routines.dart';
|
||||
|
||||
void main() {
|
||||
late GymStateNotifier notifier;
|
||||
late ProviderContainer container;
|
||||
|
||||
setUp(() {
|
||||
container = ProviderContainer.test();
|
||||
notifier = container.read(gymStateProvider.notifier);
|
||||
notifier.state = notifier.state.copyWith(
|
||||
showExercisePages: false,
|
||||
showTimerPages: false,
|
||||
dayId: 1,
|
||||
iteration: 1,
|
||||
routine: getTestRoutine(),
|
||||
);
|
||||
notifier.calculatePages();
|
||||
});
|
||||
|
||||
Widget renderWidget({locale = 'en'}) {
|
||||
return UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: ProgressionTab(PageController()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'Smoke and golden test',
|
||||
(WidgetTester tester) async {
|
||||
tester.view.physicalSize = const Size(500, 1000);
|
||||
tester.view.devicePixelRatio = 1.0; // Ensure correct pixel ratio
|
||||
|
||||
await tester.pumpWidget(renderWidget());
|
||||
|
||||
if (Platform.isLinux) {
|
||||
await expectLater(
|
||||
find.byType(MaterialApp),
|
||||
matchesGoldenFile('goldens/gym_mode_progression_tab.png'),
|
||||
);
|
||||
}
|
||||
},
|
||||
tags: ['golden'],
|
||||
);
|
||||
|
||||
testWidgets('Opens the exercise swap widget', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(renderWidget());
|
||||
|
||||
expect(find.byType(ExerciseSwapWidget), findsNothing);
|
||||
|
||||
await tester.tap(find.byKey(Key('swap-icon-${notifier.state.pages[1].uuid}')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ExerciseSwapWidget), findsOne);
|
||||
});
|
||||
|
||||
testWidgets('Opens the add exercise widget', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(renderWidget());
|
||||
|
||||
expect(find.byType(ExerciseAddWidget), findsNothing);
|
||||
|
||||
await tester.tap(find.byKey(Key('add-icon-${notifier.state.pages[1].uuid}')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ExerciseAddWidget), findsOne);
|
||||
});
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
|
||||
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/base_provider.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/screens/gym_mode.dart';
|
||||
import 'package:wger/screens/routine_screen.dart';
|
||||
import 'package:wger/widgets/routines/forms/reps_unit.dart';
|
||||
import 'package:wger/widgets/routines/forms/rir.dart';
|
||||
import 'package:wger/widgets/routines/forms/weight_unit.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/exercise_overview.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/log_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/session_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/start_page.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/timer.dart';
|
||||
|
||||
import '../../test_data/exercises.dart';
|
||||
import '../../test_data/routines.dart';
|
||||
import 'gym_mode_screen_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([WgerBaseProvider, ExercisesProvider, RoutinesProvider])
|
||||
void main() {
|
||||
final key = GlobalKey<NavigatorState>();
|
||||
|
||||
final mockRoutinesProvider = MockRoutinesProvider();
|
||||
final mockExerciseProvider = MockExercisesProvider();
|
||||
final testRoutine = getTestRoutine();
|
||||
final testExercises = getTestExercises();
|
||||
|
||||
setUp(() {
|
||||
when(mockRoutinesProvider.findById(any)).thenReturn(testRoutine);
|
||||
when(mockRoutinesProvider.items).thenReturn([testRoutine]);
|
||||
when(mockRoutinesProvider.repetitionUnits).thenReturn(testRepetitionUnits);
|
||||
when(mockRoutinesProvider.findRepetitionUnitById(1)).thenReturn(testRepetitionUnit1);
|
||||
when(mockRoutinesProvider.weightUnits).thenReturn(testWeightUnits);
|
||||
when(mockRoutinesProvider.findWeightUnitById(1)).thenReturn(testWeightUnit1);
|
||||
when(
|
||||
mockRoutinesProvider.fetchAndSetRoutineFull(any),
|
||||
).thenAnswer((_) => Future.value(testRoutine));
|
||||
|
||||
SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty();
|
||||
});
|
||||
|
||||
Widget renderGymMode({locale = 'en'}) {
|
||||
return ChangeNotifierProvider<RoutinesProvider>(
|
||||
create: (context) => mockRoutinesProvider,
|
||||
child: ChangeNotifierProvider<ExercisesProvider>(
|
||||
create: (context) => mockExerciseProvider,
|
||||
child: riverpod.ProviderScope(
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
navigatorKey: key,
|
||||
home: TextButton(
|
||||
onPressed: () => key.currentState!.push(
|
||||
MaterialPageRoute<void>(
|
||||
settings: const RouteSettings(arguments: GymModeArguments(1, 1, 1)),
|
||||
builder: (_) => const GymModeScreen(),
|
||||
),
|
||||
),
|
||||
child: const SizedBox(),
|
||||
),
|
||||
routes: {RoutineScreen.routeName: (ctx) => const RoutineScreen()},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('Test the widgets on the gym mode screen', (WidgetTester tester) async {
|
||||
when(mockExerciseProvider.findExerciseById(1)).thenReturn(testExercises[0]);
|
||||
when(mockExerciseProvider.findExerciseById(6)).thenReturn(testExercises[5]);
|
||||
when(
|
||||
mockExerciseProvider.findExercisesByVariationId(
|
||||
null,
|
||||
exerciseIdToExclude: anyNamed('exerciseIdToExclude'),
|
||||
),
|
||||
).thenReturn([]);
|
||||
|
||||
await withClock(Clock.fixed(DateTime(2025, 3, 29, 14, 33)), () async {
|
||||
await tester.pumpWidget(renderGymMode());
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byType(TextButton));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
//await tester.ensureVisible(find.byKey(Key(key as String)));
|
||||
//
|
||||
// Start page
|
||||
//
|
||||
expect(find.byType(StartPage), findsOneWidget);
|
||||
expect(find.text('Your workout today'), findsOneWidget);
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.toc), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsNothing);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - exercise overview page
|
||||
//
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.byType(ExerciseOverview), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.toc), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.drag(find.byType(ExerciseOverview), const Offset(-500.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - Log
|
||||
//
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
expect(find.byType(Form), findsOneWidget);
|
||||
// print(find.byType(Form));
|
||||
expect(find.byType(ListTile), findsNWidgets(3), reason: 'Two logs and the switch tile');
|
||||
expect(find.text('10 × 10 kg (1.5 RiR)'), findsOneWidget);
|
||||
expect(find.text('12 × 10 kg (2 RiR)'), findsOneWidget);
|
||||
expect(find.text('Make sure to warm up'), findsOneWidget, reason: 'Set comment');
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.toc), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
|
||||
// Form shows only weight and reps
|
||||
expect(find.byType(SwitchListTile), findsOneWidget);
|
||||
expect(find.byType(TextFormField), findsNWidgets(2));
|
||||
expect(find.byType(RepetitionUnitInputWidget), findsNothing);
|
||||
expect(find.byType(WeightUnitInputWidget), findsNothing);
|
||||
expect(find.byType(RiRInputWidget), findsNothing);
|
||||
|
||||
// Form shows unit and rir after tapping the toggle button
|
||||
await tester.tap(find.byType(SwitchListTile));
|
||||
await tester.pump();
|
||||
expect(find.byType(RepetitionUnitInputWidget), findsOneWidget);
|
||||
expect(find.byType(WeightUnitInputWidget), findsOneWidget);
|
||||
expect(find.byType(RiRInputWidget), findsOneWidget);
|
||||
await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - pause
|
||||
//
|
||||
expect(find.text('Pause'), findsOneWidget);
|
||||
expect(find.byType(TimerCountdownWidget), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.toc), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - log
|
||||
//
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
expect(find.byType(Form), findsOneWidget);
|
||||
await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Pause
|
||||
//
|
||||
expect(find.text('Pause'), findsOneWidget);
|
||||
expect(find.byType(TimerCountdownWidget), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Bench press - log
|
||||
//
|
||||
expect(find.text('Bench press'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
expect(find.byType(Form), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Pause
|
||||
//
|
||||
expect(find.text('Pause'), findsOneWidget);
|
||||
expect(find.byType(TimerCountdownWidget), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - overview
|
||||
//
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byType(ExerciseOverview), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - log
|
||||
//
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - timer
|
||||
//
|
||||
expect(find.byType(TimerWidget), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - log
|
||||
//
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - timer
|
||||
//
|
||||
expect(find.byType(TimerWidget), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - log
|
||||
//
|
||||
expect(find.text('Side raises'), findsOneWidget);
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Side raises - timer
|
||||
//
|
||||
expect(find.byType(TimerWidget), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
//
|
||||
// Session
|
||||
//
|
||||
expect(find.text('Workout session'), findsOneWidget);
|
||||
expect(find.byType(SessionPage), findsOneWidget);
|
||||
expect(find.byType(Form), findsOneWidget);
|
||||
expect(find.byIcon(Icons.sentiment_very_dissatisfied), findsOneWidget);
|
||||
expect(find.byIcon(Icons.sentiment_neutral), findsOneWidget);
|
||||
expect(find.byIcon(Icons.sentiment_very_satisfied), findsOneWidget);
|
||||
expect(
|
||||
find.text('14:33'),
|
||||
findsNWidgets(2),
|
||||
reason: 'start and end time are the same',
|
||||
);
|
||||
final toggleButtons = tester.widget<ToggleButtons>(find.byType(ToggleButtons));
|
||||
expect(toggleButtons.isSelected[1], isTrue);
|
||||
expect(find.byIcon(Icons.chevron_left), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||
expect(find.byIcon(Icons.chevron_right), findsNothing);
|
||||
|
||||
//
|
||||
});
|
||||
});
|
||||
}
|
||||
128
test/routine/models/log_test.dart
Normal file
128
test/routine/models/log_test.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
|
||||
void main() {
|
||||
group('Log.volume', () {
|
||||
test('returns weight * repetitions for metric (kg) and repetition unit', () {
|
||||
final log = Log(
|
||||
exerciseId: 1,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 100,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 5,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
expect(log.volume(metric: true), equals(500));
|
||||
});
|
||||
|
||||
test('returns 0 when weight unit does not match metric flag', () {
|
||||
final log = Log(
|
||||
exerciseId: 2,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 100,
|
||||
weightUnitId: WEIGHT_UNIT_LB, // pounds
|
||||
repetitions: 5,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
// metric = true expects KG -> mismatch -> 0
|
||||
expect(log.volume(metric: true), equals(0));
|
||||
});
|
||||
|
||||
test('returns weight * repetitions for imperial (lb) when metric=false', () {
|
||||
final log = Log(
|
||||
exerciseId: 3,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 220, // lb
|
||||
weightUnitId: WEIGHT_UNIT_LB,
|
||||
repetitions: 3,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
expect(log.volume(metric: false), equals(660));
|
||||
});
|
||||
|
||||
test('returns 0 when repetitions unit is not repetitions', () {
|
||||
final log = Log(
|
||||
exerciseId: 4,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 50,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 10,
|
||||
repetitionsUnitId: 999, // some other unit id
|
||||
);
|
||||
|
||||
expect(log.volume(metric: true), equals(0));
|
||||
});
|
||||
|
||||
test('returns 0 when weight or repetitions are null', () {
|
||||
final a = Log(
|
||||
exerciseId: 5,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: null,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 5,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
final b = Log(
|
||||
exerciseId: 6,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 50,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: null,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
expect(a.volume(metric: true), equals(0));
|
||||
expect(b.volume(metric: true), equals(0));
|
||||
});
|
||||
|
||||
test('works with non-integer (num) weight and repetitions', () {
|
||||
final log = Log(
|
||||
exerciseId: 7,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 10.5,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 3,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
expect(log.volume(metric: true), closeTo(31.5, 0.0001));
|
||||
});
|
||||
});
|
||||
}
|
||||
175
test/routine/models/session.dart
Normal file
175
test/routine/models/session.dart
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/models/workouts/session.dart';
|
||||
|
||||
void main() {
|
||||
group('WorkoutSession.volume', () {
|
||||
test('sums metric volumes correctly', () {
|
||||
final session = WorkoutSession(routineId: 1);
|
||||
|
||||
final a = Log(
|
||||
exerciseId: 1,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 100,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 3,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
final b = Log(
|
||||
exerciseId: 2,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 50,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 2,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
session.logs = [a, b];
|
||||
|
||||
final vol = session.volume;
|
||||
expect(vol['metric'], equals(100 * 3 + 50 * 2));
|
||||
expect(vol['imperial'], equals(0));
|
||||
});
|
||||
|
||||
test('sums imperial volumes correctly', () {
|
||||
final session = WorkoutSession(routineId: 1);
|
||||
|
||||
final a = Log(
|
||||
exerciseId: 3,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 220,
|
||||
weightUnitId: WEIGHT_UNIT_LB,
|
||||
repetitions: 4,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
final b = Log(
|
||||
exerciseId: 4,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 150,
|
||||
weightUnitId: WEIGHT_UNIT_LB,
|
||||
repetitions: 1,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
session.logs = [a, b];
|
||||
|
||||
final vol = session.volume;
|
||||
expect(vol['imperial'], equals(220 * 4 + 150 * 1));
|
||||
expect(vol['metric'], equals(0));
|
||||
});
|
||||
|
||||
test('ignores logs with non-matching units or non-rep units', () {
|
||||
final session = WorkoutSession(routineId: 1);
|
||||
|
||||
final a = Log(
|
||||
exerciseId: 5,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 100,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 5,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
final b = Log(
|
||||
exerciseId: 6,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 60,
|
||||
weightUnitId: WEIGHT_UNIT_LB, // different unit
|
||||
repetitions: 2,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
final c = Log(
|
||||
exerciseId: 7,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 30,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 20,
|
||||
repetitionsUnitId: 999, // some other repetition unit -> should be ignored
|
||||
);
|
||||
|
||||
session.logs = [a, b, c];
|
||||
|
||||
final vol = session.volume;
|
||||
// only 'a' should count for metric, only 'b' for imperial
|
||||
expect(vol['metric'], equals(100 * 5));
|
||||
expect(vol['imperial'], equals(60 * 2));
|
||||
});
|
||||
|
||||
test('returns zero for empty logs', () {
|
||||
final session = WorkoutSession(routineId: 1);
|
||||
session.logs = [];
|
||||
|
||||
final vol = session.volume;
|
||||
expect(vol['metric'], equals(0));
|
||||
expect(vol['imperial'], equals(0));
|
||||
});
|
||||
|
||||
test('works with fractional weights and reps', () {
|
||||
final session = WorkoutSession(routineId: 1);
|
||||
|
||||
final a = Log(
|
||||
exerciseId: 8,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 10.5,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 3,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
final b = Log(
|
||||
exerciseId: 9,
|
||||
routineId: 1,
|
||||
rir: null,
|
||||
date: DateTime.now(),
|
||||
weight: 5.25,
|
||||
weightUnitId: WEIGHT_UNIT_KG,
|
||||
repetitions: 2.5,
|
||||
repetitionsUnitId: REP_UNIT_REPETITIONS_ID,
|
||||
);
|
||||
|
||||
session.logs = [a, b];
|
||||
|
||||
final vol = session.volume;
|
||||
expect(vol['metric'], closeTo(10.5 * 3 + 5.25 * 2.5, 1e-9));
|
||||
expect(vol['imperial'], equals(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/screens/routine_logs_screen.dart';
|
||||
import 'package:wger/screens/routine_screen.dart';
|
||||
import 'package:wger/widgets/routines/workout_logs.dart';
|
||||
import 'package:wger/widgets/routines/logs/log_overview_routine.dart';
|
||||
|
||||
import '../../test_data/routines.dart';
|
||||
import 'routine_logs_screen_test.mocks.dart';
|
||||
|
||||
@@ -23,7 +23,6 @@ import 'package:wger/providers/base_provider.dart' as _i4;
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
class _FakeAuthProvider_0 extends _i1.SmartFake implements _i2.AuthProvider {
|
||||
_FakeAuthProvider_0(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
@@ -66,23 +65,34 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
as _i3.Client);
|
||||
|
||||
@override
|
||||
set auth(_i2.AuthProvider? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#auth, value), returnValueForMissingStub: null);
|
||||
set auth(_i2.AuthProvider? _auth) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, _auth),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i3.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i3.Client? _client) => super.noSuchMethod(
|
||||
Invocation.setter(#client, _client),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {#includeAuth: includeAuth}),
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(String? path, {int? id, String? objectMethod, Map<String, dynamic>? query}) =>
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -120,15 +130,22 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
_i5.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i5.Future<Map<String, dynamic>> patch(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i5.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@@ -137,7 +154,10 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#deleteRequest, [url, id])),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.Response>);
|
||||
|
||||
@@ -66,23 +66,34 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
as _i3.Client);
|
||||
|
||||
@override
|
||||
set auth(_i2.AuthProvider? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#auth, value), returnValueForMissingStub: null);
|
||||
set auth(_i2.AuthProvider? value) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i3.Client? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, value), returnValueForMissingStub: null);
|
||||
set client(_i3.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {#includeAuth: includeAuth}),
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(String? path, {int? id, String? objectMethod, Map<String, dynamic>? query}) =>
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -120,15 +131,22 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
_i5.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i5.Future<Map<String, dynamic>> patch(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i5.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@@ -137,7 +155,10 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(this, Invocation.method(#deleteRequest, [url, id])),
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.Response>);
|
||||
|
||||
@@ -84,32 +84,45 @@ class MockBodyWeightProvider extends _i1.Mock implements _i10.BodyWeightProvider
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@override
|
||||
List<_i3.WeightEntry> get items =>
|
||||
(super.noSuchMethod(Invocation.getter(#items), returnValue: <_i3.WeightEntry>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#items),
|
||||
returnValue: <_i3.WeightEntry>[],
|
||||
)
|
||||
as List<_i3.WeightEntry>);
|
||||
|
||||
@override
|
||||
set items(List<_i3.WeightEntry>? entries) =>
|
||||
super.noSuchMethod(Invocation.setter(#items, entries), returnValueForMissingStub: null);
|
||||
set items(List<_i3.WeightEntry>? entries) => super.noSuchMethod(
|
||||
Invocation.setter(#items, entries),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.WeightEntry findById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findById, [id]),
|
||||
returnValue: _FakeWeightEntry_1(this, Invocation.method(#findById, [id])),
|
||||
returnValue: _FakeWeightEntry_1(
|
||||
this,
|
||||
Invocation.method(#findById, [id]),
|
||||
),
|
||||
)
|
||||
as _i3.WeightEntry);
|
||||
|
||||
@@ -121,7 +134,9 @@ class MockBodyWeightProvider extends _i1.Mock implements _i10.BodyWeightProvider
|
||||
_i11.Future<List<_i3.WeightEntry>> fetchAndSetEntries() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchAndSetEntries, []),
|
||||
returnValue: _i11.Future<List<_i3.WeightEntry>>.value(<_i3.WeightEntry>[]),
|
||||
returnValue: _i11.Future<List<_i3.WeightEntry>>.value(
|
||||
<_i3.WeightEntry>[],
|
||||
),
|
||||
)
|
||||
as _i11.Future<List<_i3.WeightEntry>>);
|
||||
|
||||
@@ -166,12 +181,16 @@ class MockBodyWeightProvider extends _i1.Mock implements _i10.BodyWeightProvider
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [UserProvider].
|
||||
@@ -184,14 +203,20 @@ class MockUserProvider extends _i1.Mock implements _i13.UserProvider {
|
||||
|
||||
@override
|
||||
_i14.ThemeMode get themeMode =>
|
||||
(super.noSuchMethod(Invocation.getter(#themeMode), returnValue: _i14.ThemeMode.system)
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#themeMode),
|
||||
returnValue: _i14.ThemeMode.system,
|
||||
)
|
||||
as _i14.ThemeMode);
|
||||
|
||||
@override
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@@ -199,33 +224,46 @@ class MockUserProvider extends _i1.Mock implements _i13.UserProvider {
|
||||
_i4.SharedPreferencesAsync get prefs =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#prefs),
|
||||
returnValue: _FakeSharedPreferencesAsync_2(this, Invocation.getter(#prefs)),
|
||||
returnValue: _FakeSharedPreferencesAsync_2(
|
||||
this,
|
||||
Invocation.getter(#prefs),
|
||||
),
|
||||
)
|
||||
as _i4.SharedPreferencesAsync);
|
||||
|
||||
@override
|
||||
set themeMode(_i14.ThemeMode? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#themeMode, value), returnValueForMissingStub: null);
|
||||
set themeMode(_i14.ThemeMode? value) => super.noSuchMethod(
|
||||
Invocation.setter(#themeMode, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set prefs(_i4.SharedPreferencesAsync? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#prefs, value), returnValueForMissingStub: null);
|
||||
set prefs(_i4.SharedPreferencesAsync? value) => super.noSuchMethod(
|
||||
Invocation.setter(#prefs, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set profile(_i15.Profile? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#profile, value), returnValueForMissingStub: null);
|
||||
set profile(_i15.Profile? value) => super.noSuchMethod(
|
||||
Invocation.setter(#profile, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void setThemeMode(_i14.ThemeMode? mode) =>
|
||||
super.noSuchMethod(Invocation.method(#setThemeMode, [mode]), returnValueForMissingStub: null);
|
||||
void setThemeMode(_i14.ThemeMode? mode) => super.noSuchMethod(
|
||||
Invocation.method(#setThemeMode, [mode]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i11.Future<void> fetchAndSetProfile() =>
|
||||
@@ -267,12 +305,16 @@ class MockUserProvider extends _i1.Mock implements _i13.UserProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [NutritionPlansProvider].
|
||||
@@ -287,7 +329,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@@ -295,41 +340,59 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
_i5.IngredientDatabase get database =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#database),
|
||||
returnValue: _FakeIngredientDatabase_3(this, Invocation.getter(#database)),
|
||||
returnValue: _FakeIngredientDatabase_3(
|
||||
this,
|
||||
Invocation.getter(#database),
|
||||
),
|
||||
)
|
||||
as _i5.IngredientDatabase);
|
||||
|
||||
@override
|
||||
List<_i9.Ingredient> get ingredients =>
|
||||
(super.noSuchMethod(Invocation.getter(#ingredients), returnValue: <_i9.Ingredient>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#ingredients),
|
||||
returnValue: <_i9.Ingredient>[],
|
||||
)
|
||||
as List<_i9.Ingredient>);
|
||||
|
||||
@override
|
||||
List<_i6.NutritionalPlan> get items =>
|
||||
(super.noSuchMethod(Invocation.getter(#items), returnValue: <_i6.NutritionalPlan>[])
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#items),
|
||||
returnValue: <_i6.NutritionalPlan>[],
|
||||
)
|
||||
as List<_i6.NutritionalPlan>);
|
||||
|
||||
@override
|
||||
set database(_i5.IngredientDatabase? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#database, value), returnValueForMissingStub: null);
|
||||
set database(_i5.IngredientDatabase? value) => super.noSuchMethod(
|
||||
Invocation.setter(#database, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set ingredients(List<_i9.Ingredient>? value) =>
|
||||
super.noSuchMethod(Invocation.setter(#ingredients, value), returnValueForMissingStub: null);
|
||||
set ingredients(List<_i9.Ingredient>? value) => super.noSuchMethod(
|
||||
Invocation.setter(#ingredients, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
|
||||
|
||||
@override
|
||||
void clear() =>
|
||||
super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null);
|
||||
void clear() => super.noSuchMethod(
|
||||
Invocation.method(#clear, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i6.NutritionalPlan findById(int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#findById, [id]),
|
||||
returnValue: _FakeNutritionalPlan_4(this, Invocation.method(#findById, [id])),
|
||||
returnValue: _FakeNutritionalPlan_4(
|
||||
this,
|
||||
Invocation.method(#findById, [id]),
|
||||
),
|
||||
)
|
||||
as _i6.NutritionalPlan);
|
||||
|
||||
@@ -360,7 +423,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchAndSetPlanSparse, [planId]),
|
||||
returnValue: _i11.Future<_i6.NutritionalPlan>.value(
|
||||
_FakeNutritionalPlan_4(this, Invocation.method(#fetchAndSetPlanSparse, [planId])),
|
||||
_FakeNutritionalPlan_4(
|
||||
this,
|
||||
Invocation.method(#fetchAndSetPlanSparse, [planId]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i11.Future<_i6.NutritionalPlan>);
|
||||
@@ -370,7 +436,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchAndSetPlanFull, [planId]),
|
||||
returnValue: _i11.Future<_i6.NutritionalPlan>.value(
|
||||
_FakeNutritionalPlan_4(this, Invocation.method(#fetchAndSetPlanFull, [planId])),
|
||||
_FakeNutritionalPlan_4(
|
||||
this,
|
||||
Invocation.method(#fetchAndSetPlanFull, [planId]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i11.Future<_i6.NutritionalPlan>);
|
||||
@@ -380,7 +449,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#addPlan, [planData]),
|
||||
returnValue: _i11.Future<_i6.NutritionalPlan>.value(
|
||||
_FakeNutritionalPlan_4(this, Invocation.method(#addPlan, [planData])),
|
||||
_FakeNutritionalPlan_4(
|
||||
this,
|
||||
Invocation.method(#addPlan, [planData]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i11.Future<_i6.NutritionalPlan>);
|
||||
@@ -433,11 +505,17 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
as _i11.Future<void>);
|
||||
|
||||
@override
|
||||
_i11.Future<_i8.MealItem> addMealItem(_i8.MealItem? mealItem, _i7.Meal? meal) =>
|
||||
_i11.Future<_i8.MealItem> addMealItem(
|
||||
_i8.MealItem? mealItem,
|
||||
_i7.Meal? meal,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#addMealItem, [mealItem, meal]),
|
||||
returnValue: _i11.Future<_i8.MealItem>.value(
|
||||
_FakeMealItem_6(this, Invocation.method(#addMealItem, [mealItem, meal])),
|
||||
_FakeMealItem_6(
|
||||
this,
|
||||
Invocation.method(#addMealItem, [mealItem, meal]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i11.Future<_i8.MealItem>);
|
||||
@@ -460,17 +538,41 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
)
|
||||
as _i11.Future<void>);
|
||||
|
||||
@override
|
||||
_i11.Future<void> cacheIngredient(
|
||||
_i9.Ingredient? ingredient, {
|
||||
_i5.IngredientDatabase? database,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#cacheIngredient,
|
||||
[ingredient],
|
||||
{#database: database},
|
||||
),
|
||||
returnValue: _i11.Future<void>.value(),
|
||||
returnValueForMissingStub: _i11.Future<void>.value(),
|
||||
)
|
||||
as _i11.Future<void>);
|
||||
|
||||
@override
|
||||
_i11.Future<_i9.Ingredient> fetchIngredient(
|
||||
int? ingredientId, {
|
||||
_i5.IngredientDatabase? database,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchIngredient, [ingredientId], {#database: database}),
|
||||
Invocation.method(
|
||||
#fetchIngredient,
|
||||
[ingredientId],
|
||||
{#database: database},
|
||||
),
|
||||
returnValue: _i11.Future<_i9.Ingredient>.value(
|
||||
_FakeIngredient_7(
|
||||
this,
|
||||
Invocation.method(#fetchIngredient, [ingredientId], {#database: database}),
|
||||
Invocation.method(
|
||||
#fetchIngredient,
|
||||
[ingredientId],
|
||||
{#database: database},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -497,7 +599,9 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
[name],
|
||||
{#languageCode: languageCode, #searchEnglish: searchEnglish},
|
||||
),
|
||||
returnValue: _i11.Future<List<_i9.Ingredient>>.value(<_i9.Ingredient>[]),
|
||||
returnValue: _i11.Future<List<_i9.Ingredient>>.value(
|
||||
<_i9.Ingredient>[],
|
||||
),
|
||||
)
|
||||
as _i11.Future<List<_i9.Ingredient>>);
|
||||
|
||||
@@ -525,7 +629,11 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
DateTime? dateTime,
|
||||
]) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#logIngredientToDiary, [mealItem, planId, dateTime]),
|
||||
Invocation.method(#logIngredientToDiary, [
|
||||
mealItem,
|
||||
planId,
|
||||
dateTime,
|
||||
]),
|
||||
returnValue: _i11.Future<void>.value(),
|
||||
returnValueForMissingStub: _i11.Future<void>.value(),
|
||||
)
|
||||
@@ -562,10 +670,14 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() =>
|
||||
super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null);
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() =>
|
||||
super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null);
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
171
test/widgets/routines/gym_mode/gym_mode_options_test.dart
Normal file
171
test/widgets/routines/gym_mode/gym_mode_options_test.dart
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
|
||||
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/start_page.dart';
|
||||
|
||||
import '../../../../test_data/routines.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
late ProviderContainer container;
|
||||
|
||||
setUp(() {
|
||||
// Use in-memory shared preferences to avoid platform channels during tests
|
||||
SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty();
|
||||
|
||||
container = ProviderContainer(
|
||||
overrides: [
|
||||
gymStateProvider.overrideWith(() => GymStateNotifier()),
|
||||
],
|
||||
);
|
||||
|
||||
final routine = getTestRoutine();
|
||||
final notifier = container.read(gymStateProvider.notifier);
|
||||
notifier.state = GymModeState(
|
||||
showExercisePages: true,
|
||||
showTimerPages: true,
|
||||
useCountdownBetweenSets: true,
|
||||
countdownDuration: const Duration(seconds: DEFAULT_COUNTDOWN_DURATION),
|
||||
alertOnCountdownEnd: false,
|
||||
dayId: routine.days.first.id,
|
||||
iteration: 1,
|
||||
routine: routine,
|
||||
);
|
||||
|
||||
notifier.calculatePages();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
container.dispose();
|
||||
});
|
||||
|
||||
Future<void> pumpGymModeOptions(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: const MaterialApp(
|
||||
locale: Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: GymModeOptions(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('Switches update the notifier state', (tester) async {
|
||||
await pumpGymModeOptions(tester);
|
||||
|
||||
// Open options (tap the ListTile to toggle _showOptions)
|
||||
final optionsTile = find.byKey(const ValueKey('gym-mode-options-tile'));
|
||||
expect(optionsTile, findsOneWidget);
|
||||
await tester.tap(optionsTile);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Toggle notify countdown first (it is only enabled while timer/countdown are active)
|
||||
final notifySwitch = find.byKey(const ValueKey('gym-mode-notify-countdown'));
|
||||
expect(notifySwitch, findsOneWidget);
|
||||
await tester.tap(notifySwitch);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Now toggle show exercises
|
||||
final showExercisesSwitch = find.byKey(const ValueKey('gym-mode-option-show-exercises'));
|
||||
expect(showExercisesSwitch, findsOneWidget);
|
||||
await tester.tap(showExercisesSwitch);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Toggle show timer (this will disable notify switch)
|
||||
final showTimerSwitch = find.byKey(const ValueKey('gym-mode-option-show-timer'));
|
||||
expect(showTimerSwitch, findsOneWidget);
|
||||
await tester.tap(showTimerSwitch);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final notifier = container.read(gymStateProvider.notifier);
|
||||
expect(notifier.state.showExercisePages, isFalse);
|
||||
expect(notifier.state.showTimerPages, isFalse);
|
||||
expect(notifier.state.alertOnCountdownEnd, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('Dropdown, text field and refresh button update notifier state', (tester) async {
|
||||
await pumpGymModeOptions(tester);
|
||||
|
||||
// Open options
|
||||
final optionsTile = find.byKey(const ValueKey('gym-mode-options-tile'));
|
||||
await tester.tap(optionsTile);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final notifier = container.read(gymStateProvider.notifier);
|
||||
|
||||
// Change dropdown (countdown type) -> switch to stopwatch (false)
|
||||
final dropdown = find.byKey(const ValueKey('countdown-type-dropdown'));
|
||||
expect(dropdown, findsOneWidget);
|
||||
|
||||
await tester.tap(dropdown);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Select the visible menu entry by its text label (English: 'Stopwatch') to avoid hit-test issues
|
||||
final stopwatchText = find.text('Stopwatch');
|
||||
expect(stopwatchText, findsWidgets);
|
||||
await tester.tap(stopwatchText.first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(notifier.state.useCountdownBetweenSets, isFalse);
|
||||
|
||||
// switch back to countdown (true)
|
||||
await tester.tap(dropdown);
|
||||
await tester.pumpAndSettle();
|
||||
final countdownText = find.text('Countdown');
|
||||
expect(countdownText, findsWidgets);
|
||||
await tester.tap(countdownText.first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(notifier.state.useCountdownBetweenSets, isTrue);
|
||||
|
||||
// Enter a new countdown duration in the TextFormField
|
||||
final countdownField = find.byKey(const ValueKey('gym-mode-default-countdown-time'));
|
||||
expect(countdownField, findsOneWidget);
|
||||
|
||||
// The TextFormField is a descendant; find the editable TextField
|
||||
final textField = find.descendant(of: countdownField, matching: find.byType(TextFormField));
|
||||
expect(textField, findsOneWidget);
|
||||
|
||||
await tester.enterText(textField, '60');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(notifier.state.countdownDuration.inSeconds, 60);
|
||||
|
||||
// Tap refresh button (suffix icon). Find IconButton inside the input decoration
|
||||
final refreshIcon = find.descendant(of: countdownField, matching: find.byIcon(Icons.refresh));
|
||||
expect(refreshIcon, findsOneWidget);
|
||||
await tester.tap(refreshIcon);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(notifier.state.countdownDuration.inSeconds, DEFAULT_COUNTDOWN_DURATION);
|
||||
});
|
||||
}
|
||||
@@ -9,13 +9,11 @@ import 'package:wger/widgets/routines/plate_calculator.dart';
|
||||
|
||||
Future<void> pumpWidget(
|
||||
WidgetTester tester, {
|
||||
PlateCalculatorNotifier? notifier,
|
||||
required ProviderContainer container,
|
||||
}) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
if (notifier != null) plateCalculatorProvider.overrideWith((ref) => notifier),
|
||||
],
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: const MaterialApp(
|
||||
locale: Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
@@ -27,12 +25,19 @@ Future<void> pumpWidget(
|
||||
}
|
||||
|
||||
void main() {
|
||||
late ProviderContainer container;
|
||||
|
||||
setUp(() {
|
||||
SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty();
|
||||
container = ProviderContainer(
|
||||
overrides: [
|
||||
plateCalculatorProvider.overrideWith(() => PlateCalculatorNotifier()),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Smoke test for ConfigureAvailablePlates', (WidgetTester tester) async {
|
||||
await pumpWidget(tester);
|
||||
await pumpWidget(tester, container: container);
|
||||
|
||||
expect(find.text('Unit'), findsWidgets);
|
||||
expect(find.text('Bar weight'), findsWidgets);
|
||||
@@ -45,7 +50,8 @@ void main() {
|
||||
'ConfigureAvailablePlates correctly updates state',
|
||||
(WidgetTester tester) async {
|
||||
// Arrange
|
||||
final notifier = PlateCalculatorNotifier();
|
||||
await pumpWidget(tester, container: container);
|
||||
final notifier = container.read(plateCalculatorProvider.notifier);
|
||||
notifier.state = PlateCalculatorState(
|
||||
isMetric: true,
|
||||
barWeight: 20,
|
||||
@@ -53,8 +59,6 @@ void main() {
|
||||
selectedPlates: [1.25, 2.5],
|
||||
);
|
||||
|
||||
await pumpWidget(tester, notifier: notifier);
|
||||
|
||||
// Correctly changes the unit
|
||||
expect(notifier.state.isMetric, isTrue);
|
||||
await tester.tap(find.byKey(const ValueKey('weightUnitDropdown')));
|
||||
|
||||
Reference in New Issue
Block a user